|
| 1 | +#include "esp_event.h" |
| 2 | +#include "esp_http_client.h" |
| 3 | +#include "esp_system.h" |
| 4 | +#include "esp_vfs.h" |
| 5 | +#include "esp_vfs_fat.h" |
| 6 | +#include "freertos/FreeRTOS.h" |
| 7 | +#include "freertos/queue.h" |
| 8 | +#include "freertos/task.h" |
| 9 | + |
| 10 | +static const char* TAG = "HTTP download"; |
| 11 | + |
| 12 | +typedef struct { |
| 13 | + FILE* fd; // For downloading directly to file on filesystem |
| 14 | + uint8_t** buffer; // Dynamically allocated buffer for downloading to RAM (malloced in event handler, used if fd is |
| 15 | + // not set) |
| 16 | + size_t size; // File size as indicated by content-length header (set in event handler) |
| 17 | + size_t received; // Amount of data received (set in event handler) |
| 18 | + bool error; // Indication that an error event happened (set in event handler) |
| 19 | + bool connected; // Indication that the HTTP client has connected to the server (set in event handler) |
| 20 | + bool finished; // Indication that the operation has completed (set in event handler) |
| 21 | + bool disconnected; // Indication that the HTTP client has disconnected from the server (set in event handler) |
| 22 | + bool out_of_memory; // Indication that malloc failed |
| 23 | + bool out_of_allocated; // Indication that the server sent more data than indicated with the content-length header |
| 24 | +} http_download_info_t; |
| 25 | + |
| 26 | +static esp_err_t _event_handler(esp_http_client_event_t* evt) { |
| 27 | + http_download_info_t* info = (http_download_info_t*)evt->user_data; |
| 28 | + switch (evt->event_id) { |
| 29 | + case HTTP_EVENT_ERROR: |
| 30 | + info->error = true; |
| 31 | + break; |
| 32 | + case HTTP_EVENT_ON_CONNECTED: |
| 33 | + info->connected = true; |
| 34 | + break; |
| 35 | + case HTTP_EVENT_HEADERS_SENT: |
| 36 | + break; |
| 37 | + case HTTP_EVENT_ON_HEADER: { |
| 38 | + const char content_length_key[] = "Content-Length"; |
| 39 | + if ((strlen(evt->header_key) == strlen(content_length_key)) && |
| 40 | + (strncasecmp(content_length_key, evt->header_key, strlen(content_length_key)) == 0)) { |
| 41 | + // Header value is content length |
| 42 | + info->size = atoi(evt->header_value); |
| 43 | + ESP_LOGI(TAG, "Size is known: %u bytes", info->size); |
| 44 | + if ((info->size > 0) && |
| 45 | + (info->buffer != NULL)) { // Buffer poiner is set, buffer pointer points to NULL |
| 46 | + *info->buffer = malloc(info->size); |
| 47 | + ESP_LOGI(TAG, "Allocated buffer (%u bytes)", info->size); |
| 48 | + if (*info->buffer == NULL) { |
| 49 | + info->out_of_memory = true; |
| 50 | + return ESP_ERR_NO_MEM; |
| 51 | + } |
| 52 | + } |
| 53 | + } else { |
| 54 | + ESP_LOGD(TAG, "Header: key=%s, value=%s", evt->header_key, evt->header_value); |
| 55 | + } |
| 56 | + break; |
| 57 | + } |
| 58 | + case HTTP_EVENT_ON_DATA: |
| 59 | + if (info->fd != NULL) { // Write directly to file on filesystem |
| 60 | + ESP_LOGI(TAG, "Writing to file @ %p (%u bytes): %u of %u bytes.", info->fd, evt->data_len, |
| 61 | + info->received + evt->data_len, info->size); |
| 62 | + fwrite(evt->data, 1, evt->data_len, info->fd); |
| 63 | + } else if (info->buffer != NULL && *info->buffer != NULL) { |
| 64 | + if (info->received + evt->data_len <= info->size) { |
| 65 | + uint8_t* dest = &((*info->buffer)[info->received]); |
| 66 | + ESP_LOGI(TAG, "Writing to RAM @ %p (%u bytes): %u of %u bytes.", dest, evt->data_len, |
| 67 | + info->received + evt->data_len, info->size); |
| 68 | + memcpy(dest, evt->data, evt->data_len); |
| 69 | + } else { |
| 70 | + ESP_LOGI(TAG, "Downloaded too much? %u with %u in content-length header", |
| 71 | + info->received + evt->data_len, info->size); |
| 72 | + info->out_of_allocated = true; |
| 73 | + return ESP_ERR_NO_MEM; |
| 74 | + } |
| 75 | + } else { |
| 76 | + return ESP_FAIL; |
| 77 | + } |
| 78 | + info->received += evt->data_len; |
| 79 | + break; |
| 80 | + case HTTP_EVENT_ON_FINISH: |
| 81 | + info->finished = true; |
| 82 | + break; |
| 83 | + case HTTP_EVENT_DISCONNECTED: |
| 84 | + info->disconnected = true; |
| 85 | + break; |
| 86 | + case HTTP_EVENT_REDIRECT: |
| 87 | + break; |
| 88 | + } |
| 89 | + return ESP_OK; |
| 90 | +} |
| 91 | + |
| 92 | +static bool download_success(esp_err_t err, http_download_info_t* info) { |
| 93 | + return (err == ESP_OK) && (!(info->error || info->out_of_allocated || info->out_of_memory)) && info->finished && |
| 94 | + (info->received == info->size); |
| 95 | +} |
| 96 | + |
| 97 | +static bool _download_file(const char* url, const char* path) { |
| 98 | + FILE* fd = fopen(path, "w"); |
| 99 | + if (fd == NULL) { |
| 100 | + ESP_LOGE(TAG, "Failed to open file"); |
| 101 | + return false; |
| 102 | + } |
| 103 | + |
| 104 | + http_download_info_t info = {0}; |
| 105 | + info.fd = fd; |
| 106 | + |
| 107 | + esp_http_client_config_t config = {.url = url, |
| 108 | + .use_global_ca_store = true, |
| 109 | + .keep_alive_enable = true, |
| 110 | + .timeout_ms = 10000, |
| 111 | + .user_data = (void*)&info, |
| 112 | + .event_handler = _event_handler}; |
| 113 | + esp_http_client_handle_t client = esp_http_client_init(&config); |
| 114 | + esp_err_t err = esp_http_client_perform(client); |
| 115 | + fclose(fd); |
| 116 | + esp_http_client_cleanup(client); |
| 117 | + return download_success(err, &info); |
| 118 | +} |
| 119 | + |
| 120 | +bool download_file(const char* url, const char* path) { |
| 121 | + int retry = 3; |
| 122 | + while (retry--) { |
| 123 | + if (_download_file(url, path)) return true; |
| 124 | + ESP_LOGI(TAG, "Download waiting to retry ..."); |
| 125 | + vTaskDelay(pdMS_TO_TICKS(5000)); |
| 126 | + } |
| 127 | + return false; |
| 128 | +} |
| 129 | + |
| 130 | +static bool _download_ram(const char* url, uint8_t** ptr, size_t* size) { |
| 131 | + http_download_info_t info = {0}; |
| 132 | + info.buffer = ptr; |
| 133 | + esp_http_client_config_t config = {.url = url, |
| 134 | + .use_global_ca_store = true, |
| 135 | + .keep_alive_enable = true, |
| 136 | + .timeout_ms = 10000, |
| 137 | + .user_data = (void*)&info, |
| 138 | + .event_handler = _event_handler}; |
| 139 | + esp_http_client_handle_t client = esp_http_client_init(&config); |
| 140 | + esp_err_t err = esp_http_client_perform(client); |
| 141 | + bool success = download_success(err, &info); |
| 142 | + if (success && (size != NULL)) *size = info.size; |
| 143 | + ESP_LOGI(TAG, "Buffer: %p -> %p", ptr, *ptr); |
| 144 | + esp_http_client_cleanup(client); |
| 145 | + return success; |
| 146 | +} |
| 147 | + |
| 148 | +bool download_ram(const char* url, uint8_t** ptr, size_t* size) { |
| 149 | + int retry = 3; |
| 150 | + while (retry--) { |
| 151 | + if (_download_ram(url, ptr, size)) return true; |
| 152 | + ESP_LOGI(TAG, "Download waiting to retry ..."); |
| 153 | + vTaskDelay(pdMS_TO_TICKS(5000)); |
| 154 | + } |
| 155 | + return false; |
| 156 | +} |
0 commit comments