From 1fecdc38918c744f8b13f94452bd91bec87e581a Mon Sep 17 00:00:00 2001 From: Jitin George Date: Thu, 12 Jul 2018 17:45:44 +0530 Subject: [PATCH] esp_https_ota: Add support for HTTPS based ota feature --- components/esp_https_ota/component.mk | 3 + .../esp_https_ota/include/esp_https_ota.h | 45 +++++ components/esp_https_ota/src/esp_https_ota.c | 130 +++++++++++++ docs/Doxyfile | 2 + .../en/api-reference/system/esp_https_ota.rst | 35 ++++ docs/en/api-reference/system/index.rst | 1 + docs/en/api-reference/system/ota.rst | 1 + .../api-reference/system/esp_https_ota.rst | 1 + examples/system/ota/README.md | 85 +++++--- .../ota/{ => native_ota_example}/Makefile | 2 +- .../system/ota/native_ota_example/README.md | 7 + .../main/Kconfig.projbuild | 21 +- .../main/component.mk | 2 + .../main/native_ota_example.c} | 181 ++++-------------- .../sdkconfig.defaults | 0 examples/system/ota/server_certs/ca_cert.pem | 0 .../system/ota/simple_ota_example/Makefile | 9 + .../system/ota/simple_ota_example/README.md | 7 + .../simple_ota_example/main/Kconfig.projbuild | 21 ++ .../ota/simple_ota_example/main/component.mk | 6 + .../main/simple_ota_example.c | 147 ++++++++++++++ .../ota/simple_ota_example/sdkconfig.defaults | 4 + 22 files changed, 518 insertions(+), 192 deletions(-) create mode 100644 components/esp_https_ota/component.mk create mode 100644 components/esp_https_ota/include/esp_https_ota.h create mode 100644 components/esp_https_ota/src/esp_https_ota.c create mode 100644 docs/en/api-reference/system/esp_https_ota.rst create mode 100644 docs/zh_CN/api-reference/system/esp_https_ota.rst rename examples/system/ota/{ => native_ota_example}/Makefile (85%) create mode 100644 examples/system/ota/native_ota_example/README.md rename examples/system/ota/{ => native_ota_example}/main/Kconfig.projbuild (52%) rename examples/system/ota/{ => native_ota_example}/main/component.mk (62%) rename examples/system/ota/{main/ota_example_main.c => native_ota_example/main/native_ota_example.c} (51%) rename examples/system/ota/{ => native_ota_example}/sdkconfig.defaults (100%) create mode 100644 examples/system/ota/server_certs/ca_cert.pem create mode 100644 examples/system/ota/simple_ota_example/Makefile create mode 100644 examples/system/ota/simple_ota_example/README.md create mode 100644 examples/system/ota/simple_ota_example/main/Kconfig.projbuild create mode 100644 examples/system/ota/simple_ota_example/main/component.mk create mode 100644 examples/system/ota/simple_ota_example/main/simple_ota_example.c create mode 100644 examples/system/ota/simple_ota_example/sdkconfig.defaults diff --git a/components/esp_https_ota/component.mk b/components/esp_https_ota/component.mk new file mode 100644 index 000000000..ebe80ffbe --- /dev/null +++ b/components/esp_https_ota/component.mk @@ -0,0 +1,3 @@ +COMPONENT_SRCDIRS := src + +COMPONENT_ADD_INCLUDEDIRS := include diff --git a/components/esp_https_ota/include/esp_https_ota.h b/components/esp_https_ota/include/esp_https_ota.h new file mode 100644 index 000000000..157195601 --- /dev/null +++ b/components/esp_https_ota/include/esp_https_ota.h @@ -0,0 +1,45 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief HTTPS OTA Firmware upgrade. + * + * This function performs HTTPS OTA Firmware upgrade + * + * @param[in] config pointer to esp_http_client_config_t structure. + * + * @note For secure HTTPS updates, the `cert_pem` member of `config` + * structure must be set to the server certificate. + * + * @return + * - ESP_OK: OTA data updated, next reboot will use specified partition. + * - ESP_FAIL: For generic failure. + * - ESP_ERR_OTA_VALIDATE_FAILED: Invalid app image + * - ESP_ERR_NO_MEM: Cannot allocate memory for OTA operation. + * - ESP_ERR_FLASH_OP_TIMEOUT or ESP_ERR_FLASH_OP_FAIL: Flash write failed. + * - For other return codes, refer OTA documentation in esp-idf's app_update component. + */ +esp_err_t esp_https_ota(const esp_http_client_config_t *config); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_https_ota/src/esp_https_ota.c b/components/esp_https_ota/src/esp_https_ota.c new file mode 100644 index 000000000..9929a1856 --- /dev/null +++ b/components/esp_https_ota/src/esp_https_ota.c @@ -0,0 +1,130 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include + +#define OTA_BUF_SIZE 256 +static const char *TAG = "esp_https_ota"; + +static void http_cleanup(esp_http_client_handle_t client) +{ + esp_http_client_close(client); + esp_http_client_cleanup(client); +} + +esp_err_t esp_https_ota(const esp_http_client_config_t *config) +{ + if (!config) { + ESP_LOGE(TAG, "esp_http_client config not found"); + return ESP_ERR_INVALID_ARG; + } + + if (!config->cert_pem) { + ESP_LOGE(TAG, "Server certificate not found in esp_http_client config"); + return ESP_FAIL; + } + + esp_http_client_handle_t client = esp_http_client_init(config); + if (client == NULL) { + ESP_LOGE(TAG, "Failed to initialise HTTP connection"); + return ESP_FAIL; + } + + if (esp_http_client_get_transport_type(client) != HTTP_TRANSPORT_OVER_SSL) { + ESP_LOGE(TAG, "Transport is not over HTTPS"); + return ESP_FAIL; + } + + esp_err_t err = esp_http_client_open(client, 0); + if (err != ESP_OK) { + esp_http_client_cleanup(client); + ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err)); + return err; + } + esp_http_client_fetch_headers(client); + + esp_ota_handle_t update_handle = 0; + const esp_partition_t *update_partition = NULL; + ESP_LOGI(TAG, "Starting OTA..."); + update_partition = esp_ota_get_next_update_partition(NULL); + if (update_partition == NULL) { + ESP_LOGE(TAG, "Passive OTA partition not found"); + http_cleanup(client); + return ESP_FAIL; + } + ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%x", + update_partition->subtype, update_partition->address); + + err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_begin failed, error=%d", err); + http_cleanup(client); + return err; + } + ESP_LOGI(TAG, "esp_ota_begin succeeded"); + ESP_LOGI(TAG, "Please Wait. This may take time"); + + esp_err_t ota_write_err = ESP_OK; + char *upgrade_data_buf = (char *)malloc(OTA_BUF_SIZE); + if (!upgrade_data_buf) { + ESP_LOGE(TAG, "Couldn't allocate memory to upgrade 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) { + ESP_LOGI(TAG, "Connection closed,all data received"); + break; + } + if (data_read < 0) { + ESP_LOGE(TAG, "Error: SSL 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; + ESP_LOGD(TAG, "Written image length %d", binary_file_len); + } + } + free(upgrade_data_buf); + http_cleanup(client); + ESP_LOGD(TAG, "Total binary data length writen: %d", binary_file_len); + + esp_err_t ota_end_err = esp_ota_end(update_handle); + if (ota_write_err != ESP_OK) { + ESP_LOGE(TAG, "Error: esp_ota_write failed! err=0x%d", err); + return ota_write_err; + } else if (ota_end_err != ESP_OK) { + ESP_LOGE(TAG, "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) { + ESP_LOGE(TAG, "esp_ota_set_boot_partition failed! err=0x%d", err); + return err; + } + ESP_LOGI(TAG, "esp_ota_set_boot_partition succeeded"); + + return ESP_OK; +} diff --git a/docs/Doxyfile b/docs/Doxyfile index 5c86893b8..d91b7cf23 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -144,6 +144,8 @@ INPUT = \ ../../components/esp32/include/esp_ipc.h \ ## Over The Air Updates (OTA) ../../components/app_update/include/esp_ota_ops.h \ + ## ESP HTTPS OTA + ../../components/esp_https_ota/include/esp_https_ota.h \ ## Sleep ## NOTE: for line below header_file.inc is not used ../../components/esp32/include/esp_sleep.h \ diff --git a/docs/en/api-reference/system/esp_https_ota.rst b/docs/en/api-reference/system/esp_https_ota.rst new file mode 100644 index 000000000..762de6cf7 --- /dev/null +++ b/docs/en/api-reference/system/esp_https_ota.rst @@ -0,0 +1,35 @@ +ESP HTTPS OTA +============= + +Overview +-------- + +``esp_https_ota`` provides simplified APIs to perform firmware upgrades over HTTPS. +It's an abstraction layer over existing OTA APIs. + +Application Example +------------------- + + .. highlight:: c + + :: + + esp_err_t do_firmware_upgrade() + { + esp_http_client_config_t config = { + .url = CONFIG_FIRMWARE_UPGRADE_URL, + .cert_pem = (char *)server_cert_pem_start, + }; + esp_err_t ret = esp_https_ota(&config); + if (ret == ESP_OK) { + esp_restart(); + } else { + return ESP_FAIL; + } + return ESP_OK; + } + +API Reference +------------- + +.. include:: /_build/inc/esp_https_ota.inc diff --git a/docs/en/api-reference/system/index.rst b/docs/en/api-reference/system/index.rst index d9a32fd4e..2b94e6d55 100644 --- a/docs/en/api-reference/system/index.rst +++ b/docs/en/api-reference/system/index.rst @@ -18,6 +18,7 @@ System API Sleep Modes Base MAC address Over The Air Updates (OTA) + ESP HTTPS OTA ESP pthread Error Codes and Helper Functions diff --git a/docs/en/api-reference/system/ota.rst b/docs/en/api-reference/system/ota.rst index d7dbb5978..825335473 100644 --- a/docs/en/api-reference/system/ota.rst +++ b/docs/en/api-reference/system/ota.rst @@ -37,6 +37,7 @@ See also * :doc:`Partition Table documentation <../../api-guides/partition-tables>` * :doc:`Lower-Level SPI Flash/Partition API <../storage/spi_flash>` +* :doc:`ESP HTTPS OTA ` Application Example ------------------- diff --git a/docs/zh_CN/api-reference/system/esp_https_ota.rst b/docs/zh_CN/api-reference/system/esp_https_ota.rst new file mode 100644 index 000000000..e831ef944 --- /dev/null +++ b/docs/zh_CN/api-reference/system/esp_https_ota.rst @@ -0,0 +1 @@ +.. include:: ../../../en/api-reference/system/esp_https_ota.rst diff --git a/examples/system/ota/README.md b/examples/system/ota/README.md index 316789dbc..2d1f42d4b 100644 --- a/examples/system/ota/README.md +++ b/examples/system/ota/README.md @@ -1,67 +1,94 @@ +# OTA Demo -# Simple OTA Demo +## Introduction -This example demonstrates a working OTA (over the air) firmware update workflow. +Over The Air (OTA) updates can be performed in esp32 in two ways: -This example is a *simplified demonstration*, for production firmware updates you should use a secure protocol such as HTTPS. +- Using native APIs which are part of OTA component. +- Using simplified APIs which are part of `esp_https_ota`, it's an abstraction layer over OTA APIs to perform updates using HTTPS. + +Both these methods are demonstrated in OTA Demo under `native_ota_example` and `simple_ota_example` respectively. + +*Note: This guide is common for both the examples* --- -# Aim +## Aim An app running on ESP32 can upgrade itself by downloading a new app "image" binary file, and storing it in flash. In this example, the ESP32 has 3 images in flash: factory, OTA_0, OTA_1. Each of these is a self-contained partition. The number of OTA image partition is determined by the partition table layout. -Flashing the example over serial with "make flash" updates the factory app image. On first boot, the bootloader loads this factory app image which then performs an OTA update (triggered in the example code). The update downloads a new image from an http server and saves it into the OTA_0 partition. At this point the example code updates the ota_data partition to indicate the new app partition, and resets. The bootloader reads ota_data, determines the new OTA image has been selected, and runs it. +Flashing the example over serial with "make flash" updates the factory app image. On first boot, the bootloader loads this factory app image which then performs an OTA update (triggered in the example code). The update downloads a new image from a HTTPS server and saves it into the OTA_0 partition. At this point the example code updates the ota_data partition to indicate the new app partition, and resets. The bootloader reads ota_data, determines the new OTA image has been selected, and runs it. -# Workflow +## Workflow The OTA_workflow.png diagram demonstrates the overall workflow: ![OTA Workflow diagram](OTA_workflow.png) -## Step 1: Connect to AP +### Step 1: Connect to AP Connect your host PC to the same AP that you will use for the ESP32. -## Step 2: Run HTTP Server - -Python has a built-in HTTP server that can be used for example purposes. +### Step 2: Run HTTPS Server For our upgrade example OTA file, we're going to use the `get-started/hello_world` example. Open a new terminal to run the HTTP server, then run these commands to build the example and start the server: +Build the example: + ``` cd $IDF_PATH/examples/get-started/hello_world make cd build -python -m SimpleHTTPServer 8070 ``` -While the server is running, the contents of the build directory can be browsed at http://localhost:8070/ +Generate self-signed certificate and key: -NB: On some systems, the command may be `python2 -m SimpleHTTPServer`. +*NOTE: `Common Name` of server certificate should be host-name of your server.* + +``` +openssl req -x509 -newkey rsa:2048 -keyout ca_key.pem -out ca_cert.pem -days 365 + +``` + +Copy the certificate to OTA example directory: + +``` +cp ca_cert.pem $IDF_PATH/examples/system/ota/server_certs/ca_cert.pem +``` + + +Start the HTTPS server: + +``` +openssl s_server -WWW -key ca_key.pem -cert ca_cert.pem -port 8070 +``` NB: You've probably noticed there is nothing special about the "hello world" example when used for OTA updates. This is because any .bin app file which is built by esp-idf can be used as an app image for OTA. The only difference is whether it is written to a factory partition or an OTA partition. If you have any firewall software running that will block incoming access to port 8070, configure it to allow access while running the example. -## Step 3: Build OTA Example +### Step 3: Build OTA Example Change back to the OTA example directory, and type `make menuconfig` to configure the OTA example. Under the "Example Configuration" submenu, fill in the following details: * WiFi SSID & Password -* IP address of your host PC as "HTTP Server" -* HTTP Port number (if using the Python HTTP server above, the default is correct) +* Firmware Upgrade URL. The URL will be look like this: -If serving the "hello world" example, you can leave the default filename as-is. +``` +https://:/ + +for e.g, +https://192.168.0.3:8070/hello-world.bin +``` Save your changes, and type `make` to build the example. -## Step 4: Flash OTA Example +### Step 4: Flash OTA Example When flashing, use the `make flash` to flash the factory image. This command will find if partition table has ota_data partition (as in our case) then ota_data will erase to initial. It allows to run the newly loaded app from a factory partition. @@ -73,31 +100,29 @@ make flash After first update, if you want to return back to factory app (or the first OTA partition, if factory partition is not present) then use the command `make erase_ota`. It erases ota_data partition to initial. -## Step 5: Run the OTA Example +### Step 5: Run the OTA Example -When the example starts up, it will print "ota: Starting OTA example..." then: +When the example starts up, it will print "Starting OTA example..." then: 1. Connect to the AP with configured SSID and password. 2. Connect to the HTTP server and download the new image. 3. Write the image to flash, and configure the next boot from this image. 4. Reboot -# Troubleshooting +## Troubleshooting * Check your PC can ping the ESP32 at its IP, and that the IP, AP and other configuration settings are correct in menuconfig. * Check if any firewall software is preventing incoming connections on the PC. -* Check you can see the configured file (default hello-world.bin) if you browse the file listing at http://127.0.0.1/ +* Check whether you can see the configured file (default hello-world.bin), by checking the output of following command: + + ``` + curl -v https://:/ + ``` + * If you have another PC or a phone, try viewing the file listing from the separate host. -## Error "ota_begin error err=0x104" +### Error "ota_begin error err=0x104" If you see this error then check that the configured (and actual) flash size is large enough for the partitions in the partition table. The default "two OTA slots" partition table only works with 4MB flash size. To use OTA with smaller flash sizes, create a custom partition table CSV (look in components/partition_table) and configure it in menuconfig. If changing partition layout, it is usually wise to run "make erase_flash" between steps. - -## Production Implementation - -If scaling this example for production use, please consider: - -* Using an encrypted communications channel such as HTTPS. -* Dealing with timeouts or WiFi disconnections while flashing. diff --git a/examples/system/ota/Makefile b/examples/system/ota/native_ota_example/Makefile similarity index 85% rename from examples/system/ota/Makefile rename to examples/system/ota/native_ota_example/Makefile index 7ffb10234..3bcbefac0 100644 --- a/examples/system/ota/Makefile +++ b/examples/system/ota/native_ota_example/Makefile @@ -3,7 +3,7 @@ # project subdirectory. # -PROJECT_NAME := ota +PROJECT_NAME := native_ota include $(IDF_PATH)/make/project.mk diff --git a/examples/system/ota/native_ota_example/README.md b/examples/system/ota/native_ota_example/README.md new file mode 100644 index 000000000..be57ce55e --- /dev/null +++ b/examples/system/ota/native_ota_example/README.md @@ -0,0 +1,7 @@ +# Native OTA example + +This example is based on `app_update` component's APIs. + +## Configuration + +Refer the README.md in the parent directory for the setup details. \ No newline at end of file diff --git a/examples/system/ota/main/Kconfig.projbuild b/examples/system/ota/native_ota_example/main/Kconfig.projbuild similarity index 52% rename from examples/system/ota/main/Kconfig.projbuild rename to examples/system/ota/native_ota_example/main/Kconfig.projbuild index 2d4b8c436..05965e942 100644 --- a/examples/system/ota/main/Kconfig.projbuild +++ b/examples/system/ota/native_ota_example/main/Kconfig.projbuild @@ -14,27 +14,12 @@ config WIFI_PASSWORD Can be left blank if the network has no security set. -config SERVER_IP - string "HTTP Server IP" - default "192.168.0.3" +config FIRMWARE_UPG_URL + string "HTTP Server URL" + default "https://192.168.0.3:8070/hello-world.bin" help HTTP Server IP to download the image file from. See example README.md for details. -config SERVER_PORT - string "HTTP Server Port" - default "8070" - help - HTTP Server port to connect to. - Should be chosen not to conflict with any other port used - on the system. - -config EXAMPLE_FILENAME - string "HTTP GET Filename" - default "/hello-world.bin" - help - Filename of the app image file to download for - the OTA update. - endmenu diff --git a/examples/system/ota/main/component.mk b/examples/system/ota/native_ota_example/main/component.mk similarity index 62% rename from examples/system/ota/main/component.mk rename to examples/system/ota/native_ota_example/main/component.mk index a98f634ea..93a42f552 100644 --- a/examples/system/ota/main/component.mk +++ b/examples/system/ota/native_ota_example/main/component.mk @@ -2,3 +2,5 @@ # "main" pseudo-component makefile. # # (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + +COMPONENT_EMBED_TXTFILES := ${IDF_PATH}/examples/system/ota/server_certs/ca_cert.pem diff --git a/examples/system/ota/main/ota_example_main.c b/examples/system/ota/native_ota_example/main/native_ota_example.c similarity index 51% rename from examples/system/ota/main/ota_example_main.c rename to examples/system/ota/native_ota_example/main/native_ota_example.c index b5e0f6062..a0e18d933 100644 --- a/examples/system/ota/main/ota_example_main.c +++ b/examples/system/ota/native_ota_example/main/native_ota_example.c @@ -6,10 +6,6 @@ software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ -#include -#include -#include - #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/event_groups.h" @@ -19,27 +15,21 @@ #include "esp_event_loop.h" #include "esp_log.h" #include "esp_ota_ops.h" +#include "esp_http_client.h" #include "nvs.h" #include "nvs_flash.h" #define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID #define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD -#define EXAMPLE_SERVER_IP CONFIG_SERVER_IP -#define EXAMPLE_SERVER_PORT CONFIG_SERVER_PORT -#define EXAMPLE_FILENAME CONFIG_EXAMPLE_FILENAME +#define EXAMPLE_SERVER_URL CONFIG_FIRMWARE_UPG_URL #define BUFFSIZE 1024 -#define TEXT_BUFFSIZE 1024 -static const char *TAG = "ota"; +static const char *TAG = "native_ota_example"; /*an ota data write buffer ready to write to the flash*/ static char ota_write_data[BUFFSIZE + 1] = { 0 }; -/*an packet receive buffer*/ -static char text[BUFFSIZE + 1] = { 0 }; -/* an image total length*/ -static int binary_file_length = 0; -/*socket id*/ -static int socket_id = -1; +extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start"); +extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end"); /* FreeRTOS event group to signal when we are connected & ready to make a request */ static EventGroupHandle_t wifi_event_group; @@ -90,85 +80,15 @@ static void initialise_wifi(void) ESP_ERROR_CHECK( esp_wifi_start() ); } -/*read buffer by byte still delim ,return read bytes counts*/ -static int read_until(char *buffer, char delim, int len) +static void http_cleanup(esp_http_client_handle_t client) { -// /*TODO: delim check,buffer check,further: do an buffer length limited*/ - int i = 0; - while (buffer[i] != delim && i < len) { - ++i; - } - return i + 1; -} - -/* resolve a packet from http socket - * return true if packet including \r\n\r\n that means http packet header finished,start to receive packet body - * otherwise return false - * */ -static bool read_past_http_header(char text[], int total_len, esp_ota_handle_t update_handle) -{ - /* i means current position */ - int i = 0, i_read_len = 0; - while (text[i] != 0 && i < total_len) { - i_read_len = read_until(&text[i], '\n', total_len); - // if we resolve \r\n line,we think packet header is finished - if (i_read_len == 2) { - int i_write_len = total_len - (i + 2); - memset(ota_write_data, 0, BUFFSIZE); - /*copy first http packet body to write buffer*/ - memcpy(ota_write_data, &(text[i + 2]), i_write_len); - - esp_err_t err = esp_ota_write( update_handle, (const void *)ota_write_data, i_write_len); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Error: esp_ota_write failed (%s)!", esp_err_to_name(err)); - return false; - } else { - ESP_LOGI(TAG, "esp_ota_write header OK"); - binary_file_length += i_write_len; - } - return true; - } - i += i_read_len; - } - return false; -} - -static bool connect_to_http_server() -{ - ESP_LOGI(TAG, "Server IP: %s Server Port:%s", EXAMPLE_SERVER_IP, EXAMPLE_SERVER_PORT); - - int http_connect_flag = -1; - struct sockaddr_in sock_info; - - socket_id = socket(AF_INET, SOCK_STREAM, 0); - if (socket_id == -1) { - ESP_LOGE(TAG, "Create socket failed!"); - return false; - } - - // set connect info - memset(&sock_info, 0, sizeof(struct sockaddr_in)); - sock_info.sin_family = AF_INET; - sock_info.sin_addr.s_addr = inet_addr(EXAMPLE_SERVER_IP); - sock_info.sin_port = htons(atoi(EXAMPLE_SERVER_PORT)); - - // connect to http server - http_connect_flag = connect(socket_id, (struct sockaddr *)&sock_info, sizeof(sock_info)); - if (http_connect_flag == -1) { - ESP_LOGE(TAG, "Connect to server failed! errno=%d", errno); - close(socket_id); - return false; - } else { - ESP_LOGI(TAG, "Connected to server"); - return true; - } - return false; + esp_http_client_close(client); + esp_http_client_cleanup(client); } static void __attribute__((noreturn)) task_fatal_error() { ESP_LOGE(TAG, "Exiting task due to fatal error..."); - close(socket_id); (void)vTaskDelete(NULL); while (1) { @@ -202,36 +122,23 @@ static void ota_example_task(void *pvParameter) xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY); ESP_LOGI(TAG, "Connect to Wifi ! Start to Connect to Server...."); - - /*connect to http server*/ - if (connect_to_http_server()) { - ESP_LOGI(TAG, "Connected to http server"); - } else { - ESP_LOGE(TAG, "Connect to http server failed!"); + + esp_http_client_config_t config = { + .url = EXAMPLE_SERVER_URL, + .cert_pem = (char *)server_cert_pem_start, + }; + esp_http_client_handle_t client = esp_http_client_init(&config); + if (client == NULL) { + ESP_LOGE(TAG, "Failed to initialise HTTP connection"); task_fatal_error(); } - - /*send GET request to http server*/ - const char *GET_FORMAT = - "GET %s HTTP/1.0\r\n" - "Host: %s:%s\r\n" - "User-Agent: esp-idf/1.0 esp32\r\n\r\n"; - - char *http_request = NULL; - int get_len = asprintf(&http_request, GET_FORMAT, EXAMPLE_FILENAME, EXAMPLE_SERVER_IP, EXAMPLE_SERVER_PORT); - if (get_len < 0) { - ESP_LOGE(TAG, "Failed to allocate memory for GET request buffer"); + err = esp_http_client_open(client, 0); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err)); + esp_http_client_cleanup(client); task_fatal_error(); } - int res = send(socket_id, http_request, get_len, 0); - free(http_request); - - if (res < 0) { - ESP_LOGE(TAG, "Send GET request to server failed"); - task_fatal_error(); - } else { - ESP_LOGI(TAG, "Send GET request to server succeeded"); - } + esp_http_client_fetch_headers(client); update_partition = esp_ota_get_next_update_partition(NULL); ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%x", @@ -241,55 +148,43 @@ static void ota_example_task(void *pvParameter) err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err)); + http_cleanup(client); task_fatal_error(); } ESP_LOGI(TAG, "esp_ota_begin succeeded"); - bool resp_body_start = false, socket_flag = true, http_200_flag = false; + int binary_file_length = 0; /*deal with all receive packet*/ - while (socket_flag) { - memset(text, 0, TEXT_BUFFSIZE); - memset(ota_write_data, 0, BUFFSIZE); - int buff_len = recv(socket_id, text, TEXT_BUFFSIZE, 0); - if (buff_len < 0) { /*receive error*/ - ESP_LOGE(TAG, "Error: receive data error! errno=%d", errno); + while (1) { + int data_read = esp_http_client_read(client, ota_write_data, BUFFSIZE); + if (data_read < 0) { + ESP_LOGE(TAG, "Error: SSL data read error"); + http_cleanup(client); task_fatal_error(); - } else if (buff_len > 0 && !resp_body_start) { /*deal with response header*/ - // only start ota when server response 200 state code - if (strstr(text, "200") == NULL && !http_200_flag) { - ESP_LOGE(TAG, "ota url is invalid or bin is not exist"); - task_fatal_error(); - } - http_200_flag = true; - memcpy(ota_write_data, text, buff_len); - resp_body_start = read_past_http_header(text, buff_len, update_handle); - } else if (buff_len > 0 && resp_body_start) { /*deal with response body*/ - memcpy(ota_write_data, text, buff_len); - err = esp_ota_write( update_handle, (const void *)ota_write_data, buff_len); + } else if (data_read > 0) { + err = esp_ota_write( update_handle, (const void *)ota_write_data, data_read); if (err != ESP_OK) { - ESP_LOGE(TAG, "Error: esp_ota_write failed (%s)!", esp_err_to_name(err)); + http_cleanup(client); task_fatal_error(); } - binary_file_length += buff_len; - ESP_LOGI(TAG, "Have written image length %d", binary_file_length); - } else if (buff_len == 0) { /*packet over*/ - socket_flag = false; - ESP_LOGI(TAG, "Connection closed, all packets received"); - close(socket_id); - } else { - ESP_LOGE(TAG, "Unexpected recv result"); + binary_file_length += data_read; + ESP_LOGD(TAG, "Written image length %d", binary_file_length); + } else if (data_read == 0) { + ESP_LOGI(TAG, "Connection closed,all data received"); + break; } } - ESP_LOGI(TAG, "Total Write binary data length : %d", binary_file_length); if (esp_ota_end(update_handle) != ESP_OK) { ESP_LOGE(TAG, "esp_ota_end failed!"); + http_cleanup(client); task_fatal_error(); } err = esp_ota_set_boot_partition(update_partition); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ota_set_boot_partition failed (%s)!", esp_err_to_name(err)); + http_cleanup(client); task_fatal_error(); } ESP_LOGI(TAG, "Prepare to restart system!"); diff --git a/examples/system/ota/sdkconfig.defaults b/examples/system/ota/native_ota_example/sdkconfig.defaults similarity index 100% rename from examples/system/ota/sdkconfig.defaults rename to examples/system/ota/native_ota_example/sdkconfig.defaults diff --git a/examples/system/ota/server_certs/ca_cert.pem b/examples/system/ota/server_certs/ca_cert.pem new file mode 100644 index 000000000..e69de29bb diff --git a/examples/system/ota/simple_ota_example/Makefile b/examples/system/ota/simple_ota_example/Makefile new file mode 100644 index 000000000..63bca1abe --- /dev/null +++ b/examples/system/ota/simple_ota_example/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := simple_ota + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/system/ota/simple_ota_example/README.md b/examples/system/ota/simple_ota_example/README.md new file mode 100644 index 000000000..437964f74 --- /dev/null +++ b/examples/system/ota/simple_ota_example/README.md @@ -0,0 +1,7 @@ +# Simple OTA example + +This example is based on `http_firmware_upgrade` component's APIs. + +## Configuration + +Refer README.md in the parent directory for setup details \ No newline at end of file diff --git a/examples/system/ota/simple_ota_example/main/Kconfig.projbuild b/examples/system/ota/simple_ota_example/main/Kconfig.projbuild new file mode 100644 index 000000000..38bf5cb4d --- /dev/null +++ b/examples/system/ota/simple_ota_example/main/Kconfig.projbuild @@ -0,0 +1,21 @@ +menu "Example Configuration" + +config WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + +config FIRMWARE_UPGRADE_URL + string "firmware upgrade url endpoint" + default "https://192.168.0.3:8070/hello-world.bin" + help + URL of server which hosts the firmware + image. +endmenu diff --git a/examples/system/ota/simple_ota_example/main/component.mk b/examples/system/ota/simple_ota_example/main/component.mk new file mode 100644 index 000000000..93a42f552 --- /dev/null +++ b/examples/system/ota/simple_ota_example/main/component.mk @@ -0,0 +1,6 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + +COMPONENT_EMBED_TXTFILES := ${IDF_PATH}/examples/system/ota/server_certs/ca_cert.pem diff --git a/examples/system/ota/simple_ota_example/main/simple_ota_example.c b/examples/system/ota/simple_ota_example/main/simple_ota_example.c new file mode 100644 index 000000000..7972f8bd5 --- /dev/null +++ b/examples/system/ota/simple_ota_example/main/simple_ota_example.c @@ -0,0 +1,147 @@ +/* OTA example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" + +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_event_loop.h" +#include "esp_log.h" +#include "esp_ota_ops.h" +#include "esp_http_client.h" +#include "esp_https_ota.h" + +#include "nvs.h" +#include "nvs_flash.h" + +static const char *TAG = "simple_ota_example"; +extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start"); +extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end"); + +/* FreeRTOS event group to signal when we are connected & ready to make a request */ +static EventGroupHandle_t wifi_event_group; + +/* The event group allows multiple bits for each event, + but we only care about one event - are we connected + to the AP with an IP? */ +const int CONNECTED_BIT = BIT0; + +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_HEADER_SENT: + ESP_LOGD(TAG, "HTTP_EVENT_HEADER_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 event_handler(void *ctx, system_event_t *event) +{ + switch (event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_GOT_IP: + xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + /* This is a workaround as ESP32 WiFi libs don't currently + auto-reassociate. */ + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); + break; + default: + break; + } + return ESP_OK; +} + +static void initialise_wifi(void) +{ + tcpip_adapter_init(); + wifi_event_group = xEventGroupCreate(); + ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) ); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); + ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); + wifi_config_t wifi_config = { + .sta = { + .ssid = CONFIG_WIFI_SSID, + .password = CONFIG_WIFI_PASSWORD, + }, + }; + ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid); + ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) ); + ESP_ERROR_CHECK( esp_wifi_start() ); +} + +void simple_ota_example_task(void * pvParameter) +{ + ESP_LOGI(TAG, "Starting OTA example..."); + + /* Wait for the callback to set the CONNECTED_BIT in the + event group. + */ + xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, + false, true, portMAX_DELAY); + ESP_LOGI(TAG, "Connect to Wifi ! Start to Connect to Server...."); + + esp_http_client_config_t config = { + .url = CONFIG_FIRMWARE_UPGRADE_URL, + .cert_pem = (char *)server_cert_pem_start, + .event_handler = _http_event_handler, + }; + esp_err_t ret = esp_https_ota(&config); + if (ret == ESP_OK) { + esp_restart(); + } else { + ESP_LOGE(TAG, "Firmware Upgrades Failed"); + } + while (1) { + vTaskDelay(1000 / portTICK_PERIOD_MS); + } +} + +void app_main() +{ + // Initialize NVS. + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES) { + // OTA app partition table has a smaller NVS partition size than the non-OTA + // partition table. This size mismatch may cause NVS initialization to fail. + // If this happens, we erase NVS partition and initialize NVS again. + ESP_ERROR_CHECK(nvs_flash_erase()); + err = nvs_flash_init(); + } + ESP_ERROR_CHECK( err ); + + initialise_wifi(); + xTaskCreate(&simple_ota_example_task, "ota_example_task", 8192, NULL, 5, NULL); +} diff --git a/examples/system/ota/simple_ota_example/sdkconfig.defaults b/examples/system/ota/simple_ota_example/sdkconfig.defaults new file mode 100644 index 000000000..2289a8230 --- /dev/null +++ b/examples/system/ota/simple_ota_example/sdkconfig.defaults @@ -0,0 +1,4 @@ +# Default sdkconfig parameters to use the OTA +# partition table layout, with a 4MB flash size +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_PARTITION_TABLE_TWO_OTA=y