diff --git a/.gitignore b/.gitignore index d054d84..8f80a6e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ build sdkconfig sdkconfig.old + +# Used during development to test OTA +# See https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/system.html +version.txt diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 374c6d0..4fe2c23 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,2 +1,2 @@ -idf_component_register(SRCS "main.c" "tic.c" "wifi.c" "mqtt.c" "sntp.c" "common.c" "libteleinfo.cpp" "libteleinfo/src/LibTeleinfo.cpp" +idf_component_register(SRCS "main.c" "tic.c" "wifi.c" "mqtt.c" "sntp.c" "common.c" "ota.c" "libteleinfo.cpp" "libteleinfo/src/LibTeleinfo.cpp" INCLUDE_DIRS ".") diff --git a/main/common.c b/main/common.c index 0425933..5ac51a1 100644 --- a/main/common.c +++ b/main/common.c @@ -1,2 +1,20 @@ #include "common.h" volatile EventGroupHandle_t services_event_group; + +char* get_nvs_string(nvs_handle_t nvs, char* key) { + size_t required_size; + esp_err_t err = nvs_get_str(nvs, key, NULL, &required_size); + if (err != ESP_OK) { + return NULL; + } + char* value = malloc(required_size); + if (!value) { + return NULL; + } + err = nvs_get_str(nvs, key, value, &required_size); + if (err != ESP_OK) { + free(value); + return NULL; + } + return value; +} diff --git a/main/common.h b/main/common.h index b7143a4..f692126 100644 --- a/main/common.h +++ b/main/common.h @@ -4,12 +4,15 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/event_groups.h" +#include "nvs_flash.h" +#include "nvs.h" #define WIFI_CONNECTED_BIT BIT0 #define MQTT_CONNECTED_BIT BIT1 #define TIME_SYNC_BIT BIT2 extern volatile EventGroupHandle_t services_event_group; +char* get_nvs_string(nvs_handle_t nvs, char* key); #define WAIT_FOR(flags) while ((xEventGroupWaitBits(services_event_group, flags, pdFALSE, pdTRUE, portMAX_DELAY) & (flags)) != (flags)) {} diff --git a/main/main.c b/main/main.c index e7c2705..b1eac04 100644 --- a/main/main.c +++ b/main/main.c @@ -14,12 +14,17 @@ #include "mqtt.h" #include "sntp.h" #include "common.h" +#include "esp_ota_ops.h" +#include "ota.h" // Embedded via component.mk extern const uint8_t cacert_pem_start[] asm("_binary_cacert_pem_start"); extern const uint8_t cacert_pem_end[] asm("_binary_cacert_pem_end"); void app_main(void) { + const esp_app_desc_t* current = esp_ota_get_app_description(); + ESP_LOGI("main", "Currently running %s version %s", current->project_name, current->version); + // NVS is used to store wifi credentials. So, we need to initialize it first. ESP_ERROR_CHECK(nvs_flash_init()); diff --git a/main/mqtt.c b/main/mqtt.c index dbf7ea3..6150154 100644 --- a/main/mqtt.c +++ b/main/mqtt.c @@ -8,8 +8,6 @@ #include "nvs_flash.h" #include "esp_event.h" #include "esp_netif.h" -#include "nvs_flash.h" -#include "nvs.h" #include "esp_log.h" #include "mqtt_client.h" #include "esp_tls.h" @@ -119,24 +117,6 @@ void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event mqtt_event_handler_cb(event_data); } -char* get_nvs_string(nvs_handle_t nvs, char* key) { - size_t required_size; - esp_err_t err = nvs_get_str(nvs, key, NULL, &required_size); - if (err != ESP_OK) { - return NULL; - } - char* value = malloc(required_size); - if (!value) { - return NULL; - } - err = nvs_get_str(nvs, key, value, &required_size); - if (err != ESP_OK) { - free(value); - return NULL; - } - return value; -} - void mqtt_init(void) { nvs_handle_t nvs; esp_err_t err = nvs_open("mqtt", NVS_READONLY, &nvs); diff --git a/main/ota.c b/main/ota.c new file mode 100644 index 0000000..fdeb1ca --- /dev/null +++ b/main/ota.c @@ -0,0 +1,94 @@ +#include +#include "freertos/FreeRTOS.h" +#include "esp_log.h" +#include "esp_https_ota.h" +#include "esp_ota_ops.h" +#include "ota.h" +#include "nvs_flash.h" +#include "nvs.h" +#include "common.h" + +static const char *OTA_LOGGER = "ota_update"; + +// Embedded via component.mk +extern const uint8_t cacert_pem_start[] asm("_binary_cacert_pem_start"); +extern const uint8_t cacert_pem_end[] asm("_binary_cacert_pem_end"); + +esp_err_t do_firmware_upgrade(char* firmware_url) { + esp_http_client_config_t config = { + .url = firmware_url, + .cert_pem = (char *)cacert_pem_start, + }; + + esp_https_ota_config_t ota_config = { + // Can be enabled in an upcoming version of the Espressif SDK + // to save some memory. + // Requires CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=4096 in sdkconfig. + // + //.partial_http_download = true, + //.max_http_request_size = 4096, + .http_config = &config, + }; + + esp_https_ota_handle_t https_ota_handle = NULL; + esp_err_t err = esp_https_ota_begin(&ota_config, &https_ota_handle); + if (https_ota_handle == NULL) { + return ESP_FAIL; + } + + while (1) { + err = esp_https_ota_perform(https_ota_handle); + if (err != ESP_ERR_HTTPS_OTA_IN_PROGRESS) { + break; + } + } + + if (err != ESP_OK) { + esp_https_ota_abort(https_ota_handle); + return err; + } + + esp_err_t ota_finish_err = esp_https_ota_finish(https_ota_handle); + if (ota_finish_err != ESP_OK) { + return ota_finish_err; + } + + esp_restart(); // this function never returns + + return ESP_OK; +} + +void trigger_ota_update(char* version) { + const esp_app_desc_t* current = esp_ota_get_app_description(); + ESP_LOGD(OTA_LOGGER, "Currently running %s version %s", current->project_name, current->version); + + if (strcmp(version, current->version) == 0) { + // already to latest version + return; + } + + nvs_handle_t nvs; + esp_err_t err = nvs_open("ota", NVS_READONLY, &nvs); + if (err != ESP_OK) { + ESP_LOGE(OTA_LOGGER, "Error (%s) opening NVS handle!\n", esp_err_to_name(err)); + return; + } + char* update_url_pattern = get_nvs_string(nvs, "update_url"); + nvs_close(nvs); + if (update_url_pattern == NULL) { + return; + } + + const size_t buffer_size = 256; + char update_url[buffer_size]; + + // Format the update URL + if (!snprintf(update_url, buffer_size, update_url_pattern, version)) { + ESP_LOGD(OTA_LOGGER, "trigger_ota_update: snprintf failed!"); + free(update_url_pattern); + return; + } + + esp_err_t ret = do_firmware_upgrade(update_url); + ESP_LOGW(OTA_LOGGER, "do_firmware_upgrade failed with error %d", ret); +} diff --git a/main/ota.h b/main/ota.h new file mode 100644 index 0000000..4ec3a69 --- /dev/null +++ b/main/ota.h @@ -0,0 +1,6 @@ +#ifndef __OTA_H__ +#define __OTA_H__ + +void trigger_ota_update(char* version); + +#endif diff --git a/provision/.gitignore b/provision/.gitignore index dae2e86..cd75a07 100644 --- a/provision/.gitignore +++ b/provision/.gitignore @@ -1,2 +1,5 @@ # contains sensitive data sdkconfig +config.dev +config.prod +sdkconfig.defaults diff --git a/provision/main/Kconfig.projbuild b/provision/main/Kconfig.projbuild index 4ede7c2..e2754b6 100644 --- a/provision/main/Kconfig.projbuild +++ b/provision/main/Kconfig.projbuild @@ -36,4 +36,10 @@ menu "Provisioning data" help MQTT password. + config FIRMWARE_URL_PATTERN + string "Firmware URL pattern" + default "" + help + Where to download a specific version of the firmware. Complete URL. Must include "%s". + endmenu diff --git a/provision/main/provision.c b/provision/main/provision.c index a13b778..bf6d275 100644 --- a/provision/main/provision.c +++ b/provision/main/provision.c @@ -135,6 +135,10 @@ void wifi_init_sta(void) nvs_set_str(nvs, "password", CONFIG_MQTT_PASSWORD); nvs_close(nvs); + ESP_ERROR_CHECK(nvs_open("ota", NVS_READWRITE, &nvs)); + nvs_set_str(nvs, "update_url", CONFIG_FIRMWARE_URL_PATTERN); + nvs_close(nvs); + nvs_dumpall(); /* The event will not be processed after unregister */