Initial sanitized SmartSpeaker snapshot
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "esp_http_client.h"
|
||||
#include "esp_https_ota.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_netif_sntp.h"
|
||||
|
||||
#include "ota_manager.h"
|
||||
#include "socket_client.h" // for socket_send_raw()
|
||||
#include "state_manager.h" // for state_set_led()
|
||||
#include "esp_crt_bundle.h" // for esp_crt_bundle_attach()
|
||||
|
||||
static const char *TAG = "ota_manager";
|
||||
|
||||
// Guard against concurrent OTA runs
|
||||
static volatile bool s_ota_running = false;
|
||||
|
||||
// URL is copied here so the task owns its memory
|
||||
#define OTA_URL_MAX_LEN 256
|
||||
static char s_ota_url[OTA_URL_MAX_LEN];
|
||||
|
||||
|
||||
static esp_err_t ota_http_event(esp_http_client_event_t *evt)
|
||||
{
|
||||
if (evt->event_id == HTTP_EVENT_ERROR ||
|
||||
evt->event_id == HTTP_EVENT_DISCONNECTED) {
|
||||
|
||||
int tls_error = 0;
|
||||
int tls_flags = 0;
|
||||
|
||||
esp_err_t tls_result =
|
||||
esp_http_client_get_and_clear_last_tls_error(
|
||||
evt->client,
|
||||
&tls_error,
|
||||
&tls_flags
|
||||
);
|
||||
|
||||
ESP_LOGE(
|
||||
"OTA",
|
||||
"HTTP failure: tls_result=%s (0x%x), "
|
||||
"tls_error=0x%x, tls_flags=0x%x, errno=%d",
|
||||
esp_err_to_name(tls_result),
|
||||
tls_result,
|
||||
tls_error,
|
||||
tls_flags,
|
||||
esp_http_client_get_errno(evt->client)
|
||||
);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* OTA task
|
||||
* ------------------------------------------------------------------ */
|
||||
static void ota_task(void *pvParam)
|
||||
{
|
||||
esp_err_t err;
|
||||
|
||||
ESP_LOGI(TAG, "OTA task started. Firmware URL: %s", s_ota_url);
|
||||
|
||||
// Pulse blue slowly during update
|
||||
state_set_led(LED_STATE_UPDATING);
|
||||
|
||||
// ---- Configure HTTP client ----
|
||||
esp_http_client_config_t http_cfg = {
|
||||
.url = s_ota_url,
|
||||
.event_handler = ota_http_event,
|
||||
.timeout_ms = 30000,
|
||||
.keep_alive_enable = true,
|
||||
.max_redirection_count = 5,
|
||||
.skip_cert_common_name_check = false, // Keep false to ensure SNI remains enabled for Cloudflare Host mapping
|
||||
.crt_bundle_attach = esp_crt_bundle_attach, // Use system root CAs to validate Cloudflare certificates
|
||||
};
|
||||
|
||||
esp_https_ota_config_t ota_cfg = {
|
||||
.http_config = &http_cfg,
|
||||
};
|
||||
|
||||
// Gate HTTPS OTA until system clock is synchronized (prevents TLS certificate validity window checks from failing)
|
||||
bool is_https = (strncmp(s_ota_url, "https", 5) == 0);
|
||||
time_t now;
|
||||
time(&now);
|
||||
if (is_https) {
|
||||
if (now < 1704067200) {
|
||||
ESP_LOGI(TAG, "Waiting for system clock synchronization (up to 15s)...");
|
||||
esp_err_t sync_err = esp_netif_sntp_sync_wait(pdMS_TO_TICKS(15000));
|
||||
time(&now);
|
||||
if (sync_err != ESP_OK || now < 1704067200) {
|
||||
ESP_LOGE(TAG, "HTTPS OTA blocked: system clock not synchronized");
|
||||
socket_send_ota_error("System clock not synchronized");
|
||||
state_set_led(LED_STATE_CONNECTED); // restore LED state
|
||||
s_ota_running = false;
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Log diagnostics: exact URL, current Unix time, total heap, internal heap
|
||||
ESP_LOGI(TAG, "Diagnostics before OTA begin: URL=%s, unix_time=%ld, free_heap_total=%d, free_heap_internal=%d",
|
||||
s_ota_url, (long)now, (int)esp_get_free_heap_size(), (int)heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
|
||||
|
||||
// Enable verbose TLS logging to pinpoint exactly where the handshake fails
|
||||
esp_log_level_set("esp-tls", ESP_LOG_VERBOSE);
|
||||
esp_log_level_set("mbedtls", ESP_LOG_VERBOSE);
|
||||
|
||||
// ---- Begin OTA handle ----
|
||||
esp_https_ota_handle_t ota_handle = NULL;
|
||||
err = esp_https_ota_begin(&ota_cfg, &ota_handle);
|
||||
if (err != ESP_OK || ota_handle == NULL) {
|
||||
char reason[64];
|
||||
snprintf(reason, sizeof(reason), "esp_https_ota_begin failed: 0x%x", err);
|
||||
ESP_LOGE(TAG, "%s", reason);
|
||||
socket_send_ota_error(reason);
|
||||
state_set_led(LED_STATE_CONNECTED); // restore LED state
|
||||
s_ota_running = false;
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- Get image size for progress reporting ----
|
||||
int image_size = esp_https_ota_get_image_size(ota_handle);
|
||||
if (image_size <= 0) {
|
||||
// Some servers don't send Content-Length; treat as unknown
|
||||
image_size = 0;
|
||||
}
|
||||
ESP_LOGI(TAG, "Firmware image size: %d bytes", image_size);
|
||||
|
||||
// ---- Stream and flash ----
|
||||
int bytes_written = 0;
|
||||
int last_reported_pct = -1;
|
||||
|
||||
while (1) {
|
||||
err = esp_https_ota_perform(ota_handle);
|
||||
if (err == ESP_ERR_HTTPS_OTA_IN_PROGRESS) {
|
||||
bytes_written = esp_https_ota_get_image_len_read(ota_handle);
|
||||
|
||||
// Report progress every 5% (or every ~32KB if size unknown)
|
||||
int pct = (image_size > 0)
|
||||
? (int)((bytes_written * 100LL) / image_size)
|
||||
: -1;
|
||||
|
||||
if (pct >= 0 && pct != last_reported_pct && (pct % 5 == 0)) {
|
||||
socket_send_ota_progress(pct, bytes_written, image_size);
|
||||
last_reported_pct = pct;
|
||||
ESP_LOGI(TAG, "OTA progress: %d%% (%d / %d bytes)", pct, bytes_written, image_size);
|
||||
} else if (pct < 0 && (bytes_written - (last_reported_pct * 32768)) >= 32768) {
|
||||
socket_send_ota_progress(0, bytes_written, 0);
|
||||
last_reported_pct = bytes_written / 32768;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (err == ESP_OK) {
|
||||
// Download complete
|
||||
break;
|
||||
}
|
||||
|
||||
// Any other error is fatal
|
||||
char reason[64];
|
||||
snprintf(reason, sizeof(reason), "esp_https_ota_perform failed: 0x%x", err);
|
||||
ESP_LOGE(TAG, "%s", reason);
|
||||
socket_send_ota_error(reason);
|
||||
esp_https_ota_abort(ota_handle);
|
||||
state_set_led(LED_STATE_CONNECTED); // restore LED state
|
||||
s_ota_running = false;
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
bytes_written = esp_https_ota_get_image_len_read(ota_handle);
|
||||
|
||||
// ---- Validate and commit ----
|
||||
if (!esp_https_ota_is_complete_data_received(ota_handle)) {
|
||||
const char *reason = "Incomplete image received";
|
||||
ESP_LOGE(TAG, "%s", reason);
|
||||
socket_send_ota_error(reason);
|
||||
esp_https_ota_abort(ota_handle);
|
||||
state_set_led(LED_STATE_CONNECTED); // restore LED state
|
||||
s_ota_running = false;
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
err = esp_https_ota_finish(ota_handle);
|
||||
if (err != ESP_OK) {
|
||||
char reason[64];
|
||||
snprintf(reason, sizeof(reason), "esp_https_ota_finish failed: 0x%x", err);
|
||||
ESP_LOGE(TAG, "%s", reason);
|
||||
socket_send_ota_error(reason);
|
||||
state_set_led(LED_STATE_CONNECTED); // restore LED state
|
||||
s_ota_running = false;
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- Success ----
|
||||
socket_send_ota_progress(100, bytes_written, bytes_written);
|
||||
ESP_LOGI(TAG, "OTA complete! %d bytes written. Rebooting in 500ms...", bytes_written);
|
||||
socket_send_ota_complete();
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
esp_restart();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Public API
|
||||
* ------------------------------------------------------------------ */
|
||||
void ota_start(const char *url)
|
||||
{
|
||||
if (s_ota_running) {
|
||||
ESP_LOGW(TAG, "OTA already in progress, ignoring request.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (url == NULL || strlen(url) == 0) {
|
||||
ESP_LOGE(TAG, "ota_start called with empty URL");
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy URL before spawning task
|
||||
strlcpy(s_ota_url, url, sizeof(s_ota_url));
|
||||
|
||||
s_ota_running = true;
|
||||
|
||||
ESP_LOGI(TAG, "Spawning OTA task for URL: %s", s_ota_url);
|
||||
|
||||
BaseType_t ret = xTaskCreate(
|
||||
ota_task,
|
||||
"ota_task",
|
||||
8192, // 8KB stack — enough for HTTP + flash ops
|
||||
NULL,
|
||||
5, // Priority: slightly above normal tasks
|
||||
NULL
|
||||
);
|
||||
|
||||
if (ret != pdPASS) {
|
||||
ESP_LOGE(TAG, "Failed to create OTA task");
|
||||
s_ota_running = false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user