├── .gitignore ├── .gitmodules ├── Makefile ├── README.md ├── main ├── Kconfig.projbuild ├── component.mk └── fota.c └── sdkconfig.defaults /.gitignore: -------------------------------------------------------------------------------- 1 | .output 2 | map.txt 3 | main/include/user_config.local.h 4 | bin 5 | build 6 | *.old 7 | sdkconfig 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "components/esp-request"] 2 | path = components/esp-request 3 | url = https://github.com/tuanpmt/esp-request.git 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # This is a project Makefile. It is assumed the directory this Makefile resides in is a 3 | # project subdirectory. 4 | # 5 | 6 | PROJECT_NAME := fota-app 7 | 8 | include $(IDF_PATH)/make/project.mk 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # ESP32 OTA demo 3 | 4 | ## BUILD STEP 5 | 1. Register http://fota.vn account & clone this app `git clone --recursive https://github.com/tuanpmt/esp32-fota.git` 6 | 2. Copy your API Key at: http://fota.vn/me 7 | 3. Build newest firmware (call it is `2.0`) 8 | 4. `make menuconfig` and set your WiFi ssid, pass, and Apikey above in `FOTA Configuration`, set `APP_VERSION = 2.0` in `main/fota.c`, then `make` 9 | 5. Upload firmware 2.0 `build\fota-app.bin` to http://fota.vn/firmware with `Version will update to = 1.0`, `This file version=2.0` 10 | 6. Set `APP_VERSION = 1.0` in `main/fota.c`, then `make flash monitor` to see ESP32 connect to the internet and download 2.0 firmware to replace 1.0 11 | 12 | ## REQUIRE 13 | - [esp-request](https://github.com/tuanpmt/esp-request) 14 | 15 | ## License 16 | 17 | This projects are released under the MIT license. Short version: this code is copyrighted to me [@TuanPM](https://twitter.com/tuanpmt), I give you permission to do wantever you want with it except remove my name from the credits. See the LICENSE file or http://opensource.org/licenses/MIT for specific terms. 18 | 19 | -------------------------------------------------------------------------------- /main/Kconfig.projbuild: -------------------------------------------------------------------------------- 1 | menu "FOTA Configuration" 2 | 3 | config WIFI_SSID 4 | string "WiFi SSID" 5 | default "myssid" 6 | help 7 | SSID (network name) for the example to connect to. 8 | 9 | config WIFI_PASSWORD 10 | string "WiFi Password" 11 | default "myssid" 12 | help 13 | WiFi password (WPA or WPA2) for the example to use. 14 | 15 | Can be left blank if the network has no security set. 16 | 17 | config APIKEY 18 | string "FOTA Api Key" 19 | default "api-key" 20 | help 21 | HTTP Server IP to download the image file from. 22 | 23 | See example README.md for details. 24 | 25 | 26 | endmenu 27 | -------------------------------------------------------------------------------- /main/component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # "main" pseudo-component makefile. 3 | # 4 | # (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) 5 | -------------------------------------------------------------------------------- /main/fota.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include "freertos/FreeRTOS.h" 5 | #include "freertos/task.h" 6 | #include "freertos/event_groups.h" 7 | 8 | #include "esp_system.h" 9 | #include "esp_wifi.h" 10 | #include "esp_event_loop.h" 11 | #include "esp_log.h" 12 | #include "esp_ota_ops.h" 13 | #include "esp_partition.h" 14 | 15 | #include "nvs.h" 16 | #include "nvs_flash.h" 17 | 18 | #include "esp_request.h" 19 | 20 | #define APP_VERSION "1.0" 21 | #define WIFI_SSID CONFIG_WIFI_SSID 22 | #define WIFI_PASS CONFIG_WIFI_PASSWORD 23 | #define APIKEY CONFIG_APIKEY 24 | #define SERVER_ENDPOINT "http://fota.vn/api/fota/%s" 25 | //or https 26 | //#define SERVER_ENDPOINT "https://fota.vn/api/fota/%s" 27 | 28 | static const char *TAG = "FOTA"; 29 | uint8_t sta_mac[6]; 30 | /* FreeRTOS event group to signal when we are connected & ready to make a request */ 31 | static EventGroupHandle_t wifi_event_group; 32 | 33 | /* The event group allows multiple bits for each event, 34 | but we only care about one event - are we connected 35 | to the AP with an IP? */ 36 | const int CONNECTED_BIT = BIT0; 37 | 38 | static esp_err_t event_handler(void *ctx, system_event_t *event) 39 | { 40 | switch(event->event_id) { 41 | case SYSTEM_EVENT_STA_START: 42 | esp_wifi_connect(); 43 | break; 44 | case SYSTEM_EVENT_STA_GOT_IP: 45 | xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); 46 | break; 47 | case SYSTEM_EVENT_STA_DISCONNECTED: 48 | /* This is a workaround as ESP32 WiFi libs don't currently 49 | auto-reassociate. */ 50 | esp_wifi_connect(); 51 | xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); 52 | break; 53 | default: 54 | break; 55 | } 56 | return ESP_OK; 57 | } 58 | 59 | static void wait_for_wifi(void) 60 | { 61 | tcpip_adapter_init(); 62 | wifi_event_group = xEventGroupCreate(); 63 | ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL)); 64 | wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); 65 | ESP_ERROR_CHECK(esp_wifi_init(&cfg)); 66 | ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); 67 | wifi_config_t wifi_config = { 68 | .sta = { 69 | .ssid = WIFI_SSID, 70 | .password = WIFI_PASS, 71 | }, 72 | }; 73 | ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid); 74 | ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); 75 | ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config)); 76 | ESP_ERROR_CHECK(esp_wifi_start()); 77 | 78 | esp_wifi_get_mac(ESP_IF_WIFI_STA, sta_mac); 79 | 80 | ESP_LOGI(TAG, "Wait for WiFi...."); 81 | 82 | ESP_LOGI(TAG,"+---------------------------------+"); 83 | ESP_LOGE(TAG,"| APP Version = %s |", APP_VERSION); 84 | ESP_LOGI(TAG,"| DeviceId = %02X:%02X:%02X:%02X:%02X:%02X |", sta_mac[0], sta_mac[1], sta_mac[2], sta_mac[3], sta_mac[4], sta_mac[5]); 85 | ESP_LOGI(TAG,"| API Key = %s |", APIKEY); 86 | ESP_LOGI(TAG,"+---------------------------------+"); 87 | vTaskDelay(5000/portTICK_PERIOD_MS); 88 | xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY); 89 | ESP_LOGI(TAG, "Connected to AP, freemem=%d", esp_get_free_heap_size()); 90 | } 91 | 92 | 93 | int download_callback(request_t *req, char *data, int len) 94 | { 95 | req_list_t *found = req->response->header; 96 | static int binary_len = -1, remain_len = -1; 97 | static esp_ota_handle_t update_handle = 0; 98 | static const esp_partition_t *update_partition = NULL; 99 | esp_err_t err; 100 | ESP_LOGI(TAG, "downloading...%d/%d bytes, remain=%d bytes", len, binary_len, remain_len); 101 | if(req->response->status_code == 200) { 102 | //first time 103 | if(binary_len == -1) { 104 | found = req_list_get_key(req->response->header, "Content-Length"); 105 | if(found) { 106 | ESP_LOGI(TAG, "Binary len=%s", (char*)found->value); 107 | binary_len = atoi(found->value); 108 | remain_len = binary_len; 109 | } else { 110 | ESP_LOGE(TAG, "Erorr get connent length"); 111 | return -1; 112 | } 113 | update_partition = esp_ota_get_next_update_partition(NULL); 114 | ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%x", 115 | update_partition->subtype, update_partition->address); 116 | assert(update_partition != NULL); 117 | 118 | err = esp_ota_begin(update_partition, binary_len, &update_handle); 119 | if(err != ESP_OK) { 120 | ESP_LOGE(TAG, "esp_ota_begin failed, error=%d", err); 121 | return -1; 122 | } 123 | ESP_LOGI(TAG, "esp_ota_begin succeeded"); 124 | ESP_LOGI(TAG, "downloading..., total=%d bytes", binary_len); 125 | } 126 | err = esp_ota_write(update_handle, (const void *)data, len); 127 | if(err != ESP_OK) { 128 | ESP_LOGE(TAG, "Error: esp_ota_write failed! err=0x%x", err); 129 | return -1; 130 | } 131 | remain_len -= len; 132 | 133 | //finish 134 | if(remain_len == 0) { 135 | if(esp_ota_end(update_handle) != ESP_OK) { 136 | ESP_LOGE(TAG, "esp_ota_end failed!"); 137 | return -1; 138 | } 139 | err = esp_ota_set_boot_partition(update_partition); 140 | if(err != ESP_OK) { 141 | ESP_LOGE(TAG, "esp_ota_set_boot_partition failed! err=0x%x", err); 142 | return -1; 143 | } 144 | ESP_LOGI(TAG, "Prepare to restart system!"); 145 | esp_restart(); 146 | } 147 | return 0; 148 | } 149 | return -1; 150 | } 151 | 152 | static void ota_task(void *pvParameter) 153 | { 154 | request_t *req; 155 | 156 | char data[64]; 157 | 158 | const esp_partition_t *configured = esp_ota_get_boot_partition(); 159 | const esp_partition_t *running = esp_ota_get_running_partition(); 160 | 161 | assert(configured == running); /* fresh from reset, should be running from configured boot partition */ 162 | ESP_LOGI(TAG, "Running partition type %d subtype %d (offset 0x%08x)", 163 | configured->type, configured->subtype, configured->address); 164 | 165 | 166 | 167 | sprintf(data, SERVER_ENDPOINT, APIKEY); 168 | req = req_new(data); 169 | req_setopt(req, REQ_SET_METHOD, "GET"); 170 | 171 | 172 | sprintf(data, "x-esp32-sta-mac:%02X:%02X:%02X:%02X:%02X:%02X", sta_mac[0], sta_mac[1], sta_mac[2], sta_mac[3], sta_mac[4], sta_mac[5]); 173 | req_setopt(req, REQ_SET_HEADER, data); 174 | 175 | sprintf(data, "x-esp32-version:%s", APP_VERSION); 176 | req_setopt(req, REQ_SET_HEADER, data); 177 | 178 | req_setopt(req, REQ_FUNC_DOWNLOAD_CB, download_callback); 179 | req_perform(req); 180 | req_clean(req); 181 | 182 | ESP_LOGE(TAG, "Goes here without reset? error was happen or no new firmware"); 183 | vTaskDelete(NULL); 184 | } 185 | 186 | 187 | void app_main() 188 | { 189 | // Initialize NVS. 190 | esp_err_t err = nvs_flash_init(); 191 | if(err == ESP_ERR_NVS_NO_FREE_PAGES) { 192 | // OTA app partition table has a smaller NVS partition size than the non-OTA 193 | // partition table. This size mismatch may cause NVS initialization to fail. 194 | // If this happens, we erase NVS partition and initialize NVS again. 195 | const esp_partition_t* nvs_partition = esp_partition_find_first( 196 | ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, NULL); 197 | assert(nvs_partition && "partition table must have an NVS partition"); 198 | ESP_ERROR_CHECK(esp_partition_erase_range(nvs_partition, 0, nvs_partition->size)); 199 | err = nvs_flash_init(); 200 | } 201 | ESP_ERROR_CHECK(err); 202 | 203 | wait_for_wifi(); 204 | xTaskCreate(&ota_task, "ota_task", 8192, NULL, 5, NULL); 205 | } 206 | -------------------------------------------------------------------------------- /sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | # Default sdkconfig parameters to use the OTA 2 | # partition table layout, with a 4MB flash size 3 | CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y 4 | CONFIG_PARTITION_TABLE_TWO_OTA=y 5 | --------------------------------------------------------------------------------