diff --git a/.gitignore b/.gitignore index fb4b0b5..a1ec5b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.DS_Store build .vscode +/.idea/ .pio diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index b4bc038..3bb6fc8 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -37,7 +37,8 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License */ #include - +#include "AirGradient.h" +#include "OtaHandler.h" #include "AgApiClient.h" #include "AgConfigure.h" #include "AgSchedule.h" @@ -49,7 +50,6 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License #include "MqttClient.h" #include "OpenMetrics.h" #include "WebServer.h" -#include #include #define LED_BAR_ANIMATION_PERIOD 100 /** ms */ @@ -82,6 +82,7 @@ static WifiConnector wifiConnector(oledDisplay, Serial, stateMachine, configuration); static OpenMetrics openMetrics(measurements, configuration, wifiConnector, apiClient); +static OtaHandler otaHandler; static LocalServer localServer(Serial, openMetrics, measurements, configuration, wifiConnector); @@ -180,6 +181,12 @@ void setup() { initMqtt(); sendDataToAg(); + #ifdef ESP8266 + // ota not supported + #else + otaHandler.updateFirmwareIfOutdated(ag->deviceId()); + #endif + apiClient.fetchServerConfiguration(); configSchedule.update(); if (apiClient.isFetchConfigureFailed()) { @@ -483,6 +490,7 @@ static void oneIndoorInit(void) { /** Show boot display */ Serial.println("Firmware Version: " + ag->getVersion()); + oledDisplay.setText("AirGradient ONE", "FW Version: ", ag->getVersion().c_str()); delay(DISPLAY_DELAY_SHOW_CONTENT_MS); diff --git a/examples/OneOpenAir/OpenMetrics.h b/examples/OneOpenAir/OpenMetrics.h index 5df2349..ed890f5 100644 --- a/examples/OneOpenAir/OpenMetrics.h +++ b/examples/OneOpenAir/OpenMetrics.h @@ -7,7 +7,7 @@ #include "AirGradient.h" #include "AgApiClient.h" -class OpenMetrics{ +class OpenMetrics { private: AirGradient *ag; Measurements &measure; diff --git a/examples/OneOpenAir/OtaHandler.h b/examples/OneOpenAir/OtaHandler.h new file mode 100644 index 0000000..e26848f --- /dev/null +++ b/examples/OneOpenAir/OtaHandler.h @@ -0,0 +1,128 @@ +#ifndef _OTA_HANDLER_H_ +#define _OTA_HANDLER_H_ + +#include +#include +#include +#include + +#define OTA_BUF_SIZE 512 +#define URL_BUF_SIZE 256 + + +class OtaHandler { +public: + 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 @ %s\n", urlAsChar); + + esp_http_client_config_t config = {}; + config.url = urlAsChar; + esp_err_t ret = attemptToPerformOta(&config); + Serial.println(ret); + if (ret == 0) { + Serial.println("OTA completed"); + esp_restart(); + } else { + Serial.println("OTA failed, maybe already up to date"); + } + } + +private: + + int attemptToPerformOta(const esp_http_client_config_t *config) { + esp_http_client_handle_t client = esp_http_client_init(config); + if (client == NULL) { + Serial.println("Failed to initialize HTTP connection"); + return -1; + } + + esp_err_t err = esp_http_client_open(client, 0); + if (err != ESP_OK) { + esp_http_client_cleanup(client); + Serial.printf("Failed to open HTTP connection: %s\n", esp_err_to_name(err)); + return -1; + } + esp_http_client_fetch_headers(client); + + esp_ota_handle_t update_handle = 0; + const esp_partition_t *update_partition = NULL; + Serial.println("Starting OTA ..."); + update_partition = esp_ota_get_next_update_partition(NULL); + if (update_partition == NULL) { + Serial.println("Passive OTA partition not found"); + cleanupHttp(client); + return ESP_FAIL; + } + Serial.printf("Writing to partition subtype %d at offset 0x%x\n", + update_partition->subtype, update_partition->address); + + err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle); + if (err != ESP_OK) { + Serial.printf("esp_ota_begin failed, error=%d\n", err); + cleanupHttp(client); + return err; + } + + esp_err_t ota_write_err = ESP_OK; + char *upgrade_data_buf = (char *)malloc(OTA_BUF_SIZE); + if (!upgrade_data_buf) { + Serial.println("Couldn't allocate memory for data buffer"); + return ESP_ERR_NO_MEM; + } + + 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 ota_write_err; + } else if (ota_end_err != ESP_OK) { + Serial.printf("Error: esp_ota_end failed! err=0x%d. Image is invalid", ota_end_err); + return ota_end_err; + } + + 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 0; + } + + void cleanupHttp(esp_http_client_handle_t client) { + esp_http_client_close(client); + esp_http_client_cleanup(client); + } + +}; + +#endif diff --git a/platformio.ini b/platformio.ini index 184ca66..af8e14f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -12,13 +12,13 @@ platform = espressif32 board = esp32-c3-devkitm-1 framework = arduino -build_flags = - -DARDUINO_USB_CDC_ON_BOOT=1 - -DARDUINO_USB_MODE=1 +build_flags = !echo '-D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 -D GIT_VERSION=\\"'$(git describe --tags --always --dirty)'\\"' board_build.partitions = partitions.csv monitor_speed = 115200 lib_deps = aglib=symlink://../arduino +monitor_filters = time + [platformio] src_dir = examples/OneOpenAir diff --git a/src/AirGradient.cpp b/src/AirGradient.cpp index 9be7f72..9c24a97 100644 --- a/src/AirGradient.cpp +++ b/src/AirGradient.cpp @@ -5,8 +5,6 @@ #include "WiFi.h" #endif -#define AG_LIB_VER "3.0.10beta2" - AirGradient::AirGradient(BoardType type) : pms5003(type), pms5003t_1(type), pms5003t_2(type), s8(type), sgp41(type), display(type), boardType(type), button(type), statusLed(type), @@ -38,7 +36,7 @@ int AirGradient::getI2cSclPin(void) { return bsp->I2C.scl_pin; } -String AirGradient::getVersion(void) { return AG_LIB_VER; } +String AirGradient::getVersion(void) { return GIT_VERSION; } BoardType AirGradient::getBoardType(void) { return boardType; } diff --git a/src/AirGradient.h b/src/AirGradient.h index e056173..6c4eb88 100644 --- a/src/AirGradient.h +++ b/src/AirGradient.h @@ -13,6 +13,10 @@ #include "Sgp41/Sgp41.h" #include "Sht/Sht.h" +#ifndef GIT_VERSION +#define GIT_VERSION "snapshot" +#endif + /** * @brief Class with define all the sensor has supported by Airgradient. Each * sensor usage must be init before use.