From b2c55c38dcc024f2ef3007843bf82f53841cbff4 Mon Sep 17 00:00:00 2001 From: nick-4711 Date: Thu, 25 Apr 2024 16:11:49 +0700 Subject: [PATCH 01/47] better handle the 304 and 400 case --- examples/OneOpenAir/OtaHandler.h | 50 +++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/examples/OneOpenAir/OtaHandler.h b/examples/OneOpenAir/OtaHandler.h index e26848f..5f75d8b 100644 --- a/examples/OneOpenAir/OtaHandler.h +++ b/examples/OneOpenAir/OtaHandler.h @@ -9,6 +9,13 @@ #define OTA_BUF_SIZE 512 #define URL_BUF_SIZE 256 +enum OtaUpdateOutcome { + UPDATE_PERFORMED, + ALREADY_UP_TO_DATE, + UPDATE_FAILED, + UDPATE_SKIPPED +}; + class OtaHandler { public: @@ -20,45 +27,54 @@ public: url += GIT_VERSION; char urlAsChar[URL_BUF_SIZE]; url.toCharArray(urlAsChar, URL_BUF_SIZE); - Serial.printf("checking for new ota @ %s\n", urlAsChar); + Serial.printf("checking for new OTA update @ %s\n", urlAsChar); esp_http_client_config_t config = {}; config.url = urlAsChar; esp_err_t ret = attemptToPerformOta(&config); Serial.println(ret); - if (ret == 0) { - Serial.println("OTA completed"); + if (ret == OtaUpdateOutcome::UPDATE_PERFORMED) { + Serial.println("OTA update performed, restarting ..."); esp_restart(); - } else { - Serial.println("OTA failed, maybe already up to date"); - } + } } private: - int attemptToPerformOta(const esp_http_client_config_t *config) { + OtaUpdateOutcome attemptToPerformOta(const esp_http_client_config_t *config) { esp_http_client_handle_t client = esp_http_client_init(config); if (client == NULL) { Serial.println("Failed to initialize HTTP connection"); - return -1; + return OtaUpdateOutcome::UPDATE_FAILED; } esp_err_t err = esp_http_client_open(client, 0); if (err != ESP_OK) { esp_http_client_cleanup(client); Serial.printf("Failed to open HTTP connection: %s\n", esp_err_to_name(err)); - return -1; + return OtaUpdateOutcome::UPDATE_FAILED; } esp_http_client_fetch_headers(client); + int httpStatusCode = esp_http_client_get_status_code(client); + if (httpStatusCode == 304) { + Serial.println("Firmware is already up to date"); + cleanupHttp(client); + return OtaUpdateOutcome::ALREADY_UP_TO_DATE; + } else if (httpStatusCode != 200) { + Serial.printf("Firmware update skipped, the server returned %d\n", httpStatusCode); + cleanupHttp(client); + return OtaUpdateOutcome::UDPATE_SKIPPED; + } + esp_ota_handle_t update_handle = 0; const esp_partition_t *update_partition = NULL; - Serial.println("Starting OTA ..."); + Serial.println("Starting OTA update ..."); update_partition = esp_ota_get_next_update_partition(NULL); if (update_partition == NULL) { Serial.println("Passive OTA partition not found"); cleanupHttp(client); - return ESP_FAIL; + return OtaUpdateOutcome::UPDATE_FAILED; } Serial.printf("Writing to partition subtype %d at offset 0x%x\n", update_partition->subtype, update_partition->address); @@ -67,14 +83,14 @@ private: if (err != ESP_OK) { Serial.printf("esp_ota_begin failed, error=%d\n", err); cleanupHttp(client); - return err; + return OtaUpdateOutcome::UPDATE_FAILED; } esp_err_t ota_write_err = ESP_OK; char *upgrade_data_buf = (char *)malloc(OTA_BUF_SIZE); if (!upgrade_data_buf) { Serial.println("Couldn't allocate memory for data buffer"); - return ESP_ERR_NO_MEM; + return OtaUpdateOutcome::UPDATE_FAILED; } int binary_file_len = 0; @@ -104,18 +120,18 @@ private: esp_err_t ota_end_err = esp_ota_end(update_handle); if (ota_write_err != ESP_OK) { Serial.printf("Error: esp_ota_write failed! err=0x%d\n", err); - return ota_write_err; + return OtaUpdateOutcome::UPDATE_FAILED; } else if (ota_end_err != ESP_OK) { Serial.printf("Error: esp_ota_end failed! err=0x%d. Image is invalid", ota_end_err); - return ota_end_err; + return OtaUpdateOutcome::UPDATE_FAILED; } err = esp_ota_set_boot_partition(update_partition); if (err != ESP_OK) { Serial.printf("esp_ota_set_boot_partition failed! err=0x%d\n", err); - return err; + return OtaUpdateOutcome::UPDATE_FAILED; } - return 0; + return OtaUpdateOutcome::UPDATE_PERFORMED; } void cleanupHttp(esp_http_client_handle_t client) { From 1580cd51aad4a3cac33b00d8ca4034072fcb8e1a Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Mon, 29 Apr 2024 09:54:37 +0700 Subject: [PATCH 02/47] fix: source code formating --- examples/OneOpenAir/OtaHandler.h | 224 ++++++++++++++++--------------- 1 file changed, 113 insertions(+), 111 deletions(-) diff --git a/examples/OneOpenAir/OtaHandler.h b/examples/OneOpenAir/OtaHandler.h index 5f75d8b..067d5bf 100644 --- a/examples/OneOpenAir/OtaHandler.h +++ b/examples/OneOpenAir/OtaHandler.h @@ -1,144 +1,146 @@ #ifndef _OTA_HANDLER_H_ #define _OTA_HANDLER_H_ -#include -#include -#include #include +#include +#include +#include #define OTA_BUF_SIZE 512 #define URL_BUF_SIZE 256 enum OtaUpdateOutcome { - UPDATE_PERFORMED, - ALREADY_UP_TO_DATE, - UPDATE_FAILED, - UDPATE_SKIPPED + UPDATE_PERFORMED, + ALREADY_UP_TO_DATE, + UPDATE_FAILED, + UDPATE_SKIPPED }; - 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 update @ %s\n", urlAsChar); - esp_http_client_config_t config = {}; - config.url = urlAsChar; - esp_err_t ret = attemptToPerformOta(&config); - Serial.println(ret); - if (ret == OtaUpdateOutcome::UPDATE_PERFORMED) { - Serial.println("OTA update performed, restarting ..."); - esp_restart(); - } + esp_http_client_config_t config = {}; + config.url = urlAsChar; + esp_err_t ret = attemptToPerformOta(&config); + Serial.println(ret); + if (ret == OtaUpdateOutcome::UPDATE_PERFORMED) { + Serial.println("OTA update performed, restarting ..."); + esp_restart(); } + } private: + 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; + } - OtaUpdateOutcome attemptToPerformOta(const esp_http_client_config_t *config) { - esp_http_client_handle_t client = esp_http_client_init(config); - if (client == NULL) { - Serial.println("Failed to initialize HTTP connection"); - return OtaUpdateOutcome::UPDATE_FAILED; - } + esp_err_t err = esp_http_client_open(client, 0); + if (err != ESP_OK) { + esp_http_client_cleanup(client); + Serial.printf("Failed to open HTTP connection: %s\n", + esp_err_to_name(err)); + return OtaUpdateOutcome::UPDATE_FAILED; + } + esp_http_client_fetch_headers(client); - esp_err_t err = esp_http_client_open(client, 0); - if (err != ESP_OK) { - esp_http_client_cleanup(client); - Serial.printf("Failed to open HTTP connection: %s\n", esp_err_to_name(err)); - return OtaUpdateOutcome::UPDATE_FAILED; - } - esp_http_client_fetch_headers(client); + int httpStatusCode = esp_http_client_get_status_code(client); + if (httpStatusCode == 304) { + Serial.println("Firmware is already up to date"); + cleanupHttp(client); + return OtaUpdateOutcome::ALREADY_UP_TO_DATE; + } else if (httpStatusCode != 200) { + Serial.printf("Firmware update skipped, the server returned %d\n", + httpStatusCode); + cleanupHttp(client); + return OtaUpdateOutcome::UDPATE_SKIPPED; + } - int httpStatusCode = esp_http_client_get_status_code(client); - if (httpStatusCode == 304) { - Serial.println("Firmware is already up to date"); - cleanupHttp(client); - return OtaUpdateOutcome::ALREADY_UP_TO_DATE; - } else if (httpStatusCode != 200) { - Serial.printf("Firmware update skipped, the server returned %d\n", httpStatusCode); - cleanupHttp(client); - return OtaUpdateOutcome::UDPATE_SKIPPED; - } + esp_ota_handle_t update_handle = 0; + const esp_partition_t *update_partition = NULL; + Serial.println("Starting OTA update ..."); + update_partition = esp_ota_get_next_update_partition(NULL); + if (update_partition == NULL) { + Serial.println("Passive OTA partition not found"); + cleanupHttp(client); + return OtaUpdateOutcome::UPDATE_FAILED; + } + Serial.printf("Writing to partition subtype %d at offset 0x%x\n", + update_partition->subtype, update_partition->address); - esp_ota_handle_t update_handle = 0; - const esp_partition_t *update_partition = NULL; - Serial.println("Starting OTA update ..."); - update_partition = esp_ota_get_next_update_partition(NULL); - if (update_partition == NULL) { - Serial.println("Passive OTA partition not found"); - cleanupHttp(client); - return OtaUpdateOutcome::UPDATE_FAILED; - } - Serial.printf("Writing to partition subtype %d at offset 0x%x\n", - update_partition->subtype, update_partition->address); + err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle); + if (err != ESP_OK) { + Serial.printf("esp_ota_begin failed, error=%d\n", err); + cleanupHttp(client); + return OtaUpdateOutcome::UPDATE_FAILED; + } - 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; - } + 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; - 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); - } - } - 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); + 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) { - 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; + break; } + binary_file_len += data_read; + // Serial.printf("Written image length %d\n", binary_file_len); + } + } + free(upgrade_data_buf); + cleanupHttp(client); + Serial.printf("# of bytes written: %d\n", binary_file_len); - 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; + 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 From 88808a2ad2ab46b385fac5525509a37ed394e30d Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Mon, 29 Apr 2024 19:34:15 +0700 Subject: [PATCH 03/47] Revert "fix: source code formating" This reverts commit 1580cd51aad4a3cac33b00d8ca4034072fcb8e1a. --- examples/OneOpenAir/OtaHandler.h | 236 +++++++++++++++---------------- 1 file changed, 117 insertions(+), 119 deletions(-) diff --git a/examples/OneOpenAir/OtaHandler.h b/examples/OneOpenAir/OtaHandler.h index 067d5bf..5f75d8b 100644 --- a/examples/OneOpenAir/OtaHandler.h +++ b/examples/OneOpenAir/OtaHandler.h @@ -1,146 +1,144 @@ #ifndef _OTA_HANDLER_H_ #define _OTA_HANDLER_H_ -#include -#include -#include #include +#include +#include +#include #define OTA_BUF_SIZE 512 #define URL_BUF_SIZE 256 enum OtaUpdateOutcome { - UPDATE_PERFORMED, - ALREADY_UP_TO_DATE, - UPDATE_FAILED, - UDPATE_SKIPPED + UPDATE_PERFORMED, + ALREADY_UP_TO_DATE, + UPDATE_FAILED, + UDPATE_SKIPPED }; + 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 update @ %s\n", urlAsChar); - esp_http_client_config_t config = {}; - config.url = urlAsChar; - esp_err_t ret = attemptToPerformOta(&config); - Serial.println(ret); - if (ret == OtaUpdateOutcome::UPDATE_PERFORMED) { - Serial.println("OTA update performed, restarting ..."); - esp_restart(); + esp_http_client_config_t config = {}; + config.url = urlAsChar; + esp_err_t ret = attemptToPerformOta(&config); + Serial.println(ret); + if (ret == OtaUpdateOutcome::UPDATE_PERFORMED) { + Serial.println("OTA update performed, restarting ..."); + esp_restart(); + } } - } private: - OtaUpdateOutcome attemptToPerformOta(const esp_http_client_config_t *config) { - esp_http_client_handle_t client = esp_http_client_init(config); - if (client == NULL) { - Serial.println("Failed to initialize HTTP connection"); - return OtaUpdateOutcome::UPDATE_FAILED; - } - esp_err_t err = esp_http_client_open(client, 0); - if (err != ESP_OK) { - esp_http_client_cleanup(client); - Serial.printf("Failed to open HTTP connection: %s\n", - esp_err_to_name(err)); - return OtaUpdateOutcome::UPDATE_FAILED; - } - esp_http_client_fetch_headers(client); - - int httpStatusCode = esp_http_client_get_status_code(client); - if (httpStatusCode == 304) { - Serial.println("Firmware is already up to date"); - cleanupHttp(client); - return OtaUpdateOutcome::ALREADY_UP_TO_DATE; - } else if (httpStatusCode != 200) { - Serial.printf("Firmware update skipped, the server returned %d\n", - httpStatusCode); - cleanupHttp(client); - return OtaUpdateOutcome::UDPATE_SKIPPED; - } - - esp_ota_handle_t update_handle = 0; - const esp_partition_t *update_partition = NULL; - Serial.println("Starting OTA update ..."); - update_partition = esp_ota_get_next_update_partition(NULL); - if (update_partition == NULL) { - Serial.println("Passive OTA partition not found"); - cleanupHttp(client); - return OtaUpdateOutcome::UPDATE_FAILED; - } - Serial.printf("Writing to partition subtype %d at offset 0x%x\n", - update_partition->subtype, update_partition->address); - - err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle); - if (err != ESP_OK) { - Serial.printf("esp_ota_begin failed, error=%d\n", err); - cleanupHttp(client); - return OtaUpdateOutcome::UPDATE_FAILED; - } - - esp_err_t ota_write_err = ESP_OK; - char *upgrade_data_buf = (char *)malloc(OTA_BUF_SIZE); - if (!upgrade_data_buf) { - Serial.println("Couldn't allocate memory for data buffer"); - return OtaUpdateOutcome::UPDATE_FAILED; - } - - int binary_file_len = 0; - 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; + 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; } - binary_file_len += data_read; - // Serial.printf("Written image length %d\n", binary_file_len); - } - } - 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; + esp_err_t err = esp_http_client_open(client, 0); + if (err != ESP_OK) { + esp_http_client_cleanup(client); + Serial.printf("Failed to open HTTP connection: %s\n", esp_err_to_name(err)); + return OtaUpdateOutcome::UPDATE_FAILED; + } + esp_http_client_fetch_headers(client); + + int httpStatusCode = esp_http_client_get_status_code(client); + if (httpStatusCode == 304) { + Serial.println("Firmware is already up to date"); + cleanupHttp(client); + return OtaUpdateOutcome::ALREADY_UP_TO_DATE; + } else if (httpStatusCode != 200) { + Serial.printf("Firmware update skipped, the server returned %d\n", httpStatusCode); + cleanupHttp(client); + return OtaUpdateOutcome::UDPATE_SKIPPED; + } + + esp_ota_handle_t update_handle = 0; + const esp_partition_t *update_partition = NULL; + Serial.println("Starting OTA update ..."); + update_partition = esp_ota_get_next_update_partition(NULL); + if (update_partition == NULL) { + Serial.println("Passive OTA partition not found"); + cleanupHttp(client); + return OtaUpdateOutcome::UPDATE_FAILED; + } + Serial.printf("Writing to partition subtype %d at offset 0x%x\n", + update_partition->subtype, update_partition->address); + + err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle); + if (err != ESP_OK) { + Serial.printf("esp_ota_begin failed, error=%d\n", err); + cleanupHttp(client); + return OtaUpdateOutcome::UPDATE_FAILED; + } + + esp_err_t ota_write_err = ESP_OK; + char *upgrade_data_buf = (char *)malloc(OTA_BUF_SIZE); + if (!upgrade_data_buf) { + Serial.println("Couldn't allocate memory for data buffer"); + return OtaUpdateOutcome::UPDATE_FAILED; + } + + int binary_file_len = 0; + 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); + } + } + free(upgrade_data_buf); + cleanupHttp(client); + Serial.printf("# of bytes written: %d\n", binary_file_len); + + esp_err_t ota_end_err = esp_ota_end(update_handle); + if (ota_write_err != ESP_OK) { + Serial.printf("Error: esp_ota_write failed! err=0x%d\n", err); + return OtaUpdateOutcome::UPDATE_FAILED; + } else if (ota_end_err != ESP_OK) { + Serial.printf("Error: esp_ota_end failed! err=0x%d. Image is invalid", ota_end_err); + return OtaUpdateOutcome::UPDATE_FAILED; + } + + err = esp_ota_set_boot_partition(update_partition); + if (err != ESP_OK) { + Serial.printf("esp_ota_set_boot_partition failed! err=0x%d\n", err); + return OtaUpdateOutcome::UPDATE_FAILED; + } + return OtaUpdateOutcome::UPDATE_PERFORMED; } - 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; + void cleanupHttp(esp_http_client_handle_t client) { + esp_http_client_close(client); + esp_http_client_cleanup(client); } - return OtaUpdateOutcome::UPDATE_PERFORMED; - } - - void cleanupHttp(esp_http_client_handle_t client) { - esp_http_client_close(client); - esp_http_client_cleanup(client); - } + }; #endif From 5e6f80153480d2def05d81b73c4ffb903e05a507 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Mon, 29 Apr 2024 19:46:13 +0700 Subject: [PATCH 04/47] re-format source code --- examples/OneOpenAir/OtaHandler.h | 224 ++++++++++++++++--------------- 1 file changed, 113 insertions(+), 111 deletions(-) diff --git a/examples/OneOpenAir/OtaHandler.h b/examples/OneOpenAir/OtaHandler.h index 5f75d8b..067d5bf 100644 --- a/examples/OneOpenAir/OtaHandler.h +++ b/examples/OneOpenAir/OtaHandler.h @@ -1,144 +1,146 @@ #ifndef _OTA_HANDLER_H_ #define _OTA_HANDLER_H_ -#include -#include -#include #include +#include +#include +#include #define OTA_BUF_SIZE 512 #define URL_BUF_SIZE 256 enum OtaUpdateOutcome { - UPDATE_PERFORMED, - ALREADY_UP_TO_DATE, - UPDATE_FAILED, - UDPATE_SKIPPED + UPDATE_PERFORMED, + ALREADY_UP_TO_DATE, + UPDATE_FAILED, + UDPATE_SKIPPED }; - 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 update @ %s\n", urlAsChar); - esp_http_client_config_t config = {}; - config.url = urlAsChar; - esp_err_t ret = attemptToPerformOta(&config); - Serial.println(ret); - if (ret == OtaUpdateOutcome::UPDATE_PERFORMED) { - Serial.println("OTA update performed, restarting ..."); - esp_restart(); - } + esp_http_client_config_t config = {}; + config.url = urlAsChar; + esp_err_t ret = attemptToPerformOta(&config); + Serial.println(ret); + if (ret == OtaUpdateOutcome::UPDATE_PERFORMED) { + Serial.println("OTA update performed, restarting ..."); + esp_restart(); } + } private: + 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; + } - OtaUpdateOutcome attemptToPerformOta(const esp_http_client_config_t *config) { - esp_http_client_handle_t client = esp_http_client_init(config); - if (client == NULL) { - Serial.println("Failed to initialize HTTP connection"); - return OtaUpdateOutcome::UPDATE_FAILED; - } + esp_err_t err = esp_http_client_open(client, 0); + if (err != ESP_OK) { + esp_http_client_cleanup(client); + Serial.printf("Failed to open HTTP connection: %s\n", + esp_err_to_name(err)); + return OtaUpdateOutcome::UPDATE_FAILED; + } + esp_http_client_fetch_headers(client); - esp_err_t err = esp_http_client_open(client, 0); - if (err != ESP_OK) { - esp_http_client_cleanup(client); - Serial.printf("Failed to open HTTP connection: %s\n", esp_err_to_name(err)); - return OtaUpdateOutcome::UPDATE_FAILED; - } - esp_http_client_fetch_headers(client); + int httpStatusCode = esp_http_client_get_status_code(client); + if (httpStatusCode == 304) { + Serial.println("Firmware is already up to date"); + cleanupHttp(client); + return OtaUpdateOutcome::ALREADY_UP_TO_DATE; + } else if (httpStatusCode != 200) { + Serial.printf("Firmware update skipped, the server returned %d\n", + httpStatusCode); + cleanupHttp(client); + return OtaUpdateOutcome::UDPATE_SKIPPED; + } - int httpStatusCode = esp_http_client_get_status_code(client); - if (httpStatusCode == 304) { - Serial.println("Firmware is already up to date"); - cleanupHttp(client); - return OtaUpdateOutcome::ALREADY_UP_TO_DATE; - } else if (httpStatusCode != 200) { - Serial.printf("Firmware update skipped, the server returned %d\n", httpStatusCode); - cleanupHttp(client); - return OtaUpdateOutcome::UDPATE_SKIPPED; - } + esp_ota_handle_t update_handle = 0; + const esp_partition_t *update_partition = NULL; + Serial.println("Starting OTA update ..."); + update_partition = esp_ota_get_next_update_partition(NULL); + if (update_partition == NULL) { + Serial.println("Passive OTA partition not found"); + cleanupHttp(client); + return OtaUpdateOutcome::UPDATE_FAILED; + } + Serial.printf("Writing to partition subtype %d at offset 0x%x\n", + update_partition->subtype, update_partition->address); - esp_ota_handle_t update_handle = 0; - const esp_partition_t *update_partition = NULL; - Serial.println("Starting OTA update ..."); - update_partition = esp_ota_get_next_update_partition(NULL); - if (update_partition == NULL) { - Serial.println("Passive OTA partition not found"); - cleanupHttp(client); - return OtaUpdateOutcome::UPDATE_FAILED; - } - Serial.printf("Writing to partition subtype %d at offset 0x%x\n", - update_partition->subtype, update_partition->address); + err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle); + if (err != ESP_OK) { + Serial.printf("esp_ota_begin failed, error=%d\n", err); + cleanupHttp(client); + return OtaUpdateOutcome::UPDATE_FAILED; + } - 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; - } + 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; - 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); - } - } - 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); + 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) { - 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; + break; } + binary_file_len += data_read; + // Serial.printf("Written image length %d\n", binary_file_len); + } + } + free(upgrade_data_buf); + cleanupHttp(client); + Serial.printf("# of bytes written: %d\n", binary_file_len); - 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; + 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 From 7a182ebb12b6ef18ec3fc53fc3de68cd5152108d Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Tue, 30 Apr 2024 19:59:43 +0700 Subject: [PATCH 05/47] Remove WiFi QrCode --- src/AgOledDisplay.cpp | 23 - src/AgOledDisplay.h | 1 - src/AgStateMachine.cpp | 17 +- src/Libraries/QRCode/.gitignore | 1 - src/Libraries/QRCode/LICENSE.txt | 26 - src/Libraries/QRCode/README.md | 677 -------------- .../QRCode/examples/QRCode/QRCode.ino | 56 -- src/Libraries/QRCode/generate_table.py | 62 -- src/Libraries/QRCode/keywords.txt | 31 - src/Libraries/QRCode/library.properties | 10 - src/Libraries/QRCode/src/qrcode.c | 876 ------------------ src/Libraries/QRCode/src/qrcode.h | 99 -- 12 files changed, 5 insertions(+), 1874 deletions(-) delete mode 100644 src/Libraries/QRCode/.gitignore delete mode 100644 src/Libraries/QRCode/LICENSE.txt delete mode 100644 src/Libraries/QRCode/README.md delete mode 100644 src/Libraries/QRCode/examples/QRCode/QRCode.ino delete mode 100644 src/Libraries/QRCode/generate_table.py delete mode 100644 src/Libraries/QRCode/keywords.txt delete mode 100644 src/Libraries/QRCode/library.properties delete mode 100644 src/Libraries/QRCode/src/qrcode.c delete mode 100644 src/Libraries/QRCode/src/qrcode.h diff --git a/src/AgOledDisplay.cpp b/src/AgOledDisplay.cpp index 554da78..1f5670f 100644 --- a/src/AgOledDisplay.cpp +++ b/src/AgOledDisplay.cpp @@ -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)) @@ -286,25 +285,3 @@ void OledDisplay::showDashboard(const char *status) { DISP()->drawStr(85, 63, strBuf); } 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()); - - 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()); - } while (DISP()->nextPage()); -} diff --git a/src/AgOledDisplay.h b/src/AgOledDisplay.h index 2be17c8..7def823 100644 --- a/src/AgOledDisplay.h +++ b/src/AgOledDisplay.h @@ -31,7 +31,6 @@ public: const char *line4); void showDashboard(void); void showDashboard(const char *status); - void showWiFiQrCode(String content, String label); }; #endif /** _AG_OLED_DISPLAY_H_ */ diff --git a/src/AgStateMachine.cpp b/src/AgStateMachine.cpp index 5efb6f7..2ae3b2d 100644 --- a/src/AgStateMachine.cpp +++ b/src/AgStateMachine.cpp @@ -414,19 +414,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; diff --git a/src/Libraries/QRCode/.gitignore b/src/Libraries/QRCode/.gitignore deleted file mode 100644 index e43b0f9..0000000 --- a/src/Libraries/QRCode/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.DS_Store diff --git a/src/Libraries/QRCode/LICENSE.txt b/src/Libraries/QRCode/LICENSE.txt deleted file mode 100644 index d59dd4d..0000000 --- a/src/Libraries/QRCode/LICENSE.txt +++ /dev/null @@ -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. - diff --git a/src/Libraries/QRCode/README.md b/src/Libraries/QRCode/README.md deleted file mode 100644 index 3fc59d0..0000000 --- a/src/Libraries/QRCode/README.md +++ /dev/null @@ -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 ---------------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
VersionSizeError CorrectionMode
NumericAlphanumericByte
121 x 21LOW412517
MEDIUM342014
QUARTILE271611
HIGH17107
225 x 25LOW774732
MEDIUM633826
QUARTILE482920
HIGH342014
329 x 29LOW1277753
MEDIUM1016142
QUARTILE774732
HIGH583524
433 x 33LOW18711478
MEDIUM1499062
QUARTILE1116746
HIGH825034
537 x 37LOW255154106
MEDIUM20212284
QUARTILE1448760
HIGH1066444
641 x 41LOW322195134
MEDIUM255154106
QUARTILE17810874
HIGH1398458
745 x 45LOW370224154
MEDIUM293178122
QUARTILE20712586
HIGH1549364
849 x 49LOW461279192
MEDIUM365221152
QUARTILE259157108
HIGH20212284
953 x 53LOW552335230
MEDIUM432262180
QUARTILE312189130
HIGH23514398
1057 x 57LOW652395271
MEDIUM513311213
QUARTILE364221151
HIGH288174119
1161 x 61LOW772468321
MEDIUM604366251
QUARTILE427259177
HIGH331200137
1265 x 65LOW883535367
MEDIUM691419287
QUARTILE489296203
HIGH374227155
1369 x 69LOW1022619425
MEDIUM796483331
QUARTILE580352241
HIGH427259177
1473 x 73LOW1101667458
MEDIUM871528362
QUARTILE621376258
HIGH468283194
1577 x 77LOW1250758520
MEDIUM991600412
QUARTILE703426292
HIGH530321220
1681 x 81LOW1408854586
MEDIUM1082656450
QUARTILE775470322
HIGH602365250
1785 x 85LOW1548938644
MEDIUM1212734504
QUARTILE876531364
HIGH674408280
1889 x 89LOW17251046718
MEDIUM1346816560
QUARTILE948574394
HIGH746452310
1993 x 93LOW19031153792
MEDIUM1500909624
QUARTILE1063644442
HIGH813493338
2097 x 97LOW20611249858
MEDIUM1600970666
QUARTILE1159702482
HIGH919557382
21101 x 101LOW22321352929
MEDIUM17081035711
QUARTILE1224742509
HIGH969587403
22105 x 105LOW240914601003
MEDIUM18721134779
QUARTILE1358823565
HIGH1056640439
23109 x 109LOW262015881091
MEDIUM20591248857
QUARTILE1468890611
HIGH1108672461
24113 x 113LOW281217041171
MEDIUM21881326911
QUARTILE1588963661
HIGH1228744511
25117 x 117LOW305718531273
MEDIUM23951451997
QUARTILE17181041715
HIGH1286779535
26121 x 121LOW328319901367
MEDIUM254415421059
QUARTILE18041094751
HIGH1425864593
27125 x 125LOW351721321465
MEDIUM270116371125
QUARTILE19331172805
HIGH1501910625
28129 x 129LOW366922231528
MEDIUM285717321190
QUARTILE20851263868
HIGH1581958658
29133 x 133LOW390923691628
MEDIUM303518391264
QUARTILE21811322908
HIGH16771016698
30137 x 137LOW415825201732
MEDIUM328919941370
QUARTILE23581429982
HIGH17821080742
31141 x 141LOW441726771840
MEDIUM348621131452
QUARTILE247314991030
HIGH18971150790
32145 x 145LOW468628401952
MEDIUM369322381538
QUARTILE267016181112
HIGH20221226842
33149 x 149LOW496530092068
MEDIUM390923691628
QUARTILE280517001168
HIGH21571307898
34153 x 153LOW525331832188
MEDIUM413425061722
QUARTILE294917871228
HIGH23011394958
35157 x 157LOW552933512303
MEDIUM434326321809
QUARTILE308118671283
HIGH23611431983
36161 x 161LOW583635372431
MEDIUM458827801911
QUARTILE324419661351
HIGH252415301051
37165 x 165LOW615337292563
MEDIUM477528941989
QUARTILE341720711423
HIGH262515911093
38169 x 169LOW647939272699
MEDIUM503930542099
QUARTILE359921811499
HIGH273516581139
39173 x 173LOW674340872809
MEDIUM531332202213
QUARTILE379122981579
HIGH292717741219
40177 x 177LOW708942962953
MEDIUM559633912331
QUARTILE399324201663
HIGH305718521273
- - -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. diff --git a/src/Libraries/QRCode/examples/QRCode/QRCode.ino b/src/Libraries/QRCode/examples/QRCode/QRCode.ino deleted file mode 100644 index 9f6a655..0000000 --- a/src/Libraries/QRCode/examples/QRCode/QRCode.ino +++ /dev/null @@ -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() { - -} diff --git a/src/Libraries/QRCode/generate_table.py b/src/Libraries/QRCode/generate_table.py deleted file mode 100644 index d5d4003..0000000 --- a/src/Libraries/QRCode/generate_table.py +++ /dev/null @@ -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 = ''' - %s - %s - LOW%s%s%s - - - MEDIUM%s%s%s - - - QUARTILE%s%s%s - - - HIGH%s%s%s - ''' - -for data in Data: - data = data[:] - size = 4 * int(data[0]) + 17 - data.insert(1, "%d x %d" % (size, size)) - print Template % tuple(data) diff --git a/src/Libraries/QRCode/keywords.txt b/src/Libraries/QRCode/keywords.txt deleted file mode 100644 index 013f895..0000000 --- a/src/Libraries/QRCode/keywords.txt +++ /dev/null @@ -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 diff --git a/src/Libraries/QRCode/library.properties b/src/Libraries/QRCode/library.properties deleted file mode 100644 index 01fdca2..0000000 --- a/src/Libraries/QRCode/library.properties +++ /dev/null @@ -1,10 +0,0 @@ -name=QRCode -version=0.0.1 -author=Richard Moore -maintainer=Richard Moore -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 diff --git a/src/Libraries/QRCode/src/qrcode.c b/src/Libraries/QRCode/src/qrcode.c deleted file mode 100644 index 0b441b3..0000000 --- a/src/Libraries/QRCode/src/qrcode.c +++ /dev/null @@ -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 -#include - -#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) { - -} -*/ diff --git a/src/Libraries/QRCode/src/qrcode.h b/src/Libraries/QRCode/src/qrcode.h deleted file mode 100644 index 6a5745e..0000000 --- a/src/Libraries/QRCode/src/qrcode.h +++ /dev/null @@ -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 - - -// 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_ */ From bc3872e631ef6c8098dcacf3683e8ad12e1c7b18 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Tue, 30 Apr 2024 20:28:34 +0700 Subject: [PATCH 06/47] fix github workflows build fail and platformio multiple project build --- .github/workflows/check.yml | 2 +- platformio.ini | 28 +++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 032873f..aa51da8 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -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" diff --git a/platformio.ini b/platformio.ini index af8e14f..fed429c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -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 From 29c1989e7826b08fa1919f99840c46fd0f2994d1 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Tue, 30 Apr 2024 20:32:53 +0700 Subject: [PATCH 07/47] Temperature configuration unit support shortname value `c` and `f` --- src/AgConfigure.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp index 09e3883..aa26c3e 100644 --- a/src/AgConfigure.cpp +++ b/src/AgConfigure.cpp @@ -476,9 +476,9 @@ bool Configuration::parse(String data, bool isLocal) { if (JSON.typeof_(root["temperatureUnit"]) == "string") { String unit = root["temperatureUnit"]; unit.toLowerCase(); - if ((unit == "c") || (unit == "celsius")) { + if (unit == "c") { temperatureUnit = 'c'; - } else if ((unit == "f") || (unit == "fahrenheit")) { + } else if (unit == "f") { temperatureUnit = 'f'; } else { failedMessage = "'temperatureUnit' value '" + unit + "' invalid"; From d94d074abe978303000e9e5315bad3f3fd259dce Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Tue, 30 Apr 2024 20:51:08 +0700 Subject: [PATCH 08/47] fix issue EEPROM override after OTA perform by use `spiffs` --- src/AgConfigure.cpp | 72 +++++++++++++++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 19 deletions(-) diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp index 09e3883..2954f40 100644 --- a/src/AgConfigure.cpp +++ b/src/AgConfigure.cpp @@ -1,6 +1,14 @@ #include "AgConfigure.h" -#include "EEPROM.h" #include "Libraries/Arduino_JSON/src/Arduino_JSON.h" +#if ESP32 +#include "FS.h" +#include "SPIFFS.h" +#else +#include "EEPROM.h" +#endif + +#define EEPROM_CONFIG_SIZE 512 +#define CONFIG_FILE_NAME "/cfg.bin" const char *CONFIGURATION_CONTROL_NAME[] = { [ConfigurationControlLocal] = "local", @@ -54,10 +62,19 @@ void Configuration::saveConfig(void) { for (int i = 0; i < sizeof(config); i++) { EEPROM.write(i, data[i]); } -#else - EEPROM.writeBytes(0, &config, sizeof(config)); -#endif EEPROM.commit(); +#else + File file = SPIFFS.open(CONFIG_FILE_NAME, "w", true); + if (file && !file.isDirectory()) { + if (file.write((const uint8_t *)&config, sizeof(config)) != + sizeof(config)) { + logError("Write SPIFFS file failed"); + } + file.close(); + } else { + logError("Open SPIFFS file to write failed"); + } +#endif logInfo("Save Config"); } @@ -70,8 +87,12 @@ void Configuration::loadConfig(void) { } readSuccess = true; #else - if (EEPROM.readBytes(0, &config, sizeof(config)) == sizeof(config)) { - readSuccess = true; + File file = SPIFFS.open(CONFIG_FILE_NAME); + if (file && !file.isDirectory()) { + if (file.readBytes((char *)&config, sizeof(config)) == sizeof(config)) { + readSuccess = true; + } + file.close(); } #endif @@ -162,7 +183,20 @@ Configuration::~Configuration() {} * @return false Failure */ bool Configuration::begin(void) { - EEPROM.begin(512); + if (sizeof(config) > EEPROM_CONFIG_SIZE) { + logError("Configuration over EEPROM_CONFIG_SIZE"); + return false; + } + +#ifdef ESP32 + if (!SPIFFS.begin(true)) { + logError("Init SPIFFS failed"); + return false; + } +#else + EEPROM.begin(EEPROM_CONFIG_SIZE); +#endif + loadConfig(); printConfig(); @@ -298,7 +332,8 @@ bool Configuration::parse(String data, bool isLocal) { } if (inUSAQI != config.inUSAQI) { - configLogInfo("pmStandard", getPMStandardString(config.inUSAQI), pmStandard); + configLogInfo("pmStandard", getPMStandardString(config.inUSAQI), + pmStandard); config.inUSAQI = inUSAQI; changed = true; } @@ -312,7 +347,7 @@ bool Configuration::parse(String data, bool isLocal) { if (JSON.typeof_(root["co2CalibrationRequested"]) == "boolean") { co2CalibrationRequested = root["co2CalibrationRequested"]; - if(co2CalibrationRequested) { + if (co2CalibrationRequested) { logInfo("co2CalibrationRequested: " + String(co2CalibrationRequested ? "True" : "False")); } @@ -327,7 +362,7 @@ bool Configuration::parse(String data, bool isLocal) { if (JSON.typeof_(root["ledBarTestRequested"]) == "boolean") { ledBarTestRequested = root["ledBarTestRequested"]; - if(ledBarTestRequested){ + if (ledBarTestRequested) { logInfo("ledBarTestRequested: " + String(ledBarTestRequested ? "True" : "False")); } @@ -384,7 +419,8 @@ bool Configuration::parse(String data, bool isLocal) { if (displayMode != config.displayMode) { changed = true; - configLogInfo("displayMode", getDisplayModeString(config.displayMode), mode); + configLogInfo("displayMode", getDisplayModeString(config.displayMode), + mode); config.displayMode = displayMode; } } else { @@ -770,15 +806,15 @@ String Configuration::getPMStandardString(bool usaqi) { return "ugm3"; } -String Configuration::getDisplayModeString(bool dispMode) { - if(dispMode){ +String Configuration::getDisplayModeString(bool dispMode) { + if (dispMode) { return String("on"); } return String("off"); } -String Configuration::getAbcDayString(int value) { - if(value <= 0){ +String Configuration::getAbcDayString(int value) { + if (value <= 0) { return String("off"); } return String(value); @@ -816,10 +852,8 @@ int Configuration::getNoxLearningOffset(void) { return config.noxLearningOffset; } -String Configuration::wifiSSID(void) { - return "airgradient-" + ag->deviceId(); -} +String Configuration::wifiSSID(void) { return "airgradient-" + ag->deviceId(); } String Configuration::wifiPass(void) { return String("cleanair"); } -void Configuration::setAirGradient(AirGradient *ag) { this->ag = ag;} +void Configuration::setAirGradient(AirGradient *ag) { this->ag = ag; } From 221730160b6af6784a8618031a5bfe36f08864b8 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Wed, 1 May 2024 21:02:57 +0700 Subject: [PATCH 09/47] fix led bar button test not work on power up --- examples/OneOpenAir/OneOpenAir.ino | 2 +- src/AgStateMachine.cpp | 34 +++++++++++++++++++----------- src/AgStateMachine.h | 2 ++ src/App/AppDef.h | 1 + 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index 3bb6fc8..6c1f9be 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -162,7 +162,7 @@ void setup() { bool connectToWifi = false; if (ag->isOne()) { if (ledBarButtonTest) { - stateMachine.executeLedBarTest(); + stateMachine.executeLedBarPowerUpTest(); } else { ledBarEnabledUpdate(); connectToWifi = true; diff --git a/src/AgStateMachine.cpp b/src/AgStateMachine.cpp index 5efb6f7..0fccf78 100644 --- a/src/AgStateMachine.cpp +++ b/src/AgStateMachine.cpp @@ -302,20 +302,24 @@ void StateMachine::co2Calibration(void) { void StateMachine::ledBarTest(void) { if (config.isLedBarTestRequested()) { - if (config.getCountry() == "TH") { - uint32_t tstart = millis(); - logInfo("Start run LED test for 2 min"); - while (1) { - ledBarRunTest(); - uint32_t ms = (uint32_t)(millis() - tstart); - if (ms >= (60 * 1000 * 2)) { - logInfo("LED test after 2 min finish"); - break; - } - } - } else { + ledBarPowerUpTest(); + } +} + +void StateMachine::ledBarPowerUpTest(void) { + if (config.getCountry() == "TH") { + uint32_t tstart = millis(); + logInfo("Start run LED test for 2 min"); + while (1) { ledBarRunTest(); + uint32_t ms = (uint32_t)(millis() - tstart); + if (ms >= (60 * 1000 * 2)) { + logInfo("LED test after 2 min finish"); + break; + } } + } else { + ledBarRunTest(); } } @@ -706,6 +710,8 @@ void StateMachine::handleLeds(AgStateMachineState state) { case AgStateMachineLedBarTest: ledBarTest(); break; + case AgStateMachineLedBarPowerUpTest: + ledBarPowerUpTest(); default: break; } @@ -759,3 +765,7 @@ void StateMachine::executeCo2Calibration(void) { void StateMachine::executeLedBarTest(void) { handleLeds(AgStateMachineLedBarTest); } + +void StateMachine::executeLedBarPowerUpTest(void) { + handleLeds(AgStateMachineLedBarPowerUpTest); +} diff --git a/src/AgStateMachine.h b/src/AgStateMachine.h index 7299bb3..fa33549 100644 --- a/src/AgStateMachine.h +++ b/src/AgStateMachine.h @@ -28,6 +28,7 @@ private: void pm25handleLeds(void); void co2Calibration(void); void ledBarTest(void); + void ledBarPowerUpTest(void); void ledBarRunTest(void); void runLedTest(char color); @@ -49,6 +50,7 @@ public: AgStateMachineState getLedState(void); void executeCo2Calibration(void); void executeLedBarTest(void); + void executeLedBarPowerUpTest(void); }; #endif /** _AG_STATE_MACHINE_H_ */ diff --git a/src/App/AppDef.h b/src/App/AppDef.h index 7009f92..546ce59 100644 --- a/src/App/AppDef.h +++ b/src/App/AppDef.h @@ -59,6 +59,7 @@ enum AgStateMachineState { /* LED bar testing */ AgStateMachineLedBarTest, + AgStateMachineLedBarPowerUpTest, /** LED: Show working state. * Display: Show dashboard */ From f08438db46dd4ff21607fde3ed5bf9dec92988d0 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Wed, 1 May 2024 21:25:35 +0700 Subject: [PATCH 10/47] Implement display / ledBar brightness --- examples/OneOpenAir/OneOpenAir.ino | 12 +++++ src/AgConfigure.cpp | 81 ++++++++++++++++++++++++++++++ src/AgConfigure.h | 8 +++ src/AgOledDisplay.cpp | 6 +++ src/AgOledDisplay.h | 1 + src/Main/LedBar.cpp | 4 +- 6 files changed, 110 insertions(+), 2 deletions(-) diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index 3bb6fc8..240cfaf 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -414,6 +414,7 @@ static void wdgFeedUpdate(void) { static void ledBarEnabledUpdate(void) { if (ag->isOne()) { + ag->ledBar.setBrighness(configuration.getLedBarBrightness()); ag->ledBar.setEnable(configuration.getLedBarMode() != LedBarModeOff); } } @@ -721,6 +722,17 @@ static void configUpdateHandle() { } } + if (configuration.isLedBarBrightnessChanged()) { + ag->ledBar.setBrighness(configuration.getLedBarBrightness()); + Serial.println("Set 'LedBarBrightness' brightness: " + + String(configuration.getLedBarBrightness())); + } + if (configuration.isDisplayBrightnessChanged()) { + oledDisplay.setBrightness(configuration.getDisplayBrightness()); + Serial.println("Set 'DisplayBrightness' brightness: " + + String(configuration.getDisplayBrightness())); + } + appDispHandler(); appLedHandler(); } diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp index 1702480..ef45cb3 100644 --- a/src/AgConfigure.cpp +++ b/src/AgConfigure.cpp @@ -125,6 +125,15 @@ void Configuration::loadConfig(void) { changed = true; logError("LedBarMode invalid, set default: co2"); } + if (config.ledBarBrightness > 100) { + config.ledBarBrightness = 100; + changed = true; + } + if (config.displayBrightness > 100) { + config.displayBrightness = 100; + changed = true; + } + if (changed) { saveConfig(); } @@ -152,6 +161,8 @@ void Configuration::defaultConfig(void) { config.tvocLearningOffset = 12; config.noxLearningOffset = 12; config.temperatureUnit = 'c'; + config.ledBarBrightness = 100; + config.displayBrightness = 100; saveConfig(); } @@ -581,6 +592,52 @@ bool Configuration::parse(String data, bool isLocal) { } } + if (JSON.typeof_(root["ledbarBrightness"]) == "number") { + int brightnress = root["ledbarBrightness"]; + if (brightnress != config.ledBarBrightness) { + if (brightnress <= 100) { + changed = true; + configLogInfo("ledbarBrightness", String(config.ledBarBrightness), + String(brightnress)); + ledBarBrightnessChanged = true; + config.ledBarBrightness = (uint8_t)brightnress; + } else { + failedMessage = + "\"ledbarBrightness\" value invalid: " + String(brightnress); + return false; + } + } + } else { + if (jsonTypeInvalid(root["ledbarBrightness"], "number")) { + failedMessage = jsonTypeInvalidMessage("ledbarBrightness", "number"); + jsonInvalid(); + return false; + } + } + + if (JSON.typeof_(root["displayBrightness"]) == "number") { + int brightness = root["displayBrightness"]; + if (brightness != config.displayBrightness) { + if (brightness <= 100) { + changed = true; + displayBrightnessChanged = true; + configLogInfo("displayBrightness", String(config.displayBrightness), + String(brightness)); + config.displayBrightness = (uint8_t)brightness; + } else { + failedMessage = + "\"displayBrightness\" value invalid: " + String(brightness); + return false; + } + } + } else { + if (jsonTypeInvalid(root["displayBrightness"], "number")) { + failedMessage = jsonTypeInvalidMessage("displayBrightness", "number"); + jsonInvalid(); + return false; + } + } + if (changed) { udpated = true; saveConfig(); @@ -643,6 +700,12 @@ String Configuration::toString(void) { /** "postDataToAirGradient" */ root["postDataToAirGradient"] = config.postDataToAirGradient; + /** Led bar brighness */ + root["ledbarBrightness"] = config.ledBarBrightness; + + /** Display brighness */ + root["displayBrightness"] = config.displayBrightness; + return JSON.stringify(root); } @@ -857,3 +920,21 @@ String Configuration::wifiSSID(void) { return "airgradient-" + ag->deviceId(); } String Configuration::wifiPass(void) { return String("cleanair"); } void Configuration::setAirGradient(AirGradient *ag) { this->ag = ag; } + +int Configuration::getLedBarBrightness(void) { return config.ledBarBrightness; } + +bool Configuration::isLedBarBrightnessChanged(void) { + bool changed = ledBarBrightnessChanged; + ledBarBrightnessChanged = false; + return changed; +} + +int Configuration::getDisplayBrightness(void) { + return config.displayBrightness; +} + +bool Configuration::isDisplayBrightnessChanged(void) { + bool changed = displayBrightnessChanged; + displayBrightnessChanged = false; + return changed; +} diff --git a/src/AgConfigure.h b/src/AgConfigure.h index 4de0c0c..6759ba6 100644 --- a/src/AgConfigure.h +++ b/src/AgConfigure.h @@ -23,6 +23,8 @@ private: bool displayMode; /** true if enable display */ uint8_t useRGBLedBar; uint8_t abcDays; + uint8_t ledBarBrightness; + uint8_t displayBrightness; int tvocLearningOffset; int noxLearningOffset; char temperatureUnit; // 'f' or 'c' @@ -36,6 +38,8 @@ private: String failedMessage; bool _noxLearnOffsetChanged; bool _tvocLearningOffsetChanged; + bool ledBarBrightnessChanged = false; + bool displayBrightnessChanged = false; AirGradient* ag; @@ -89,6 +93,10 @@ public: String wifiSSID(void); String wifiPass(void); void setAirGradient(AirGradient *ag); + bool isLedBarBrightnessChanged(void); + int getLedBarBrightness(void); + bool isDisplayBrightnessChanged(void); + int getDisplayBrightness(void); }; #endif /** _AG_CONFIG_H_ */ diff --git a/src/AgOledDisplay.cpp b/src/AgOledDisplay.cpp index 554da78..8a033bb 100644 --- a/src/AgOledDisplay.cpp +++ b/src/AgOledDisplay.cpp @@ -94,6 +94,8 @@ bool OledDisplay::begin(void) { return false; } + setBrightness(config.getDisplayBrightness()); + isBegin = true; logInfo("begin"); return true; @@ -308,3 +310,7 @@ void OledDisplay::showWiFiQrCode(String content, String label) { DISP()->drawStr(x_start, 60, label.c_str()); } while (DISP()->nextPage()); } + +void OledDisplay::setBrightness(int percent) { + DISP()->setContrast((127 * percent) / 100); +} diff --git a/src/AgOledDisplay.h b/src/AgOledDisplay.h index 2be17c8..69fb398 100644 --- a/src/AgOledDisplay.h +++ b/src/AgOledDisplay.h @@ -32,6 +32,7 @@ public: void showDashboard(void); void showDashboard(const char *status); void showWiFiQrCode(String content, String label); + void setBrightness(int percent); }; #endif /** _AG_OLED_DISPLAY_H_ */ diff --git a/src/Main/LedBar.cpp b/src/Main/LedBar.cpp index 2627d55..b00f36c 100644 --- a/src/Main/LedBar.cpp +++ b/src/Main/LedBar.cpp @@ -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) { if (this->isBegin() == false) { return; } - pixel()->setBrightness(brightness); + pixel()->setBrightness((brightness * 0xff) / 100); } /** From 01c42387ed79eeefd98425fca6e44bae1e724a99 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Wed, 1 May 2024 21:27:01 +0700 Subject: [PATCH 11/47] fix: typo --- src/AgConfigure.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp index ef45cb3..7027895 100644 --- a/src/AgConfigure.cpp +++ b/src/AgConfigure.cpp @@ -700,10 +700,10 @@ String Configuration::toString(void) { /** "postDataToAirGradient" */ root["postDataToAirGradient"] = config.postDataToAirGradient; - /** Led bar brighness */ + /** Led bar brightness */ root["ledbarBrightness"] = config.ledBarBrightness; - /** Display brighness */ + /** Display brightness */ root["displayBrightness"] = config.displayBrightness; return JSON.stringify(root); From f32d6b1bbea6d4c65ac66783107e332848fd130d Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Thu, 2 May 2024 09:10:43 +0700 Subject: [PATCH 12/47] Remove country dependency on LED bar button test --- src/AgStateMachine.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/AgStateMachine.cpp b/src/AgStateMachine.cpp index 0fccf78..0a3665a 100644 --- a/src/AgStateMachine.cpp +++ b/src/AgStateMachine.cpp @@ -302,25 +302,25 @@ void StateMachine::co2Calibration(void) { void StateMachine::ledBarTest(void) { if (config.isLedBarTestRequested()) { - ledBarPowerUpTest(); + if (config.getCountry() == "TH") { + uint32_t tstart = millis(); + logInfo("Start run LED test for 2 min"); + while (1) { + ledBarRunTest(); + uint32_t ms = (uint32_t)(millis() - tstart); + if (ms >= (60 * 1000 * 2)) { + logInfo("LED test after 2 min finish"); + break; + } + } + } else { + ledBarRunTest(); + } } } void StateMachine::ledBarPowerUpTest(void) { - if (config.getCountry() == "TH") { - uint32_t tstart = millis(); - logInfo("Start run LED test for 2 min"); - while (1) { - ledBarRunTest(); - uint32_t ms = (uint32_t)(millis() - tstart); - if (ms >= (60 * 1000 * 2)) { - logInfo("LED test after 2 min finish"); - break; - } - } - } else { - ledBarRunTest(); - } + ledBarRunTest(); } void StateMachine::ledBarRunTest(void) { From 44931567396b6e50dcdabfdcdf32f6f6b0e7d7d6 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Thu, 2 May 2024 10:19:49 +0700 Subject: [PATCH 13/47] Implement regular OTA update attempt / indicate OTA processing on display --- examples/OneOpenAir/OneOpenAir.ino | 10 ++++- examples/OneOpenAir/OtaHandler.h | 62 ++++++++++++++++++++++++--- src/AgConfigure.cpp | 68 +++++++++++++++++++++++++++++- src/AgConfigure.h | 5 +++ src/AgOledDisplay.cpp | 49 +++++++++++++++++++++ src/AgOledDisplay.h | 7 +++ src/AgStateMachine.cpp | 44 +++++++++++++++++++ src/AgStateMachine.h | 8 ++++ src/AgWiFiConnector.cpp | 6 +++ src/App/AppDef.h | 3 ++ 10 files changed, 252 insertions(+), 10 deletions(-) diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index 240cfaf..52a4190 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -82,7 +82,7 @@ static WifiConnector wifiConnector(oledDisplay, Serial, stateMachine, configuration); static OpenMetrics openMetrics(measurements, configuration, wifiConnector, apiClient); -static OtaHandler otaHandler; +static OtaHandler otaHandler(stateMachine, configuration); static LocalServer localServer(Serial, openMetrics, measurements, configuration, wifiConnector); @@ -157,6 +157,7 @@ void setup() { apiClient.setAirGradient(ag); openMetrics.setAirGradient(ag); localServer.setAirGraident(ag); + otaHandler.setAirGradient(ag); /** Connecting wifi */ bool connectToWifi = false; @@ -184,7 +185,7 @@ void setup() { #ifdef ESP8266 // ota not supported #else - otaHandler.updateFirmwareIfOutdated(ag->deviceId()); + // otaHandler.updateFirmwareIfOutdated(ag->deviceId()); #endif apiClient.fetchServerConfiguration(); @@ -733,6 +734,11 @@ static void configUpdateHandle() { String(configuration.getDisplayBrightness())); } + String newVer = configuration.newFirmwareVersion(); + if (newVer.length()) { + otaHandler.updateFirmwareIfOutdated(newVer); + } + appDispHandler(); appLedHandler(); } diff --git a/examples/OneOpenAir/OtaHandler.h b/examples/OneOpenAir/OtaHandler.h index 067d5bf..8775503 100644 --- a/examples/OneOpenAir/OtaHandler.h +++ b/examples/OneOpenAir/OtaHandler.h @@ -5,8 +5,11 @@ #include #include #include +#include "AgConfigure.h" +#include "AgStateMachine.h" +#include "AirGradient.h" -#define OTA_BUF_SIZE 512 +#define OTA_BUF_SIZE 1024 #define URL_BUF_SIZE 256 enum OtaUpdateOutcome { @@ -18,10 +21,23 @@ enum OtaUpdateOutcome { class OtaHandler { public: - void updateFirmwareIfOutdated(String deviceId) { + OtaHandler(StateMachine &sm, Configuration &config) + : sm(sm), config(config) {} + void setAirGradient(AirGradient *ag) { this->ag = ag; }; - String url = "http://hw.airgradient.com/sensors/airgradient:" + deviceId + - "/generic/os/firmware.bin"; + void updateFirmwareIfOutdated(String newVersion) { + int lastOta = config.getLastOta(); + // Retry OTA after last udpate 24h + if (lastOta != 0 && lastOta < (60 * 60 * 24)) { + Serial.println("Ignore OTA cause last update is " + String(lastOta) + + String("sec")); + Serial.println("Retry again after 24h"); + return; + } + + String url = + "http://hw.airgradient.com/sensors/airgradient:" + ag->deviceId() + + "/generic/os/firmware.bin"; url += "?current_firmware="; url += GIT_VERSION; char urlAsChar[URL_BUF_SIZE]; @@ -30,16 +46,31 @@ public: esp_http_client_config_t config = {}; config.url = urlAsChar; - esp_err_t ret = attemptToPerformOta(&config); + OtaUpdateOutcome ret = attemptToPerformOta(&config, newVersion); + + // Update last OTA time whatever result. + this->config.updateLastOta(); + Serial.println(ret); if (ret == OtaUpdateOutcome::UPDATE_PERFORMED) { Serial.println("OTA update performed, restarting ..."); + int i = 6; + while (i != 0) { + i = i - 1; + sm.executeOTA(StateMachine::OtaState::OTA_STATE_SUCCESS, "", i); + delay(1000); + } esp_restart(); } } private: - OtaUpdateOutcome attemptToPerformOta(const esp_http_client_config_t *config) { + AirGradient *ag; + StateMachine &sm; + Configuration &config; + + OtaUpdateOutcome attemptToPerformOta(const esp_http_client_config_t *config, + String newVersion) { esp_http_client_handle_t client = esp_http_client_init(config); if (client == NULL) { Serial.println("Failed to initialize HTTP connection"); @@ -94,6 +125,14 @@ private: } 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. + sm.executeOTA(StateMachine::OtaState::OTA_STATE_BEGIN, newVersion, 0); + + // 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); @@ -103,16 +142,25 @@ private: } if (data_read < 0) { Serial.println("Data read error"); + sm.executeOTA(StateMachine::OtaState::OTA_STATE_FAIL, "", 0); 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) { + sm.executeOTA(StateMachine::OtaState::OTA_STATE_FAIL, "", 0); break; } binary_file_len += data_read; - // Serial.printf("Written image length %d\n", binary_file_len); + + int percent = (binary_file_len * 100) / totalSize; + uint32_t ms = (uint32_t)(millis() - lastUpdate); + if (ms >= 250) { + sm.executeOTA(StateMachine::OtaState::OTA_STATE_PROCESSING, "", + percent); + lastUpdate = millis(); + } } } free(upgrade_data_buf); diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp index 7027895..2c3cdad 100644 --- a/src/AgConfigure.cpp +++ b/src/AgConfigure.cpp @@ -6,6 +6,7 @@ #else #include "EEPROM.h" #endif +#include #define EEPROM_CONFIG_SIZE 512 #define CONFIG_FILE_NAME "/cfg.bin" @@ -163,6 +164,7 @@ void Configuration::defaultConfig(void) { config.temperatureUnit = 'c'; config.ledBarBrightness = 100; config.displayBrightness = 100; + config.lastOta = 0; saveConfig(); } @@ -171,7 +173,10 @@ void Configuration::defaultConfig(void) { * @brief Show configuration as JSON string message over log * */ -void Configuration::printConfig(void) { logInfo(toString().c_str()); } +void Configuration::printConfig(void) { + logInfo(toString().c_str()); + logInfo("Last OTA time: " + String(config.lastOta)); +} /** * @brief Construct a new Ag Configure:: Ag Configure object @@ -638,6 +643,18 @@ bool Configuration::parse(String data, bool isLocal) { } } + if (JSON.typeof_(root["targetFirmware"]) == "string") { + String newVer = root["targetFirmware"]; + String curVer = String(GIT_VERSION); + if (curVer != newVer) { + logInfo("Detected new firwmare version: " + newVer); + otaNewFirmwareVersion = newVer; + udpated = true; + } else { + otaNewFirmwareVersion = String(""); + } + } + if (changed) { udpated = true; saveConfig(); @@ -938,3 +955,52 @@ bool Configuration::isDisplayBrightnessChanged(void) { displayBrightnessChanged = false; return changed; } + +/** + * @brief Get number of sec from last OTA + * + * @return int < 0 is invalid, 0 mean there is no OTA trigger. + */ +int Configuration::getLastOta(void) { + struct tm timeInfo; + if (getLocalTime(&timeInfo) == false) { + logError("Get localtime failed"); + return -1; + } + int curYear = timeInfo.tm_year + 1900; + if (curYear < 2024) { + logError("Current year " + String(curYear) + String(" invalid")); + return -1; + } + time_t curTime = mktime(&timeInfo); + logInfo("Last ota time: " + String(config.lastOta)); + if (config.lastOta == 0) { + return 0; + } + + int sec = curTime - config.lastOta; + logInfo("Last ota secconds: " + String(sec)); + return sec; +} + +void Configuration::updateLastOta(void) { + struct tm timeInfo; + if (getLocalTime(&timeInfo) == false) { + logError("updateLastOta: Get localtime failed"); + return; + } + int curYear = timeInfo.tm_year + 1900; + if (curYear < 2024) { + logError("updateLastOta: lolcal time invalid"); + return; + } + config.lastOta = mktime(&timeInfo); + logInfo("Last OTA: " + String(config.lastOta)); + saveConfig(); +} + +String Configuration::newFirmwareVersion(void) { + String newFw = otaNewFirmwareVersion; + otaNewFirmwareVersion = String(""); + return newFw; +} diff --git a/src/AgConfigure.h b/src/AgConfigure.h index 6759ba6..bb07626 100644 --- a/src/AgConfigure.h +++ b/src/AgConfigure.h @@ -28,6 +28,7 @@ private: int tvocLearningOffset; int noxLearningOffset; char temperatureUnit; // 'f' or 'c' + time_t lastOta; uint32_t _check; }; @@ -40,6 +41,7 @@ private: bool _tvocLearningOffsetChanged; bool ledBarBrightnessChanged = false; bool displayBrightnessChanged = false; + String otaNewFirmwareVersion; AirGradient* ag; @@ -97,6 +99,9 @@ public: int getLedBarBrightness(void); bool isDisplayBrightnessChanged(void); int getDisplayBrightness(void); + int getLastOta(void); + void updateLastOta(void); + String newFirmwareVersion(void); }; #endif /** _AG_CONFIG_H_ */ diff --git a/src/AgOledDisplay.cpp b/src/AgOledDisplay.cpp index 8a033bb..3e20c54 100644 --- a/src/AgOledDisplay.cpp +++ b/src/AgOledDisplay.cpp @@ -50,6 +50,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 * @@ -314,3 +324,42 @@ void OledDisplay::showWiFiQrCode(String content, String label) { void OledDisplay::setBrightness(int percent) { DISP()->setContrast((127 * percent) / 100); } + +void OledDisplay::showNewFirmwareVersion(String version) { + DISP()->firstPage(); + do { + DISP()->setFont(u8g2_font_t0_16_tf); + setCentralText(20, "Firmware Update"); + setCentralText(40, "New version"); + setCentralText(60, version.c_str()); + } while (DISP()->nextPage()); +} + +void OledDisplay::showNewFirmwareUpdating(String percent) { + DISP()->firstPage(); + do { + DISP()->setFont(u8g2_font_t0_16_tf); + setCentralText(20, "Firmware Update"); + setCentralText(50, String("Updating... ") + percent + String("%")); + } while (DISP()->nextPage()); +} + +void OledDisplay::showNewFirmwareSuccess(String count) { + DISP()->firstPage(); + do { + DISP()->setFont(u8g2_font_t0_16_tf); + setCentralText(20, "Firmware Update"); + setCentralText(40, "Success"); + setCentralText(60, String("Rebooting... ") + count); + } while (DISP()->nextPage()); +} + +void OledDisplay::showNewFirmwareFailed(void) { + DISP()->firstPage(); + do { + DISP()->setFont(u8g2_font_t0_16_tf); + setCentralText(20, "Firmware Update"); + setCentralText(40, "Failed"); + setCentralText(60, String("Retry after 24h")); + } while (DISP()->nextPage()); +} diff --git a/src/AgOledDisplay.h b/src/AgOledDisplay.h index 69fb398..4a5044b 100644 --- a/src/AgOledDisplay.h +++ b/src/AgOledDisplay.h @@ -16,6 +16,9 @@ private: Measurements &value; 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); @@ -33,6 +36,10 @@ public: void showDashboard(const char *status); void showWiFiQrCode(String content, String label); void setBrightness(int percent); + void showNewFirmwareVersion(String version); + void showNewFirmwareUpdating(String percent); + void showNewFirmwareSuccess(String count); + void showNewFirmwareFailed(void); }; #endif /** _AG_OLED_DISPLAY_H_ */ diff --git a/src/AgStateMachine.cpp b/src/AgStateMachine.cpp index 5efb6f7..57635fe 100644 --- a/src/AgStateMachine.cpp +++ b/src/AgStateMachine.cpp @@ -759,3 +759,47 @@ void StateMachine::executeCo2Calibration(void) { void StateMachine::executeLedBarTest(void) { handleLeds(AgStateMachineLedBarTest); } + +void StateMachine::executeOTA(StateMachine::OtaState state, String msg, + int processing) { + switch (state) { + case OtaState::OTA_STATE_BEGIN: { + if (ag->isOne()) { + disp.showNewFirmwareVersion(msg); + } else { + logInfo("New firmware: " + msg); + } + delay(2500); + break; + } + case OtaState::OTA_STATE_FAIL: { + if (ag->isOne()) { + disp.showNewFirmwareFailed(); + } else { + logError("Firmware update: failed"); + } + + delay(2500); + break; + } + case OtaState::OTA_STATE_PROCESSING: { + if (ag->isOne()) { + disp.showNewFirmwareUpdating(String(processing)); + } else { + logInfo("Firmware update: " + String(processing) + String("%")); + } + + break; + } + case OtaState::OTA_STATE_SUCCESS: { + if (ag->isOne()) { + disp.showNewFirmwareSuccess(String(processing)); + } else { + logInfo("Rebooting... " + String(processing)); + } + break; + } + default: + break; + } +} diff --git a/src/AgStateMachine.h b/src/AgStateMachine.h index 7299bb3..a783810 100644 --- a/src/AgStateMachine.h +++ b/src/AgStateMachine.h @@ -49,6 +49,14 @@ public: AgStateMachineState getLedState(void); void executeCo2Calibration(void); void executeLedBarTest(void); + + enum OtaState { + OTA_STATE_BEGIN, + OTA_STATE_FAIL, + OTA_STATE_PROCESSING, + OTA_STATE_SUCCESS + }; + void executeOTA(OtaState state, String msg, int processing); }; #endif /** _AG_STATE_MACHINE_H_ */ diff --git a/src/AgWiFiConnector.cpp b/src/AgWiFiConnector.cpp index 0e7f291..8ac9495 100644 --- a/src/AgWiFiConnector.cpp +++ b/src/AgWiFiConnector.cpp @@ -1,5 +1,6 @@ #include "AgWiFiConnector.h" #include "Libraries/WiFiManager/WiFiManager.h" +#include #define WIFI_CONNECT_COUNTDOWN_MAX 180 #define WIFI_HOTSPOT_PASSWORD_DEFAULT "cleanair" @@ -158,6 +159,11 @@ bool WifiConnector::connect(void) { config.setPostToAirGradient(result != "T"); } hasPortalConfig = false; + + /** Configure internet time */ + const char *ntp_server = "pool.ntp.org"; + configTime(0, 0, ntp_server); + logInfo("Set internet time server: " + String(ntp_server)); } #else _wifiProcess(); diff --git a/src/App/AppDef.h b/src/App/AppDef.h index 7009f92..2cc8ee8 100644 --- a/src/App/AppDef.h +++ b/src/App/AppDef.h @@ -60,6 +60,9 @@ enum AgStateMachineState { /* LED bar testing */ AgStateMachineLedBarTest, + /** OTA perform, show display status */ + AgStateMachineOtaPerform, + /** LED: Show working state. * Display: Show dashboard */ AgStateMachineNormal, From d2723de0f803c5113655275cfd61ceb7d262a39b Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Thu, 2 May 2024 18:22:26 +0700 Subject: [PATCH 14/47] Remove `OtaHandler` constructor --- examples/OneOpenAir/OneOpenAir.ino | 39 +++++++++++++--- examples/OneOpenAir/OtaHandler.h | 72 +++++++++++++++--------------- 2 files changed, 69 insertions(+), 42 deletions(-) diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index 52a4190..f3d5111 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -82,7 +82,7 @@ static WifiConnector wifiConnector(oledDisplay, Serial, stateMachine, configuration); static OpenMetrics openMetrics(measurements, configuration, wifiConnector, apiClient); -static OtaHandler otaHandler(stateMachine, configuration); +static OtaHandler otaHandler; static LocalServer localServer(Serial, openMetrics, measurements, configuration, wifiConnector); @@ -93,6 +93,7 @@ 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,6 +113,7 @@ static void factoryConfigReset(void); static void wdgFeedUpdate(void); static void ledBarEnabledUpdate(void); static bool sgp41Init(void); +static void otaHandlerCallback(StateMachine::OtaState state, String mesasge); AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, oledDisplayLedBarSchedule); AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL, @@ -157,7 +159,6 @@ void setup() { apiClient.setAirGradient(ag); openMetrics.setAirGradient(ag); localServer.setAirGraident(ag); - otaHandler.setAirGradient(ag); /** Connecting wifi */ bool connectToWifi = false; @@ -434,6 +435,25 @@ static bool sgp41Init(void) { return false; } +static void otaHandlerCallback(StateMachine::OtaState state, String mesasge) { + switch (state) { + case StateMachine::OtaState::OTA_STATE_BEGIN: + stateMachine.executeOTA(state, fwNewVersion, 0); + break; + case StateMachine::OtaState::OTA_STATE_FAIL: + stateMachine.executeOTA(state, "", 0); + break; + case StateMachine::OtaState::OTA_STATE_PROCESSING: + stateMachine.executeOTA(state, "", mesasge.toInt()); + break; + case StateMachine::OtaState::OTA_STATE_SUCCESS: + stateMachine.executeOTA(state, "", mesasge.toInt()); + break; + default: + break; + } +} + static void sendDataToAg() { /** Change oledDisplay and led state */ if (ag->isOne()) { @@ -734,9 +754,18 @@ static void configUpdateHandle() { String(configuration.getDisplayBrightness())); } - String newVer = configuration.newFirmwareVersion(); - if (newVer.length()) { - otaHandler.updateFirmwareIfOutdated(newVer); + fwNewVersion = configuration.newFirmwareVersion(); + if (fwNewVersion.length()) { + int lastOta = configuration.getLastOta(); + if (lastOta != 0 && lastOta < (60 * 60 * 24)) { + Serial.println("Ignore OTA cause last update is " + String(lastOta) + + String("sec")); + Serial.println("Retry again after 24h"); + } else { + configuration.updateLastOta(); + otaHandler.setHandlerCallback(otaHandlerCallback); + otaHandler.updateFirmwareIfOutdated(ag->deviceId()); + } } appDispHandler(); diff --git a/examples/OneOpenAir/OtaHandler.h b/examples/OneOpenAir/OtaHandler.h index 8775503..5fb7a31 100644 --- a/examples/OneOpenAir/OtaHandler.h +++ b/examples/OneOpenAir/OtaHandler.h @@ -1,13 +1,13 @@ #ifndef _OTA_HANDLER_H_ #define _OTA_HANDLER_H_ +#include "AgConfigure.h" +#include "AgStateMachine.h" +#include "AirGradient.h" #include #include #include #include -#include "AgConfigure.h" -#include "AgStateMachine.h" -#include "AirGradient.h" #define OTA_BUF_SIZE 1024 #define URL_BUF_SIZE 256 @@ -19,25 +19,14 @@ enum OtaUpdateOutcome { UDPATE_SKIPPED }; +typedef void(*OtaHandlerCallback_t)(StateMachine::OtaState state, + String message); + class OtaHandler { public: - OtaHandler(StateMachine &sm, Configuration &config) - : sm(sm), config(config) {} - void setAirGradient(AirGradient *ag) { this->ag = ag; }; - - void updateFirmwareIfOutdated(String newVersion) { - int lastOta = config.getLastOta(); - // Retry OTA after last udpate 24h - if (lastOta != 0 && lastOta < (60 * 60 * 24)) { - Serial.println("Ignore OTA cause last update is " + String(lastOta) + - String("sec")); - Serial.println("Retry again after 24h"); - return; - } - - String url = - "http://hw.airgradient.com/sensors/airgradient:" + ag->deviceId() + - "/generic/os/firmware.bin"; + 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]; @@ -46,31 +35,30 @@ public: esp_http_client_config_t config = {}; config.url = urlAsChar; - OtaUpdateOutcome ret = attemptToPerformOta(&config, newVersion); - - // Update last OTA time whatever result. - this->config.updateLastOta(); - + OtaUpdateOutcome ret = attemptToPerformOta(&config); Serial.println(ret); if (ret == OtaUpdateOutcome::UPDATE_PERFORMED) { Serial.println("OTA update performed, restarting ..."); int i = 6; while (i != 0) { i = i - 1; - sm.executeOTA(StateMachine::OtaState::OTA_STATE_SUCCESS, "", i); + if (this->callback) { + this->callback(StateMachine::OtaState::OTA_STATE_SUCCESS, String(i)); + } delay(1000); } esp_restart(); } } -private: - AirGradient *ag; - StateMachine &sm; - Configuration &config; + void setHandlerCallback(OtaHandlerCallback_t callback) { + this->callback = callback; + } - OtaUpdateOutcome attemptToPerformOta(const esp_http_client_config_t *config, - String newVersion) { +private: + OtaHandlerCallback_t callback; + + OtaUpdateOutcome attemptToPerformOta(const esp_http_client_config_t *config) { esp_http_client_handle_t client = esp_http_client_init(config); if (client == NULL) { Serial.println("Failed to initialize HTTP connection"); @@ -129,7 +117,9 @@ private: Serial.println("File size: " + String(totalSize) + String(" bytes")); // Show display start update new firmware. - sm.executeOTA(StateMachine::OtaState::OTA_STATE_BEGIN, newVersion, 0); + if (this->callback) { + this->callback(StateMachine::OtaState::OTA_STATE_BEGIN, ""); + } // Download file and write new firmware to OTA partition uint32_t lastUpdate = millis(); @@ -142,14 +132,18 @@ private: } if (data_read < 0) { Serial.println("Data read error"); - sm.executeOTA(StateMachine::OtaState::OTA_STATE_FAIL, "", 0); + if (this->callback) { + this->callback(StateMachine::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) { - sm.executeOTA(StateMachine::OtaState::OTA_STATE_FAIL, "", 0); + if (this->callback) { + this->callback(StateMachine::OtaState::OTA_STATE_FAIL, ""); + } break; } binary_file_len += data_read; @@ -157,8 +151,12 @@ private: 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); + // sm.executeOTA(StateMachine::OtaState::OTA_STATE_PROCESSING, "", + // percent); + if (this->callback) { + this->callback(StateMachine::OtaState::OTA_STATE_PROCESSING, + String(percent)); + } lastUpdate = millis(); } } From 8eb8d4a1ec77356e32067080b45fc94fa0b72e58 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Fri, 3 May 2024 17:27:05 +0700 Subject: [PATCH 15/47] Remove dependency from `StateMachine` --- examples/OneOpenAir/OneOpenAir.ino | 65 +++++++++++++++++++++++++----- examples/OneOpenAir/OtaHandler.h | 23 ++++++----- src/AgStateMachine.cpp | 44 -------------------- src/AgStateMachine.h | 8 ---- 4 files changed, 68 insertions(+), 72 deletions(-) diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index f3d5111..f0f2038 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -113,7 +113,9 @@ static void factoryConfigReset(void); static void wdgFeedUpdate(void); static void ledBarEnabledUpdate(void); static bool sgp41Init(void); -static void otaHandlerCallback(StateMachine::OtaState state, String mesasge); +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, @@ -435,25 +437,68 @@ static bool sgp41Init(void) { return false; } -static void otaHandlerCallback(StateMachine::OtaState state, String mesasge) { +static void otaHandlerCallback(OtaState state, String mesasge) { switch (state) { - case StateMachine::OtaState::OTA_STATE_BEGIN: - stateMachine.executeOTA(state, fwNewVersion, 0); + case OtaState::OTA_STATE_BEGIN: + displayExecuteOta(state, fwNewVersion, 0); break; - case StateMachine::OtaState::OTA_STATE_FAIL: - stateMachine.executeOTA(state, "", 0); + case OtaState::OTA_STATE_FAIL: + displayExecuteOta(state, "", 0); break; - case StateMachine::OtaState::OTA_STATE_PROCESSING: - stateMachine.executeOTA(state, "", mesasge.toInt()); + case OtaState::OTA_STATE_PROCESSING: + displayExecuteOta(state, "", mesasge.toInt()); break; - case StateMachine::OtaState::OTA_STATE_SUCCESS: - stateMachine.executeOTA(state, "", mesasge.toInt()); + 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.showNewFirmwareVersion(msg); + } else { + Serial.println("New firmware: " + msg); + } + delay(2500); + break; + } + case OtaState::OTA_STATE_FAIL: { + if (ag->isOne()) { + oledDisplay.showNewFirmwareFailed(); + } else { + Serial.println("Error: Firmware update: failed"); + } + + delay(2500); + break; + } + case OtaState::OTA_STATE_PROCESSING: { + if (ag->isOne()) { + oledDisplay.showNewFirmwareUpdating(String(processing)); + } else { + Serial.println("Firmware update: " + String(processing) + String("%")); + } + + break; + } + case OtaState::OTA_STATE_SUCCESS: { + if (ag->isOne()) { + oledDisplay.showNewFirmwareSuccess(String(processing)); + } else { + Serial.println("Rebooting... " + String(processing)); + } + break; + } + default: + break; + } +} + static void sendDataToAg() { /** Change oledDisplay and led state */ if (ag->isOne()) { diff --git a/examples/OneOpenAir/OtaHandler.h b/examples/OneOpenAir/OtaHandler.h index 5fb7a31..f5ace5b 100644 --- a/examples/OneOpenAir/OtaHandler.h +++ b/examples/OneOpenAir/OtaHandler.h @@ -1,9 +1,5 @@ #ifndef _OTA_HANDLER_H_ #define _OTA_HANDLER_H_ - -#include "AgConfigure.h" -#include "AgStateMachine.h" -#include "AirGradient.h" #include #include #include @@ -19,7 +15,14 @@ enum OtaUpdateOutcome { UDPATE_SKIPPED }; -typedef void(*OtaHandlerCallback_t)(StateMachine::OtaState state, +enum OtaState { + OTA_STATE_BEGIN, + OTA_STATE_FAIL, + OTA_STATE_PROCESSING, + OTA_STATE_SUCCESS +}; + +typedef void(*OtaHandlerCallback_t)(OtaState state, String message); class OtaHandler { @@ -43,7 +46,7 @@ public: while (i != 0) { i = i - 1; if (this->callback) { - this->callback(StateMachine::OtaState::OTA_STATE_SUCCESS, String(i)); + this->callback(OtaState::OTA_STATE_SUCCESS, String(i)); } delay(1000); } @@ -118,7 +121,7 @@ private: // Show display start update new firmware. if (this->callback) { - this->callback(StateMachine::OtaState::OTA_STATE_BEGIN, ""); + this->callback(OtaState::OTA_STATE_BEGIN, ""); } // Download file and write new firmware to OTA partition @@ -133,7 +136,7 @@ private: if (data_read < 0) { Serial.println("Data read error"); if (this->callback) { - this->callback(StateMachine::OtaState::OTA_STATE_FAIL, ""); + this->callback(OtaState::OTA_STATE_FAIL, ""); } break; } @@ -142,7 +145,7 @@ private: update_handle, (const void *)upgrade_data_buf, data_read); if (ota_write_err != ESP_OK) { if (this->callback) { - this->callback(StateMachine::OtaState::OTA_STATE_FAIL, ""); + this->callback(OtaState::OTA_STATE_FAIL, ""); } break; } @@ -154,7 +157,7 @@ private: // sm.executeOTA(StateMachine::OtaState::OTA_STATE_PROCESSING, "", // percent); if (this->callback) { - this->callback(StateMachine::OtaState::OTA_STATE_PROCESSING, + this->callback(OtaState::OTA_STATE_PROCESSING, String(percent)); } lastUpdate = millis(); diff --git a/src/AgStateMachine.cpp b/src/AgStateMachine.cpp index 57635fe..5efb6f7 100644 --- a/src/AgStateMachine.cpp +++ b/src/AgStateMachine.cpp @@ -759,47 +759,3 @@ void StateMachine::executeCo2Calibration(void) { void StateMachine::executeLedBarTest(void) { handleLeds(AgStateMachineLedBarTest); } - -void StateMachine::executeOTA(StateMachine::OtaState state, String msg, - int processing) { - switch (state) { - case OtaState::OTA_STATE_BEGIN: { - if (ag->isOne()) { - disp.showNewFirmwareVersion(msg); - } else { - logInfo("New firmware: " + msg); - } - delay(2500); - break; - } - case OtaState::OTA_STATE_FAIL: { - if (ag->isOne()) { - disp.showNewFirmwareFailed(); - } else { - logError("Firmware update: failed"); - } - - delay(2500); - break; - } - case OtaState::OTA_STATE_PROCESSING: { - if (ag->isOne()) { - disp.showNewFirmwareUpdating(String(processing)); - } else { - logInfo("Firmware update: " + String(processing) + String("%")); - } - - break; - } - case OtaState::OTA_STATE_SUCCESS: { - if (ag->isOne()) { - disp.showNewFirmwareSuccess(String(processing)); - } else { - logInfo("Rebooting... " + String(processing)); - } - break; - } - default: - break; - } -} diff --git a/src/AgStateMachine.h b/src/AgStateMachine.h index a783810..7299bb3 100644 --- a/src/AgStateMachine.h +++ b/src/AgStateMachine.h @@ -49,14 +49,6 @@ public: AgStateMachineState getLedState(void); void executeCo2Calibration(void); void executeLedBarTest(void); - - enum OtaState { - OTA_STATE_BEGIN, - OTA_STATE_FAIL, - OTA_STATE_PROCESSING, - OTA_STATE_SUCCESS - }; - void executeOTA(OtaState state, String msg, int processing); }; #endif /** _AG_STATE_MACHINE_H_ */ From 7c2f8e5b9b863981b2e4654f12f3d487a441fe18 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Tue, 7 May 2024 15:00:32 +0700 Subject: [PATCH 16/47] Add WiFi reset to factory default: connect to SSID `airgradient` after led bar test and button still keep pressed. --- examples/OneOpenAir/OneOpenAir.ino | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index 2f29ee8..29c0af2 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -51,6 +51,7 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License #include "OpenMetrics.h" #include "WebServer.h" #include +#include #define LED_BAR_ANIMATION_PERIOD 100 /** ms */ #define DISP_UPDATE_INTERVAL 2500 /** ms */ @@ -163,6 +164,11 @@ void setup() { if (ag->isOne()) { if (ledBarButtonTest) { stateMachine.executeLedBarPowerUpTest(); + if (ag->button.getState() == PushButton::BUTTON_PRESSED) { + WiFi.begin("airgradient", "cleanair"); + Serial.println("WiFi Credential reset to factory defaults"); + ESP.restart(); + } } else { ledBarEnabledUpdate(); connectToWifi = true; From 955172d3d3fb0f6103c93a0cf8b1a9e88f5de068 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Wed, 8 May 2024 12:22:34 +0700 Subject: [PATCH 17/47] Move structure configure to JSON --- src/AgConfigure.cpp | 980 +++++++++++++++++++++++++++----------------- src/AgConfigure.h | 25 +- 2 files changed, 594 insertions(+), 411 deletions(-) diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp index 7027895..8b642fd 100644 --- a/src/AgConfigure.cpp +++ b/src/AgConfigure.cpp @@ -7,8 +7,8 @@ #include "EEPROM.h" #endif -#define EEPROM_CONFIG_SIZE 512 -#define CONFIG_FILE_NAME "/cfg.bin" +#define EEPROM_CONFIG_SIZE 1024 +#define CONFIG_FILE_NAME "/cfg.json" const char *CONFIGURATION_CONTROL_NAME[] = { [ConfigurationControlLocal] = "local", @@ -21,6 +21,27 @@ const char *LED_BAR_MODE_NAMES[] = { [LedBarModeCO2] = "co2", }; +#define JSON_PROP_NAME(name) jprop_##name +#define JSON_PROP_DEF(name) const char *JSON_PROP_NAME(name) = #name + +JSON_PROP_DEF(model); +JSON_PROP_DEF(country); +JSON_PROP_DEF(pmStandard); +JSON_PROP_DEF(ledBarMode); +JSON_PROP_DEF(displayMode); +JSON_PROP_DEF(abcDays); +JSON_PROP_DEF(tvocLearningOffset); +JSON_PROP_DEF(noxLearningOffset); +JSON_PROP_DEF(mqttBrokerUrl); +JSON_PROP_DEF(temperatureUnit); +JSON_PROP_DEF(configurationControl); +JSON_PROP_DEF(postDataToAirGradient); +JSON_PROP_DEF(ledbarBrightness); +JSON_PROP_DEF(displayBrightness); +JSON_PROP_DEF(co2CalibrationRequested); +JSON_PROP_DEF(ledBarTestRequested); +JSONVar jconfig; + static bool jsonTypeInvalid(JSONVar root, String validType) { String type = JSON.typeof_(root); if (type == validType || type == "undefined" || type == "unknown" || @@ -52,22 +73,17 @@ String Configuration::getLedBarModeName(LedBarMode mode) { * */ void Configuration::saveConfig(void) { - config._check = 0; - int len = sizeof(config) - sizeof(config._check); - uint8_t *data = (uint8_t *)&config; - for (int i = 0; i < len; i++) { - config._check += data[i]; - } + String data = toString(); + int len = data.length(); #ifdef ESP8266 - for (int i = 0; i < sizeof(config); i++) { + for (int i = 0; i < len; i++) { EEPROM.write(i, data[i]); } EEPROM.commit(); #else File file = SPIFFS.open(CONFIG_FILE_NAME, "w", true); if (file && !file.isDirectory()) { - if (file.write((const uint8_t *)&config, sizeof(config)) != - sizeof(config)) { + if (file.write((const uint8_t *)data.c_str(), len) != len) { logError("Write SPIFFS file failed"); } file.close(); @@ -80,65 +96,30 @@ void Configuration::saveConfig(void) { void Configuration::loadConfig(void) { bool readSuccess = false; + char *buf = (char *)malloc(EEPROM_CONFIG_SIZE); + if (buf == NULL) { + logError("Malloc read file buffer failed"); + return; + } + memset(buf, 0, EEPROM_CONFIG_SIZE); #ifdef ESP8266 - uint8_t *data = (uint8_t *)&config; - for (int i = 0; i < sizeof(config); i++) { - data[i] = EEPROM.read(i); + for (int i = 0; i < EEPROM_CONFIG_SIZE; i++) { + buf[i] = EEPROM.read(i); } readSuccess = true; #else File file = SPIFFS.open(CONFIG_FILE_NAME); if (file && !file.isDirectory()) { - if (file.readBytes((char *)&config, sizeof(config)) == sizeof(config)) { - readSuccess = true; - } + logInfo("Read file"); + file.readBytes(buf, file.size()); + readSuccess = true; file.close(); + } else { + SPIFFS.format(); } #endif - - if (!readSuccess) { - logError("Load configure failed"); - defaultConfig(); - } else { - uint32_t sum = 0; - uint8_t *data = (uint8_t *)&config; - int len = sizeof(config) - sizeof(config._check); - for (int i = 0; i < len; i++) { - sum += data[i]; - } - - if (sum != config._check) { - logError("Configure validate invalid"); - defaultConfig(); - } else { - /** Correct configuration parameter value. */ - bool changed = false; - if ((config.temperatureUnit != 'c') && (config.temperatureUnit != 'f')) { - config.temperatureUnit = 'c'; - changed = true; - logError("Temperture unit invalid, set default 'c'"); - } - if ((config.useRGBLedBar != (uint8_t)LedBarModeCO2) && - (config.useRGBLedBar != (uint8_t)LedBarModePm) && - (config.useRGBLedBar != (uint8_t)LedBarModeOff)) { - config.useRGBLedBar = (uint8_t)LedBarModeCO2; - changed = true; - logError("LedBarMode invalid, set default: co2"); - } - if (config.ledBarBrightness > 100) { - config.ledBarBrightness = 100; - changed = true; - } - if (config.displayBrightness > 100) { - config.displayBrightness = 100; - changed = true; - } - - if (changed) { - saveConfig(); - } - } - } + toConfig(buf); + free(buf); } /** @@ -146,23 +127,24 @@ void Configuration::loadConfig(void) { * */ void Configuration::defaultConfig(void) { - // Default country is null - memset(config.country, 0, sizeof(config.country)); - // Default MQTT broker is null. - memset(config.mqttBroker, 0, sizeof(config.mqttBroker)); + jconfig = JSON.parse("{}"); + jconfig[jprop_country] = ""; + jconfig[jprop_mqttBrokerUrl] = ""; + jconfig[jprop_configurationControl] = + String(CONFIGURATION_CONTROL_NAME + [ConfigurationControl::ConfigurationControlBoth]); + jconfig[jprop_pmStandard] = getPMStandardString(false); + jconfig[jprop_temperatureUnit] = "c"; + jconfig[jprop_postDataToAirGradient] = true; + jconfig[jprop_displayMode] = getDisplayModeString(true); + jconfig[jprop_ledbarBrightness] = 100; + jconfig[jprop_displayBrightness] = 100; + jconfig[jprop_ledBarMode] = getLedBarModeName(LedBarMode::LedBarModeCO2); - config.configurationControl = ConfigurationControl::ConfigurationControlBoth; - config.inUSAQI = false; // pmStandard = ugm3 - config.inF = false; - config.postDataToAirGradient = true; - config.displayMode = true; - config.useRGBLedBar = LedBarMode::LedBarModeCO2; - config.abcDays = 8; - config.tvocLearningOffset = 12; - config.noxLearningOffset = 12; - config.temperatureUnit = 'c'; - config.ledBarBrightness = 100; - config.displayBrightness = 100; + jconfig[jprop_tvocLearningOffset] = 12; + jconfig[jprop_noxLearningOffset] = 12; + jconfig[jprop_abcDays] = 8; + jconfig[jprop_model] = ""; saveConfig(); } @@ -171,7 +153,10 @@ void Configuration::defaultConfig(void) { * @brief Show configuration as JSON string message over log * */ -void Configuration::printConfig(void) { logInfo(toString().c_str()); } +void Configuration::printConfig(void) { + String cfg = toString(); + logInfo(cfg); +} /** * @brief Construct a new Ag Configure:: Ag Configure object @@ -194,11 +179,6 @@ Configuration::~Configuration() {} * @return false Failure */ bool Configuration::begin(void) { - if (sizeof(config) > EEPROM_CONFIG_SIZE) { - logError("Configuration over EEPROM_CONFIG_SIZE"); - return false; - } - #ifdef ESP32 if (!SPIFFS.begin(true)) { logError("Init SPIFFS failed"); @@ -228,7 +208,7 @@ bool Configuration::parse(String data, bool isLocal) { JSONVar root = JSON.parse(data); failedMessage = ""; - if (JSON.typeof_(root) == "undefined") { + if (root == undefined) { failedMessage = "JSON invalid"; logError(failedMessage); return false; @@ -239,43 +219,33 @@ bool Configuration::parse(String data, bool isLocal) { bool changed = false; /** Get ConfigurationControl */ + String lasCtrl = jconfig[jprop_configurationControl]; if (isLocal) { - uint8_t configurationControl = config.configurationControl; - if (JSON.typeof_(root["configurationControl"]) == "string") { - String configurationControl = root["configurationControl"]; - if (configurationControl != - String(CONFIGURATION_CONTROL_NAME[config.configurationControl])) { - if (configurationControl == - String(CONFIGURATION_CONTROL_NAME - [ConfigurationControl::ConfigurationControlLocal])) { - config.configurationControl = - (uint8_t)ConfigurationControl::ConfigurationControlLocal; + if (JSON.typeof_(root[jprop_configurationControl]) == "string") { + String ctrl = root[jprop_configurationControl]; + if (ctrl == + String(CONFIGURATION_CONTROL_NAME + [ConfigurationControl::ConfigurationControlBoth]) || + ctrl == + String(CONFIGURATION_CONTROL_NAME + [ConfigurationControl::ConfigurationControlLocal]) || + ctrl == + String(CONFIGURATION_CONTROL_NAME + [ConfigurationControl::ConfigurationControlCloud])) { + if (ctrl != lasCtrl) { + jconfig[jprop_configurationControl] = ctrl; changed = true; - } else if (configurationControl == - String( - CONFIGURATION_CONTROL_NAME - [ConfigurationControl::ConfigurationControlCloud])) { - config.configurationControl = - (uint8_t)ConfigurationControl::ConfigurationControlCloud; - changed = true; - } else if (configurationControl == - String( - CONFIGURATION_CONTROL_NAME - [ConfigurationControl::ConfigurationControlBoth])) { - config.configurationControl = - (uint8_t)ConfigurationControl::ConfigurationControlBoth; - changed = true; - } else { - failedMessage = jsonValueInvalidMessage("configurationControl", - configurationControl); - jsonInvalid(); - return false; } + } else { + failedMessage = + jsonValueInvalidMessage(String(jprop_configurationControl), ctrl); + jsonInvalid(); + return false; } } else { - if (jsonTypeInvalid(root["configurationControl"], "string")) { - failedMessage = - jsonTypeInvalidMessage("configurationControl", "string"); + if (jsonTypeInvalid(root[jprop_configurationControl], "string")) { + failedMessage = jsonTypeInvalidMessage( + String(jprop_configurationControl), "string"); jsonInvalid(); return false; } @@ -284,21 +254,21 @@ bool Configuration::parse(String data, bool isLocal) { if (changed) { changed = false; saveConfig(); - configLogInfo( - "configurationControl", - String(CONFIGURATION_CONTROL_NAME[configurationControl]), - String(CONFIGURATION_CONTROL_NAME[config.configurationControl])); + configLogInfo(String(jprop_configurationControl), lasCtrl, + jconfig[jprop_configurationControl]); } - if ((config.configurationControl == - (byte)ConfigurationControl::ConfigurationControlCloud)) { + if (jconfig[jprop_configurationControl] == + String(CONFIGURATION_CONTROL_NAME + [ConfigurationControl::ConfigurationControlCloud])) { failedMessage = "Local configure ignored"; jsonInvalid(); return false; } } else { - if (config.configurationControl == - (byte)ConfigurationControl::ConfigurationControlLocal) { + if (jconfig[jprop_configurationControl] == + String(CONFIGURATION_CONTROL_NAME + [ConfigurationControl::ConfigurationControlLocal])) { failedMessage = "Cloud configure ignored"; jsonInvalid(); return false; @@ -306,13 +276,14 @@ bool Configuration::parse(String data, bool isLocal) { } char temperatureUnit = 0; - if (JSON.typeof_(root["country"]) == "string") { - String country = root["country"]; + if (JSON.typeof_(root[jprop_country]) == "string") { + String country = root[jprop_country]; if (country.length() == 2) { - if (country != String(config.country)) { + String oldCountry = jconfig[jprop_country]; + if (country != oldCountry) { changed = true; - configLogInfo("country", String(config.country), country); - snprintf(config.country, sizeof(config.country), country.c_str()); + configLogInfo(String(jprop_country), oldCountry, country); + jconfig[jprop_country] = country; } } else { failedMessage = "Country name " + country + @@ -322,245 +293,247 @@ bool Configuration::parse(String data, bool isLocal) { return false; } } else { - if (jsonTypeInvalid(root["country"], "string")) { - failedMessage = jsonTypeInvalidMessage("country", "string"); + if (jsonTypeInvalid(root[jprop_country], "string")) { + failedMessage = jsonTypeInvalidMessage(String(jprop_country), "string"); jsonInvalid(); return false; } } - if (JSON.typeof_(root["pmStandard"]) == "string") { - String pmStandard = root["pmStandard"]; - bool inUSAQI = true; - if (pmStandard == getPMStandardString(false)) { - inUSAQI = false; - } else if (pmStandard == getPMStandardString(true)) { - inUSAQI = true; + if (JSON.typeof_(root[jprop_pmStandard]) == "string") { + String standard = root[jprop_pmStandard]; + if (standard == getPMStandardString(true) || + standard == getPMStandardString(false)) { + String oldStandard = jconfig[jprop_pmStandard]; + if (standard != oldStandard) { + configLogInfo(String(jprop_pmStandard), oldStandard, standard); + jconfig[jprop_pmStandard] = standard; + changed = true; + } } else { - failedMessage = jsonValueInvalidMessage("pmStandard", pmStandard); + failedMessage = + jsonValueInvalidMessage(String(jprop_pmStandard), standard); jsonInvalid(); return false; } - - if (inUSAQI != config.inUSAQI) { - configLogInfo("pmStandard", getPMStandardString(config.inUSAQI), - pmStandard); - config.inUSAQI = inUSAQI; - changed = true; - } } else { - if (jsonTypeInvalid(root["pmStandard"], "string")) { - failedMessage = jsonTypeInvalidMessage("pmStandard", "string"); + if (jsonTypeInvalid(root[jprop_pmStandard], "string")) { + failedMessage = + jsonTypeInvalidMessage(String(jprop_pmStandard), "string"); jsonInvalid(); return false; } } - if (JSON.typeof_(root["co2CalibrationRequested"]) == "boolean") { - co2CalibrationRequested = root["co2CalibrationRequested"]; + if (JSON.typeof_(root[jprop_co2CalibrationRequested]) == "boolean") { + co2CalibrationRequested = root[jprop_co2CalibrationRequested]; if (co2CalibrationRequested) { - logInfo("co2CalibrationRequested: " + + logInfo(String(jprop_co2CalibrationRequested) + String(": ") + String(co2CalibrationRequested ? "True" : "False")); } } else { - if (jsonTypeInvalid(root["co2CalibrationRequested"], "boolean")) { - failedMessage = - jsonTypeInvalidMessage("co2CalibrationRequested", "boolean"); + if (jsonTypeInvalid(root[jprop_co2CalibrationRequested], "boolean")) { + failedMessage = jsonTypeInvalidMessage( + String(jprop_co2CalibrationRequested), "boolean"); jsonInvalid(); return false; } } - if (JSON.typeof_(root["ledBarTestRequested"]) == "boolean") { - ledBarTestRequested = root["ledBarTestRequested"]; + if (JSON.typeof_(root[jprop_ledBarTestRequested]) == "boolean") { + ledBarTestRequested = root[jprop_ledBarTestRequested]; if (ledBarTestRequested) { - logInfo("ledBarTestRequested: " + + logInfo(String(jprop_ledBarTestRequested) + String(": ") + String(ledBarTestRequested ? "True" : "False")); } } else { - if (jsonTypeInvalid(root["ledBarTestRequested"], "boolean")) { - failedMessage = jsonTypeInvalidMessage("ledBarTestRequested", "boolean"); + if (jsonTypeInvalid(root[jprop_ledBarTestRequested], "boolean")) { + failedMessage = + jsonTypeInvalidMessage(String(jprop_ledBarTestRequested), "boolean"); jsonInvalid(); return false; } } - if (JSON.typeof_(root["ledBarMode"]) == "string") { - String mode = root["ledBarMode"]; - uint8_t ledBarMode = LedBarModeOff; - if (mode == String(LED_BAR_MODE_NAMES[LedBarModeCO2])) { - ledBarMode = LedBarModeCO2; - } else if (mode == String(LED_BAR_MODE_NAMES[LedBarModePm])) { - ledBarMode = LedBarModePm; - } else if (mode == String(LED_BAR_MODE_NAMES[LedBarModeOff])) { - ledBarMode = LedBarModeOff; + if (JSON.typeof_(root[jprop_ledBarMode]) == "string") { + String mode = root[jprop_ledBarMode]; + if (mode == getLedBarModeName(LedBarMode::LedBarModeCO2) || + mode == getLedBarModeName(LedBarMode::LedBarModeOff) || + mode == getLedBarModeName(LedBarMode::LedBarModePm)) { + String oldMode = jconfig[jprop_ledBarMode]; + if (mode != oldMode) { + jconfig[jprop_ledBarMode] = mode; + changed = true; + } } else { - failedMessage = jsonValueInvalidMessage("ledBarMode", mode); + failedMessage = jsonValueInvalidMessage(String(jprop_ledBarMode), mode); jsonInvalid(); return false; } - - if (ledBarMode != config.useRGBLedBar) { - configLogInfo("useRGBLedBar", - String(LED_BAR_MODE_NAMES[config.useRGBLedBar]), - String(LED_BAR_MODE_NAMES[ledBarMode])); - config.useRGBLedBar = ledBarMode; - changed = true; - } } else { - if (jsonTypeInvalid(root["ledBarMode"], "string")) { - failedMessage = jsonTypeInvalidMessage("ledBarMode", "string"); + if (jsonTypeInvalid(root[jprop_ledBarMode], "string")) { + failedMessage = + jsonTypeInvalidMessage(String(jprop_ledBarMode), "string"); jsonInvalid(); return false; } } - if (JSON.typeof_(root["displayMode"]) == "string") { - String mode = root["displayMode"]; - bool displayMode = false; - if (mode == getDisplayModeString(true)) { - displayMode = true; - } else if (mode == getDisplayModeString(false)) { - displayMode = false; + if (JSON.typeof_(root[jprop_displayMode]) == "string") { + String mode = root[jprop_displayMode]; + if (mode == getDisplayModeString(true) || + mode == getDisplayModeString(false)) { + String oldMode = jconfig[jprop_displayMode]; + if (mode != oldMode) { + jconfig[jprop_displayMode] = mode; + changed = true; + } } else { - failedMessage = jsonTypeInvalidMessage("displayMode", mode); + jsonValueInvalidMessage(String(jprop_displayMode), mode); jsonInvalid(); return false; } - - if (displayMode != config.displayMode) { - changed = true; - configLogInfo("displayMode", getDisplayModeString(config.displayMode), - mode); - config.displayMode = displayMode; - } } else { - if (jsonTypeInvalid(root["displayMode"], "string")) { - failedMessage = jsonTypeInvalidMessage("displayMode", "string"); + if (jsonTypeInvalid(root[jprop_displayMode], "string")) { + failedMessage = + jsonTypeInvalidMessage(String(jprop_displayMode), "string"); jsonInvalid(); return false; } } - if (JSON.typeof_(root["abcDays"]) == "number") { - int abcDays = root["abcDays"]; - if (abcDays <= 0) { - abcDays = 0; + if (JSON.typeof_(root[jprop_abcDays]) == "number") { + int value = root[jprop_abcDays]; + if (value <= 0) { + value = 0; } - if (abcDays != config.abcDays) { - logInfo("Set abcDays: " + String(abcDays)); - configLogInfo("abcDays", getAbcDayString(config.abcDays), - String(getAbcDayString(abcDays))); - config.abcDays = abcDays; + int oldValue = jconfig[jprop_abcDays]; + if (value != oldValue) { + logInfo(String("Set ") + String(jprop_abcDays) + String(": ") + + String(value)); + configLogInfo(String(jprop_abcDays), getAbcDayString(oldValue), + String(getAbcDayString(value))); + + jconfig[jprop_abcDays] = value; changed = true; } } else { - if (jsonTypeInvalid(root["abcDays"], "number")) { - failedMessage = jsonTypeInvalidMessage("abcDays", "number"); + if (jsonTypeInvalid(root[jprop_abcDays], "number")) { + failedMessage = jsonTypeInvalidMessage(String(jprop_abcDays), "number"); jsonInvalid(); return false; } } _tvocLearningOffsetChanged = false; - if (JSON.typeof_(root["tvocLearningOffset"]) == "number") { - int tvocLearningOffset = root["tvocLearningOffset"]; - if (tvocLearningOffset != config.tvocLearningOffset) { - changed = true; - _tvocLearningOffsetChanged = true; - configLogInfo("tvocLearningOffset", String(config.tvocLearningOffset), - String(tvocLearningOffset)); - config.tvocLearningOffset = tvocLearningOffset; + if (JSON.typeof_(root[jprop_tvocLearningOffset]) == "number") { + int value = root[jprop_tvocLearningOffset]; + int oldValue = jconfig[jprop_tvocLearningOffset]; + if (value < 0) { + jsonValueInvalidMessage(String(jprop_tvocLearningOffset), String(value)); + jsonInvalid(); + return false; + } else { + if (value != oldValue) { + changed = true; + _tvocLearningOffsetChanged = true; + configLogInfo(String(jprop_tvocLearningOffset), String(oldValue), + String(value)); + jconfig[jprop_tvocLearningOffset] = value; + } } } else { - if (jsonTypeInvalid(root["tvocLearningOffset"], "number")) { - failedMessage = jsonTypeInvalidMessage("tvocLearningOffset", "number"); + if (jsonTypeInvalid(root[jprop_tvocLearningOffset], "number")) { + failedMessage = + jsonTypeInvalidMessage(String(jprop_tvocLearningOffset), "number"); jsonInvalid(); return false; } } _noxLearnOffsetChanged = false; - if (JSON.typeof_(root["noxLearningOffset"]) == "number") { - int noxLearningOffset = root["noxLearningOffset"]; - if (noxLearningOffset != config.noxLearningOffset) { - changed = true; - _noxLearnOffsetChanged = true; - configLogInfo("noxLearningOffset", String(config.noxLearningOffset), - String(noxLearningOffset)); - config.noxLearningOffset = noxLearningOffset; - } - } else { - if (jsonTypeInvalid(root["noxLearningOffset"], "number")) { - failedMessage = jsonTypeInvalidMessage("noxLearningOffset", "number"); - jsonInvalid(); - return false; - } - } - - if (JSON.typeof_(root["mqttBrokerUrl"]) == "string") { - String broker = root["mqttBrokerUrl"]; - if (broker.length() < sizeof(config.mqttBroker)) { - if (broker != String(config.mqttBroker)) { + if (JSON.typeof_(root[jprop_noxLearningOffset]) == "number") { + int value = root[jprop_noxLearningOffset]; + int oldValue = jconfig[jprop_noxLearningOffset]; + if (value > 0) { + if (value != oldValue) { changed = true; - configLogInfo("mqttBrokerUrl", String(config.mqttBroker), broker); - snprintf(config.mqttBroker, sizeof(config.mqttBroker), broker.c_str()); + _noxLearnOffsetChanged = true; + configLogInfo(String(jprop_noxLearningOffset), String(oldValue), + String(value)); + jconfig[jprop_noxLearningOffset] = value; } } else { + failedMessage = jsonValueInvalidMessage(String(jprop_noxLearningOffset), + String(value)); + jsonInvalid(); + return false; + } + } else { + if (jsonTypeInvalid(root[jprop_noxLearningOffset], "number")) { failedMessage = - "'mqttBroker' value length invalid: " + String(broker.length()); - jsonInvalid(); - return false; - } - } else { - if (jsonTypeInvalid(root["mqttBrokerUrl"], "string")) { - failedMessage = jsonTypeInvalidMessage("mqttBrokerUrl", "string"); + jsonTypeInvalidMessage(String(jprop_noxLearningOffset), "number"); jsonInvalid(); return false; } } - if (JSON.typeof_(root["temperatureUnit"]) == "string") { - String unit = root["temperatureUnit"]; + if (JSON.typeof_(root[jprop_mqttBrokerUrl]) == "string") { + String broker = root[jprop_mqttBrokerUrl]; + String oldBroker = jconfig[jprop_mqttBrokerUrl]; + if (broker != oldBroker) { + changed = true; + configLogInfo(String(jprop_mqttBrokerUrl), oldBroker, broker); + jconfig[jprop_mqttBrokerUrl] = broker; + } + } else { + if (jsonTypeInvalid(root[jprop_mqttBrokerUrl], "string")) { + failedMessage = + jsonTypeInvalidMessage(String(jprop_mqttBrokerUrl), "string"); + jsonInvalid(); + return false; + } + } + + if (JSON.typeof_(root[jprop_temperatureUnit]) == "string") { + String unit = root[jprop_temperatureUnit]; + String oldUnit = jconfig[jprop_temperatureUnit]; unit.toLowerCase(); - if (unit == "c") { - temperatureUnit = 'c'; - } else if (unit == "f") { - temperatureUnit = 'f'; + if (unit == "c" || unit == "f") { + if (unit != oldUnit) { + changed = true; + jconfig[jprop_temperatureUnit] = unit; + configLogInfo(String(jprop_temperatureUnit), oldUnit, unit); + } } else { - failedMessage = "'temperatureUnit' value '" + unit + "' invalid"; - logError(failedMessage); - return false; - } - } else { - if (jsonTypeInvalid(root["temperatureUnit"], "string")) { - failedMessage = jsonTypeInvalidMessage("temperatureUnit", "string"); + jsonValueInvalidMessage(String(jprop_temperatureUnit), unit); + jsonInvalid(); + return false; + } + } else { + if (jsonTypeInvalid(root[jprop_temperatureUnit], "string")) { + failedMessage = + jsonTypeInvalidMessage(String(jprop_temperatureUnit), "string"); jsonInvalid(); return false; } - } - - if (temperatureUnit != 0 && temperatureUnit != config.temperatureUnit) { - changed = true; - configLogInfo("temperatureUnit", String(config.temperatureUnit), - String(temperatureUnit)); - config.temperatureUnit = temperatureUnit; } if (isLocal) { - if (JSON.typeof_(root["postDataToAirGradient"]) == "boolean") { - bool post = root["postDataToAirGradient"]; - if (post != config.postDataToAirGradient) { + if (JSON.typeof_(root[jprop_postDataToAirGradient]) == "boolean") { + bool value = root[jprop_postDataToAirGradient]; + bool oldValue = jconfig[jprop_postDataToAirGradient]; + if (value != oldValue) { changed = true; - configLogInfo("postDataToAirGradient", - String(config.postDataToAirGradient ? "true" : "false"), - String(post ? "true" : "false")); - config.postDataToAirGradient = post; + configLogInfo(String(jprop_postDataToAirGradient), + String(oldValue ? "true" : "false"), + String(value ? "true" : "false")); + jconfig[jprop_postDataToAirGradient] = value; } } else { - if (jsonTypeInvalid(root["postDataToAirGradient"], "boolean")) { - failedMessage = - jsonTypeInvalidMessage("postDataToAirGradient", "boolean"); + if (jsonTypeInvalid(root[jprop_postDataToAirGradient], "boolean")) { + failedMessage = jsonTypeInvalidMessage( + String(jprop_postDataToAirGradient), "boolean"); jsonInvalid(); return false; } @@ -569,70 +542,70 @@ bool Configuration::parse(String data, bool isLocal) { /** Parse data only got from AirGradient server */ if (isLocal == false) { - if (JSON.typeof_(root["model"]) == "string") { - String model = root["model"]; - if (model.length() < sizeof(config.model)) { - if (model != String(config.model)) { - changed = true; - configLogInfo("model", String(config.model), model); - snprintf(config.model, sizeof(config.model), model.c_str()); - } - } else { - failedMessage = - "'modal' value length invalid: " + String(model.length()); - jsonInvalid(); - return false; + if (JSON.typeof_(root[jprop_model]) == "string") { + String model = root[jprop_model]; + String oldModel = jconfig[jprop_model]; + if (model != oldModel) { + changed = true; + configLogInfo(String(jprop_model), oldModel, model); + jconfig[jprop_model] = model; } } else { - if (jsonTypeInvalid(root["model"], "string")) { - failedMessage = jsonTypeInvalidMessage("model", "string"); + if (jsonTypeInvalid(root[jprop_model], "string")) { + failedMessage = jsonTypeInvalidMessage(String(jprop_model), "string"); jsonInvalid(); return false; } } } - if (JSON.typeof_(root["ledbarBrightness"]) == "number") { - int brightnress = root["ledbarBrightness"]; - if (brightnress != config.ledBarBrightness) { - if (brightnress <= 100) { + if (JSON.typeof_(root[jprop_ledbarBrightness]) == "number") { + int value = root[jprop_ledbarBrightness]; + int oldValue = jconfig[jprop_ledbarBrightness]; + if (value >= 0 && value <= 100) { + if (value != oldValue) { changed = true; - configLogInfo("ledbarBrightness", String(config.ledBarBrightness), - String(brightnress)); + configLogInfo(String(jprop_ledbarBrightness), String(oldValue), + String(value)); ledBarBrightnessChanged = true; - config.ledBarBrightness = (uint8_t)brightnress; - } else { - failedMessage = - "\"ledbarBrightness\" value invalid: " + String(brightnress); - return false; + jconfig[jprop_ledbarBrightness] = value; } + } else { + failedMessage = jsonValueInvalidMessage(String(jprop_ledbarBrightness), + String(value)); + jsonInvalid(); + return false; } } else { - if (jsonTypeInvalid(root["ledbarBrightness"], "number")) { - failedMessage = jsonTypeInvalidMessage("ledbarBrightness", "number"); + if (jsonTypeInvalid(root[jprop_ledbarBrightness], "number")) { + failedMessage = + jsonTypeInvalidMessage(String(jprop_ledbarBrightness), "number"); jsonInvalid(); return false; } } - if (JSON.typeof_(root["displayBrightness"]) == "number") { - int brightness = root["displayBrightness"]; - if (brightness != config.displayBrightness) { - if (brightness <= 100) { + if (JSON.typeof_(root[jprop_displayBrightness]) == "number") { + int value = root[jprop_displayBrightness]; + int oldValue = jconfig[jprop_displayBrightness]; + if (value >= 0 && value <= 100) { + if (value != oldValue) { changed = true; displayBrightnessChanged = true; - configLogInfo("displayBrightness", String(config.displayBrightness), - String(brightness)); - config.displayBrightness = (uint8_t)brightness; - } else { - failedMessage = - "\"displayBrightness\" value invalid: " + String(brightness); - return false; + configLogInfo(String(jprop_displayBrightness), String(oldValue), + String(value)); + jconfig[jprop_displayBrightness] = value; } + } else { + failedMessage = jsonValueInvalidMessage(String(jprop_displayBrightness), + String(value)); + jsonInvalid(); + return false; } } else { - if (jsonTypeInvalid(root["displayBrightness"], "number")) { - failedMessage = jsonTypeInvalidMessage("displayBrightness", "number"); + if (jsonTypeInvalid(root[jprop_displayBrightness], "number")) { + failedMessage = + jsonTypeInvalidMessage(String(jprop_displayBrightness), "number"); jsonInvalid(); return false; } @@ -656,58 +629,7 @@ bool Configuration::parse(String data, bool isLocal) { * * @return String */ -String Configuration::toString(void) { - JSONVar root; - - /** "country" */ - root["country"] = String(config.country); - - /** "pmStandard" */ - root["pmStandard"] = getPMStandardString(config.inUSAQI); - - /** co2CalibrationRequested */ - /** ledBarTestRequested */ - - /** "ledBarMode" */ - root["ledBarMode"] = getLedBarModeName(); - - /** "displayMode" */ - if (config.displayMode) { - root["displayMode"] = "on"; - } else { - root["displayMode"] = "off"; - } - - /** "abcDays" */ - root["abcDays"] = config.abcDays; - - /** "tvocLearningOffset" */ - root["tvocLearningOffset"] = config.tvocLearningOffset; - - /** "noxLearningOffset" */ - root["noxLearningOffset"] = config.noxLearningOffset; - - /** "mqttBrokerUrl" */ - root["mqttBrokerUrl"] = String(config.mqttBroker); - - /** "temperatureUnit" */ - root["temperatureUnit"] = String(config.temperatureUnit); - - /** configurationControl */ - root["configurationControl"] = - String(CONFIGURATION_CONTROL_NAME[config.configurationControl]); - - /** "postDataToAirGradient" */ - root["postDataToAirGradient"] = config.postDataToAirGradient; - - /** Led bar brightness */ - root["ledbarBrightness"] = config.ledBarBrightness; - - /** Display brightness */ - root["displayBrightness"] = config.displayBrightness; - - return JSON.stringify(root); -} +String Configuration::toString(void) { return JSON.stringify(jconfig); } /** * @brief Temperature unit (F or C) @@ -716,7 +638,8 @@ String Configuration::toString(void) { * @return false C */ bool Configuration::isTemperatureUnitInF(void) { - return (config.temperatureUnit == 'f'); + String unit = jconfig[jprop_temperatureUnit]; + return (unit == "f"); } /** @@ -724,7 +647,10 @@ bool Configuration::isTemperatureUnitInF(void) { * * @return String */ -String Configuration::getCountry(void) { return String(config.country); } +String Configuration::getCountry(void) { + String country = jconfig[jprop_country]; + return country; +} /** * @brief PM unit standard (USAQI, ugm3) @@ -732,14 +658,20 @@ String Configuration::getCountry(void) { return String(config.country); } * @return true USAQI * @return false ugm3 */ -bool Configuration::isPmStandardInUSAQI(void) { return config.inUSAQI; } +bool Configuration::isPmStandardInUSAQI(void) { + String standard = jconfig[jprop_pmStandard]; + return (standard == getPMStandardString(true)); +} /** * @brief Get CO2 calibration ABC time * * @return int Number of day */ -int Configuration::getCO2CalibrationAbcDays(void) { return config.abcDays; } +int Configuration::getCO2CalibrationAbcDays(void) { + int value = jconfig[jprop_abcDays]; + return value; +} /** * @brief Get Led Bar Mode @@ -747,7 +679,17 @@ int Configuration::getCO2CalibrationAbcDays(void) { return config.abcDays; } * @return LedBarMode */ LedBarMode Configuration::getLedBarMode(void) { - return (LedBarMode)config.useRGBLedBar; + String mode = jconfig[jprop_ledBarMode]; + if (mode == getLedBarModeName(LedBarModeCO2)) { + return LedBarModeCO2; + } + if (mode == getLedBarModeName(LedBarModeOff)) { + return LedBarModeOff; + } + if (mode == getLedBarModeName(LedBarModePm)) { + return LedBarModePm; + } + return LedBarModeOff; } /** @@ -756,7 +698,8 @@ LedBarMode Configuration::getLedBarMode(void) { * @return String */ String Configuration::getLedBarModeName(void) { - return getLedBarModeName((LedBarMode)config.useRGBLedBar); + String mode = jconfig[jprop_ledBarMode]; + return mode; } /** @@ -765,7 +708,13 @@ String Configuration::getLedBarModeName(void) { * @return true On * @return false Off */ -bool Configuration::getDisplayMode(void) { return config.displayMode; } +bool Configuration::getDisplayMode(void) { + String mode = jconfig[jprop_displayMode]; + if (mode == getDisplayModeString(true)) { + return true; + } + return false; +} /** * @brief Get MQTT uri @@ -773,7 +722,8 @@ bool Configuration::getDisplayMode(void) { return config.displayMode; } * @return String */ String Configuration::getMqttBrokerUri(void) { - return String(config.mqttBroker); + String broker = jconfig[jprop_mqttBrokerUrl]; + return broker; } /** @@ -783,7 +733,8 @@ String Configuration::getMqttBrokerUri(void) { * @return false No-Post */ bool Configuration::isPostDataToAirGradient(void) { - return config.postDataToAirGradient; + bool post = jconfig[jprop_postDataToAirGradient]; + return post; } /** @@ -792,7 +743,20 @@ bool Configuration::isPostDataToAirGradient(void) { * @return ConfigurationControl */ ConfigurationControl Configuration::getConfigurationControl(void) { - return (ConfigurationControl)config.configurationControl; + String ctrl = jconfig[jprop_configurationControl]; + if (ctrl == String(CONFIGURATION_CONTROL_NAME + [ConfigurationControl::ConfigurationControlBoth])) { + return ConfigurationControl::ConfigurationControlBoth; + } + if (ctrl == String(CONFIGURATION_CONTROL_NAME + [ConfigurationControl::ConfigurationControlLocal])) { + return ConfigurationControl::ConfigurationControlLocal; + } + if (ctrl == String(CONFIGURATION_CONTROL_NAME + [ConfigurationControl::ConfigurationControlCloud])) { + return ConfigurationControl::ConfigurationControlCloud; + } + return ConfigurationControl::ConfigurationControlBoth; } /** @@ -835,7 +799,10 @@ void Configuration::reset(void) { * * @return String */ -String Configuration::getModel(void) { return String(config.model); } +String Configuration::getModel(void) { + String model = jconfig[jprop_model]; + return model; +} bool Configuration::isUpdated(void) { bool updated = this->udpated; @@ -883,11 +850,244 @@ String Configuration::getAbcDayString(int value) { return String(value); } +void Configuration::toConfig(const char *buf) { + logInfo("Parse file to JSON"); + JSONVar root = JSON.parse(buf); + if (!(root == undefined)) { + jconfig = root; + } + + bool changed = false; + bool isInvalid = false; + + /** Validate country */ + if (JSON.typeof_(jconfig[jprop_country]) != "string") { + isInvalid = true; + } else { + String country = jconfig[jprop_country]; + if (country.length() != 2) { + isInvalid = true; + } else { + isInvalid = false; + } + } + if (isInvalid) { + jconfig[jprop_country] = ""; + changed = true; + logInfo("toConfig: country changed"); + } + + /** validate: PM standard */ + if (JSON.typeof_(jconfig[jprop_pmStandard]) != "string") { + isInvalid = true; + } else { + String standard = jconfig[jprop_pmStandard]; + if (standard != getPMStandardString(true) && + standard != getPMStandardString(false)) { + isInvalid = true; + } else { + isInvalid = false; + } + } + if (isInvalid) { + jconfig[jprop_pmStandard] = getPMStandardString(false); + changed = true; + logInfo("toConfig: pmStandard changed"); + } + + /** validate led bar mode */ + if (JSON.typeof_(jconfig[jprop_ledBarMode]) != "string") { + isInvalid = true; + } else { + String mode = jconfig[jprop_ledBarMode]; + if (mode != getLedBarModeName(LedBarMode::LedBarModeCO2) && + mode != getLedBarModeName(LedBarMode::LedBarModeOff) && + mode != getLedBarModeName(LedBarMode::LedBarModePm)) { + isInvalid = true; + } else { + isInvalid = false; + } + } + if (isInvalid) { + jconfig[jprop_ledBarMode] = getLedBarModeName(LedBarMode::LedBarModeCO2); + changed = true; + logInfo("toConfig: ledBarMode changed"); + } + + /** validate display mode */ + if (JSON.typeof_(jconfig[jprop_displayMode]) != "string") { + isInvalid = true; + } else { + String mode = jconfig[jprop_displayMode]; + if (mode != getDisplayModeString(true) && + mode != getDisplayModeString(false)) { + isInvalid = true; + } else { + isInvalid = false; + } + } + if (isInvalid) { + jconfig[jprop_displayMode] = getDisplayModeString(true); + changed = true; + logInfo("toConfig: displayMode changed"); + } + + /** validate abcday */ + if (JSON.typeof_(jconfig[jprop_abcDays]) != "number") { + isInvalid = true; + } else { + isInvalid = false; + } + if (isInvalid) { + jconfig[jprop_abcDays] = 8; + changed = true; + logInfo("toConfig: abcDays changed"); + } + + /** validate tvoc learning offset */ + if (JSON.typeof_(jconfig[jprop_tvocLearningOffset]) != "number") { + isInvalid = true; + } else { + int value = jconfig[jprop_tvocLearningOffset]; + if (value < 0) { + isInvalid = true; + } else { + isInvalid = false; + } + } + if (isInvalid) { + jconfig[jprop_tvocLearningOffset] = 12; + changed = true; + logInfo("toConfig: tvocLearningOffset changed"); + } + + /** validate nox learning offset */ + if (JSON.typeof_(jconfig[jprop_noxLearningOffset]) != "number") { + isInvalid = true; + } else { + int value = jconfig[jprop_noxLearningOffset]; + if (value < 0) { + isInvalid = true; + } else { + isInvalid = false; + } + } + if (isInvalid) { + jconfig[jprop_noxLearningOffset] = 12; + changed = true; + logInfo("toConfig: noxLearningOffset changed"); + } + + /** validate mqtt broker */ + if (JSON.typeof_(jconfig[jprop_mqttBrokerUrl]) != "string") { + isInvalid = true; + } else { + isInvalid = false; + } + if (isInvalid) { + changed = true; + jconfig[jprop_mqttBrokerUrl] = ""; + logInfo("toConfig: mqttBroker changed"); + } + + /** Validate temperature unit */ + if (JSON.typeof_(jconfig[jprop_temperatureUnit]) != "string") { + isInvalid = true; + } else { + String unit = jconfig[jprop_temperatureUnit]; + if (unit != "c" && unit != "f") { + isInvalid = true; + } else { + isInvalid = false; + } + } + if (isInvalid) { + jconfig[jprop_temperatureUnit] = "c"; + changed = true; + logInfo("toConfig: temperatureUnit changed"); + } + + /** validate configuration control */ + if (JSON.typeof_(jprop_configurationControl) != "string") { + isInvalid = true; + } else { + String ctrl = jconfig[jprop_configurationControl]; + if (ctrl != String(CONFIGURATION_CONTROL_NAME + [ConfigurationControl::ConfigurationControlBoth]) && + ctrl != String(CONFIGURATION_CONTROL_NAME + [ConfigurationControl::ConfigurationControlLocal]) && + ctrl != String(CONFIGURATION_CONTROL_NAME + [ConfigurationControl::ConfigurationControlCloud])) { + isInvalid = true; + } else { + isInvalid = false; + } + } + if (isInvalid) { + jconfig[jprop_configurationControl] = + String(CONFIGURATION_CONTROL_NAME + [ConfigurationControl::ConfigurationControlBoth]); + changed = true; + logInfo("toConfig: configurationControl changed"); + } + + /** Validate post to airgradient cloud */ + if (JSON.typeof_(jconfig[jprop_postDataToAirGradient]) != "boolean") { + isInvalid = true; + } else { + isInvalid = false; + } + if (isInvalid) { + jconfig[jprop_postDataToAirGradient] = true; + changed = true; + logInfo("toConfig: postToAirGradient changed"); + } + + /** validate led bar brightness */ + if (JSON.typeof_(jconfig[jprop_ledbarBrightness]) != "number") { + isInvalid = true; + } else { + int value = jconfig[jprop_ledbarBrightness]; + if (value < 0 || value > 100) { + isInvalid = true; + } else { + isInvalid = false; + } + } + if (isInvalid) { + jconfig[jprop_ledbarBrightness] = 100; + changed = true; + logInfo("toConfig: ledBarBrightness changed"); + } + + /** Validate display brightness */ + if (JSON.typeof_(jconfig[jprop_displayBrightness]) != "number") { + isInvalid = true; + } else { + int value = jconfig[jprop_displayBrightness]; + if (value < 0 || value > 100) { + isInvalid = true; + } else { + isInvalid = false; + } + } + if (isInvalid) { + jconfig[jprop_displayBrightness] = 100; + changed = true; + logInfo("toConfig: displayBrightness changed"); + } + + if (changed) { + saveConfig(); + } +} + String Configuration::getFailedMesage(void) { return failedMessage; } void Configuration::setPostToAirGradient(bool enable) { - if (enable != config.postDataToAirGradient) { - config.postDataToAirGradient = enable; + bool oldEnabled = jconfig[jprop_postDataToAirGradient]; + if (enable != oldEnabled) { + jconfig[jprop_postDataToAirGradient] = enable; logInfo("postDataToAirGradient set to: " + String(enable)); saveConfig(); } else { @@ -908,11 +1108,13 @@ bool Configuration::tvocLearnOffsetChanged(void) { } int Configuration::getTvocLearningOffset(void) { - return config.tvocLearningOffset; + int value = jconfig[jprop_tvocLearningOffset]; + return value; } int Configuration::getNoxLearningOffset(void) { - return config.noxLearningOffset; + int value = jconfig[jprop_noxLearningOffset]; + return value; } String Configuration::wifiSSID(void) { return "airgradient-" + ag->deviceId(); } @@ -921,7 +1123,10 @@ String Configuration::wifiPass(void) { return String("cleanair"); } void Configuration::setAirGradient(AirGradient *ag) { this->ag = ag; } -int Configuration::getLedBarBrightness(void) { return config.ledBarBrightness; } +int Configuration::getLedBarBrightness(void) { + int value = jconfig[jprop_ledbarBrightness]; + return value; +} bool Configuration::isLedBarBrightnessChanged(void) { bool changed = ledBarBrightnessChanged; @@ -930,7 +1135,8 @@ bool Configuration::isLedBarBrightnessChanged(void) { } int Configuration::getDisplayBrightness(void) { - return config.displayBrightness; + int value = jconfig[jprop_displayBrightness]; + return value; } bool Configuration::isDisplayBrightnessChanged(void) { diff --git a/src/AgConfigure.h b/src/AgConfigure.h index 6759ba6..1b254ad 100644 --- a/src/AgConfigure.h +++ b/src/AgConfigure.h @@ -8,30 +8,6 @@ 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; - uint8_t ledBarBrightness; - uint8_t displayBrightness; - int tvocLearningOffset; - int noxLearningOffset; - char temperatureUnit; // 'f' or 'c' - - uint32_t _check; - }; - struct Config config; bool co2CalibrationRequested; bool ledBarTestRequested; bool udpated; @@ -55,6 +31,7 @@ private: String getPMStandardString(bool usaqi); String getDisplayModeString(bool dispMode); String getAbcDayString(int value); + void toConfig(const char* buf); public: Configuration(Stream &debugLog); From cca1ab69bbcb4afcd14e4f2a1ab05aba65c43f2e Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Wed, 8 May 2024 12:31:54 +0700 Subject: [PATCH 18/47] Move rebooting process out of `OtaHandler` --- examples/OneOpenAir/OneOpenAir.ino | 20 ++++++++++++++++---- examples/OneOpenAir/OtaHandler.h | 15 ++++++--------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index f0f2038..54c2815 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -487,10 +487,22 @@ static void displayExecuteOta(OtaState state, String msg, int processing) { break; } case OtaState::OTA_STATE_SUCCESS: { - if (ag->isOne()) { - oledDisplay.showNewFirmwareSuccess(String(processing)); - } else { - Serial.println("Rebooting... " + String(processing)); + 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.showNewFirmwareSuccess(String(i)); + } else { + Serial.println("Rebooting... " + String(i)); + } + + delay(1000); + } + esp_restart(); } break; } diff --git a/examples/OneOpenAir/OtaHandler.h b/examples/OneOpenAir/OtaHandler.h index f5ace5b..3dc4242 100644 --- a/examples/OneOpenAir/OtaHandler.h +++ b/examples/OneOpenAir/OtaHandler.h @@ -41,16 +41,13 @@ public: OtaUpdateOutcome ret = attemptToPerformOta(&config); Serial.println(ret); if (ret == OtaUpdateOutcome::UPDATE_PERFORMED) { - Serial.println("OTA update performed, restarting ..."); - int i = 6; - while (i != 0) { - i = i - 1; - if (this->callback) { - this->callback(OtaState::OTA_STATE_SUCCESS, String(i)); - } - delay(1000); + if (this->callback) { + this->callback(OtaState::OTA_STATE_SUCCESS, ""); + } + } else { + if(this->callback) { + this->callback(OtaState::OTA_STATE_FAIL, ""); } - esp_restart(); } } From da6326db0fded87110163d7fc93f200afe6e202d Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Thu, 9 May 2024 14:32:42 +0700 Subject: [PATCH 19/47] Add display ask for offline/online mode --- examples/OneOpenAir/OneOpenAir.ino | 35 +++++++++++++++++++++++------- src/AgConfigure.cpp | 22 +++++++++++++++++++ src/AgConfigure.h | 2 ++ 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index 2f29ee8..189b1ee 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -89,7 +89,6 @@ 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; @@ -165,8 +164,30 @@ void setup() { stateMachine.executeLedBarPowerUpTest(); } else { ledBarEnabledUpdate(); - connectToWifi = true; } + + /** Show message confirm offline mode. */ + oledDisplay.setText( + "Press now for", + configuration.isOfflineMode() ? "online mode" : "offline mode", ""); + uint32_t stime = 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 ms = (uint32_t)(millis() - stime); + if (ms >= 3000) { + break; + } + } + connectToWifi = !configuration.isOfflineMode(); + } else { connectToWifi = true; } @@ -200,8 +221,6 @@ void setup() { } else { ledBarEnabledUpdate(); } - } else { - offlineMode = true; } } } @@ -249,9 +268,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(); } @@ -528,7 +547,7 @@ static void oneIndoorInit(void) { } /** Run LED test on start up */ - oledDisplay.setText("Press now for", "LED test &", "offline mode"); + oledDisplay.setText("Press now for", "LED test", ""); ledBarButtonTest = false; uint32_t stime = millis(); while (true) { diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp index 8b642fd..8ade2c3 100644 --- a/src/AgConfigure.cpp +++ b/src/AgConfigure.cpp @@ -40,6 +40,7 @@ JSON_PROP_DEF(ledbarBrightness); JSON_PROP_DEF(displayBrightness); JSON_PROP_DEF(co2CalibrationRequested); JSON_PROP_DEF(ledBarTestRequested); +JSON_PROP_DEF(offlineMode); JSONVar jconfig; static bool jsonTypeInvalid(JSONVar root, String validType) { @@ -145,6 +146,7 @@ void Configuration::defaultConfig(void) { jconfig[jprop_noxLearningOffset] = 12; jconfig[jprop_abcDays] = 8; jconfig[jprop_model] = ""; + jconfig[jprop_offlineMode] = false; saveConfig(); } @@ -1077,6 +1079,15 @@ void Configuration::toConfig(const char *buf) { logInfo("toConfig: displayBrightness changed"); } + if (JSON.typeof_(jconfig[jprop_offlineMode]) != "boolean") { + isInvalid = true; + } else { + isInvalid = false; + } + if (isInvalid) { + jconfig[jprop_offlineMode] = false; + } + if (changed) { saveConfig(); } @@ -1139,6 +1150,17 @@ int Configuration::getDisplayBrightness(void) { return value; } +bool Configuration::isOfflineMode(void) { + bool offline = jconfig[jprop_offlineMode]; + return offline; +} + +void Configuration::setOfflineMode(bool offline) { + logInfo("Set offline mode: " + String(offline ? "True" : "False")); + jconfig[jprop_offlineMode] = offline; + saveConfig(); +} + bool Configuration::isDisplayBrightnessChanged(void) { bool changed = displayBrightnessChanged; displayBrightnessChanged = false; diff --git a/src/AgConfigure.h b/src/AgConfigure.h index 1b254ad..bb57a67 100644 --- a/src/AgConfigure.h +++ b/src/AgConfigure.h @@ -74,6 +74,8 @@ public: int getLedBarBrightness(void); bool isDisplayBrightnessChanged(void); int getDisplayBrightness(void); + bool isOfflineMode(void); + void setOfflineMode(bool offline); }; #endif /** _AG_CONFIG_H_ */ From c98d078d4cf1d066a2f8fa9fdaaa4ce6ffd58718 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Thu, 9 May 2024 14:56:40 +0700 Subject: [PATCH 20/47] Resolve complex build failed --- src/AgConfigure.cpp | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp index 97ed593..a03c22f 100644 --- a/src/AgConfigure.cpp +++ b/src/AgConfigure.cpp @@ -41,6 +41,7 @@ JSON_PROP_DEF(ledbarBrightness); JSON_PROP_DEF(displayBrightness); JSON_PROP_DEF(co2CalibrationRequested); JSON_PROP_DEF(ledBarTestRequested); +JSON_PROP_DEF(lastOta); JSONVar jconfig; static bool jsonTypeInvalid(JSONVar root, String validType) { @@ -1090,6 +1091,17 @@ void Configuration::toConfig(const char *buf) { logInfo("toConfig: displayBrightness changed"); } + /** Last OTA */ + if(JSON.typeof_(jconfig[jprop_lastOta]) != "number") { + isInvalid = true; + } else { + isInvalid = false; + } + if(isInvalid) { + jconfig[jprop_lastOta] = 0; + changed = true; + } + if (changed) { saveConfig(); } @@ -1174,13 +1186,14 @@ int Configuration::getLastOta(void) { logError("Current year " + String(curYear) + String(" invalid")); return -1; } + time_t lastOta = jconfig[jprop_lastOta]; time_t curTime = mktime(&timeInfo); - logInfo("Last ota time: " + String(config.lastOta)); - if (config.lastOta == 0) { + logInfo("Last ota time: " + String(lastOta)); + if (lastOta == 0) { return 0; } - int sec = curTime - config.lastOta; + int sec = curTime - lastOta; logInfo("Last ota secconds: " + String(sec)); return sec; } @@ -1196,8 +1209,10 @@ void Configuration::updateLastOta(void) { logError("updateLastOta: lolcal time invalid"); return; } - config.lastOta = mktime(&timeInfo); - logInfo("Last OTA: " + String(config.lastOta)); + + time_t lastOta = mktime(&timeInfo); + jconfig[jprop_lastOta] = lastOta; + logInfo("Last OTA: " + String(lastOta)); saveConfig(); } From 066e81b186dcbe0c3cd15362fcb7083e00f6f0d7 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Thu, 9 May 2024 15:03:27 +0700 Subject: [PATCH 21/47] fix esp8266 build fail --- src/AgConfigure.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp index a03c22f..a78f147 100644 --- a/src/AgConfigure.cpp +++ b/src/AgConfigure.cpp @@ -1186,7 +1186,8 @@ int Configuration::getLastOta(void) { logError("Current year " + String(curYear) + String(" invalid")); return -1; } - time_t lastOta = jconfig[jprop_lastOta]; + double _t = jconfig[jprop_lastOta]; + time_t lastOta = (time_t)_t; time_t curTime = mktime(&timeInfo); logInfo("Last ota time: " + String(lastOta)); if (lastOta == 0) { @@ -1211,7 +1212,7 @@ void Configuration::updateLastOta(void) { } time_t lastOta = mktime(&timeInfo); - jconfig[jprop_lastOta] = lastOta; + jconfig[jprop_lastOta] = (unsigned long)lastOta; logInfo("Last OTA: " + String(lastOta)); saveConfig(); } From a3c9727b02dca54cc6caab74c31073ec2951bad4 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Fri, 10 May 2024 07:19:11 +0700 Subject: [PATCH 22/47] Update variable a descriptive name --- examples/OneOpenAir/OneOpenAir.ino | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index deb440a..198bc0e 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -180,7 +180,7 @@ void setup() { oledDisplay.setText( "Press now for", configuration.isOfflineMode() ? "online mode" : "offline mode", ""); - uint32_t stime = millis(); + uint32_t startTime = millis(); while (true) { if (ag->button.getState() == ag->button.BUTTON_PRESSED) { configuration.setOfflineMode(!configuration.isOfflineMode()); @@ -191,8 +191,8 @@ void setup() { delay(1000); break; } - uint32_t ms = (uint32_t)(millis() - stime); - if (ms >= 3000) { + uint32_t periodMs = (uint32_t)(millis() - startTime); + if (periodMs >= 3000) { break; } } From 279ccb8bfb4c6e0aca566d5cdd3e1fedff551b69 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Fri, 10 May 2024 09:11:43 +0700 Subject: [PATCH 23/47] update configuration filename, log mesage and add configuration default value --- src/AgConfigure.cpp | 89 ++++++++++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 37 deletions(-) diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp index a78f147..c76e964 100644 --- a/src/AgConfigure.cpp +++ b/src/AgConfigure.cpp @@ -9,7 +9,7 @@ #include #define EEPROM_CONFIG_SIZE 1024 -#define CONFIG_FILE_NAME "/cfg.json" +#define CONFIG_FILE_NAME "/AgConfigure_Configuration.json" const char *CONFIGURATION_CONTROL_NAME[] = { [ConfigurationControlLocal] = "local", @@ -42,6 +42,23 @@ JSON_PROP_DEF(displayBrightness); JSON_PROP_DEF(co2CalibrationRequested); JSON_PROP_DEF(ledBarTestRequested); JSON_PROP_DEF(lastOta); + +#define jprop_model_default "" +#define jprop_country_default "" +#define jprop_pmStandard_default getPMStandardString(false) +#define jprop_ledBarMode_default getLedBarModeName(LedBarMode::LedBarModeCO2) +#define jprop_displayMode_default getDisplayModeString(true) +#define jprop_abcDays_default 8 +#define jprop_tvocLearningOffset_default 12 +#define jprop_noxLearningOffset_default 12 +#define jprop_mqttBrokerUrl_default "" +#define jprop_temperatureUnit_default "c" +#define jprop_configurationControl_default String(CONFIGURATION_CONTROL_NAME[ConfigurationControl::ConfigurationControlBoth]) +#define jprop_postDataToAirGradient_default true +#define jprop_ledbarBrightness_default 100 +#define jprop_displayBrightness_default 100 +#define jprop_lastOta_default 0 + JSONVar jconfig; static bool jsonTypeInvalid(JSONVar root, String validType) { @@ -97,7 +114,6 @@ void Configuration::saveConfig(void) { } void Configuration::loadConfig(void) { - bool readSuccess = false; char *buf = (char *)malloc(EEPROM_CONFIG_SIZE); if (buf == NULL) { logError("Malloc read file buffer failed"); @@ -112,9 +128,12 @@ void Configuration::loadConfig(void) { #else File file = SPIFFS.open(CONFIG_FILE_NAME); if (file && !file.isDirectory()) { - logInfo("Read file"); - file.readBytes(buf, file.size()); - readSuccess = true; + logInfo("Reading file..."); + if(file.readBytes(buf, file.size()) != file.size()) { + logError("Reading file: failed - size not match"); + } else { + logInfo("Reading file: success"); + } file.close(); } else { SPIFFS.format(); @@ -130,23 +149,21 @@ void Configuration::loadConfig(void) { */ void Configuration::defaultConfig(void) { jconfig = JSON.parse("{}"); - jconfig[jprop_country] = ""; - jconfig[jprop_mqttBrokerUrl] = ""; - jconfig[jprop_configurationControl] = - String(CONFIGURATION_CONTROL_NAME - [ConfigurationControl::ConfigurationControlBoth]); - jconfig[jprop_pmStandard] = getPMStandardString(false); - jconfig[jprop_temperatureUnit] = "c"; - jconfig[jprop_postDataToAirGradient] = true; - jconfig[jprop_displayMode] = getDisplayModeString(true); - jconfig[jprop_ledbarBrightness] = 100; - jconfig[jprop_displayBrightness] = 100; - jconfig[jprop_ledBarMode] = getLedBarModeName(LedBarMode::LedBarModeCO2); - jconfig[jprop_tvocLearningOffset] = 12; - jconfig[jprop_noxLearningOffset] = 12; - jconfig[jprop_abcDays] = 8; - jconfig[jprop_model] = ""; + jconfig[jprop_country] = jprop_country_default; + jconfig[jprop_mqttBrokerUrl] = jprop_mqttBrokerUrl_default; + jconfig[jprop_configurationControl] = jprop_configurationControl_default; + jconfig[jprop_pmStandard] = jprop_pmStandard_default; + jconfig[jprop_temperatureUnit] = jprop_temperatureUnit_default; + jconfig[jprop_postDataToAirGradient] = jprop_postDataToAirGradient_default; + jconfig[jprop_displayMode] = getDisplayModeString(true); + jconfig[jprop_ledbarBrightness] = jprop_ledbarBrightness_default; + jconfig[jprop_displayBrightness] = jprop_displayBrightness_default; + jconfig[jprop_ledBarMode] = jprop_ledbarBrightness_default; + jconfig[jprop_tvocLearningOffset] = jprop_tvocLearningOffset_default; + jconfig[jprop_noxLearningOffset] = jprop_noxLearningOffset_default; + jconfig[jprop_abcDays] = jprop_abcDays_default; + jconfig[jprop_model] = jprop_model_default; saveConfig(); } @@ -886,7 +903,7 @@ void Configuration::toConfig(const char *buf) { } } if (isInvalid) { - jconfig[jprop_country] = ""; + jconfig[jprop_country] = jprop_country_default; changed = true; logInfo("toConfig: country changed"); } @@ -904,7 +921,7 @@ void Configuration::toConfig(const char *buf) { } } if (isInvalid) { - jconfig[jprop_pmStandard] = getPMStandardString(false); + jconfig[jprop_pmStandard] = jprop_pmStandard_default; changed = true; logInfo("toConfig: pmStandard changed"); } @@ -923,7 +940,7 @@ void Configuration::toConfig(const char *buf) { } } if (isInvalid) { - jconfig[jprop_ledBarMode] = getLedBarModeName(LedBarMode::LedBarModeCO2); + jconfig[jprop_ledBarMode] = jprop_ledBarMode_default; changed = true; logInfo("toConfig: ledBarMode changed"); } @@ -941,7 +958,7 @@ void Configuration::toConfig(const char *buf) { } } if (isInvalid) { - jconfig[jprop_displayMode] = getDisplayModeString(true); + jconfig[jprop_displayMode] = jprop_displayMode_default; changed = true; logInfo("toConfig: displayMode changed"); } @@ -953,7 +970,7 @@ void Configuration::toConfig(const char *buf) { isInvalid = false; } if (isInvalid) { - jconfig[jprop_abcDays] = 8; + jconfig[jprop_abcDays] = jprop_abcDays_default; changed = true; logInfo("toConfig: abcDays changed"); } @@ -970,7 +987,7 @@ void Configuration::toConfig(const char *buf) { } } if (isInvalid) { - jconfig[jprop_tvocLearningOffset] = 12; + jconfig[jprop_tvocLearningOffset] = jprop_tvocLearningOffset_default; changed = true; logInfo("toConfig: tvocLearningOffset changed"); } @@ -987,7 +1004,7 @@ void Configuration::toConfig(const char *buf) { } } if (isInvalid) { - jconfig[jprop_noxLearningOffset] = 12; + jconfig[jprop_noxLearningOffset] = jprop_noxLearningOffset_default; changed = true; logInfo("toConfig: noxLearningOffset changed"); } @@ -1000,7 +1017,7 @@ void Configuration::toConfig(const char *buf) { } if (isInvalid) { changed = true; - jconfig[jprop_mqttBrokerUrl] = ""; + jconfig[jprop_mqttBrokerUrl] = jprop_mqttBrokerUrl_default; logInfo("toConfig: mqttBroker changed"); } @@ -1016,7 +1033,7 @@ void Configuration::toConfig(const char *buf) { } } if (isInvalid) { - jconfig[jprop_temperatureUnit] = "c"; + jconfig[jprop_temperatureUnit] = jprop_temperatureUnit_default; changed = true; logInfo("toConfig: temperatureUnit changed"); } @@ -1038,9 +1055,7 @@ void Configuration::toConfig(const char *buf) { } } if (isInvalid) { - jconfig[jprop_configurationControl] = - String(CONFIGURATION_CONTROL_NAME - [ConfigurationControl::ConfigurationControlBoth]); + jconfig[jprop_configurationControl] =jprop_configurationControl_default; changed = true; logInfo("toConfig: configurationControl changed"); } @@ -1052,7 +1067,7 @@ void Configuration::toConfig(const char *buf) { isInvalid = false; } if (isInvalid) { - jconfig[jprop_postDataToAirGradient] = true; + jconfig[jprop_postDataToAirGradient] = jprop_postDataToAirGradient_default; changed = true; logInfo("toConfig: postToAirGradient changed"); } @@ -1069,7 +1084,7 @@ void Configuration::toConfig(const char *buf) { } } if (isInvalid) { - jconfig[jprop_ledbarBrightness] = 100; + jconfig[jprop_ledbarBrightness] = jprop_ledbarBrightness_default; changed = true; logInfo("toConfig: ledBarBrightness changed"); } @@ -1086,7 +1101,7 @@ void Configuration::toConfig(const char *buf) { } } if (isInvalid) { - jconfig[jprop_displayBrightness] = 100; + jconfig[jprop_displayBrightness] = jprop_displayBrightness_default; changed = true; logInfo("toConfig: displayBrightness changed"); } @@ -1098,7 +1113,7 @@ void Configuration::toConfig(const char *buf) { isInvalid = false; } if(isInvalid) { - jconfig[jprop_lastOta] = 0; + jconfig[jprop_lastOta] = jprop_lastOta_default; changed = true; } From e3dee42b4ba35759dfbd5fea4178adabec47e4fd Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Fri, 10 May 2024 09:16:22 +0700 Subject: [PATCH 24/47] fix esp8266 build fail --- src/AgConfigure.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp index c76e964..609f800 100644 --- a/src/AgConfigure.cpp +++ b/src/AgConfigure.cpp @@ -124,7 +124,6 @@ void Configuration::loadConfig(void) { for (int i = 0; i < EEPROM_CONFIG_SIZE; i++) { buf[i] = EEPROM.read(i); } - readSuccess = true; #else File file = SPIFFS.open(CONFIG_FILE_NAME); if (file && !file.isDirectory()) { From 1bcb9bf5eed663dbefe4273f94cfc716c1ba11bc Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Sat, 11 May 2024 19:29:02 +0700 Subject: [PATCH 25/47] Adjust CO2 Colors and Ranges --- src/AgStateMachine.cpp | 67 ++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 39 deletions(-) diff --git a/src/AgStateMachine.cpp b/src/AgStateMachine.cpp index ac8d51c..6ee296d 100644 --- a/src/AgStateMachine.cpp +++ b/src/AgStateMachine.cpp @@ -63,10 +63,10 @@ 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) { + } 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); @@ -75,20 +75,20 @@ void StateMachine::co2handleLeds(void) { 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) { + } else if (co2Value <= 1250) { + /** OOOO; 4 */ + ag->ledBar.setColor(255, 165, 0, ag->ledBar.getNumberOfLeds() - 1); + ag->ledBar.setColor(255, 165, 0, ag->ledBar.getNumberOfLeds() - 2); + ag->ledBar.setColor(255, 165, 0, ag->ledBar.getNumberOfLeds() - 3); + ag->ledBar.setColor(255, 165, 0, ag->ledBar.getNumberOfLeds() - 4); + } else if (co2Value <= 1500) { + /** OOOOO; 5 */ + ag->ledBar.setColor(255, 165, 0, ag->ledBar.getNumberOfLeds() - 1); + ag->ledBar.setColor(255, 165, 0, ag->ledBar.getNumberOfLeds() - 2); + ag->ledBar.setColor(255, 165, 0, ag->ledBar.getNumberOfLeds() - 3); + ag->ledBar.setColor(255, 165, 0, ag->ledBar.getNumberOfLeds() - 4); + ag->ledBar.setColor(255, 165, 0, 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); @@ -96,7 +96,7 @@ void StateMachine::co2handleLeds(void) { 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) { + } 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); @@ -105,28 +105,17 @@ void StateMachine::co2handleLeds(void) { 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 */ + } else if (co2Value <= 3000) { + /** PPPPPPPP; 8 */ + ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 1); + ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 2); + ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 3); + ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 4); + ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 5); + ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 6); + ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 7); + ag->ledBar.setColor(153, 153, 0, 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); From 31c60dcbece0b5dd68073f520d6d8906bd15f3cf Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Sun, 12 May 2024 11:48:03 +0700 Subject: [PATCH 26/47] fix build failed --- src/AgConfigure.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp index f110698..dfccdc6 100644 --- a/src/AgConfigure.cpp +++ b/src/AgConfigure.cpp @@ -165,7 +165,7 @@ void Configuration::defaultConfig(void) { jconfig[jprop_noxLearningOffset] = jprop_noxLearningOffset_default; jconfig[jprop_abcDays] = jprop_abcDays_default; jconfig[jprop_model] = jprop_model_default; - jconfig[lastOta] = jprop_lastOta_default; + jconfig[jprop_lastOta] = jprop_lastOta_default; jconfig[jprop_offlineMode] = jprop_offlineMode_default; saveConfig(); From 7a4255b2bb1fa9067cef56b352865889fd54c77d Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Mon, 13 May 2024 10:34:06 +0700 Subject: [PATCH 27/47] Turn off LED bar and Display if brightness is 0%, fix #114 --- examples/OneOpenAir/OneOpenAir.ino | 13 ++++++-- src/AgOledDisplay.cpp | 48 ++++++++++++++++++++++++++++-- src/AgOledDisplay.h | 1 + 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index 198bc0e..a865575 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -239,6 +239,9 @@ void setup() { 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(); @@ -443,8 +446,14 @@ static void wdgFeedUpdate(void) { static void ledBarEnabledUpdate(void) { if (ag->isOne()) { - ag->ledBar.setBrighness(configuration.getLedBarBrightness()); - 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.setBrighness(brightness); + ag->ledBar.setEnable(configuration.getLedBarMode() != LedBarModeOff); + } } } diff --git a/src/AgOledDisplay.cpp b/src/AgOledDisplay.cpp index 1bee257..d5164e2 100644 --- a/src/AgOledDisplay.cpp +++ b/src/AgOledDisplay.cpp @@ -103,7 +103,12 @@ bool OledDisplay::begin(void) { return false; } - setBrightness(config.getDisplayBrightness()); + /** 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"); @@ -148,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); @@ -180,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); @@ -201,6 +214,10 @@ void OledDisplay::showDashboard(void) { showDashboard(NULL); } * */ void OledDisplay::showDashboard(const char *status) { + if (isDisplayOff) { + return; + } + char strBuf[10]; DISP()->firstPage(); @@ -299,10 +316,25 @@ void OledDisplay::showDashboard(const char *status) { } void OledDisplay::setBrightness(int percent) { - DISP()->setContrast((127 * percent) / 100); + if (percent == 0) { + isDisplayOff = true; + + // Clear display. + DISP()->firstPage(); + do { + } while (DISP()->nextPage()); + + } else { + isDisplayOff = false; + DISP()->setContrast((127 * percent) / 100); + } } void OledDisplay::showNewFirmwareVersion(String version) { + if (isDisplayOff) { + return; + } + DISP()->firstPage(); do { DISP()->setFont(u8g2_font_t0_16_tf); @@ -313,6 +345,10 @@ void OledDisplay::showNewFirmwareVersion(String version) { } void OledDisplay::showNewFirmwareUpdating(String percent) { + if (isDisplayOff) { + return; + } + DISP()->firstPage(); do { DISP()->setFont(u8g2_font_t0_16_tf); @@ -322,6 +358,10 @@ void OledDisplay::showNewFirmwareUpdating(String percent) { } void OledDisplay::showNewFirmwareSuccess(String count) { + if (isDisplayOff) { + return; + } + DISP()->firstPage(); do { DISP()->setFont(u8g2_font_t0_16_tf); @@ -332,6 +372,10 @@ void OledDisplay::showNewFirmwareSuccess(String count) { } void OledDisplay::showNewFirmwareFailed(void) { + if (isDisplayOff) { + return; + } + DISP()->firstPage(); do { DISP()->setFont(u8g2_font_t0_16_tf); diff --git a/src/AgOledDisplay.h b/src/AgOledDisplay.h index 6770c38..9d06354 100644 --- a/src/AgOledDisplay.h +++ b/src/AgOledDisplay.h @@ -14,6 +14,7 @@ private: bool isBegin = false; void *u8g2 = NULL; Measurements &value; + bool isDisplayOff = false; void showTempHum(bool hasStatus); void setCentralText(int y, String text); From 22dc2136e4b2180dd4f94dcc3b3a73dbb1ff8a6c Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Mon, 13 May 2024 11:18:08 +0700 Subject: [PATCH 28/47] Update watchdog reset message --- examples/OneOpenAir/OneOpenAir.ino | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index a865575..0514c5b 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -440,7 +440,7 @@ 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(); } @@ -584,11 +584,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()); @@ -1101,10 +1096,19 @@ static void updatePm(void) { } static void sendDataToServer(void) { + /** Ignore send data to server if postToAirGradient disabled */ + if (configuration.isPostDataToAirGradient() == false) { + 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++; From a6d8936ea61fd38d5bc5acaa0446284d1e847c90 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Mon, 13 May 2024 11:47:37 +0700 Subject: [PATCH 29/47] Fix factory reset failed, the configuration not set to default. #112 --- examples/OneOpenAir/OneOpenAir.ino | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index 0514c5b..f458d6e 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -402,9 +402,8 @@ static void factoryConfigReset(void) { mqttTask = NULL; } - /** Disconnect WIFI */ - wifiConnector.disconnect(); - wifiConnector.reset(); + /** Reset WIFI */ + WiFi.disconnect(true, true); /** Reset local config */ configuration.reset(); From 1b69e8a599014f6a08f0f718387cb117df6222f7 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Mon, 13 May 2024 12:02:17 +0700 Subject: [PATCH 30/47] Offline mode should not shown status on display. #111 --- examples/OneOpenAir/OneOpenAir.ino | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index f458d6e..876d667 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -874,14 +874,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); } } From 1e81c9b12589cf50a27fd81b51ac3a66644ed9fb Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Mon, 13 May 2024 13:52:14 +0700 Subject: [PATCH 31/47] Fix issue: dashboard not show if get cloud configuration failed. --- src/AgStateMachine.cpp | 25 ++++++++++++++++--------- src/AgStateMachine.h | 1 + 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/AgStateMachine.cpp b/src/AgStateMachine.cpp index 6ee296d..e1b18a8 100644 --- a/src/AgStateMachine.cpp +++ b/src/AgStateMachine.cpp @@ -454,15 +454,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; } @@ -489,8 +493,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; } diff --git a/src/AgStateMachine.h b/src/AgStateMachine.h index fa33549..7dfe0c4 100644 --- a/src/AgStateMachine.h +++ b/src/AgStateMachine.h @@ -17,6 +17,7 @@ private: Measurements &value; Configuration &config; bool addToDashBoard = false; + bool addToDashBoardToggle = false; uint32_t addToDashboardTime; int wifiConnectCountDown; int ledBarAnimationCount; From 5b18a8353dccc8ca68dfde221daf64b2d266ac1a Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Mon, 13 May 2024 14:43:53 +0700 Subject: [PATCH 32/47] Fix issue: LED bar button test pressed but WiFi connection still perform. --- examples/OneOpenAir/OneOpenAir.ino | 44 ++++++++++++++++-------------- src/AgApiClient.cpp | 3 +- src/AgConfigure.cpp | 6 +++- src/AgConfigure.h | 2 ++ 4 files changed, 33 insertions(+), 22 deletions(-) diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index 876d667..ec099af 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -176,28 +176,32 @@ void setup() { ledBarEnabledUpdate(); } - /** Show message confirm offline mode. */ - 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()); + /** 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; + 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 { + configuration.setOfflineModeWithoutSave(true); } - connectToWifi = !configuration.isOfflineMode(); - } else { connectToWifi = true; } @@ -1099,7 +1103,7 @@ static void updatePm(void) { static void sendDataToServer(void) { /** Ignore send data to server if postToAirGradient disabled */ - if (configuration.isPostDataToAirGradient() == false) { + if (configuration.isPostDataToAirGradient() == false || configuration.isOfflineMode()) { return; } diff --git a/src/AgApiClient.cpp b/src/AgApiClient.cpp index 1a2a978..699d5ae 100644 --- a/src/AgApiClient.cpp +++ b/src/AgApiClient.cpp @@ -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 diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp index dfccdc6..97099f3 100644 --- a/src/AgConfigure.cpp +++ b/src/AgConfigure.cpp @@ -1193,7 +1193,7 @@ int Configuration::getDisplayBrightness(void) { bool Configuration::isOfflineMode(void) { bool offline = jconfig[jprop_offlineMode]; - return offline; + return (offline || _offlineMode); } void Configuration::setOfflineMode(bool offline) { @@ -1202,6 +1202,10 @@ void Configuration::setOfflineMode(bool offline) { saveConfig(); } +void Configuration::setOfflineModeWithoutSave(bool offline) { + _offlineMode = offline; +} + bool Configuration::isDisplayBrightnessChanged(void) { bool changed = displayBrightnessChanged; displayBrightnessChanged = false; diff --git a/src/AgConfigure.h b/src/AgConfigure.h index 3b859ee..8fc852c 100644 --- a/src/AgConfigure.h +++ b/src/AgConfigure.h @@ -17,6 +17,7 @@ private: bool ledBarBrightnessChanged = false; bool displayBrightnessChanged = false; String otaNewFirmwareVersion; + bool _offlineMode = false; AirGradient* ag; @@ -80,6 +81,7 @@ public: String newFirmwareVersion(void); bool isOfflineMode(void); void setOfflineMode(bool offline); + void setOfflineModeWithoutSave(bool offline); }; #endif /** _AG_CONFIG_H_ */ From f23c7e9e31e205c155adb47be782d9c75195633d Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Mon, 13 May 2024 15:07:10 +0700 Subject: [PATCH 33/47] Set offline mode incase wifi is not configuraion or configuration ignored. --- examples/OneOpenAir/OneOpenAir.ino | 5 +++++ src/AgWiFiConnector.cpp | 13 +++++++++++++ src/AgWiFiConnector.h | 1 + 3 files changed, 19 insertions(+) diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index ec099af..e60c1ed 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -238,6 +238,11 @@ void setup() { } } } + /** 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()) { diff --git a/src/AgWiFiConnector.cpp b/src/AgWiFiConnector.cpp index 8ac9495..669d526 100644 --- a/src/AgWiFiConnector.cpp +++ b/src/AgWiFiConnector.cpp @@ -345,3 +345,16 @@ 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; +} diff --git a/src/AgWiFiConnector.h b/src/AgWiFiConnector.h index 3856f62..090f0d2 100644 --- a/src/AgWiFiConnector.h +++ b/src/AgWiFiConnector.h @@ -50,6 +50,7 @@ public: void reset(void); int RSSI(void); String localIpStr(void); + bool hasConfigurated(void); }; #endif /** _AG_WIFI_CONNECTOR_H_ */ From 3201fd8d9c02a6133cac3d04543119af062359ed Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Mon, 13 May 2024 17:43:42 +0700 Subject: [PATCH 34/47] Fix: Configuratoin failed response mesasge and failed condition handle. --- src/AgConfigure.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp index dfccdc6..f3dd511 100644 --- a/src/AgConfigure.cpp +++ b/src/AgConfigure.cpp @@ -411,6 +411,7 @@ bool Configuration::parse(String data, bool isLocal) { changed = true; } } else { + failedMessage = jsonValueInvalidMessage(String(jprop_displayMode), mode); jsonInvalid(); return false; @@ -503,10 +504,16 @@ bool Configuration::parse(String data, bool isLocal) { if (JSON.typeof_(root[jprop_mqttBrokerUrl]) == "string") { String broker = root[jprop_mqttBrokerUrl]; String oldBroker = jconfig[jprop_mqttBrokerUrl]; - if (broker != oldBroker) { - changed = true; - configLogInfo(String(jprop_mqttBrokerUrl), oldBroker, broker); - jconfig[jprop_mqttBrokerUrl] = broker; + if (broker.length() <= 255) { + if (broker != oldBroker) { + changed = true; + configLogInfo(String(jprop_mqttBrokerUrl), oldBroker, broker); + jconfig[jprop_mqttBrokerUrl] = broker; + } + } else { + failedMessage = "\"mqttBrokerUrl\" length should <= 255"; + jsonInvalid(); + return false; } } else { if (jsonTypeInvalid(root[jprop_mqttBrokerUrl], "string")) { @@ -528,7 +535,7 @@ bool Configuration::parse(String data, bool isLocal) { configLogInfo(String(jprop_temperatureUnit), oldUnit, unit); } } else { - jsonValueInvalidMessage(String(jprop_temperatureUnit), unit); + failedMessage = jsonValueInvalidMessage(String(jprop_temperatureUnit), unit); jsonInvalid(); return false; } From 5cb838af29331ce9c407a54c031bf19c1a9c7203 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Mon, 13 May 2024 18:11:46 +0700 Subject: [PATCH 35/47] fix: OpenAir send incorrect model(firmware mode) --- examples/OneOpenAir/LocalServer.cpp | 6 +++++- src/AgConfigure.cpp | 14 ++++++++++++++ src/AgConfigure.h | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/examples/OneOpenAir/LocalServer.cpp b/examples/OneOpenAir/LocalServer.cpp index 96adab0..1b88e8e 100644 --- a/examples/OneOpenAir/LocalServer.cpp +++ b/examples/OneOpenAir/LocalServer.cpp @@ -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) { diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp index f3dd511..e947bef 100644 --- a/src/AgConfigure.cpp +++ b/src/AgConfigure.cpp @@ -672,6 +672,20 @@ bool Configuration::parse(String data, bool isLocal) { */ String Configuration::toString(void) { return JSON.stringify(jconfig); } +/** + * @brief Get current configuration value as JSON string + * + * @param fwMode Firmware mode value + * @return String + */ +String Configuration::toString(AgFirmwareMode fwMode) { + String model = jconfig[jprop_model]; + jconfig[jprop_model] = AgFirmwareModeName(fwMode); + String value = toString(); + jconfig[jprop_model] = model; + return value; +} + /** * @brief Temperature unit (F or C) * diff --git a/src/AgConfigure.h b/src/AgConfigure.h index 3b859ee..6a3bb0b 100644 --- a/src/AgConfigure.h +++ b/src/AgConfigure.h @@ -47,6 +47,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); From 45c72798663c5ed7e774b9535a6d5f0c01f6610d Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Mon, 13 May 2024 18:25:03 +0700 Subject: [PATCH 36/47] Fix: Firmware mode `O-1PP` send miss data channel-2 --- src/AgValue.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/AgValue.cpp b/src/AgValue.cpp index 4894439..9d1fd87 100644 --- a/src/AgValue.cpp +++ b/src/AgValue.cpp @@ -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; From 4612f4b7935fdc064a499ac6df021ae5921381c0 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Mon, 13 May 2024 19:00:34 +0700 Subject: [PATCH 37/47] Add condition to handle configuration changed. --- examples/OneOpenAir/OneOpenAir.ino | 66 ++++++++++++++++-------------- src/AgStateMachine.cpp | 3 ++ 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index 876d667..ca18519 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -795,9 +795,11 @@ static void configUpdateHandle() { return; } - ledBarEnabledUpdate(); + if (ag->isOne()) { + ledBarEnabledUpdate(); + stateMachine.executeLedBarTest(); + } stateMachine.executeCo2Calibration(); - stateMachine.executeLedBarTest(); String mqttUri = configuration.getMqttBrokerUri(); if (mqttClient.isCurrentUri(mqttUri) == false) { @@ -805,38 +807,42 @@ 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"; - } - 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); + int oldTvocOffset = ag->sgp41.getTvocLearningOffset(); + int oldNoxOffset = ag->sgp41.getNoxLearningOffset(); + bool result = sgp41Init(); + const char *resultStr = "successful"; + if (!result) { + resultStr = "failure"; + } + if (oldTvocOffset != configuration.getTvocLearningOffset()) { + Serial.printf("Setting tvocLearningOffset from %d to %d hours %s\r\n", + oldTvocOffset, configuration.getTvocLearningOffset(), + resultStr); + } + if (oldNoxOffset != configuration.getNoxLearningOffset()) { + Serial.printf("Setting noxLearningOffset from %d to %d hours %s\r\n", + oldNoxOffset, configuration.getNoxLearningOffset(), + resultStr); + } } } - if (configuration.isLedBarBrightnessChanged()) { - ag->ledBar.setBrighness(configuration.getLedBarBrightness()); - Serial.println("Set 'LedBarBrightness' brightness: " + - String(configuration.getLedBarBrightness())); - } - if (configuration.isDisplayBrightnessChanged()) { - oledDisplay.setBrightness(configuration.getDisplayBrightness()); - Serial.println("Set 'DisplayBrightness' brightness: " + - String(configuration.getDisplayBrightness())); + if (ag->isOne()) { + if (configuration.isLedBarBrightnessChanged()) { + ag->ledBar.setBrighness(configuration.getLedBarBrightness()); + Serial.println("Set 'LedBarBrightness' brightness: " + + String(configuration.getLedBarBrightness())); + } + if (configuration.isDisplayBrightnessChanged()) { + oledDisplay.setBrightness(configuration.getDisplayBrightness()); + Serial.println("Set 'DisplayBrightness' brightness: " + + String(configuration.getDisplayBrightness())); + } } fwNewVersion = configuration.newFirmwareVersion(); diff --git a/src/AgStateMachine.cpp b/src/AgStateMachine.cpp index 6ee296d..6c94198 100644 --- a/src/AgStateMachine.cpp +++ b/src/AgStateMachine.cpp @@ -394,6 +394,9 @@ StateMachine::~StateMachine() {} void StateMachine::displayHandle(AgStateMachineState state) { // Ignore handle if not ONE_INDOOR board if (!ag->isOne()) { + if (state == AgStateMachineCo2Calibration) { + co2Calibration(); + } return; } From 799217e7241a9cad2acaaead1dc7118ac4a2a6dd Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Mon, 13 May 2024 21:25:00 +0700 Subject: [PATCH 38/47] Remove `displayMode` from configuration, #123 --- src/AgConfigure.cpp | 66 --------------------------------------------- src/AgConfigure.h | 1 - 2 files changed, 67 deletions(-) diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp index 0ee0838..0cae473 100644 --- a/src/AgConfigure.cpp +++ b/src/AgConfigure.cpp @@ -29,7 +29,6 @@ JSON_PROP_DEF(model); JSON_PROP_DEF(country); JSON_PROP_DEF(pmStandard); JSON_PROP_DEF(ledBarMode); -JSON_PROP_DEF(displayMode); JSON_PROP_DEF(abcDays); JSON_PROP_DEF(tvocLearningOffset); JSON_PROP_DEF(noxLearningOffset); @@ -48,7 +47,6 @@ JSON_PROP_DEF(offlineMode); #define jprop_country_default "" #define jprop_pmStandard_default getPMStandardString(false) #define jprop_ledBarMode_default getLedBarModeName(LedBarMode::LedBarModeCO2) -#define jprop_displayMode_default getDisplayModeString(true) #define jprop_abcDays_default 8 #define jprop_tvocLearningOffset_default 12 #define jprop_noxLearningOffset_default 12 @@ -157,7 +155,6 @@ void Configuration::defaultConfig(void) { jconfig[jprop_pmStandard] = jprop_pmStandard_default; jconfig[jprop_temperatureUnit] = jprop_temperatureUnit_default; jconfig[jprop_postDataToAirGradient] = jprop_postDataToAirGradient_default; - jconfig[jprop_displayMode] = getDisplayModeString(true); jconfig[jprop_ledbarBrightness] = jprop_ledbarBrightness_default; jconfig[jprop_displayBrightness] = jprop_displayBrightness_default; jconfig[jprop_ledBarMode] = jprop_ledbarBrightness_default; @@ -401,30 +398,6 @@ bool Configuration::parse(String data, bool isLocal) { } } - if (JSON.typeof_(root[jprop_displayMode]) == "string") { - String mode = root[jprop_displayMode]; - if (mode == getDisplayModeString(true) || - mode == getDisplayModeString(false)) { - String oldMode = jconfig[jprop_displayMode]; - if (mode != oldMode) { - jconfig[jprop_displayMode] = mode; - changed = true; - } - } else { - failedMessage = - jsonValueInvalidMessage(String(jprop_displayMode), mode); - jsonInvalid(); - return false; - } - } else { - if (jsonTypeInvalid(root[jprop_displayMode], "string")) { - failedMessage = - jsonTypeInvalidMessage(String(jprop_displayMode), "string"); - jsonInvalid(); - return false; - } - } - if (JSON.typeof_(root[jprop_abcDays]) == "number") { int value = root[jprop_abcDays]; if (value <= 0) { @@ -757,20 +730,6 @@ String Configuration::getLedBarModeName(void) { return mode; } -/** - * @brief Get display mode - * - * @return true On - * @return false Off - */ -bool Configuration::getDisplayMode(void) { - String mode = jconfig[jprop_displayMode]; - if (mode == getDisplayModeString(true)) { - return true; - } - return false; -} - /** * @brief Get MQTT uri * @@ -891,13 +850,6 @@ String Configuration::getPMStandardString(bool usaqi) { return "ugm3"; } -String Configuration::getDisplayModeString(bool dispMode) { - if (dispMode) { - return String("on"); - } - return String("off"); -} - String Configuration::getAbcDayString(int value) { if (value <= 0) { return String("off"); @@ -969,24 +921,6 @@ void Configuration::toConfig(const char *buf) { logInfo("toConfig: ledBarMode changed"); } - /** validate display mode */ - if (JSON.typeof_(jconfig[jprop_displayMode]) != "string") { - isInvalid = true; - } else { - String mode = jconfig[jprop_displayMode]; - if (mode != getDisplayModeString(true) && - mode != getDisplayModeString(false)) { - isInvalid = true; - } else { - isInvalid = false; - } - } - if (isInvalid) { - jconfig[jprop_displayMode] = jprop_displayMode_default; - changed = true; - logInfo("toConfig: displayMode changed"); - } - /** validate abcday */ if (JSON.typeof_(jconfig[jprop_abcDays]) != "number") { isInvalid = true; diff --git a/src/AgConfigure.h b/src/AgConfigure.h index 0f0f41d..13b7dc0 100644 --- a/src/AgConfigure.h +++ b/src/AgConfigure.h @@ -31,7 +31,6 @@ 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); From a71c038864d7138c53885e8d1df632a48773f812 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Mon, 13 May 2024 21:28:37 +0700 Subject: [PATCH 39/47] Fix camelCase, #122 --- src/AgConfigure.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp index 0cae473..bff679b 100644 --- a/src/AgConfigure.cpp +++ b/src/AgConfigure.cpp @@ -36,7 +36,7 @@ JSON_PROP_DEF(mqttBrokerUrl); JSON_PROP_DEF(temperatureUnit); JSON_PROP_DEF(configurationControl); JSON_PROP_DEF(postDataToAirGradient); -JSON_PROP_DEF(ledbarBrightness); +JSON_PROP_DEF(ledBarBrightness); JSON_PROP_DEF(displayBrightness); JSON_PROP_DEF(co2CalibrationRequested); JSON_PROP_DEF(ledBarTestRequested); @@ -54,7 +54,7 @@ JSON_PROP_DEF(offlineMode); #define jprop_temperatureUnit_default "c" #define jprop_configurationControl_default String(CONFIGURATION_CONTROL_NAME[ConfigurationControl::ConfigurationControlBoth]) #define jprop_postDataToAirGradient_default true -#define jprop_ledbarBrightness_default 100 +#define jprop_ledBarBrightness_default 100 #define jprop_displayBrightness_default 100 #define jprop_lastOta_default 0 #define jprop_offlineMode_default false @@ -155,9 +155,9 @@ void Configuration::defaultConfig(void) { jconfig[jprop_pmStandard] = jprop_pmStandard_default; jconfig[jprop_temperatureUnit] = jprop_temperatureUnit_default; jconfig[jprop_postDataToAirGradient] = jprop_postDataToAirGradient_default; - jconfig[jprop_ledbarBrightness] = jprop_ledbarBrightness_default; + jconfig[jprop_ledBarBrightness] = jprop_ledBarBrightness_default; jconfig[jprop_displayBrightness] = jprop_displayBrightness_default; - jconfig[jprop_ledBarMode] = jprop_ledbarBrightness_default; + jconfig[jprop_ledBarMode] = jprop_ledBarBrightness_default; jconfig[jprop_tvocLearningOffset] = jprop_tvocLearningOffset_default; jconfig[jprop_noxLearningOffset] = jprop_noxLearningOffset_default; jconfig[jprop_abcDays] = jprop_abcDays_default; @@ -561,27 +561,27 @@ bool Configuration::parse(String data, bool isLocal) { } } - if (JSON.typeof_(root[jprop_ledbarBrightness]) == "number") { - int value = root[jprop_ledbarBrightness]; - int oldValue = jconfig[jprop_ledbarBrightness]; + if (JSON.typeof_(root[jprop_ledBarBrightness]) == "number") { + int value = root[jprop_ledBarBrightness]; + int oldValue = jconfig[jprop_ledBarBrightness]; if (value >= 0 && value <= 100) { if (value != oldValue) { changed = true; - configLogInfo(String(jprop_ledbarBrightness), String(oldValue), + configLogInfo(String(jprop_ledBarBrightness), String(oldValue), String(value)); ledBarBrightnessChanged = true; - jconfig[jprop_ledbarBrightness] = value; + jconfig[jprop_ledBarBrightness] = value; } } else { - failedMessage = jsonValueInvalidMessage(String(jprop_ledbarBrightness), + failedMessage = jsonValueInvalidMessage(String(jprop_ledBarBrightness), String(value)); jsonInvalid(); return false; } } else { - if (jsonTypeInvalid(root[jprop_ledbarBrightness], "number")) { + if (jsonTypeInvalid(root[jprop_ledBarBrightness], "number")) { failedMessage = - jsonTypeInvalidMessage(String(jprop_ledbarBrightness), "number"); + jsonTypeInvalidMessage(String(jprop_ledBarBrightness), "number"); jsonInvalid(); return false; } @@ -1031,10 +1031,10 @@ void Configuration::toConfig(const char *buf) { } /** validate led bar brightness */ - if (JSON.typeof_(jconfig[jprop_ledbarBrightness]) != "number") { + if (JSON.typeof_(jconfig[jprop_ledBarBrightness]) != "number") { isInvalid = true; } else { - int value = jconfig[jprop_ledbarBrightness]; + int value = jconfig[jprop_ledBarBrightness]; if (value < 0 || value > 100) { isInvalid = true; } else { @@ -1042,7 +1042,7 @@ void Configuration::toConfig(const char *buf) { } } if (isInvalid) { - jconfig[jprop_ledbarBrightness] = jprop_ledbarBrightness_default; + jconfig[jprop_ledBarBrightness] = jprop_ledBarBrightness_default; changed = true; logInfo("toConfig: ledBarBrightness changed"); } @@ -1131,7 +1131,7 @@ String Configuration::wifiPass(void) { return String("cleanair"); } void Configuration::setAirGradient(AirGradient *ag) { this->ag = ag; } int Configuration::getLedBarBrightness(void) { - int value = jconfig[jprop_ledbarBrightness]; + int value = jconfig[jprop_ledBarBrightness]; return value; } From a8c8246632ee679c2ec43550b07974c01696f517 Mon Sep 17 00:00:00 2001 From: nick-4711 Date: Wed, 15 May 2024 11:05:37 +0700 Subject: [PATCH 40/47] added doc regarding the OTA mechanism --- docs/ota-updates.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 docs/ota-updates.md diff --git a/docs/ota-updates.md b/docs/ota-updates.md new file mode 100644 index 0000000..d233d0b --- /dev/null +++ b/docs/ota-updates.md @@ -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 From 9a2bbd7a57a7dd4a6eaa27b85576c20a85d7ca09 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Wed, 15 May 2024 14:02:49 +0700 Subject: [PATCH 41/47] fix: Purple color --- src/AgStateMachine.cpp | 204 +++++++++++++++++++++-------------------- 1 file changed, 105 insertions(+), 99 deletions(-) diff --git a/src/AgStateMachine.cpp b/src/AgStateMachine.cpp index d9b565d..ca7547c 100644 --- a/src/AgStateMachine.cpp +++ b/src/AgStateMachine.cpp @@ -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 * @@ -65,67 +71,67 @@ void StateMachine::co2handleLeds(void) { int co2Value = value.CO2; if (co2Value <= 600) { /** G; 1 */ - ag->ledBar.setColor(0, 255, 0, ag->ledBar.getNumberOfLeds() - 1); + 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); + 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(255, 165, 0, ag->ledBar.getNumberOfLeds() - 1); - ag->ledBar.setColor(255, 165, 0, ag->ledBar.getNumberOfLeds() - 2); - ag->ledBar.setColor(255, 165, 0, ag->ledBar.getNumberOfLeds() - 3); - ag->ledBar.setColor(255, 165, 0, ag->ledBar.getNumberOfLeds() - 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(255, 165, 0, ag->ledBar.getNumberOfLeds() - 1); - ag->ledBar.setColor(255, 165, 0, ag->ledBar.getNumberOfLeds() - 2); - ag->ledBar.setColor(255, 165, 0, ag->ledBar.getNumberOfLeds() - 3); - ag->ledBar.setColor(255, 165, 0, ag->ledBar.getNumberOfLeds() - 4); - ag->ledBar.setColor(255, 165, 0, ag->ledBar.getNumberOfLeds() - 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); + 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); + 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(153, 153, 0, ag->ledBar.getNumberOfLeds() - 1); - ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 2); - ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 3); - ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 4); - ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 5); - ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 6); - ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 7); - ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 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); } } @@ -137,78 +143,78 @@ void StateMachine::pm25handleLeds(void) { int pm25Value = value.pm25_1; if (pm25Value <= 5) { /** G; 1 */ - ag->ledBar.setColor(0, 255, 0, ag->ledBar.getNumberOfLeds() - 1); + 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); + 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); + 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); + 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) { /** 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); + 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); + ag->ledBar.setColor(RGB_COLOR_Y, 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); + 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 (pm25Value <= 65) { /** 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); + 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 <= 150) { /** 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); + 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) { /** 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); + ag->ledBar.setColor(RGB_COLOR_R, 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); } } From 563bdfe4b23dc3dc918006b845d1b5ae8c156da6 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Wed, 15 May 2024 17:09:06 +0700 Subject: [PATCH 42/47] Change the OTA update period use timestamp to bootCount --- examples/OneOpenAir/OneOpenAir.ino | 22 ++++++++--- src/AgConfigure.cpp | 61 ------------------------------ src/AgConfigure.h | 2 - src/AgValue.h | 1 + src/AgWiFiConnector.cpp | 6 --- 5 files changed, 17 insertions(+), 75 deletions(-) diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index 41ffebf..aedf8ee 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -856,13 +856,23 @@ static void configUpdateHandle() { fwNewVersion = configuration.newFirmwareVersion(); if (fwNewVersion.length()) { - int lastOta = configuration.getLastOta(); - if (lastOta != 0 && lastOta < (60 * 60 * 24)) { - Serial.println("Ignore OTA cause last update is " + String(lastOta) + - String("sec")); - Serial.println("Retry again after 24h"); + bool doOta = false; + if (measurements.otaBootCount == 0) { + doOta = true; + Serial.println("First OTA"); } else { - configuration.updateLastOta(); + if ((measurements.bootCount - measurements.otaBootCount) >= 30) { + doOta = true; + } else { + Serial.println( + "OTA ignore, try again next " + + String(30 - (measurements.bootCount - measurements.otaBootCount)) + + String(" boots")); + } + } + + if (doOta) { + measurements.otaBootCount = measurements.bootCount; otaHandler.setHandlerCallback(otaHandlerCallback); otaHandler.updateFirmwareIfOutdated(ag->deviceId()); } diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp index bff679b..ead430e 100644 --- a/src/AgConfigure.cpp +++ b/src/AgConfigure.cpp @@ -40,7 +40,6 @@ JSON_PROP_DEF(ledBarBrightness); JSON_PROP_DEF(displayBrightness); JSON_PROP_DEF(co2CalibrationRequested); JSON_PROP_DEF(ledBarTestRequested); -JSON_PROP_DEF(lastOta); JSON_PROP_DEF(offlineMode); #define jprop_model_default "" @@ -56,7 +55,6 @@ JSON_PROP_DEF(offlineMode); #define jprop_postDataToAirGradient_default true #define jprop_ledBarBrightness_default 100 #define jprop_displayBrightness_default 100 -#define jprop_lastOta_default 0 #define jprop_offlineMode_default false JSONVar jconfig; @@ -162,7 +160,6 @@ void Configuration::defaultConfig(void) { jconfig[jprop_noxLearningOffset] = jprop_noxLearningOffset_default; jconfig[jprop_abcDays] = jprop_abcDays_default; jconfig[jprop_model] = jprop_model_default; - jconfig[jprop_lastOta] = jprop_lastOta_default; jconfig[jprop_offlineMode] = jprop_offlineMode_default; saveConfig(); @@ -1073,17 +1070,6 @@ void Configuration::toConfig(const char *buf) { jconfig[jprop_offlineMode] = false; } - /** Last OTA */ - if(JSON.typeof_(jconfig[jprop_lastOta]) != "number") { - isInvalid = true; - } else { - isInvalid = false; - } - if(isInvalid) { - jconfig[jprop_lastOta] = jprop_lastOta_default; - changed = true; - } - if (changed) { saveConfig(); } @@ -1167,53 +1153,6 @@ bool Configuration::isDisplayBrightnessChanged(void) { return changed; } -/** - * @brief Get number of sec from last OTA - * - * @return int < 0 is invalid, 0 mean there is no OTA trigger. - */ -int Configuration::getLastOta(void) { - struct tm timeInfo; - if (getLocalTime(&timeInfo) == false) { - logError("Get localtime failed"); - return -1; - } - int curYear = timeInfo.tm_year + 1900; - if (curYear < 2024) { - logError("Current year " + String(curYear) + String(" invalid")); - return -1; - } - double _t = jconfig[jprop_lastOta]; - time_t lastOta = (time_t)_t; - time_t curTime = mktime(&timeInfo); - logInfo("Last ota time: " + String(lastOta)); - if (lastOta == 0) { - return 0; - } - - int sec = curTime - lastOta; - logInfo("Last ota secconds: " + String(sec)); - return sec; -} - -void Configuration::updateLastOta(void) { - struct tm timeInfo; - if (getLocalTime(&timeInfo) == false) { - logError("updateLastOta: Get localtime failed"); - return; - } - int curYear = timeInfo.tm_year + 1900; - if (curYear < 2024) { - logError("updateLastOta: lolcal time invalid"); - return; - } - - time_t lastOta = mktime(&timeInfo); - jconfig[jprop_lastOta] = (unsigned long)lastOta; - logInfo("Last OTA: " + String(lastOta)); - saveConfig(); -} - String Configuration::newFirmwareVersion(void) { String newFw = otaNewFirmwareVersion; otaNewFirmwareVersion = String(""); diff --git a/src/AgConfigure.h b/src/AgConfigure.h index 13b7dc0..c3eda36 100644 --- a/src/AgConfigure.h +++ b/src/AgConfigure.h @@ -76,8 +76,6 @@ public: int getLedBarBrightness(void); bool isDisplayBrightnessChanged(void); int getDisplayBrightness(void); - int getLastOta(void); - void updateLastOta(void); String newFirmwareVersion(void); bool isOfflineMode(void); void setOfflineMode(bool offline); diff --git a/src/AgValue.h b/src/AgValue.h index 3176482..7446411 100644 --- a/src/AgValue.h +++ b/src/AgValue.h @@ -69,6 +69,7 @@ public: int countPosition; const int targetCount = 20; int bootCount; + int otaBootCount; String toString(bool isLocal, AgFirmwareMode fwMode, int rssi, void* _ag, void* _config); }; diff --git a/src/AgWiFiConnector.cpp b/src/AgWiFiConnector.cpp index 669d526..c8a9d8d 100644 --- a/src/AgWiFiConnector.cpp +++ b/src/AgWiFiConnector.cpp @@ -1,6 +1,5 @@ #include "AgWiFiConnector.h" #include "Libraries/WiFiManager/WiFiManager.h" -#include #define WIFI_CONNECT_COUNTDOWN_MAX 180 #define WIFI_HOTSPOT_PASSWORD_DEFAULT "cleanair" @@ -159,11 +158,6 @@ bool WifiConnector::connect(void) { config.setPostToAirGradient(result != "T"); } hasPortalConfig = false; - - /** Configure internet time */ - const char *ntp_server = "pool.ntp.org"; - configTime(0, 0, ntp_server); - logInfo("Set internet time server: " + String(ntp_server)); } #else _wifiProcess(); From 55ede2b04d803820e80375a4f8c6a1e83a97c84d Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Wed, 15 May 2024 17:11:26 +0700 Subject: [PATCH 43/47] turn led indicate wifi failed, cloud fail and get cloud configuration fail to off. --- examples/OneOpenAir/OneOpenAir.ino | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index 41ffebf..8ee63c5 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -874,13 +874,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); From cad5d1f0e72b68f180ae6e39334e9b7c0dffac0f Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Wed, 15 May 2024 17:13:39 +0700 Subject: [PATCH 44/47] fix print number value wrong format --- examples/OneOpenAir/OneOpenAir.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index 41ffebf..9713e18 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -972,7 +972,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; From 4f9f800cce2cf72519d374880961eb5018a9b974 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Wed, 15 May 2024 17:15:36 +0700 Subject: [PATCH 45/47] Update local configuration response failed message --- src/AgConfigure.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp index bff679b..710f96a 100644 --- a/src/AgConfigure.cpp +++ b/src/AgConfigure.cpp @@ -280,7 +280,8 @@ bool Configuration::parse(String data, bool isLocal) { if (jconfig[jprop_configurationControl] == String(CONFIGURATION_CONTROL_NAME [ConfigurationControl::ConfigurationControlCloud])) { - failedMessage = "Local configure ignored"; + failedMessage = "Monitor set to accept only configuration from the " + "cloud. Use property configurationControl to change."; jsonInvalid(); return false; } From f55fa6a6179ce7c944cc62042f44535fc96a6a37 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Wed, 15 May 2024 17:20:10 +0700 Subject: [PATCH 46/47] Update configuration log mesasge --- src/AgConfigure.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp index bff679b..9127e45 100644 --- a/src/AgConfigure.cpp +++ b/src/AgConfigure.cpp @@ -617,7 +617,7 @@ bool Configuration::parse(String data, bool isLocal) { String newVer = root["targetFirmware"]; String curVer = String(GIT_VERSION); if (curVer != newVer) { - logInfo("Detected new firwmare version: " + newVer); + logInfo("Detected new firmware version: " + newVer); otaNewFirmwareVersion = newVer; udpated = true; } else { @@ -630,7 +630,7 @@ bool Configuration::parse(String data, bool isLocal) { saveConfig(); printConfig(); } else { - logInfo("Nothing changed ignore udpate"); + logInfo("Update ignored due to local unofficial changes"); if (ledBarTestRequested || co2CalibrationRequested) { udpated = true; } From 9e44cd89d97a9c4350cc13fd3bee2912881a2a74 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Thu, 16 May 2024 12:50:38 +0700 Subject: [PATCH 47/47] Update PM2.5 LED bar color and level --- src/AgStateMachine.cpp | 64 +++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/AgStateMachine.cpp b/src/AgStateMachine.cpp index ca7547c..38e2d3e 100644 --- a/src/AgStateMachine.cpp +++ b/src/AgStateMachine.cpp @@ -141,40 +141,40 @@ void StateMachine::co2handleLeds(void) { */ void StateMachine::pm25handleLeds(void) { int pm25Value = value.pm25_1; - if (pm25Value <= 5) { + if (pm25Value < 5) { /** G; 1 */ ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1); - } else if (pm25Value <= 10) { + } else if (pm25Value < 10) { /** GG; 2 */ ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1); ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 2); - } else if (pm25Value <= 20) { + } else if (pm25Value < 20) { /** YYY; 3 */ ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 1); ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 2); ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 3); - } else if (pm25Value <= 35) { + } else if (pm25Value < 35) { /** YYYY; 4 */ ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 1); ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 2); ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 3); ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 4); - } else if (pm25Value <= 45) { - /** YYYYY; 5 */ - 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); - ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 5); - } else if (pm25Value <= 55) { - /** RRRRRR; 6 */ - ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1); - ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2); - ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 3); - ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 4); - ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 5); - ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6); - } else if (pm25Value <= 65) { + } 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(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1); ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2); @@ -183,7 +183,7 @@ void StateMachine::pm25handleLeds(void) { 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 <= 150) { + } else if (pm25Value < 200) { /** RRRRRRRR; 8 */ ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1); ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2); @@ -193,17 +193,17 @@ void StateMachine::pm25handleLeds(void) { 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) { - /** RRRRRRRRR; 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); - ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 9); + } 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(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 1);