1 /* OTA example
2 
3    This example code is in the Public Domain (or CC0 licensed, at your option.)
4 
5    Unless required by applicable law or agreed to in writing, this
6    software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
7    CONDITIONS OF ANY KIND, either express or implied.
8 */
9 #include <string.h>
10 #include "freertos/FreeRTOS.h"
11 #include "freertos/task.h"
12 #include "esp_system.h"
13 #include "esp_event.h"
14 #include "esp_log.h"
15 #include "esp_ota_ops.h"
16 #include "esp_http_client.h"
17 #include "esp_flash_partitions.h"
18 #include "esp_partition.h"
19 #include "nvs.h"
20 #include "nvs_flash.h"
21 #include "driver/gpio.h"
22 #include "protocol_examples_common.h"
23 #include "errno.h"
24 
25 #if CONFIG_EXAMPLE_CONNECT_WIFI
26 #include "esp_wifi.h"
27 #endif
28 
29 #define BUFFSIZE 1024
30 #define HASH_LEN 32 /* SHA-256 digest length */
31 
32 static const char *TAG = "native_ota_example";
33 /*an ota data write buffer ready to write to the flash*/
34 static char ota_write_data[BUFFSIZE + 1] = { 0 };
35 extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
36 extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end");
37 
38 #define OTA_URL_SIZE 256
39 
http_cleanup(esp_http_client_handle_t client)40 static void http_cleanup(esp_http_client_handle_t client)
41 {
42     esp_http_client_close(client);
43     esp_http_client_cleanup(client);
44 }
45 
task_fatal_error(void)46 static void __attribute__((noreturn)) task_fatal_error(void)
47 {
48     ESP_LOGE(TAG, "Exiting task due to fatal error...");
49     (void)vTaskDelete(NULL);
50 
51     while (1) {
52         ;
53     }
54 }
55 
print_sha256(const uint8_t * image_hash,const char * label)56 static void print_sha256 (const uint8_t *image_hash, const char *label)
57 {
58     char hash_print[HASH_LEN * 2 + 1];
59     hash_print[HASH_LEN * 2] = 0;
60     for (int i = 0; i < HASH_LEN; ++i) {
61         sprintf(&hash_print[i * 2], "%02x", image_hash[i]);
62     }
63     ESP_LOGI(TAG, "%s: %s", label, hash_print);
64 }
65 
infinite_loop(void)66 static void infinite_loop(void)
67 {
68     int i = 0;
69     ESP_LOGI(TAG, "When a new firmware is available on the server, press the reset button to download it");
70     while(1) {
71         ESP_LOGI(TAG, "Waiting for a new firmware ... %d", ++i);
72         vTaskDelay(2000 / portTICK_PERIOD_MS);
73     }
74 }
75 
ota_example_task(void * pvParameter)76 static void ota_example_task(void *pvParameter)
77 {
78     esp_err_t err;
79     /* update handle : set by esp_ota_begin(), must be freed via esp_ota_end() */
80     esp_ota_handle_t update_handle = 0 ;
81     const esp_partition_t *update_partition = NULL;
82 
83     ESP_LOGI(TAG, "Starting OTA example");
84 
85     const esp_partition_t *configured = esp_ota_get_boot_partition();
86     const esp_partition_t *running = esp_ota_get_running_partition();
87 
88     if (configured != running) {
89         ESP_LOGW(TAG, "Configured OTA boot partition at offset 0x%08x, but running from offset 0x%08x",
90                  configured->address, running->address);
91         ESP_LOGW(TAG, "(This can happen if either the OTA boot data or preferred boot image become corrupted somehow.)");
92     }
93     ESP_LOGI(TAG, "Running partition type %d subtype %d (offset 0x%08x)",
94              running->type, running->subtype, running->address);
95 
96     esp_http_client_config_t config = {
97         .url = CONFIG_EXAMPLE_FIRMWARE_UPG_URL,
98         .cert_pem = (char *)server_cert_pem_start,
99         .timeout_ms = CONFIG_EXAMPLE_OTA_RECV_TIMEOUT,
100         .keep_alive_enable = true,
101     };
102 
103 #ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN
104     char url_buf[OTA_URL_SIZE];
105     if (strcmp(config.url, "FROM_STDIN") == 0) {
106         example_configure_stdin_stdout();
107         fgets(url_buf, OTA_URL_SIZE, stdin);
108         int len = strlen(url_buf);
109         url_buf[len - 1] = '\0';
110         config.url = url_buf;
111     } else {
112         ESP_LOGE(TAG, "Configuration mismatch: wrong firmware upgrade image url");
113         abort();
114     }
115 #endif
116 
117 #ifdef CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK
118     config.skip_cert_common_name_check = true;
119 #endif
120 
121     esp_http_client_handle_t client = esp_http_client_init(&config);
122     if (client == NULL) {
123         ESP_LOGE(TAG, "Failed to initialise HTTP connection");
124         task_fatal_error();
125     }
126     err = esp_http_client_open(client, 0);
127     if (err != ESP_OK) {
128         ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
129         esp_http_client_cleanup(client);
130         task_fatal_error();
131     }
132     esp_http_client_fetch_headers(client);
133 
134     update_partition = esp_ota_get_next_update_partition(NULL);
135     assert(update_partition != NULL);
136     ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%x",
137              update_partition->subtype, update_partition->address);
138 
139     int binary_file_length = 0;
140     /*deal with all receive packet*/
141     bool image_header_was_checked = false;
142     while (1) {
143         int data_read = esp_http_client_read(client, ota_write_data, BUFFSIZE);
144         if (data_read < 0) {
145             ESP_LOGE(TAG, "Error: SSL data read error");
146             http_cleanup(client);
147             task_fatal_error();
148         } else if (data_read > 0) {
149             if (image_header_was_checked == false) {
150                 esp_app_desc_t new_app_info;
151                 if (data_read > sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) {
152                     // check current version with downloading
153                     memcpy(&new_app_info, &ota_write_data[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t));
154                     ESP_LOGI(TAG, "New firmware version: %s", new_app_info.version);
155 
156                     esp_app_desc_t running_app_info;
157                     if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) {
158                         ESP_LOGI(TAG, "Running firmware version: %s", running_app_info.version);
159                     }
160 
161                     const esp_partition_t* last_invalid_app = esp_ota_get_last_invalid_partition();
162                     esp_app_desc_t invalid_app_info;
163                     if (esp_ota_get_partition_description(last_invalid_app, &invalid_app_info) == ESP_OK) {
164                         ESP_LOGI(TAG, "Last invalid firmware version: %s", invalid_app_info.version);
165                     }
166 
167                     // check current version with last invalid partition
168                     if (last_invalid_app != NULL) {
169                         if (memcmp(invalid_app_info.version, new_app_info.version, sizeof(new_app_info.version)) == 0) {
170                             ESP_LOGW(TAG, "New version is the same as invalid version.");
171                             ESP_LOGW(TAG, "Previously, there was an attempt to launch the firmware with %s version, but it failed.", invalid_app_info.version);
172                             ESP_LOGW(TAG, "The firmware has been rolled back to the previous version.");
173                             http_cleanup(client);
174                             infinite_loop();
175                         }
176                     }
177 #ifndef CONFIG_EXAMPLE_SKIP_VERSION_CHECK
178                     if (memcmp(new_app_info.version, running_app_info.version, sizeof(new_app_info.version)) == 0) {
179                         ESP_LOGW(TAG, "Current running version is the same as a new. We will not continue the update.");
180                         http_cleanup(client);
181                         infinite_loop();
182                     }
183 #endif
184 
185                     image_header_was_checked = true;
186 
187                     err = esp_ota_begin(update_partition, OTA_WITH_SEQUENTIAL_WRITES, &update_handle);
188                     if (err != ESP_OK) {
189                         ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err));
190                         http_cleanup(client);
191                         esp_ota_abort(update_handle);
192                         task_fatal_error();
193                     }
194                     ESP_LOGI(TAG, "esp_ota_begin succeeded");
195                 } else {
196                     ESP_LOGE(TAG, "received package is not fit len");
197                     http_cleanup(client);
198                     esp_ota_abort(update_handle);
199                     task_fatal_error();
200                 }
201             }
202             err = esp_ota_write( update_handle, (const void *)ota_write_data, data_read);
203             if (err != ESP_OK) {
204                 http_cleanup(client);
205                 esp_ota_abort(update_handle);
206                 task_fatal_error();
207             }
208             binary_file_length += data_read;
209             ESP_LOGD(TAG, "Written image length %d", binary_file_length);
210         } else if (data_read == 0) {
211            /*
212             * As esp_http_client_read never returns negative error code, we rely on
213             * `errno` to check for underlying transport connectivity closure if any
214             */
215             if (errno == ECONNRESET || errno == ENOTCONN) {
216                 ESP_LOGE(TAG, "Connection closed, errno = %d", errno);
217                 break;
218             }
219             if (esp_http_client_is_complete_data_received(client) == true) {
220                 ESP_LOGI(TAG, "Connection closed");
221                 break;
222             }
223         }
224     }
225     ESP_LOGI(TAG, "Total Write binary data length: %d", binary_file_length);
226     if (esp_http_client_is_complete_data_received(client) != true) {
227         ESP_LOGE(TAG, "Error in receiving complete file");
228         http_cleanup(client);
229         esp_ota_abort(update_handle);
230         task_fatal_error();
231     }
232 
233     err = esp_ota_end(update_handle);
234     if (err != ESP_OK) {
235         if (err == ESP_ERR_OTA_VALIDATE_FAILED) {
236             ESP_LOGE(TAG, "Image validation failed, image is corrupted");
237         } else {
238             ESP_LOGE(TAG, "esp_ota_end failed (%s)!", esp_err_to_name(err));
239         }
240         http_cleanup(client);
241         task_fatal_error();
242     }
243 
244     err = esp_ota_set_boot_partition(update_partition);
245     if (err != ESP_OK) {
246         ESP_LOGE(TAG, "esp_ota_set_boot_partition failed (%s)!", esp_err_to_name(err));
247         http_cleanup(client);
248         task_fatal_error();
249     }
250     ESP_LOGI(TAG, "Prepare to restart system!");
251     esp_restart();
252     return ;
253 }
254 
diagnostic(void)255 static bool diagnostic(void)
256 {
257     gpio_config_t io_conf;
258     io_conf.intr_type    = GPIO_INTR_DISABLE;
259     io_conf.mode         = GPIO_MODE_INPUT;
260     io_conf.pin_bit_mask = (1ULL << CONFIG_EXAMPLE_GPIO_DIAGNOSTIC);
261     io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
262     io_conf.pull_up_en   = GPIO_PULLUP_ENABLE;
263     gpio_config(&io_conf);
264 
265     ESP_LOGI(TAG, "Diagnostics (5 sec)...");
266     vTaskDelay(5000 / portTICK_PERIOD_MS);
267 
268     bool diagnostic_is_ok = gpio_get_level(CONFIG_EXAMPLE_GPIO_DIAGNOSTIC);
269 
270     gpio_reset_pin(CONFIG_EXAMPLE_GPIO_DIAGNOSTIC);
271     return diagnostic_is_ok;
272 }
273 
app_main(void)274 void app_main(void)
275 {
276     uint8_t sha_256[HASH_LEN] = { 0 };
277     esp_partition_t partition;
278 
279     // get sha256 digest for the partition table
280     partition.address   = ESP_PARTITION_TABLE_OFFSET;
281     partition.size      = ESP_PARTITION_TABLE_MAX_LEN;
282     partition.type      = ESP_PARTITION_TYPE_DATA;
283     esp_partition_get_sha256(&partition, sha_256);
284     print_sha256(sha_256, "SHA-256 for the partition table: ");
285 
286     // get sha256 digest for bootloader
287     partition.address   = ESP_BOOTLOADER_OFFSET;
288     partition.size      = ESP_PARTITION_TABLE_OFFSET;
289     partition.type      = ESP_PARTITION_TYPE_APP;
290     esp_partition_get_sha256(&partition, sha_256);
291     print_sha256(sha_256, "SHA-256 for bootloader: ");
292 
293     // get sha256 digest for running partition
294     esp_partition_get_sha256(esp_ota_get_running_partition(), sha_256);
295     print_sha256(sha_256, "SHA-256 for current firmware: ");
296 
297     const esp_partition_t *running = esp_ota_get_running_partition();
298     esp_ota_img_states_t ota_state;
299     if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK) {
300         if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) {
301             // run diagnostic function ...
302             bool diagnostic_is_ok = diagnostic();
303             if (diagnostic_is_ok) {
304                 ESP_LOGI(TAG, "Diagnostics completed successfully! Continuing execution ...");
305                 esp_ota_mark_app_valid_cancel_rollback();
306             } else {
307                 ESP_LOGE(TAG, "Diagnostics failed! Start rollback to the previous version ...");
308                 esp_ota_mark_app_invalid_rollback_and_reboot();
309             }
310         }
311     }
312 
313     // Initialize NVS.
314     esp_err_t err = nvs_flash_init();
315     if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
316         // OTA app partition table has a smaller NVS partition size than the non-OTA
317         // partition table. This size mismatch may cause NVS initialization to fail.
318         // If this happens, we erase NVS partition and initialize NVS again.
319         ESP_ERROR_CHECK(nvs_flash_erase());
320         err = nvs_flash_init();
321     }
322     ESP_ERROR_CHECK( err );
323 
324     ESP_ERROR_CHECK(esp_netif_init());
325     ESP_ERROR_CHECK(esp_event_loop_create_default());
326 
327     /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
328      * Read "Establishing Wi-Fi or Ethernet Connection" section in
329      * examples/protocols/README.md for more information about this function.
330      */
331     ESP_ERROR_CHECK(example_connect());
332 
333 #if CONFIG_EXAMPLE_CONNECT_WIFI
334     /* Ensure to disable any WiFi power save mode, this allows best throughput
335      * and hence timings for overall OTA operation.
336      */
337     esp_wifi_set_ps(WIFI_PS_NONE);
338 #endif // CONFIG_EXAMPLE_CONNECT_WIFI
339 
340     xTaskCreate(&ota_example_task, "ota_example_task", 8192, NULL, 5, NULL);
341 }
342