|
|
|
@@ -1,17 +1,16 @@ |
|
|
|
static const char *TAG = "ota"; |
|
|
|
#include <esp_log.h> |
|
|
|
|
|
|
|
#include <mdns.h> |
|
|
|
#include <esp_ota_ops.h> |
|
|
|
#include <lwip/apps/tftp_server.h> |
|
|
|
|
|
|
|
#include <freertos/FreeRTOS.h> |
|
|
|
#include <freertos/task.h> |
|
|
|
|
|
|
|
#include "ota.hpp" |
|
|
|
#include <cstring> |
|
|
|
|
|
|
|
#include "ota.hpp" |
|
|
|
|
|
|
|
// The real version probably lives somewhere else. Too lazy to find it |
|
|
|
template <class T> static inline T MIN(T a, T b) { return a < b ? a : b; } |
|
|
|
|
|
|
|
// TODO: Stick this somewhere useful? |
|
|
|
static void reboot_soon_task(void*) { |
|
|
|
@@ -30,134 +29,141 @@ static void reboot_soon() { |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
// HTTP Server Helpers. |
|
|
|
// TODO: Share these? |
|
|
|
static inline esp_err_t serv_err(httpd_req_t *req, const char *msg) { |
|
|
|
ESP_LOGE(TAG, "%s", msg); |
|
|
|
return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, msg); |
|
|
|
} |
|
|
|
|
|
|
|
static inline esp_err_t serv_progress(httpd_req_t *req, int progress) { |
|
|
|
ESP_LOGI(TAG, "%d%% complete", progress); |
|
|
|
/* |
|
|
|
char msg[100]; |
|
|
|
snprintf(msg, sizeof(msg), "%d%% complete\n", progress); |
|
|
|
httpd_resp_set_status(req, "102 Processing"); |
|
|
|
httpd_resp_set_type(req, HTTPD_TYPE_TEXT); |
|
|
|
return httpd_resp_send(req, msg, HTTPD_RESP_USE_STRLEN); |
|
|
|
*/ |
|
|
|
return ESP_OK; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// OTA POST |
|
|
|
static esp_err_t ota_post_handler(httpd_req_t *req) |
|
|
|
{ |
|
|
|
esp_err_t err; |
|
|
|
|
|
|
|
const esp_partition_t *part = esp_ota_get_next_update_partition(NULL); |
|
|
|
if (!part) return serv_err(req, "Could not find update partition"); |
|
|
|
|
|
|
|
int remaining = req->content_len; |
|
|
|
ESP_LOGI(TAG, "Starting OTA: %d bytes", remaining); |
|
|
|
class OTA_Session { |
|
|
|
public: |
|
|
|
OTA_Session() : |
|
|
|
part(esp_ota_get_next_update_partition(NULL)), |
|
|
|
ota(0), failed(false), total_written(0) |
|
|
|
{ |
|
|
|
if (!part) { |
|
|
|
ESP_LOGE(TAG, "Could not find update partition"); |
|
|
|
failed = true; |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
esp_ota_handle_t ota; |
|
|
|
err = esp_ota_begin(part, remaining, &ota); |
|
|
|
if (err != ESP_OK) { |
|
|
|
ESP_LOGE(TAG, "esp_ota_begin: %d", err); |
|
|
|
return serv_err(req, "Could not start OTA"); |
|
|
|
auto err = esp_ota_begin(part, OTA_SIZE_UNKNOWN, &ota); |
|
|
|
if (err != ESP_OK) { |
|
|
|
ESP_LOGE(TAG, "esp_ota_begin: %d", err); |
|
|
|
failed = true; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
constexpr int progress_step = 10; |
|
|
|
int next_progress = 0; |
|
|
|
constexpr size_t CHUNK_MAX = 4096; |
|
|
|
char *chunk_buf = new char[CHUNK_MAX]; |
|
|
|
while (remaining > 0) { |
|
|
|
int chunk_size = MIN((unsigned int)remaining, CHUNK_MAX); |
|
|
|
|
|
|
|
int nread = httpd_req_recv(req, chunk_buf, chunk_size); |
|
|
|
if (nread <= 0) { /* 0 return value indicates connection closed */ |
|
|
|
ESP_LOGE(TAG, "httpd_req_recv(%d): %d", chunk_size, nread); |
|
|
|
~OTA_Session() { |
|
|
|
if (failed) { |
|
|
|
ESP_LOGE(TAG, "Aborting OTA due to previous failure"); |
|
|
|
esp_ota_abort(ota); |
|
|
|
delete[] chunk_buf; |
|
|
|
|
|
|
|
/* Check if timeout occurred */ |
|
|
|
if (nread == HTTPD_SOCK_ERR_TIMEOUT) { |
|
|
|
/* In case of timeout one can choose to retry calling |
|
|
|
* httpd_req_recv(), but to keep it simple, here we |
|
|
|
* respond with an HTTP 408 (Request Timeout) error */ |
|
|
|
httpd_resp_send_408(req); |
|
|
|
} |
|
|
|
|
|
|
|
/* In case of error, returning ESP_FAIL will |
|
|
|
* ensure that the underlying socket is closed */ |
|
|
|
return ESP_FAIL; |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
err = esp_ota_write(ota, chunk_buf, nread); |
|
|
|
ESP_LOGI(TAG, "Finalizing OTA (%d bytes)", total_written); |
|
|
|
auto err = esp_ota_end(ota); |
|
|
|
if (err != ESP_OK) { |
|
|
|
ESP_LOGE(TAG, "esp_ota_write(%d): %d", nread, err); |
|
|
|
ESP_LOGE(TAG, "esp_ota_end: %d", err); |
|
|
|
esp_ota_abort(ota); |
|
|
|
delete[] chunk_buf; |
|
|
|
return serv_err(req, "Failed to write OTA chunk"); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
ESP_LOGI(TAG, "Setting new boot partition"); |
|
|
|
err = esp_ota_set_boot_partition(part); |
|
|
|
if (err != ESP_OK) { |
|
|
|
ESP_LOGE(TAG, "esp_ota_set_boot_partition: %d", err); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
remaining -= nread; |
|
|
|
ESP_LOGI(TAG, "OTA Successful"); |
|
|
|
|
|
|
|
reboot_soon(); |
|
|
|
} |
|
|
|
|
|
|
|
int progress = 100 * (req->content_len - remaining) / req->content_len; |
|
|
|
if (progress >= next_progress) { |
|
|
|
serv_progress(req, progress); |
|
|
|
next_progress += progress_step; |
|
|
|
bool write(const void *data, int len) { |
|
|
|
if (failed) return false; |
|
|
|
auto err = esp_ota_write(ota, data, len); |
|
|
|
if (err != ESP_OK) { |
|
|
|
ESP_LOGE(TAG, "esp_ota_write(%d): %d", len, err); |
|
|
|
failed = true; |
|
|
|
return false; |
|
|
|
} |
|
|
|
total_written += len; |
|
|
|
return true; |
|
|
|
} |
|
|
|
delete[] chunk_buf; |
|
|
|
|
|
|
|
ESP_LOGI(TAG, "Finalizing OTA"); |
|
|
|
private: |
|
|
|
const esp_partition_t* part; |
|
|
|
esp_ota_handle_t ota; |
|
|
|
bool failed; |
|
|
|
int total_written; |
|
|
|
}; |
|
|
|
|
|
|
|
err = esp_ota_end(ota); |
|
|
|
if (err != ESP_OK) { |
|
|
|
ESP_LOGE(TAG, "esp_ota_end: %d", err); |
|
|
|
esp_ota_abort(ota); |
|
|
|
return serv_err(req, "Failed to finalize OTA"); |
|
|
|
|
|
|
|
static void* ota_open(const char *filename, const char *mode, u8_t write) { |
|
|
|
if (strcmp(mode, "octet") != 0) { |
|
|
|
ESP_LOGE(TAG, "Unexpected mode: %s", mode); |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
|
|
|
|
ESP_LOGI(TAG, "Setting new boot partition"); |
|
|
|
err = esp_ota_set_boot_partition(part); |
|
|
|
if (err != ESP_OK) { |
|
|
|
ESP_LOGE(TAG, "esp_ota_set_boot_partition: %d", err); |
|
|
|
return serv_err(req, "Failed to set partition"); |
|
|
|
if (strcmp(filename, "blinky.bin") != 0) { |
|
|
|
ESP_LOGE(TAG, "Unexpected filename: %s", mode); |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
|
|
|
|
ESP_LOGI(TAG, "OTA Successful"); |
|
|
|
httpd_resp_sendstr(req, "OTA Successful\n"); |
|
|
|
if (!write) { |
|
|
|
ESP_LOGE(TAG, "Illegal read attempt"); |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
|
|
|
|
reboot_soon(); |
|
|
|
ESP_LOGI(TAG, "Starting OTA"); |
|
|
|
|
|
|
|
return ESP_FAIL; |
|
|
|
return new OTA_Session(); |
|
|
|
} |
|
|
|
|
|
|
|
static void ota_close(void* handle) { |
|
|
|
OTA_Session *session = (OTA_Session*)handle; |
|
|
|
if (session) delete session; |
|
|
|
else ESP_LOGE(TAG, "Attempt to close invalid session"); |
|
|
|
} |
|
|
|
|
|
|
|
int start_ota_serv(httpd_handle_t server) { |
|
|
|
ESP_LOGI(TAG, "Starting OTA server"); |
|
|
|
static int ota_read(void* handle, void* buf, int len) { |
|
|
|
ESP_LOGE(TAG, "Super illegal read of %d bytes", len); |
|
|
|
return -1; |
|
|
|
} |
|
|
|
|
|
|
|
// Register handlers (move these to components) |
|
|
|
httpd_uri_t uri_ota_post = { |
|
|
|
.uri = "/ota", |
|
|
|
.method = HTTP_POST, |
|
|
|
.handler = ota_post_handler, |
|
|
|
.user_ctx = NULL |
|
|
|
}; |
|
|
|
ESP_ERROR_CHECK(httpd_register_uri_handler(server, &uri_ota_post)); |
|
|
|
static int ota_write(void* handle, struct pbuf* p) { |
|
|
|
OTA_Session *session = (OTA_Session*)handle; |
|
|
|
if (!session) { |
|
|
|
ESP_LOGE(TAG, "Attempt to write to invalid session"); |
|
|
|
return -1; |
|
|
|
} |
|
|
|
|
|
|
|
// mDNS Entry |
|
|
|
mdns_service_add(NULL, "_http", "_tcp", 80, NULL, 0); |
|
|
|
mdns_service_instance_name_set("_http", "_tcp", "ESP32S2 OTA"); |
|
|
|
for (; p; p = p->next) { |
|
|
|
if (!session->write(p->payload, p->len)) { |
|
|
|
ESP_LOGE(TAG, "Failed to write %d bytes", p->len); |
|
|
|
return -1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static tftp_context ota_context = { |
|
|
|
.open = ota_open, |
|
|
|
.close = ota_close, |
|
|
|
.read = ota_read, |
|
|
|
.write = ota_write |
|
|
|
}; |
|
|
|
|
|
|
|
int start_ota_serv() { |
|
|
|
ESP_LOGI(TAG, "Starting OTA server"); |
|
|
|
|
|
|
|
ESP_ERROR_CHECK(tftp_init(&ota_context)); |
|
|
|
|
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int stop_ota_serv() { |
|
|
|
// TODO |
|
|
|
return -1; |
|
|
|
tftp_cleanup(); |
|
|
|
return 0; |
|
|
|
} |