diff --git a/components/appfs b/components/appfs index 6a1563b..3311e97 160000 --- a/components/appfs +++ b/components/appfs @@ -1 +1 @@ -Subproject commit 6a1563b35f38b426a13d616ab45a1feeee366b43 +Subproject commit 3311e975db898f5177e6b88026489a40972a7f1f diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 29a9994..bc32bd7 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,4 +1,5 @@ idf_component_register( - SRCS "main.c" "menu.c" "fpga_test.c" "pax_keyboard.c" "system_wrapper.c" "appfs_wrapper.c" "graphics_wrapper.c" "settings.c" "wifi_connection.c" "rp2040_updater.c" + SRCS "main.c" "menu.c" "fpga_test.c" "pax_keyboard.c" "system_wrapper.c" "appfs_wrapper.c" "graphics_wrapper.c" "settings.c" "wifi_connection.c" "rp2040_updater.c" "wifi_ota.c" INCLUDE_DIRS "." "include" + EMBED_TXTFILES ${project_dir}/server_certs/isrgrootx1.pem ) diff --git a/main/include/wifi_connection.h b/main/include/wifi_connection.h index 7c011ab..a46eb13 100644 --- a/main/include/wifi_connection.h +++ b/main/include/wifi_connection.h @@ -5,4 +5,5 @@ #include "esp_wifi.h" -bool wifi_init(const char* aSsid, const char* aPassword, wifi_auth_mode_t aAuthmode, uint8_t aRetryMax); +void wifi_init(); +bool wifi_connect(const char* aSsid, const char* aPassword, wifi_auth_mode_t aAuthmode, uint8_t aRetryMax); diff --git a/main/include/wifi_ota.h b/main/include/wifi_ota.h new file mode 100644 index 0000000..8026dc4 --- /dev/null +++ b/main/include/wifi_ota.h @@ -0,0 +1,3 @@ +#pragma once + +void ota_update(); diff --git a/main/main.c b/main/main.c index 9d8b981..4a271cd 100644 --- a/main/main.c +++ b/main/main.c @@ -35,6 +35,8 @@ #include "efuse.h" +#include "wifi_ota.h" + static const char *TAG = "main"; typedef enum action { @@ -279,6 +281,30 @@ void display_fatal_error(pax_buf_t* pax_buffer, ILI9341* ili9341, const char* li ili9341_write(ili9341, pax_buffer->buf); } +void wifi_connect_to_stored() { + nvs_handle_t handle; + nvs_open("system", NVS_READWRITE, &handle); + char ssid[33]; + char password[33]; + size_t requiredSize; + esp_err_t res = nvs_get_str(handle, "wifi.ssid", NULL, &requiredSize); + if (res != ESP_OK) { + strcpy(ssid, ""); + } else if (requiredSize < sizeof(ssid)) { + res = nvs_get_str(handle, "wifi.ssid", ssid, &requiredSize); + if (res != ESP_OK) strcpy(ssid, ""); + res = nvs_get_str(handle, "wifi.password", NULL, &requiredSize); + if (res != ESP_OK) { + strcpy(password, ""); + } else if (requiredSize < sizeof(password)) { + res = nvs_get_str(handle, "wifi.password", password, &requiredSize); + if (res != ESP_OK) strcpy(password, ""); + } + } + nvs_close(handle); + wifi_connect(ssid, password, WIFI_AUTH_WPA2_PSK, 3); +} + void app_main(void) { esp_err_t res; @@ -379,6 +405,9 @@ void app_main(void) { ws2812_init(GPIO_LED_DATA); uint8_t ledBuffer[15] = {50, 0, 0, 50, 0, 0, 50, 0, 0, 50, 0, 0, 50, 0, 0}; ws2812_send_data(ledBuffer, sizeof(ledBuffer)); + + /* Start WiFi */ + wifi_init(); /* Launcher menu */ while (true) { @@ -399,29 +428,12 @@ void app_main(void) { appfs_store_app(pax_buffer, ili9341, framebuffer); } else if (menu_action == ACTION_WIFI_CONNECT) { graphics_task(pax_buffer, ili9341, framebuffer, NULL, "Connecting..."); - nvs_handle_t handle; - nvs_open("system", NVS_READWRITE, &handle); - char ssid[33]; - char password[33]; - size_t requiredSize; - esp_err_t res = nvs_get_str(handle, "wifi.ssid", NULL, &requiredSize); - if (res != ESP_OK) { - strcpy(ssid, ""); - } else if (requiredSize < sizeof(ssid)) { - res = nvs_get_str(handle, "wifi.ssid", ssid, &requiredSize); - if (res != ESP_OK) strcpy(ssid, ""); - res = nvs_get_str(handle, "wifi.password", NULL, &requiredSize); - if (res != ESP_OK) { - strcpy(password, ""); - } else if (requiredSize < sizeof(password)) { - res = nvs_get_str(handle, "wifi.password", password, &requiredSize); - if (res != ESP_OK) strcpy(password, ""); - } - } - nvs_close(&handle); - wifi_init(ssid, password, WIFI_AUTH_WPA2_PSK, 3); + wifi_connect_to_stored(); } else if (menu_action == ACTION_OTA) { + graphics_task(pax_buffer, ili9341, framebuffer, NULL, "Connecting..."); + wifi_connect_to_stored(); graphics_task(pax_buffer, ili9341, framebuffer, NULL, "Firmware update..."); + ota_update(); } else if (menu_action == ACTION_SETTINGS) { while (true) { menu_wifi_settings(rp2040->queue, pax_buffer, ili9341, framebuffer, &menu_action); @@ -457,7 +469,7 @@ void app_main(void) { } else { graphics_task(pax_buffer, ili9341, framebuffer, NULL, "Canceled"); } - nvs_close(&handle); + nvs_close(handle); } else if (menu_action == ACTION_WIFI_LIST) { nvs_handle_t handle; nvs_open("system", NVS_READWRITE, &handle); @@ -478,7 +490,7 @@ void app_main(void) { if (res != ESP_OK) strcpy(password, ""); } } - nvs_close(&handle); + nvs_close(handle); char buffer[300]; snprintf(buffer, sizeof(buffer), "SSID is %s\nPassword is %s", ssid, password); graphics_task(pax_buffer, ili9341, framebuffer, NULL, buffer); diff --git a/main/wifi_connection.c b/main/wifi_connection.c index 7883289..931a4c9 100644 --- a/main/wifi_connection.c +++ b/main/wifi_connection.c @@ -41,9 +41,7 @@ static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_ } } -bool wifi_init(const char* aSsid, const char* aPassword, wifi_auth_mode_t aAuthmode, uint8_t aRetryMax) { - gRetryCounter = 0; - gRetryMax = aRetryMax; +void wifi_init() { s_wifi_event_group = xEventGroupCreate(); ESP_ERROR_CHECK(esp_netif_init()); @@ -58,7 +56,13 @@ bool wifi_init(const char* aSsid, const char* aPassword, wifi_auth_mode_t aAuthm esp_event_handler_instance_t instance_got_ip; ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id)); ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip)); + + ESP_ERROR_CHECK(esp_wifi_stop()); +} +bool wifi_connect(const char* aSsid, const char* aPassword, wifi_auth_mode_t aAuthmode, uint8_t aRetryMax) { + gRetryCounter = 0; + gRetryMax = aRetryMax; wifi_config_t wifi_config = {0}; strcpy((char*) wifi_config.sta.ssid, aSsid); strcpy((char*) wifi_config.sta.password, aPassword); diff --git a/main/wifi_ota.c b/main/wifi_ota.c new file mode 100644 index 0000000..40fe9c1 --- /dev/null +++ b/main/wifi_ota.c @@ -0,0 +1,208 @@ +#include "wifi_ota.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_ota_ops.h" +#include "esp_http_client.h" +#include "esp_https_ota.h" +#include "string.h" +#include "esp_crt_bundle.h" +#include "nvs.h" +#include "nvs_flash.h" +#include +#include "esp_wifi.h" + +#define HASH_LEN 32 + +static const char *TAG = "OTA update"; + +extern const uint8_t server_cert_pem_start[] asm("_binary_isrgrootx1_pem_start"); +extern const uint8_t server_cert_pem_end[] asm("_binary_isrgrootx1_pem_end"); + +esp_err_t _http_event_handler(esp_http_client_event_t *evt) +{ + switch (evt->event_id) { + case HTTP_EVENT_ERROR: + ESP_LOGD(TAG, "HTTP_EVENT_ERROR"); + break; + case HTTP_EVENT_ON_CONNECTED: + ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED"); + break; + case HTTP_EVENT_HEADERS_SENT: + ESP_LOGD(TAG, "HTTP_EVENT_HEADERS_SENT"); + break; + case HTTP_EVENT_ON_HEADER: + ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value); + break; + case HTTP_EVENT_ON_DATA: + ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len); + break; + case HTTP_EVENT_ON_FINISH: + ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH"); + break; + case HTTP_EVENT_DISCONNECTED: + ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED"); + break; + } + return ESP_OK; +} + +static esp_err_t validate_image_header(esp_app_desc_t *new_app_info) { + if (new_app_info == NULL) { + return ESP_ERR_INVALID_ARG; + } + + const esp_partition_t *running = esp_ota_get_running_partition(); + esp_app_desc_t running_app_info; + if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) { + ESP_LOGI(TAG, "Running firmware version: %s", running_app_info.version); + } + +/* + if (memcmp(new_app_info->version, running_app_info.version, sizeof(new_app_info->version)) == 0) { + ESP_LOGW(TAG, "Current running version is the same as a new. We will not continue the update."); + return ESP_FAIL; + } +*/ + + return ESP_OK; +} + +static esp_err_t _http_client_init_cb(esp_http_client_handle_t http_client) { + esp_err_t err = ESP_OK; + /* Uncomment to add custom headers to HTTP request */ + // err = esp_http_client_set_header(http_client, "Custom-Header", "Value"); + return err; +} + +void ota_task(void *pvParameter) { + ESP_LOGI(TAG, "Starting OTA update"); +/* + esp_netif_t *netif = get_example_netif_from_desc(bind_interface_name); + if (netif == NULL) { + ESP_LOGE(TAG, "Can't find netif from interface description"); + abort(); + } + struct ifreq ifr; + esp_netif_get_netif_impl_name(netif, ifr.ifr_name); + ESP_LOGI(TAG, "Bind interface name is %s", ifr.ifr_name); +*/ + esp_http_client_config_t config = { + .url = "https://ota.bodge.team/mch2022.bin", + .crt_bundle_attach = esp_crt_bundle_attach, + .cert_pem = (char *)server_cert_pem_start, + .event_handler = _http_event_handler, + .keep_alive_enable = true, +/* + .if_name = &ifr, +*/ + }; + + esp_https_ota_config_t ota_config = { + .http_config = &config, + .http_client_init_cb = _http_client_init_cb, // Register a callback to be invoked after esp_http_client is initialized +#ifdef CONFIG_EXAMPLE_ENABLE_PARTIAL_HTTP_DOWNLOAD + .partial_http_download = true, + .max_http_request_size = CONFIG_EXAMPLE_HTTP_REQUEST_SIZE, +#endif + }; + + //config.skip_cert_common_name_check = true; + + ESP_LOGI(TAG, "Attempting to download update from %s", config.url); + + esp_https_ota_handle_t https_ota_handle = NULL; + esp_err_t err = esp_https_ota_begin(&ota_config, &https_ota_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ESP HTTPS OTA Begin failed"); + vTaskDelete(NULL); + } + + esp_app_desc_t app_desc; + err = esp_https_ota_get_img_desc(https_ota_handle, &app_desc); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_https_ota_read_img_desc failed"); + esp_https_ota_abort(https_ota_handle); + vTaskDelete(NULL); + } + err = validate_image_header(&app_desc); + if (err != ESP_OK) { + ESP_LOGE(TAG, "image header verification failed"); + esp_https_ota_abort(https_ota_handle); + vTaskDelete(NULL); + } + + esp_err_t ota_finish_err = ESP_OK; + while (1) { + err = esp_https_ota_perform(https_ota_handle); + if (err != ESP_ERR_HTTPS_OTA_IN_PROGRESS) { + break; + } + // esp_https_ota_perform returns after every read operation which gives user the ability to + // monitor the status of OTA upgrade by calling esp_https_ota_get_image_len_read, which gives length of image + // data read so far. + ESP_LOGD(TAG, "Image bytes read: %d", esp_https_ota_get_image_len_read(https_ota_handle)); + } + + if (esp_https_ota_is_complete_data_received(https_ota_handle) != true) { + // the OTA image was not completely received and user can customise the response to this situation. + ESP_LOGE(TAG, "Complete data was not received."); + } else { + ota_finish_err = esp_https_ota_finish(https_ota_handle); + if ((err == ESP_OK) && (ota_finish_err == ESP_OK)) { + ESP_LOGI(TAG, "ESP_HTTPS_OTA upgrade successful. Rebooting ..."); + vTaskDelay(1000 / portTICK_PERIOD_MS); + esp_restart(); + } else { + if (ota_finish_err == ESP_ERR_OTA_VALIDATE_FAILED) { + ESP_LOGE(TAG, "Image validation failed, image is corrupted"); + } + ESP_LOGE(TAG, "ESP_HTTPS_OTA upgrade failed 0x%x", ota_finish_err); + vTaskDelete(NULL); + } + } + + esp_https_ota_abort(https_ota_handle); + vTaskDelete(NULL); + esp_restart(); +} + +static void print_sha256(const uint8_t *image_hash, const char *label) { + char hash_print[HASH_LEN * 2 + 1]; + hash_print[HASH_LEN * 2] = 0; + for (int i = 0; i < HASH_LEN; ++i) { + sprintf(&hash_print[i * 2], "%02x", image_hash[i]); + } + ESP_LOGI(TAG, "%s %s", label, hash_print); +} + +static void get_sha256_of_partitions(void) { + uint8_t sha_256[HASH_LEN] = { 0 }; + esp_partition_t partition; + + // get sha256 digest for bootloader + partition.address = ESP_BOOTLOADER_OFFSET; + partition.size = ESP_PARTITION_TABLE_OFFSET; + partition.type = ESP_PARTITION_TYPE_APP; + esp_partition_get_sha256(&partition, sha_256); + print_sha256(sha_256, "SHA-256 for bootloader: "); + + // get sha256 digest for running partition + esp_partition_get_sha256(esp_ota_get_running_partition(), sha_256); + print_sha256(sha_256, "SHA-256 for current firmware: "); +} + +void ota_update(void) { + get_sha256_of_partitions(); + + esp_wifi_set_ps(WIFI_PS_NONE); // Disable any WiFi power save mode + + xTaskCreate(&ota_task, "OTA update", 8192, NULL, 5, NULL); + + while (1) { + vTaskDelay(1000 / portTICK_PERIOD_MS); + } +} diff --git a/server_certs/isrgrootx1.pem b/server_certs/isrgrootx1.pem new file mode 100644 index 0000000..b85c803 --- /dev/null +++ b/server_certs/isrgrootx1.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE-----