mirror of
https://github.com/airgradienthq/arduino.git
synced 2025-06-26 16:21:33 +02:00
Compare commits
147 Commits
Author | SHA1 | Date | |
---|---|---|---|
8a83e408d3 | |||
7a1a0337d1 | |||
6bdd4cb02f | |||
38bd758b69 | |||
6aa19ea3e6 | |||
da323b1a46 | |||
2ae90444bb | |||
21e802da33 | |||
e0869fbaf0 | |||
70662091ec | |||
960d2bad64 | |||
7abf7f5e6a | |||
6bad4fd04b | |||
a17f18b3db | |||
3da4900462 | |||
d2d81f6b4b | |||
0b12f56513 | |||
f7e811e34b | |||
037bb37184 | |||
fde510ba96 | |||
14b152a2a7 | |||
ef6b041529 | |||
11ecea1493 | |||
3e2e8b15eb | |||
4249a86fd5 | |||
4a58b0b1c7 | |||
5f93329e96 | |||
59d189e1d3 | |||
2e62abe2d7 | |||
7569b114bf | |||
3c1d0a862f | |||
4d883af77e | |||
e9224a5de0 | |||
af0fbadd80 | |||
79f6c040c7 | |||
f262866148 | |||
78a2a78020 | |||
65e759fb90 | |||
3fc7e4b55e | |||
5857388c2d | |||
5770b41fd4 | |||
d85d890878 | |||
9fbd31d0c8 | |||
c5b7c43293 | |||
7550ef7b0c | |||
805546b78e | |||
59880f4be5 | |||
ee7837a471 | |||
ebbf0adf2f | |||
d9551dc560 | |||
6ea0ab9272 | |||
6e54409512 | |||
f35bc4feaa | |||
c04ab90fd2 | |||
ed02f66ca2 | |||
4b94926651 | |||
f505b39247 | |||
9e44cd89d9 | |||
e7b95c0bde | |||
b72394b004 | |||
1544989fe6 | |||
d7b5e999c1 | |||
9b0210f7b5 | |||
3715266f8c | |||
a4eb607174 | |||
f55fa6a617 | |||
4f9f800cce | |||
cad5d1f0e7 | |||
55ede2b04d | |||
563bdfe4b2 | |||
e2154af85f | |||
9a2bbd7a57 | |||
a8c8246632 | |||
a71c038864 | |||
799217e724 | |||
b3f02f0a58 | |||
c640cf773e | |||
c145666fcb | |||
466bb0eb21 | |||
4612f4b793 | |||
45c7279866 | |||
5cb838af29 | |||
3201fd8d9c | |||
f23c7e9e31 | |||
5b18a8353d | |||
1e81c9b125 | |||
3dae4cb06d | |||
b4745ef55d | |||
348cb2663a | |||
1b69e8a599 | |||
d17ad3cbab | |||
a6d8936ea6 | |||
8b428855b0 | |||
22dc2136e4 | |||
f23f81f575 | |||
7a4255b2bb | |||
d844ab09fa | |||
31c60dcbec | |||
f0749783fe | |||
d34605a018 | |||
1bcb9bf5ee | |||
296bf49e5e | |||
e3dee42b4b | |||
279ccb8bfb | |||
a3c9727b02 | |||
1b886d9843 | |||
066e81b186 | |||
c98d078d4c | |||
cb98183e20 | |||
0e1734b35d | |||
da6326db0f | |||
ad1da129c0 | |||
8a90fe511b | |||
cca1ab69bb | |||
955172d3d3 | |||
4500f41ed3 | |||
7c2f8e5b9b | |||
fb84b53077 | |||
3359b1817b | |||
8eb8d4a1ec | |||
d2723de0f8 | |||
4493156739 | |||
0acb7d470d | |||
8428442dea | |||
f32d6b1bbe | |||
46600f59a3 | |||
01c42387ed | |||
f08438db46 | |||
221730160b | |||
d40b1d37a8 | |||
ecd3aa988f | |||
095787d1f2 | |||
d94d074abe | |||
cdef9822b3 | |||
29c1989e78 | |||
bc3872e631 | |||
7a182ebb12 | |||
5e6f801534 | |||
88808a2ad2 | |||
a4fc954712 | |||
1580cd51aa | |||
3efba24e24 | |||
940dd0167c | |||
6b4f86e7e4 | |||
e5a0c6bc7b | |||
b8fdb38db8 | |||
b2c55c38dc |
2
.github/workflows/check.yml
vendored
2
.github/workflows/check.yml
vendored
@ -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"
|
||||
|
@ -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
22
docs/ota-updates.md
Normal file
@ -0,0 +1,22 @@
|
||||
## OTA Updates
|
||||
|
||||
From [firmware version 3.1.1](https://github.com/airgradienthq/arduino/tree/3.1.1) onwards, the AirGradient ONE and Open Air monitors support over the air (OTA) updates.
|
||||
|
||||
#### Mechanism
|
||||
|
||||
Upon compilation of an official release the git tag (GIT_VERSION) is compiled into the binary.
|
||||
|
||||
The device attempts to update to the latest version on startup and in regular intervals using URL
|
||||
|
||||
http://hw.airgradient.com/sensors/{deviceId}/generic/os/firmware.bin?current_firmware={GIT_VERSION}
|
||||
|
||||
If does pass the version it is currently running on along to the server through URL parameter 'current_firmware'.
|
||||
This allows the server to identify if the device is already running on the latest version or should update.
|
||||
|
||||
The following scenarios are possible
|
||||
|
||||
1. The device is already on the latest firmware. Then the server returns a 304 with a short explanation text in the body saying this.
|
||||
2. The device reports a firmware unknown to the server. A 400 with an empty payload is returned in this case and the update is not performed. This case is relevant for local changes. The GIT_VERSION then defaults to "snapshot" which is unknown to the server.
|
||||
3. There is an update available. A 200 along with the binary data of the new version is returned and the update is performed.
|
||||
|
||||
More information about the implementation details are available here: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/ota.html
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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++;
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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; }
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
};
|
||||
|
1074
src/AgConfigure.cpp
1074
src/AgConfigure.cpp
File diff suppressed because it is too large
Load Diff
@ -8,34 +8,17 @@
|
||||
|
||||
class Configuration : public PrintLog {
|
||||
private:
|
||||
struct Config {
|
||||
char model[20];
|
||||
char country[3]; /** Country name has only 2 character, ex: TH = Thailand */
|
||||
char mqttBroker[256]; /** MQTT broker URI */
|
||||
bool inUSAQI; /** If PM standard "ugm3" inUSAQI = false, otherwise is true
|
||||
*/
|
||||
bool inF; /** Temperature unit F */
|
||||
bool postDataToAirGradient; /** If true, monitor will not POST data to
|
||||
airgradient server. Make sure no error
|
||||
message shown on monitor */
|
||||
uint8_t configurationControl; /** If true, configuration from airgradient
|
||||
server will be ignored */
|
||||
bool displayMode; /** true if enable display */
|
||||
uint8_t useRGBLedBar;
|
||||
uint8_t abcDays;
|
||||
int tvocLearningOffset;
|
||||
int noxLearningOffset;
|
||||
char temperatureUnit; // 'f' or 'c'
|
||||
|
||||
uint32_t _check;
|
||||
};
|
||||
struct Config config;
|
||||
bool co2CalibrationRequested;
|
||||
bool ledBarTestRequested;
|
||||
bool udpated;
|
||||
String failedMessage;
|
||||
bool _noxLearnOffsetChanged;
|
||||
bool _tvocLearningOffsetChanged;
|
||||
bool ledBarBrightnessChanged = false;
|
||||
bool displayBrightnessChanged = false;
|
||||
String otaNewFirmwareVersion;
|
||||
bool _offlineMode = false;
|
||||
bool _ledBarModeChanged = false;
|
||||
|
||||
AirGradient* ag;
|
||||
|
||||
@ -49,8 +32,8 @@ private:
|
||||
void jsonInvalid(void);
|
||||
void configLogInfo(String name, String fromValue, String toValue);
|
||||
String getPMStandardString(bool usaqi);
|
||||
String getDisplayModeString(bool dispMode);
|
||||
String getAbcDayString(int value);
|
||||
void toConfig(const char* buf);
|
||||
|
||||
public:
|
||||
Configuration(Stream &debugLog);
|
||||
@ -65,6 +48,7 @@ public:
|
||||
bool begin(void);
|
||||
bool parse(String data, bool isLocal);
|
||||
String toString(void);
|
||||
String toString(AgFirmwareMode fwMode);
|
||||
bool isTemperatureUnitInF(void);
|
||||
String getCountry(void);
|
||||
bool isPmStandardInUSAQI(void);
|
||||
@ -89,6 +73,15 @@ public:
|
||||
String wifiSSID(void);
|
||||
String wifiPass(void);
|
||||
void setAirGradient(AirGradient *ag);
|
||||
bool isLedBarBrightnessChanged(void);
|
||||
int getLedBarBrightness(void);
|
||||
bool isDisplayBrightnessChanged(void);
|
||||
int getDisplayBrightness(void);
|
||||
String newFirmwareVersion(void);
|
||||
bool isOfflineMode(void);
|
||||
void setOfflineMode(bool offline);
|
||||
void setOfflineModeWithoutSave(bool offline);
|
||||
bool isLedBarModeChanged(void);
|
||||
};
|
||||
|
||||
#endif /** _AG_CONFIG_H_ */
|
||||
|
@ -1,6 +1,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());
|
||||
}
|
||||
|
@ -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_ */
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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_ */
|
||||
|
@ -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) {
|
||||
|
@ -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; }
|
||||
|
@ -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_ */
|
||||
|
@ -59,6 +59,10 @@ enum AgStateMachineState {
|
||||
|
||||
/* LED bar testing */
|
||||
AgStateMachineLedBarTest,
|
||||
AgStateMachineLedBarPowerUpTest,
|
||||
|
||||
/** OTA perform, show display status */
|
||||
AgStateMachineOtaPerform,
|
||||
|
||||
/** LED: Show working state.
|
||||
* Display: Show dashboard */
|
||||
|
1
src/Libraries/QRCode/.gitignore
vendored
1
src/Libraries/QRCode/.gitignore
vendored
@ -1 +0,0 @@
|
||||
.DS_Store
|
@ -1,26 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
This library is written and maintained by Richard Moore.
|
||||
Major parts were derived from Project Nayuki's library.
|
||||
|
||||
Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode)
|
||||
Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
@ -1,677 +0,0 @@
|
||||
QRCode
|
||||
======
|
||||
|
||||
A simple library for generating [QR codes](https://en.wikipedia.org/wiki/QR_code) in C,
|
||||
optimized for processing and memory constrained systems.
|
||||
|
||||
**Features:**
|
||||
|
||||
- Stack-based (no heap necessary; but you can use heap if you want)
|
||||
- Low-memory foot print (relatively)
|
||||
- Compile-time stripping of unecessary logic and constants
|
||||
- MIT License; do with this as you please
|
||||
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
To install this library, download and save it to your Arduino libraries directory.
|
||||
|
||||
Rename the directory to QRCode (if downloaded from GitHub, the filename may be
|
||||
qrcode-master; library names may not contain the hyphen, so it must be renamed)
|
||||
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
**Generate a QR Code**
|
||||
|
||||
```c
|
||||
// The structure to manage the QR code
|
||||
QRCode qrcode;
|
||||
|
||||
// Allocate a chunk of memory to store the QR code
|
||||
uint8_t qrcodeBytes[qrcode_getBufferSize()];
|
||||
|
||||
qrcode_initText(&qrcode, qrcodeBytes, 3, ECC_LOW, "HELLO WORLD");
|
||||
```
|
||||
|
||||
**Draw a QR Code**
|
||||
|
||||
How a QR code is used will vary greatly from project to project. For example:
|
||||
|
||||
- Display on an OLED screen (128x64 nicely supports 2 side-by-side version 3 QR codes)
|
||||
- Print as a bitmap on a thermal printer
|
||||
- Store as a BMP (or with a some extra work, possibly a PNG) on an SD card
|
||||
|
||||
The following example prints a QR code to the Serial Monitor (it likely will
|
||||
not be scannable, but is just for demonstration purposes).
|
||||
|
||||
```c
|
||||
for (uint8 y = 0; y < qrcode.size; y++) {
|
||||
for (uint8 x = 0; x < qrcode.size; x++) {
|
||||
if (qrcode_getModule(&qrcode, x, y) {
|
||||
Serial.print("**");
|
||||
} else {
|
||||
Serial.print(" ");
|
||||
}
|
||||
}
|
||||
Serial.print("\n");
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
What is Version, Error Correction and Mode?
|
||||
-------------------------------------------
|
||||
|
||||
A QR code is composed of many little squares, called **modules**, which represent
|
||||
encoded data, with additional error correction (allowing partially damaged QR
|
||||
codes to still be read).
|
||||
|
||||
The **version** of a QR code is a number between 1 and 40 (inclusive), which indicates
|
||||
the size of the QR code. The width and height of a QR code are always equal (it is
|
||||
square) and are equal to `4 * version + 17`.
|
||||
|
||||
The level of **error correction** is a number between 0 and 3 (inclusive), or can be
|
||||
one of the symbolic names ECC_LOW, ECC_MEDIUM, ECC_QUARTILE and ECC_HIGH. Higher
|
||||
levels of error correction sacrifice data capacity, but allow a larger portion of
|
||||
the QR code to be damaged or unreadable.
|
||||
|
||||
The **mode** of a QR code is determined by the data being encoded. Each mode is encoded
|
||||
internally using a compact representation, so lower modes can contain more data.
|
||||
|
||||
- **NUMERIC:** numbers (`0-9`)
|
||||
- **ALPHANUMERIC:** uppercase letters (`A-Z`), numbers (`0-9`), the space (` `), dollar sign (`$`), percent sign (`%`), asterisk (`*`), plus (`+`), minus (`-`), decimal point (`.`), slash (`/`) and colon (`:`).
|
||||
- **BYTE:** any character
|
||||
|
||||
|
||||
Data Capacities
|
||||
---------------
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th rowspan="2">Version</th>
|
||||
<th rowspan="2">Size</th>
|
||||
<th rowspan="2">Error Correction</th>
|
||||
<th colspan="3">Mode</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Numeric</th>
|
||||
<th>Alphanumeric</th>
|
||||
<th>Byte</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">1</td>
|
||||
<td rowspan="4">21 x 21</td>
|
||||
<td>LOW</td><td>41</td><td>25</td><td>17</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>34</td><td>20</td><td>14</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>27</td><td>16</td><td>11</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>17</td><td>10</td><td>7</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">2</td>
|
||||
<td rowspan="4">25 x 25</td>
|
||||
<td>LOW</td><td>77</td><td>47</td><td>32</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>63</td><td>38</td><td>26</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>48</td><td>29</td><td>20</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>34</td><td>20</td><td>14</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">3</td>
|
||||
<td rowspan="4">29 x 29</td>
|
||||
<td>LOW</td><td>127</td><td>77</td><td>53</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>101</td><td>61</td><td>42</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>77</td><td>47</td><td>32</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>58</td><td>35</td><td>24</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">4</td>
|
||||
<td rowspan="4">33 x 33</td>
|
||||
<td>LOW</td><td>187</td><td>114</td><td>78</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>149</td><td>90</td><td>62</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>111</td><td>67</td><td>46</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>82</td><td>50</td><td>34</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">5</td>
|
||||
<td rowspan="4">37 x 37</td>
|
||||
<td>LOW</td><td>255</td><td>154</td><td>106</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>202</td><td>122</td><td>84</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>144</td><td>87</td><td>60</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>106</td><td>64</td><td>44</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">6</td>
|
||||
<td rowspan="4">41 x 41</td>
|
||||
<td>LOW</td><td>322</td><td>195</td><td>134</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>255</td><td>154</td><td>106</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>178</td><td>108</td><td>74</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>139</td><td>84</td><td>58</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">7</td>
|
||||
<td rowspan="4">45 x 45</td>
|
||||
<td>LOW</td><td>370</td><td>224</td><td>154</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>293</td><td>178</td><td>122</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>207</td><td>125</td><td>86</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>154</td><td>93</td><td>64</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">8</td>
|
||||
<td rowspan="4">49 x 49</td>
|
||||
<td>LOW</td><td>461</td><td>279</td><td>192</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>365</td><td>221</td><td>152</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>259</td><td>157</td><td>108</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>202</td><td>122</td><td>84</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">9</td>
|
||||
<td rowspan="4">53 x 53</td>
|
||||
<td>LOW</td><td>552</td><td>335</td><td>230</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>432</td><td>262</td><td>180</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>312</td><td>189</td><td>130</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>235</td><td>143</td><td>98</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">10</td>
|
||||
<td rowspan="4">57 x 57</td>
|
||||
<td>LOW</td><td>652</td><td>395</td><td>271</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>513</td><td>311</td><td>213</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>364</td><td>221</td><td>151</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>288</td><td>174</td><td>119</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">11</td>
|
||||
<td rowspan="4">61 x 61</td>
|
||||
<td>LOW</td><td>772</td><td>468</td><td>321</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>604</td><td>366</td><td>251</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>427</td><td>259</td><td>177</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>331</td><td>200</td><td>137</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">12</td>
|
||||
<td rowspan="4">65 x 65</td>
|
||||
<td>LOW</td><td>883</td><td>535</td><td>367</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>691</td><td>419</td><td>287</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>489</td><td>296</td><td>203</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>374</td><td>227</td><td>155</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">13</td>
|
||||
<td rowspan="4">69 x 69</td>
|
||||
<td>LOW</td><td>1022</td><td>619</td><td>425</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>796</td><td>483</td><td>331</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>580</td><td>352</td><td>241</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>427</td><td>259</td><td>177</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">14</td>
|
||||
<td rowspan="4">73 x 73</td>
|
||||
<td>LOW</td><td>1101</td><td>667</td><td>458</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>871</td><td>528</td><td>362</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>621</td><td>376</td><td>258</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>468</td><td>283</td><td>194</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">15</td>
|
||||
<td rowspan="4">77 x 77</td>
|
||||
<td>LOW</td><td>1250</td><td>758</td><td>520</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>991</td><td>600</td><td>412</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>703</td><td>426</td><td>292</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>530</td><td>321</td><td>220</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">16</td>
|
||||
<td rowspan="4">81 x 81</td>
|
||||
<td>LOW</td><td>1408</td><td>854</td><td>586</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>1082</td><td>656</td><td>450</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>775</td><td>470</td><td>322</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>602</td><td>365</td><td>250</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">17</td>
|
||||
<td rowspan="4">85 x 85</td>
|
||||
<td>LOW</td><td>1548</td><td>938</td><td>644</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>1212</td><td>734</td><td>504</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>876</td><td>531</td><td>364</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>674</td><td>408</td><td>280</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">18</td>
|
||||
<td rowspan="4">89 x 89</td>
|
||||
<td>LOW</td><td>1725</td><td>1046</td><td>718</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>1346</td><td>816</td><td>560</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>948</td><td>574</td><td>394</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>746</td><td>452</td><td>310</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">19</td>
|
||||
<td rowspan="4">93 x 93</td>
|
||||
<td>LOW</td><td>1903</td><td>1153</td><td>792</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>1500</td><td>909</td><td>624</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>1063</td><td>644</td><td>442</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>813</td><td>493</td><td>338</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">20</td>
|
||||
<td rowspan="4">97 x 97</td>
|
||||
<td>LOW</td><td>2061</td><td>1249</td><td>858</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>1600</td><td>970</td><td>666</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>1159</td><td>702</td><td>482</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>919</td><td>557</td><td>382</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">21</td>
|
||||
<td rowspan="4">101 x 101</td>
|
||||
<td>LOW</td><td>2232</td><td>1352</td><td>929</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>1708</td><td>1035</td><td>711</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>1224</td><td>742</td><td>509</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>969</td><td>587</td><td>403</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">22</td>
|
||||
<td rowspan="4">105 x 105</td>
|
||||
<td>LOW</td><td>2409</td><td>1460</td><td>1003</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>1872</td><td>1134</td><td>779</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>1358</td><td>823</td><td>565</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>1056</td><td>640</td><td>439</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">23</td>
|
||||
<td rowspan="4">109 x 109</td>
|
||||
<td>LOW</td><td>2620</td><td>1588</td><td>1091</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>2059</td><td>1248</td><td>857</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>1468</td><td>890</td><td>611</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>1108</td><td>672</td><td>461</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">24</td>
|
||||
<td rowspan="4">113 x 113</td>
|
||||
<td>LOW</td><td>2812</td><td>1704</td><td>1171</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>2188</td><td>1326</td><td>911</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>1588</td><td>963</td><td>661</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>1228</td><td>744</td><td>511</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">25</td>
|
||||
<td rowspan="4">117 x 117</td>
|
||||
<td>LOW</td><td>3057</td><td>1853</td><td>1273</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>2395</td><td>1451</td><td>997</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>1718</td><td>1041</td><td>715</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>1286</td><td>779</td><td>535</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">26</td>
|
||||
<td rowspan="4">121 x 121</td>
|
||||
<td>LOW</td><td>3283</td><td>1990</td><td>1367</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>2544</td><td>1542</td><td>1059</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>1804</td><td>1094</td><td>751</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>1425</td><td>864</td><td>593</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">27</td>
|
||||
<td rowspan="4">125 x 125</td>
|
||||
<td>LOW</td><td>3517</td><td>2132</td><td>1465</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>2701</td><td>1637</td><td>1125</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>1933</td><td>1172</td><td>805</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>1501</td><td>910</td><td>625</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">28</td>
|
||||
<td rowspan="4">129 x 129</td>
|
||||
<td>LOW</td><td>3669</td><td>2223</td><td>1528</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>2857</td><td>1732</td><td>1190</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>2085</td><td>1263</td><td>868</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>1581</td><td>958</td><td>658</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">29</td>
|
||||
<td rowspan="4">133 x 133</td>
|
||||
<td>LOW</td><td>3909</td><td>2369</td><td>1628</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>3035</td><td>1839</td><td>1264</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>2181</td><td>1322</td><td>908</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>1677</td><td>1016</td><td>698</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">30</td>
|
||||
<td rowspan="4">137 x 137</td>
|
||||
<td>LOW</td><td>4158</td><td>2520</td><td>1732</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>3289</td><td>1994</td><td>1370</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>2358</td><td>1429</td><td>982</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>1782</td><td>1080</td><td>742</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">31</td>
|
||||
<td rowspan="4">141 x 141</td>
|
||||
<td>LOW</td><td>4417</td><td>2677</td><td>1840</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>3486</td><td>2113</td><td>1452</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>2473</td><td>1499</td><td>1030</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>1897</td><td>1150</td><td>790</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">32</td>
|
||||
<td rowspan="4">145 x 145</td>
|
||||
<td>LOW</td><td>4686</td><td>2840</td><td>1952</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>3693</td><td>2238</td><td>1538</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>2670</td><td>1618</td><td>1112</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>2022</td><td>1226</td><td>842</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">33</td>
|
||||
<td rowspan="4">149 x 149</td>
|
||||
<td>LOW</td><td>4965</td><td>3009</td><td>2068</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>3909</td><td>2369</td><td>1628</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>2805</td><td>1700</td><td>1168</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>2157</td><td>1307</td><td>898</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">34</td>
|
||||
<td rowspan="4">153 x 153</td>
|
||||
<td>LOW</td><td>5253</td><td>3183</td><td>2188</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>4134</td><td>2506</td><td>1722</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>2949</td><td>1787</td><td>1228</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>2301</td><td>1394</td><td>958</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">35</td>
|
||||
<td rowspan="4">157 x 157</td>
|
||||
<td>LOW</td><td>5529</td><td>3351</td><td>2303</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>4343</td><td>2632</td><td>1809</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>3081</td><td>1867</td><td>1283</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>2361</td><td>1431</td><td>983</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">36</td>
|
||||
<td rowspan="4">161 x 161</td>
|
||||
<td>LOW</td><td>5836</td><td>3537</td><td>2431</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>4588</td><td>2780</td><td>1911</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>3244</td><td>1966</td><td>1351</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>2524</td><td>1530</td><td>1051</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">37</td>
|
||||
<td rowspan="4">165 x 165</td>
|
||||
<td>LOW</td><td>6153</td><td>3729</td><td>2563</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>4775</td><td>2894</td><td>1989</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>3417</td><td>2071</td><td>1423</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>2625</td><td>1591</td><td>1093</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">38</td>
|
||||
<td rowspan="4">169 x 169</td>
|
||||
<td>LOW</td><td>6479</td><td>3927</td><td>2699</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>5039</td><td>3054</td><td>2099</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>3599</td><td>2181</td><td>1499</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>2735</td><td>1658</td><td>1139</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">39</td>
|
||||
<td rowspan="4">173 x 173</td>
|
||||
<td>LOW</td><td>6743</td><td>4087</td><td>2809</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>5313</td><td>3220</td><td>2213</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>3791</td><td>2298</td><td>1579</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>2927</td><td>1774</td><td>1219</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">40</td>
|
||||
<td rowspan="4">177 x 177</td>
|
||||
<td>LOW</td><td>7089</td><td>4296</td><td>2953</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>5596</td><td>3391</td><td>2331</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>3993</td><td>2420</td><td>1663</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>3057</td><td>1852</td><td>1273</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
Special Thanks
|
||||
--------------
|
||||
|
||||
A HUGE thank you to [Project Nayuki](https://www.nayuki.io/) for the
|
||||
[QR code C++ library](https://github.com/nayuki/QR-Code-generator/tree/master/cpp)
|
||||
which was critical in development of this library.
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
MIT License.
|
@ -1,56 +0,0 @@
|
||||
/**
|
||||
* QRCode
|
||||
*
|
||||
* A quick example of generating a QR code.
|
||||
*
|
||||
* This prints the QR code to the serial monitor as solid blocks. Each module
|
||||
* is two characters wide, since the monospace font used in the serial monitor
|
||||
* is approximately twice as tall as wide.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "qrcode.h"
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
// Start time
|
||||
uint32_t dt = millis();
|
||||
|
||||
// Create the QR code
|
||||
QRCode qrcode;
|
||||
uint8_t qrcodeData[qrcode_getBufferSize(3)];
|
||||
qrcode_initText(&qrcode, qrcodeData, 3, 0, "HELLO WORLD");
|
||||
|
||||
// Delta time
|
||||
dt = millis() - dt;
|
||||
Serial.print("QR Code Generation Time: ");
|
||||
Serial.print(dt);
|
||||
Serial.print("\n");
|
||||
|
||||
// Top quiet zone
|
||||
Serial.print("\n\n\n\n");
|
||||
|
||||
for (uint8_t y = 0; y < qrcode.size; y++) {
|
||||
|
||||
// Left quiet zone
|
||||
Serial.print(" ");
|
||||
|
||||
// Each horizontal module
|
||||
for (uint8_t x = 0; x < qrcode.size; x++) {
|
||||
|
||||
// Print each module (UTF-8 \u2588 is a solid block)
|
||||
Serial.print(qrcode_getModule(&qrcode, x, y) ? "\u2588\u2588": " ");
|
||||
|
||||
}
|
||||
|
||||
Serial.print("\n");
|
||||
}
|
||||
|
||||
// Bottom quiet zone
|
||||
Serial.print("\n\n\n\n");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
Data = [
|
||||
["1", "41", "25", "17", "34", "20", "14","27", "16", "11","17", "10", "7"],
|
||||
["2", "77", "47", "32", "63", "38", "26", "48", "29", "20", "34", "20", "14"],
|
||||
["3", "127", "77", "53", "101", "61", "42", "77", "47", "32", "58", "35", "24"],
|
||||
["4", "187", "114", "78", "149", "90", "62", "111", "67", "46", "82", "50", "34"],
|
||||
["5", "255", "154", "106", "202", "122", "84", "144", "87", "60", "106", "64", "44"],
|
||||
["6", "322", "195", "134", "255", "154", "106", "178", "108", "74", "139", "84", "58"],
|
||||
["7", "370", "224", "154", "293", "178", "122", "207", "125", "86", "154", "93", "64"],
|
||||
["8", "461", "279", "192", "365", "221", "152", "259", "157", "108", "202", "122", "84"],
|
||||
["9", "552", "335", "230", "432", "262", "180", "312", "189", "130", "235", "143", "98"],
|
||||
["10", "652", "395", "271", "513", "311", "213", "364", "221", "151", "288", "174", "119"],
|
||||
["11", "772", "468", "321", "604", "366", "251", "427", "259", "177", "331", "200", "137"],
|
||||
["12", "883", "535", "367", "691", "419", "287", "489", "296", "203", "374", "227", "155"],
|
||||
["13", "1022", "619", "425", "796", "483", "331", "580", "352", "241", "427", "259", "177"],
|
||||
["14", "1101", "667", "458", "871", "528", "362", "621", "376", "258", "468", "283", "194"],
|
||||
["15", "1250", "758", "520", "991", "600", "412", "703", "426", "292", "530", "321", "220"],
|
||||
["16", "1408", "854", "586", "1082", "656", "450", "775", "470", "322", "602", "365", "250"],
|
||||
["17", "1548", "938", "644", "1212", "734", "504", "876", "531", "364", "674", "408", "280"],
|
||||
["18", "1725", "1046", "718", "1346", "816", "560", "948", "574", "394", "746", "452", "310"],
|
||||
["19", "1903", "1153", "792", "1500", "909", "624", "1063", "644", "442", "813", "493", "338"],
|
||||
["20", "2061", "1249", "858", "1600", "970", "666", "1159", "702", "482", "919", "557", "382"],
|
||||
["21", "2232", "1352", "929", "1708", "1035", "711", "1224", "742", "509", "969", "587", "403"],
|
||||
["22", "2409", "1460", "1003", "1872", "1134", "779", "1358", "823", "565", "1056", "640", "439"],
|
||||
["23", "2620", "1588", "1091", "2059", "1248", "857", "1468", "890", "611", "1108", "672", "461"],
|
||||
["24", "2812", "1704", "1171", "2188", "1326", "911", "1588", "963", "661", "1228", "744", "511"],
|
||||
["25", "3057", "1853", "1273", "2395", "1451", "997", "1718", "1041", "715", "1286", "779", "535"],
|
||||
["26", "3283", "1990", "1367", "2544", "1542", "1059", "1804", "1094", "751", "1425", "864", "593"],
|
||||
["27", "3517", "2132", "1465", "2701", "1637", "1125", "1933", "1172", "805", "1501", "910", "625"],
|
||||
["28", "3669", "2223", "1528", "2857", "1732", "1190", "2085", "1263", "868", "1581", "958", "658"],
|
||||
["29", "3909", "2369", "1628", "3035", "1839", "1264", "2181", "1322", "908", "1677", "1016", "698"],
|
||||
["30", "4158", "2520", "1732", "3289", "1994", "1370", "2358", "1429", "982", "1782", "1080", "742"],
|
||||
["31", "4417", "2677", "1840", "3486", "2113", "1452", "2473", "1499", "1030", "1897", "1150", "790"],
|
||||
["32", "4686", "2840", "1952", "3693", "2238", "1538", "2670", "1618", "1112", "2022", "1226", "842"],
|
||||
["33", "4965", "3009", "2068", "3909", "2369", "1628", "2805", "1700", "1168", "2157", "1307", "898"],
|
||||
["34", "5253", "3183", "2188", "4134", "2506", "1722", "2949", "1787", "1228", "2301", "1394", "958"],
|
||||
["35", "5529", "3351", "2303", "4343", "2632", "1809", "3081", "1867", "1283", "2361", "1431", "983"],
|
||||
["36", "5836", "3537", "2431", "4588", "2780", "1911", "3244", "1966", "1351", "2524", "1530", "1051"],
|
||||
["37", "6153", "3729", "2563", "4775", "2894", "1989", "3417", "2071", "1423", "2625", "1591", "1093"],
|
||||
["38", "6479", "3927", "2699", "5039", "3054", "2099", "3599", "2181", "1499", "2735", "1658", "1139"],
|
||||
["39", "6743", "4087", "2809", "5313", "3220", "2213", "3791", "2298", "1579", "2927", "1774", "1219"],
|
||||
["40", "7089", "4296", "2953", "5596", "3391", "2331", "3993", "2420", "1663", "3057", "1852", "1273"],
|
||||
]
|
||||
Template = ''' <tr>
|
||||
<td rowspan="4">%s</td>
|
||||
<td rowspan="4">%s</td>
|
||||
<td>LOW</td><td>%s</td><td>%s</td><td>%s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>%s</td><td>%s</td><td>%s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>%s</td><td>%s</td><td>%s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>%s</td><td>%s</td><td>%s</td>
|
||||
</tr>'''
|
||||
|
||||
for data in Data:
|
||||
data = data[:]
|
||||
size = 4 * int(data[0]) + 17
|
||||
data.insert(1, "%d x %d" % (size, size))
|
||||
print Template % tuple(data)
|
@ -1,31 +0,0 @@
|
||||
|
||||
# Datatypes (KEYWORD1)
|
||||
|
||||
bool KEYWORD1
|
||||
uint8_t KEYWORD1
|
||||
QRCode KEYWORD1
|
||||
|
||||
|
||||
# Methods and Functions (KEYWORD2)
|
||||
|
||||
qrcode_getBufferSize KEYWORD2
|
||||
qrcode_initText KEYWORD2
|
||||
qrcode_initBytes KEYWORD2
|
||||
qrcode_getModule KEYWORD2
|
||||
|
||||
|
||||
# Instances (KEYWORD2)
|
||||
|
||||
|
||||
# Constants (LITERAL1)
|
||||
|
||||
false LITERAL1
|
||||
true LITERAL1
|
||||
|
||||
ECC_LOW LITERAL1
|
||||
ECC_MEDIUM LITERAL1
|
||||
ECC_QUARTILE LITERAL1
|
||||
ECC_HIGH LITERAL1
|
||||
MODE_NUMERIC LITERAL1
|
||||
MODE_ALPHANUMERIC LITERAL1
|
||||
MODE_BYTE LITERAL1
|
@ -1,10 +0,0 @@
|
||||
name=QRCode
|
||||
version=0.0.1
|
||||
author=Richard Moore <me@ricmoo.com>
|
||||
maintainer=Richard Moore <me@ricmoo.com>
|
||||
sentence=A simple QR code generation library.
|
||||
paragraph=A simple QR code generation library.
|
||||
category=Other
|
||||
url=https://github.com/ricmoo/qrcode/
|
||||
architectures=*
|
||||
includes=qrcode.h
|
@ -1,876 +0,0 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* This library is written and maintained by Richard Moore.
|
||||
* Major parts were derived from Project Nayuki's library.
|
||||
*
|
||||
* Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode)
|
||||
* Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Special thanks to Nayuki (https://www.nayuki.io/) from which this library was
|
||||
* heavily inspired and compared against.
|
||||
*
|
||||
* See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp
|
||||
*/
|
||||
|
||||
#include "qrcode.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#pragma mark - Error Correction Lookup tables
|
||||
|
||||
#if LOCK_VERSION == 0
|
||||
|
||||
static const uint16_t NUM_ERROR_CORRECTION_CODEWORDS[4][40] = {
|
||||
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
|
||||
{ 10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372}, // Medium
|
||||
{ 7, 10, 15, 20, 26, 36, 40, 48, 60, 72, 80, 96, 104, 120, 132, 144, 168, 180, 196, 224, 224, 252, 270, 300, 312, 336, 360, 390, 420, 450, 480, 510, 540, 570, 570, 600, 630, 660, 720, 750}, // Low
|
||||
{ 17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430}, // High
|
||||
{ 13, 22, 36, 52, 72, 96, 108, 132, 160, 192, 224, 260, 288, 320, 360, 408, 448, 504, 546, 600, 644, 690, 750, 810, 870, 952, 1020, 1050, 1140, 1200, 1290, 1350, 1440, 1530, 1590, 1680, 1770, 1860, 1950, 2040}, // Quartile
|
||||
};
|
||||
|
||||
static const uint8_t NUM_ERROR_CORRECTION_BLOCKS[4][40] = {
|
||||
// Version: (note that index 0 is for padding, and is set to an illegal value)
|
||||
// 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
|
||||
{ 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium
|
||||
{ 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low
|
||||
{ 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High
|
||||
{ 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile
|
||||
};
|
||||
|
||||
static const uint16_t NUM_RAW_DATA_MODULES[40] = {
|
||||
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
|
||||
208, 359, 567, 807, 1079, 1383, 1568, 1936, 2336, 2768, 3232, 3728, 4256, 4651, 5243, 5867, 6523,
|
||||
// 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
|
||||
7211, 7931, 8683, 9252, 10068, 10916, 11796, 12708, 13652, 14628, 15371, 16411, 17483, 18587,
|
||||
// 32, 33, 34, 35, 36, 37, 38, 39, 40
|
||||
19723, 20891, 22091, 23008, 24272, 25568, 26896, 28256, 29648
|
||||
};
|
||||
|
||||
// @TODO: Put other LOCK_VERSIONS here
|
||||
#elif LOCK_VERSION == 3
|
||||
|
||||
static const int16_t NUM_ERROR_CORRECTION_CODEWORDS[4] = {
|
||||
26, 15, 44, 36
|
||||
};
|
||||
|
||||
static const int8_t NUM_ERROR_CORRECTION_BLOCKS[4] = {
|
||||
1, 1, 2, 2
|
||||
};
|
||||
|
||||
static const uint16_t NUM_RAW_DATA_MODULES = 567;
|
||||
|
||||
#else
|
||||
|
||||
#error Unsupported LOCK_VERSION (add it...)
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
static int max(int a, int b) {
|
||||
if (a > b) { return a; }
|
||||
return b;
|
||||
}
|
||||
|
||||
/*
|
||||
static int abs(int value) {
|
||||
if (value < 0) { return -value; }
|
||||
return value;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
#pragma mark - Mode testing and conversion
|
||||
|
||||
static int8_t getAlphanumeric(char c) {
|
||||
|
||||
if (c >= '0' && c <= '9') { return (c - '0'); }
|
||||
if (c >= 'A' && c <= 'Z') { return (c - 'A' + 10); }
|
||||
|
||||
switch (c) {
|
||||
case ' ': return 36;
|
||||
case '$': return 37;
|
||||
case '%': return 38;
|
||||
case '*': return 39;
|
||||
case '+': return 40;
|
||||
case '-': return 41;
|
||||
case '.': return 42;
|
||||
case '/': return 43;
|
||||
case ':': return 44;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool isAlphanumeric(const char *text, uint16_t length) {
|
||||
while (length != 0) {
|
||||
if (getAlphanumeric(text[--length]) == -1) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool isNumeric(const char *text, uint16_t length) {
|
||||
while (length != 0) {
|
||||
char c = text[--length];
|
||||
if (c < '0' || c > '9') { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Counting
|
||||
|
||||
// We store the following tightly packed (less 8) in modeInfo
|
||||
// <=9 <=26 <= 40
|
||||
// NUMERIC ( 10, 12, 14);
|
||||
// ALPHANUMERIC ( 9, 11, 13);
|
||||
// BYTE ( 8, 16, 16);
|
||||
static char getModeBits(uint8_t version, uint8_t mode) {
|
||||
// Note: We use 15 instead of 16; since 15 doesn't exist and we cannot store 16 (8 + 8) in 3 bits
|
||||
// hex(int("".join(reversed([('00' + bin(x - 8)[2:])[-3:] for x in [10, 9, 8, 12, 11, 15, 14, 13, 15]])), 2))
|
||||
unsigned int modeInfo = 0x7bbb80a;
|
||||
|
||||
#if LOCK_VERSION == 0 || LOCK_VERSION > 9
|
||||
if (version > 9) { modeInfo >>= 9; }
|
||||
#endif
|
||||
|
||||
#if LOCK_VERSION == 0 || LOCK_VERSION > 26
|
||||
if (version > 26) { modeInfo >>= 9; }
|
||||
#endif
|
||||
|
||||
char result = 8 + ((modeInfo >> (3 * mode)) & 0x07);
|
||||
if (result == 15) { result = 16; }
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - BitBucket
|
||||
|
||||
typedef struct BitBucket {
|
||||
uint32_t bitOffsetOrWidth;
|
||||
uint16_t capacityBytes;
|
||||
uint8_t *data;
|
||||
} BitBucket;
|
||||
|
||||
/*
|
||||
void bb_dump(BitBucket *bitBuffer) {
|
||||
printf("Buffer: ");
|
||||
for (uint32_t i = 0; i < bitBuffer->capacityBytes; i++) {
|
||||
printf("%02x", bitBuffer->data[i]);
|
||||
if ((i % 4) == 3) { printf(" "); }
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
*/
|
||||
|
||||
static uint16_t bb_getGridSizeBytes(uint8_t size) {
|
||||
return (((size * size) + 7) / 8);
|
||||
}
|
||||
|
||||
static uint16_t bb_getBufferSizeBytes(uint32_t bits) {
|
||||
return ((bits + 7) / 8);
|
||||
}
|
||||
|
||||
static void bb_initBuffer(BitBucket *bitBuffer, uint8_t *data, int32_t capacityBytes) {
|
||||
bitBuffer->bitOffsetOrWidth = 0;
|
||||
bitBuffer->capacityBytes = capacityBytes;
|
||||
bitBuffer->data = data;
|
||||
|
||||
memset(data, 0, bitBuffer->capacityBytes);
|
||||
}
|
||||
|
||||
static void bb_initGrid(BitBucket *bitGrid, uint8_t *data, uint8_t size) {
|
||||
bitGrid->bitOffsetOrWidth = size;
|
||||
bitGrid->capacityBytes = bb_getGridSizeBytes(size);
|
||||
bitGrid->data = data;
|
||||
|
||||
memset(data, 0, bitGrid->capacityBytes);
|
||||
}
|
||||
|
||||
static void bb_appendBits(BitBucket *bitBuffer, uint32_t val, uint8_t length) {
|
||||
uint32_t offset = bitBuffer->bitOffsetOrWidth;
|
||||
for (int8_t i = length - 1; i >= 0; i--, offset++) {
|
||||
bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7));
|
||||
}
|
||||
bitBuffer->bitOffsetOrWidth = offset;
|
||||
}
|
||||
/*
|
||||
void bb_setBits(BitBucket *bitBuffer, uint32_t val, int offset, uint8_t length) {
|
||||
for (int8_t i = length - 1; i >= 0; i--, offset++) {
|
||||
bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7));
|
||||
}
|
||||
}
|
||||
*/
|
||||
static void bb_setBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool on) {
|
||||
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
|
||||
uint8_t mask = 1 << (7 - (offset & 0x07));
|
||||
if (on) {
|
||||
bitGrid->data[offset >> 3] |= mask;
|
||||
} else {
|
||||
bitGrid->data[offset >> 3] &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
static void bb_invertBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool invert) {
|
||||
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
|
||||
uint8_t mask = 1 << (7 - (offset & 0x07));
|
||||
bool on = ((bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0);
|
||||
if (on ^ invert) {
|
||||
bitGrid->data[offset >> 3] |= mask;
|
||||
} else {
|
||||
bitGrid->data[offset >> 3] &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
static bool bb_getBit(BitBucket *bitGrid, uint8_t x, uint8_t y) {
|
||||
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
|
||||
return (bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Drawing Patterns
|
||||
|
||||
// XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical
|
||||
// properties, calling applyMask(m) twice with the same value is equivalent to no change at all.
|
||||
// This means it is possible to apply a mask, undo it, and try another mask. Note that a final
|
||||
// well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.).
|
||||
static void applyMask(BitBucket *modules, BitBucket *isFunction, uint8_t mask) {
|
||||
uint8_t size = modules->bitOffsetOrWidth;
|
||||
|
||||
for (uint8_t y = 0; y < size; y++) {
|
||||
for (uint8_t x = 0; x < size; x++) {
|
||||
if (bb_getBit(isFunction, x, y)) { continue; }
|
||||
|
||||
bool invert = 0;
|
||||
switch (mask) {
|
||||
case 0: invert = (x + y) % 2 == 0; break;
|
||||
case 1: invert = y % 2 == 0; break;
|
||||
case 2: invert = x % 3 == 0; break;
|
||||
case 3: invert = (x + y) % 3 == 0; break;
|
||||
case 4: invert = (x / 3 + y / 2) % 2 == 0; break;
|
||||
case 5: invert = x * y % 2 + x * y % 3 == 0; break;
|
||||
case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break;
|
||||
case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break;
|
||||
}
|
||||
bb_invertBit(modules, x, y, invert);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void setFunctionModule(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y, bool on) {
|
||||
bb_setBit(modules, x, y, on);
|
||||
bb_setBit(isFunction, x, y, true);
|
||||
}
|
||||
|
||||
// Draws a 9*9 finder pattern including the border separator, with the center module at (x, y).
|
||||
static void drawFinderPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) {
|
||||
uint8_t size = modules->bitOffsetOrWidth;
|
||||
|
||||
for (int8_t i = -4; i <= 4; i++) {
|
||||
for (int8_t j = -4; j <= 4; j++) {
|
||||
uint8_t dist = max(abs(i), abs(j)); // Chebyshev/infinity norm
|
||||
int16_t xx = x + j, yy = y + i;
|
||||
if (0 <= xx && xx < size && 0 <= yy && yy < size) {
|
||||
setFunctionModule(modules, isFunction, xx, yy, dist != 2 && dist != 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draws a 5*5 alignment pattern, with the center module at (x, y).
|
||||
static void drawAlignmentPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) {
|
||||
for (int8_t i = -2; i <= 2; i++) {
|
||||
for (int8_t j = -2; j <= 2; j++) {
|
||||
setFunctionModule(modules, isFunction, x + j, y + i, max(abs(i), abs(j)) != 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draws two copies of the format bits (with its own error correction code)
|
||||
// based on the given mask and this object's error correction level field.
|
||||
static void drawFormatBits(BitBucket *modules, BitBucket *isFunction, uint8_t ecc, uint8_t mask) {
|
||||
|
||||
uint8_t size = modules->bitOffsetOrWidth;
|
||||
|
||||
// Calculate error correction code and pack bits
|
||||
uint32_t data = ecc << 3 | mask; // errCorrLvl is uint2, mask is uint3
|
||||
uint32_t rem = data;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
rem = (rem << 1) ^ ((rem >> 9) * 0x537);
|
||||
}
|
||||
|
||||
data = data << 10 | rem;
|
||||
data ^= 0x5412; // uint15
|
||||
|
||||
// Draw first copy
|
||||
for (uint8_t i = 0; i <= 5; i++) {
|
||||
setFunctionModule(modules, isFunction, 8, i, ((data >> i) & 1) != 0);
|
||||
}
|
||||
|
||||
setFunctionModule(modules, isFunction, 8, 7, ((data >> 6) & 1) != 0);
|
||||
setFunctionModule(modules, isFunction, 8, 8, ((data >> 7) & 1) != 0);
|
||||
setFunctionModule(modules, isFunction, 7, 8, ((data >> 8) & 1) != 0);
|
||||
|
||||
for (int8_t i = 9; i < 15; i++) {
|
||||
setFunctionModule(modules, isFunction, 14 - i, 8, ((data >> i) & 1) != 0);
|
||||
}
|
||||
|
||||
// Draw second copy
|
||||
for (int8_t i = 0; i <= 7; i++) {
|
||||
setFunctionModule(modules, isFunction, size - 1 - i, 8, ((data >> i) & 1) != 0);
|
||||
}
|
||||
|
||||
for (int8_t i = 8; i < 15; i++) {
|
||||
setFunctionModule(modules, isFunction, 8, size - 15 + i, ((data >> i) & 1) != 0);
|
||||
}
|
||||
|
||||
setFunctionModule(modules, isFunction, 8, size - 8, true);
|
||||
}
|
||||
|
||||
|
||||
// Draws two copies of the version bits (with its own error correction code),
|
||||
// based on this object's version field (which only has an effect for 7 <= version <= 40).
|
||||
static void drawVersion(BitBucket *modules, BitBucket *isFunction, uint8_t version) {
|
||||
|
||||
int8_t size = modules->bitOffsetOrWidth;
|
||||
|
||||
#if LOCK_VERSION != 0 && LOCK_VERSION < 7
|
||||
return;
|
||||
|
||||
#else
|
||||
if (version < 7) { return; }
|
||||
|
||||
// Calculate error correction code and pack bits
|
||||
uint32_t rem = version; // version is uint6, in the range [7, 40]
|
||||
for (uint8_t i = 0; i < 12; i++) {
|
||||
rem = (rem << 1) ^ ((rem >> 11) * 0x1F25);
|
||||
}
|
||||
|
||||
uint32_t data = version << 12 | rem; // uint18
|
||||
|
||||
// Draw two copies
|
||||
for (uint8_t i = 0; i < 18; i++) {
|
||||
bool bit = ((data >> i) & 1) != 0;
|
||||
uint8_t a = size - 11 + i % 3, b = i / 3;
|
||||
setFunctionModule(modules, isFunction, a, b, bit);
|
||||
setFunctionModule(modules, isFunction, b, a, bit);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
static void drawFunctionPatterns(BitBucket *modules, BitBucket *isFunction, uint8_t version, uint8_t ecc) {
|
||||
|
||||
uint8_t size = modules->bitOffsetOrWidth;
|
||||
|
||||
// Draw the horizontal and vertical timing patterns
|
||||
for (uint8_t i = 0; i < size; i++) {
|
||||
setFunctionModule(modules, isFunction, 6, i, i % 2 == 0);
|
||||
setFunctionModule(modules, isFunction, i, 6, i % 2 == 0);
|
||||
}
|
||||
|
||||
// Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules)
|
||||
drawFinderPattern(modules, isFunction, 3, 3);
|
||||
drawFinderPattern(modules, isFunction, size - 4, 3);
|
||||
drawFinderPattern(modules, isFunction, 3, size - 4);
|
||||
|
||||
#if LOCK_VERSION == 0 || LOCK_VERSION > 1
|
||||
|
||||
if (version > 1) {
|
||||
|
||||
// Draw the numerous alignment patterns
|
||||
|
||||
uint8_t alignCount = version / 7 + 2;
|
||||
uint8_t step;
|
||||
if (version != 32) {
|
||||
step = (version * 4 + alignCount * 2 + 1) / (2 * alignCount - 2) * 2; // ceil((size - 13) / (2*numAlign - 2)) * 2
|
||||
} else { // C-C-C-Combo breaker!
|
||||
step = 26;
|
||||
}
|
||||
|
||||
uint8_t alignPositionIndex = alignCount - 1;
|
||||
uint8_t alignPosition[alignCount];
|
||||
|
||||
alignPosition[0] = 6;
|
||||
|
||||
uint8_t size = version * 4 + 17;
|
||||
for (uint8_t i = 0, pos = size - 7; i < alignCount - 1; i++, pos -= step) {
|
||||
alignPosition[alignPositionIndex--] = pos;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < alignCount; i++) {
|
||||
for (uint8_t j = 0; j < alignCount; j++) {
|
||||
if ((i == 0 && j == 0) || (i == 0 && j == alignCount - 1) || (i == alignCount - 1 && j == 0)) {
|
||||
continue; // Skip the three finder corners
|
||||
} else {
|
||||
drawAlignmentPattern(modules, isFunction, alignPosition[i], alignPosition[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Draw configuration data
|
||||
drawFormatBits(modules, isFunction, ecc, 0); // Dummy mask value; overwritten later in the constructor
|
||||
drawVersion(modules, isFunction, version);
|
||||
}
|
||||
|
||||
|
||||
// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire
|
||||
// data area of this QR Code symbol. Function modules need to be marked off before this is called.
|
||||
static void drawCodewords(BitBucket *modules, BitBucket *isFunction, BitBucket *codewords) {
|
||||
|
||||
uint32_t bitLength = codewords->bitOffsetOrWidth;
|
||||
uint8_t *data = codewords->data;
|
||||
|
||||
uint8_t size = modules->bitOffsetOrWidth;
|
||||
|
||||
// Bit index into the data
|
||||
uint32_t i = 0;
|
||||
|
||||
// Do the funny zigzag scan
|
||||
for (int16_t right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair
|
||||
if (right == 6) { right = 5; }
|
||||
|
||||
for (uint8_t vert = 0; vert < size; vert++) { // Vertical counter
|
||||
for (int j = 0; j < 2; j++) {
|
||||
uint8_t x = right - j; // Actual x coordinate
|
||||
bool upwards = ((right & 2) == 0) ^ (x < 6);
|
||||
uint8_t y = upwards ? size - 1 - vert : vert; // Actual y coordinate
|
||||
if (!bb_getBit(isFunction, x, y) && i < bitLength) {
|
||||
bb_setBit(modules, x, y, ((data[i >> 3] >> (7 - (i & 7))) & 1) != 0);
|
||||
i++;
|
||||
}
|
||||
// If there are any remainder bits (0 to 7), they are already
|
||||
// set to 0/false/white when the grid of modules was initialized
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#pragma mark - Penalty Calculation
|
||||
|
||||
#define PENALTY_N1 3
|
||||
#define PENALTY_N2 3
|
||||
#define PENALTY_N3 40
|
||||
#define PENALTY_N4 10
|
||||
|
||||
// Calculates and returns the penalty score based on state of this QR Code's current modules.
|
||||
// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score.
|
||||
// @TODO: This can be optimized by working with the bytes instead of bits.
|
||||
static uint32_t getPenaltyScore(BitBucket *modules) {
|
||||
uint32_t result = 0;
|
||||
|
||||
uint8_t size = modules->bitOffsetOrWidth;
|
||||
|
||||
// Adjacent modules in row having same color
|
||||
for (uint8_t y = 0; y < size; y++) {
|
||||
|
||||
bool colorX = bb_getBit(modules, 0, y);
|
||||
for (uint8_t x = 1, runX = 1; x < size; x++) {
|
||||
bool cx = bb_getBit(modules, x, y);
|
||||
if (cx != colorX) {
|
||||
colorX = cx;
|
||||
runX = 1;
|
||||
|
||||
} else {
|
||||
runX++;
|
||||
if (runX == 5) {
|
||||
result += PENALTY_N1;
|
||||
} else if (runX > 5) {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adjacent modules in column having same color
|
||||
for (uint8_t x = 0; x < size; x++) {
|
||||
bool colorY = bb_getBit(modules, x, 0);
|
||||
for (uint8_t y = 1, runY = 1; y < size; y++) {
|
||||
bool cy = bb_getBit(modules, x, y);
|
||||
if (cy != colorY) {
|
||||
colorY = cy;
|
||||
runY = 1;
|
||||
} else {
|
||||
runY++;
|
||||
if (runY == 5) {
|
||||
result += PENALTY_N1;
|
||||
} else if (runY > 5) {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t black = 0;
|
||||
for (uint8_t y = 0; y < size; y++) {
|
||||
uint16_t bitsRow = 0, bitsCol = 0;
|
||||
for (uint8_t x = 0; x < size; x++) {
|
||||
bool color = bb_getBit(modules, x, y);
|
||||
|
||||
// 2*2 blocks of modules having same color
|
||||
if (x > 0 && y > 0) {
|
||||
bool colorUL = bb_getBit(modules, x - 1, y - 1);
|
||||
bool colorUR = bb_getBit(modules, x, y - 1);
|
||||
bool colorL = bb_getBit(modules, x - 1, y);
|
||||
if (color == colorUL && color == colorUR && color == colorL) {
|
||||
result += PENALTY_N2;
|
||||
}
|
||||
}
|
||||
|
||||
// Finder-like pattern in rows and columns
|
||||
bitsRow = ((bitsRow << 1) & 0x7FF) | color;
|
||||
bitsCol = ((bitsCol << 1) & 0x7FF) | bb_getBit(modules, y, x);
|
||||
|
||||
// Needs 11 bits accumulated
|
||||
if (x >= 10) {
|
||||
if (bitsRow == 0x05D || bitsRow == 0x5D0) {
|
||||
result += PENALTY_N3;
|
||||
}
|
||||
if (bitsCol == 0x05D || bitsCol == 0x5D0) {
|
||||
result += PENALTY_N3;
|
||||
}
|
||||
}
|
||||
|
||||
// Balance of black and white modules
|
||||
if (color) { black++; }
|
||||
}
|
||||
}
|
||||
|
||||
// Find smallest k such that (45-5k)% <= dark/total <= (55+5k)%
|
||||
uint16_t total = size * size;
|
||||
for (uint16_t k = 0; black * 20 < (9 - k) * total || black * 20 > (11 + k) * total; k++) {
|
||||
result += PENALTY_N4;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Reed-Solomon Generator
|
||||
|
||||
static uint8_t rs_multiply(uint8_t x, uint8_t y) {
|
||||
// Russian peasant multiplication
|
||||
// See: https://en.wikipedia.org/wiki/Ancient_Egyptian_multiplication
|
||||
uint16_t z = 0;
|
||||
for (int8_t i = 7; i >= 0; i--) {
|
||||
z = (z << 1) ^ ((z >> 7) * 0x11D);
|
||||
z ^= ((y >> i) & 1) * x;
|
||||
}
|
||||
return z;
|
||||
}
|
||||
|
||||
static void rs_init(uint8_t degree, uint8_t *coeff) {
|
||||
memset(coeff, 0, degree);
|
||||
coeff[degree - 1] = 1;
|
||||
|
||||
// Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}),
|
||||
// drop the highest term, and store the rest of the coefficients in order of descending powers.
|
||||
// Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D).
|
||||
uint16_t root = 1;
|
||||
for (uint8_t i = 0; i < degree; i++) {
|
||||
// Multiply the current product by (x - r^i)
|
||||
for (uint8_t j = 0; j < degree; j++) {
|
||||
coeff[j] = rs_multiply(coeff[j], root);
|
||||
if (j + 1 < degree) {
|
||||
coeff[j] ^= coeff[j + 1];
|
||||
}
|
||||
}
|
||||
root = (root << 1) ^ ((root >> 7) * 0x11D); // Multiply by 0x02 mod GF(2^8/0x11D)
|
||||
}
|
||||
}
|
||||
|
||||
static void rs_getRemainder(uint8_t degree, uint8_t *coeff, uint8_t *data, uint8_t length, uint8_t *result, uint8_t stride) {
|
||||
// Compute the remainder by performing polynomial division
|
||||
|
||||
//for (uint8_t i = 0; i < degree; i++) { result[] = 0; }
|
||||
//memset(result, 0, degree);
|
||||
|
||||
for (uint8_t i = 0; i < length; i++) {
|
||||
uint8_t factor = data[i] ^ result[0];
|
||||
for (uint8_t j = 1; j < degree; j++) {
|
||||
result[(j - 1) * stride] = result[j * stride];
|
||||
}
|
||||
result[(degree - 1) * stride] = 0;
|
||||
|
||||
for (uint8_t j = 0; j < degree; j++) {
|
||||
result[j * stride] ^= rs_multiply(coeff[j], factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#pragma mark - QrCode
|
||||
|
||||
static int8_t encodeDataCodewords(BitBucket *dataCodewords, const uint8_t *text, uint16_t length, uint8_t version) {
|
||||
int8_t mode = MODE_BYTE;
|
||||
|
||||
if (isNumeric((char*)text, length)) {
|
||||
mode = MODE_NUMERIC;
|
||||
bb_appendBits(dataCodewords, 1 << MODE_NUMERIC, 4);
|
||||
bb_appendBits(dataCodewords, length, getModeBits(version, MODE_NUMERIC));
|
||||
|
||||
uint16_t accumData = 0;
|
||||
uint8_t accumCount = 0;
|
||||
for (uint16_t i = 0; i < length; i++) {
|
||||
accumData = accumData * 10 + ((char)(text[i]) - '0');
|
||||
accumCount++;
|
||||
if (accumCount == 3) {
|
||||
bb_appendBits(dataCodewords, accumData, 10);
|
||||
accumData = 0;
|
||||
accumCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 1 or 2 digits remaining
|
||||
if (accumCount > 0) {
|
||||
bb_appendBits(dataCodewords, accumData, accumCount * 3 + 1);
|
||||
}
|
||||
|
||||
} else if (isAlphanumeric((char*)text, length)) {
|
||||
mode = MODE_ALPHANUMERIC;
|
||||
bb_appendBits(dataCodewords, 1 << MODE_ALPHANUMERIC, 4);
|
||||
bb_appendBits(dataCodewords, length, getModeBits(version, MODE_ALPHANUMERIC));
|
||||
|
||||
uint16_t accumData = 0;
|
||||
uint8_t accumCount = 0;
|
||||
for (uint16_t i = 0; i < length; i++) {
|
||||
accumData = accumData * 45 + getAlphanumeric((char)(text[i]));
|
||||
accumCount++;
|
||||
if (accumCount == 2) {
|
||||
bb_appendBits(dataCodewords, accumData, 11);
|
||||
accumData = 0;
|
||||
accumCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 1 character remaining
|
||||
if (accumCount > 0) {
|
||||
bb_appendBits(dataCodewords, accumData, 6);
|
||||
}
|
||||
|
||||
} else {
|
||||
bb_appendBits(dataCodewords, 1 << MODE_BYTE, 4);
|
||||
bb_appendBits(dataCodewords, length, getModeBits(version, MODE_BYTE));
|
||||
for (uint16_t i = 0; i < length; i++) {
|
||||
bb_appendBits(dataCodewords, (char)(text[i]), 8);
|
||||
}
|
||||
}
|
||||
|
||||
//bb_setBits(dataCodewords, length, 4, getModeBits(version, mode));
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
static void performErrorCorrection(uint8_t version, uint8_t ecc, BitBucket *data) {
|
||||
|
||||
// See: http://www.thonky.com/qr-code-tutorial/structure-final-message
|
||||
|
||||
#if LOCK_VERSION == 0
|
||||
uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc][version - 1];
|
||||
uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc][version - 1];
|
||||
uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1];
|
||||
#else
|
||||
uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc];
|
||||
uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc];
|
||||
uint16_t moduleCount = NUM_RAW_DATA_MODULES;
|
||||
#endif
|
||||
|
||||
uint8_t blockEccLen = totalEcc / numBlocks;
|
||||
uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks;
|
||||
uint8_t shortBlockLen = moduleCount / 8 / numBlocks;
|
||||
|
||||
uint8_t shortDataBlockLen = shortBlockLen - blockEccLen;
|
||||
|
||||
uint8_t result[data->capacityBytes];
|
||||
memset(result, 0, sizeof(result));
|
||||
|
||||
uint8_t coeff[blockEccLen];
|
||||
rs_init(blockEccLen, coeff);
|
||||
|
||||
uint16_t offset = 0;
|
||||
uint8_t *dataBytes = data->data;
|
||||
|
||||
|
||||
// Interleave all short blocks
|
||||
for (uint8_t i = 0; i < shortDataBlockLen; i++) {
|
||||
uint16_t index = i;
|
||||
uint8_t stride = shortDataBlockLen;
|
||||
for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) {
|
||||
result[offset++] = dataBytes[index];
|
||||
|
||||
#if LOCK_VERSION == 0 || LOCK_VERSION >= 5
|
||||
if (blockNum == numShortBlocks) { stride++; }
|
||||
#endif
|
||||
index += stride;
|
||||
}
|
||||
}
|
||||
|
||||
// Version less than 5 only have short blocks
|
||||
#if LOCK_VERSION == 0 || LOCK_VERSION >= 5
|
||||
{
|
||||
// Interleave long blocks
|
||||
uint16_t index = shortDataBlockLen * (numShortBlocks + 1);
|
||||
uint8_t stride = shortDataBlockLen;
|
||||
for (uint8_t blockNum = 0; blockNum < numBlocks - numShortBlocks; blockNum++) {
|
||||
result[offset++] = dataBytes[index];
|
||||
|
||||
if (blockNum == 0) { stride++; }
|
||||
index += stride;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Add all ecc blocks, interleaved
|
||||
uint8_t blockSize = shortDataBlockLen;
|
||||
for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) {
|
||||
|
||||
#if LOCK_VERSION == 0 || LOCK_VERSION >= 5
|
||||
if (blockNum == numShortBlocks) { blockSize++; }
|
||||
#endif
|
||||
rs_getRemainder(blockEccLen, coeff, dataBytes, blockSize, &result[offset + blockNum], numBlocks);
|
||||
dataBytes += blockSize;
|
||||
}
|
||||
|
||||
memcpy(data->data, result, data->capacityBytes);
|
||||
data->bitOffsetOrWidth = moduleCount;
|
||||
}
|
||||
|
||||
// We store the Format bits tightly packed into a single byte (each of the 4 modes is 2 bits)
|
||||
// The format bits can be determined by ECC_FORMAT_BITS >> (2 * ecc)
|
||||
static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0);
|
||||
|
||||
|
||||
#pragma mark - Public QRCode functions
|
||||
|
||||
uint16_t qrcode_getBufferSize(uint8_t version) {
|
||||
return bb_getGridSizeBytes(4 * version + 17);
|
||||
}
|
||||
|
||||
// @TODO: Return error if data is too big.
|
||||
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length) {
|
||||
uint8_t size = version * 4 + 17;
|
||||
qrcode->version = version;
|
||||
qrcode->size = size;
|
||||
qrcode->ecc = ecc;
|
||||
qrcode->modules = modules;
|
||||
|
||||
uint8_t eccFormatBits = (ECC_FORMAT_BITS >> (2 * ecc)) & 0x03;
|
||||
|
||||
#if LOCK_VERSION == 0
|
||||
uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1];
|
||||
uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits][version - 1];
|
||||
#else
|
||||
version = LOCK_VERSION;
|
||||
uint16_t moduleCount = NUM_RAW_DATA_MODULES;
|
||||
uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits];
|
||||
#endif
|
||||
|
||||
struct BitBucket codewords;
|
||||
uint8_t codewordBytes[bb_getBufferSizeBytes(moduleCount)];
|
||||
bb_initBuffer(&codewords, codewordBytes, (int32_t)sizeof(codewordBytes));
|
||||
|
||||
// Place the data code words into the buffer
|
||||
int8_t mode = encodeDataCodewords(&codewords, data, length, version);
|
||||
|
||||
if (mode < 0) { return -1; }
|
||||
qrcode->mode = mode;
|
||||
|
||||
// Add terminator and pad up to a byte if applicable
|
||||
uint32_t padding = (dataCapacity * 8) - codewords.bitOffsetOrWidth;
|
||||
if (padding > 4) { padding = 4; }
|
||||
bb_appendBits(&codewords, 0, padding);
|
||||
bb_appendBits(&codewords, 0, (8 - codewords.bitOffsetOrWidth % 8) % 8);
|
||||
|
||||
// Pad with alternate bytes until data capacity is reached
|
||||
for (uint8_t padByte = 0xEC; codewords.bitOffsetOrWidth < (dataCapacity * 8); padByte ^= 0xEC ^ 0x11) {
|
||||
bb_appendBits(&codewords, padByte, 8);
|
||||
}
|
||||
|
||||
BitBucket modulesGrid;
|
||||
bb_initGrid(&modulesGrid, modules, size);
|
||||
|
||||
BitBucket isFunctionGrid;
|
||||
uint8_t isFunctionGridBytes[bb_getGridSizeBytes(size)];
|
||||
bb_initGrid(&isFunctionGrid, isFunctionGridBytes, size);
|
||||
|
||||
// Draw function patterns, draw all codewords, do masking
|
||||
drawFunctionPatterns(&modulesGrid, &isFunctionGrid, version, eccFormatBits);
|
||||
performErrorCorrection(version, eccFormatBits, &codewords);
|
||||
drawCodewords(&modulesGrid, &isFunctionGrid, &codewords);
|
||||
|
||||
// Find the best (lowest penalty) mask
|
||||
uint8_t mask = 0;
|
||||
int32_t minPenalty = INT32_MAX;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, i);
|
||||
applyMask(&modulesGrid, &isFunctionGrid, i);
|
||||
int penalty = getPenaltyScore(&modulesGrid);
|
||||
if (penalty < minPenalty) {
|
||||
mask = i;
|
||||
minPenalty = penalty;
|
||||
}
|
||||
applyMask(&modulesGrid, &isFunctionGrid, i); // Undoes the mask due to XOR
|
||||
}
|
||||
|
||||
qrcode->mask = mask;
|
||||
|
||||
// Overwrite old format bits
|
||||
drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, mask);
|
||||
|
||||
// Apply the final choice of mask
|
||||
applyMask(&modulesGrid, &isFunctionGrid, mask);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data) {
|
||||
return qrcode_initBytes(qrcode, modules, version, ecc, (uint8_t*)data, strlen(data));
|
||||
}
|
||||
|
||||
bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y) {
|
||||
if (x < 0 || x >= qrcode->size || y < 0 || y >= qrcode->size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t offset = y * qrcode->size + x;
|
||||
return (qrcode->modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0;
|
||||
}
|
||||
|
||||
/*
|
||||
uint8_t qrcode_getHexLength(QRCode *qrcode) {
|
||||
return ((qrcode->size * qrcode->size) + 7) / 4;
|
||||
}
|
||||
|
||||
void qrcode_getHex(QRCode *qrcode, char *result) {
|
||||
|
||||
}
|
||||
*/
|
@ -1,99 +0,0 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* This library is written and maintained by Richard Moore.
|
||||
* Major parts were derived from Project Nayuki's library.
|
||||
*
|
||||
* Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode)
|
||||
* Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Special thanks to Nayuki (https://www.nayuki.io/) from which this library was
|
||||
* heavily inspired and compared against.
|
||||
*
|
||||
* See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __QRCODE_H_
|
||||
#define __QRCODE_H_
|
||||
|
||||
#ifndef __cplusplus
|
||||
typedef unsigned char bool;
|
||||
static const bool false = 0;
|
||||
static const bool true = 1;
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
// QR Code Format Encoding
|
||||
#define MODE_NUMERIC 0
|
||||
#define MODE_ALPHANUMERIC 1
|
||||
#define MODE_BYTE 2
|
||||
|
||||
|
||||
// Error Correction Code Levels
|
||||
#define ECC_LOW 0
|
||||
#define ECC_MEDIUM 1
|
||||
#define ECC_QUARTILE 2
|
||||
#define ECC_HIGH 3
|
||||
|
||||
|
||||
// If set to non-zero, this library can ONLY produce QR codes at that version
|
||||
// This saves a lot of dynamic memory, as the codeword tables are skipped
|
||||
#ifndef LOCK_VERSION
|
||||
#define LOCK_VERSION 0
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct QRCode {
|
||||
uint8_t version;
|
||||
uint8_t size;
|
||||
uint8_t ecc;
|
||||
uint8_t mode;
|
||||
uint8_t mask;
|
||||
uint8_t *modules;
|
||||
} QRCode;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"{
|
||||
#endif /* __cplusplus */
|
||||
|
||||
|
||||
|
||||
uint16_t qrcode_getBufferSize(uint8_t version);
|
||||
|
||||
int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data);
|
||||
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length);
|
||||
|
||||
bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y);
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
|
||||
#endif /* __QRCODE_H_ */
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
Reference in New Issue
Block a user