|
|
|
@@ -0,0 +1,821 @@
|
|
|
|
|
/*
|
|
|
|
|
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "control_plane.h"
|
|
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
#include "control_plane_internal.h"
|
|
|
|
|
#include "multimodal/control_plane_multimodal_device_auth.h"
|
|
|
|
|
#include "domain.h"
|
|
|
|
|
#include "esp_attr.h"
|
|
|
|
|
#include "esp_heap_caps.h"
|
|
|
|
|
#include "esp_log.h"
|
|
|
|
|
#include "freertos/FreeRTOS.h"
|
|
|
|
|
#include "freertos/task.h"
|
|
|
|
|
#include "sdkconfig.h"
|
|
|
|
|
#include "soc/soc_caps.h"
|
|
|
|
|
|
|
|
|
|
#ifndef CONFIG_TQ_OTA_ENABLE
|
|
|
|
|
#define CONFIG_TQ_OTA_ENABLE 0
|
|
|
|
|
#endif
|
|
|
|
|
#ifndef CONFIG_TQ_OTA_REPORT_URL
|
|
|
|
|
#define CONFIG_TQ_OTA_REPORT_URL "https://gz.api.talkingq.com/ota/api/ota/versionInfoReport"
|
|
|
|
|
#endif
|
|
|
|
|
#ifndef CONFIG_TQ_OTA_INTERNAL_AUTH_SECRET
|
|
|
|
|
#define CONFIG_TQ_OTA_INTERNAL_AUTH_SECRET ""
|
|
|
|
|
#endif
|
|
|
|
|
#ifndef CONFIG_TQ_OTA_POLL_INTERVAL_MS
|
|
|
|
|
#define CONFIG_TQ_OTA_POLL_INTERVAL_MS 21600000
|
|
|
|
|
#endif
|
|
|
|
|
#ifndef CONFIG_TQ_OTA_CONFIRMATION_POLL_MS
|
|
|
|
|
#define CONFIG_TQ_OTA_CONFIRMATION_POLL_MS 21600000
|
|
|
|
|
#endif
|
|
|
|
|
#ifndef CONFIG_TQ_OTA_HTTP_TIMEOUT_MS
|
|
|
|
|
#define CONFIG_TQ_OTA_HTTP_TIMEOUT_MS 15000
|
|
|
|
|
#endif
|
|
|
|
|
#ifndef CONFIG_TQ_OTA_INSTALL_TIMEOUT_MS
|
|
|
|
|
#define CONFIG_TQ_OTA_INSTALL_TIMEOUT_MS 600000
|
|
|
|
|
#endif
|
|
|
|
|
#ifndef CONFIG_TQ_OTA_RETRY_BASE_MS
|
|
|
|
|
#define CONFIG_TQ_OTA_RETRY_BASE_MS 21600000
|
|
|
|
|
#endif
|
|
|
|
|
#ifndef CONFIG_TQ_OTA_RETRY_MAX_MS
|
|
|
|
|
#define CONFIG_TQ_OTA_RETRY_MAX_MS 21600000
|
|
|
|
|
#endif
|
|
|
|
|
#ifndef CONFIG_TQ_OTA_TASK_STACK_BYTES
|
|
|
|
|
#define CONFIG_TQ_OTA_TASK_STACK_BYTES 16384
|
|
|
|
|
#endif
|
|
|
|
|
#ifndef CONFIG_TQ_OTA_MIN_FREE_INTERNAL_BYTES
|
|
|
|
|
#define CONFIG_TQ_OTA_MIN_FREE_INTERNAL_BYTES 32768
|
|
|
|
|
#endif
|
|
|
|
|
#ifndef CONFIG_TQ_OTA_MIN_LARGEST_DMA_BYTES
|
|
|
|
|
#define CONFIG_TQ_OTA_MIN_LARGEST_DMA_BYTES 4096
|
|
|
|
|
#endif
|
|
|
|
|
#ifndef CONFIG_TQ_NETWORK_TASK_CORE
|
|
|
|
|
#define CONFIG_TQ_NETWORK_TASK_CORE 0
|
|
|
|
|
#endif
|
|
|
|
|
#ifndef CONFIG_TQ_LOCAL_WAKE_ENABLE
|
|
|
|
|
#define CONFIG_TQ_LOCAL_WAKE_ENABLE 0
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#define CONTROL_PLANE_OTA_STOP_POLL_MS 20
|
|
|
|
|
#define CONTROL_PLANE_OTA_SLEEP_SLICE_MS 200
|
|
|
|
|
#define CONTROL_PLANE_OTA_START_GATE_MS 5000
|
|
|
|
|
#define CONTROL_PLANE_OTA_INITIAL_GATE_DELAY_MS 500
|
|
|
|
|
#define CONTROL_PLANE_OTA_TRANSPORT_RELEASE_WAIT_MS 100
|
|
|
|
|
#define CONTROL_PLANE_OTA_TASK_STACK_WARN_BYTES 4096U
|
|
|
|
|
#define CONTROL_PLANE_OTA_TASK_STACK_WORDS \
|
|
|
|
|
((CONFIG_TQ_OTA_TASK_STACK_BYTES + sizeof(StackType_t) - 1U) / sizeof(StackType_t))
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
bool acquired;
|
|
|
|
|
bool restore_multimodal;
|
|
|
|
|
bool restore_local_wake;
|
|
|
|
|
} control_plane_ota_voice_window_t;
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
char device_id[96];
|
|
|
|
|
char serial_number[96];
|
|
|
|
|
char version[DOMAIN_OTA_VERSION_MAX_LEN];
|
|
|
|
|
char jwt[CONTROL_PLANE_DEVICE_AUTH_JWT_MAX_LEN];
|
|
|
|
|
domain_ota_report_request_t request;
|
|
|
|
|
} control_plane_ota_report_context_t;
|
|
|
|
|
|
|
|
|
|
static const char *TAG = "cp_ota";
|
|
|
|
|
static portMUX_TYPE s_ota_lock = portMUX_INITIALIZER_UNLOCKED;
|
|
|
|
|
static TaskHandle_t s_ota_task;
|
|
|
|
|
static bool s_ota_stop_requested;
|
|
|
|
|
static bool s_ota_voice_services_reserved;
|
|
|
|
|
static DRAM_ATTR StaticTask_t s_ota_task_buffer;
|
|
|
|
|
static DRAM_ATTR StackType_t s_ota_task_stack[CONTROL_PLANE_OTA_TASK_STACK_WORDS];
|
|
|
|
|
static control_plane_ota_snapshot_t s_ota_snapshot = {
|
|
|
|
|
.state = CONTROL_PLANE_OTA_STATE_DISABLED,
|
|
|
|
|
.last_http_status = -1,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void control_plane_ota_worker(void *arg);
|
|
|
|
|
|
|
|
|
|
static bool control_plane_ota_start_default_local_wake(void)
|
|
|
|
|
{
|
|
|
|
|
#if CONFIG_TQ_LOCAL_WAKE_ENABLE
|
|
|
|
|
return control_plane_local_wake_start() == ESP_OK;
|
|
|
|
|
#endif
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool control_plane_ota_stop_is_requested(void)
|
|
|
|
|
{
|
|
|
|
|
bool requested;
|
|
|
|
|
portENTER_CRITICAL(&s_ota_lock);
|
|
|
|
|
requested = s_ota_stop_requested;
|
|
|
|
|
portEXIT_CRITICAL(&s_ota_lock);
|
|
|
|
|
return requested;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool control_plane_ota_voice_services_allowed(void)
|
|
|
|
|
{
|
|
|
|
|
bool allowed;
|
|
|
|
|
portENTER_CRITICAL(&s_ota_lock);
|
|
|
|
|
allowed = !s_ota_voice_services_reserved;
|
|
|
|
|
portEXIT_CRITICAL(&s_ota_lock);
|
|
|
|
|
return allowed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void control_plane_ota_set_state(control_plane_ota_state_t state,
|
|
|
|
|
esp_err_t error,
|
|
|
|
|
int http_status)
|
|
|
|
|
{
|
|
|
|
|
portENTER_CRITICAL(&s_ota_lock);
|
|
|
|
|
s_ota_snapshot.state = state;
|
|
|
|
|
s_ota_snapshot.last_error = error;
|
|
|
|
|
s_ota_snapshot.last_http_status = http_status;
|
|
|
|
|
portEXIT_CRITICAL(&s_ota_lock);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void control_plane_ota_set_candidate(const domain_ota_firmware_t *firmware)
|
|
|
|
|
{
|
|
|
|
|
portENTER_CRITICAL(&s_ota_lock);
|
|
|
|
|
strlcpy(s_ota_snapshot.target_version,
|
|
|
|
|
firmware->version,
|
|
|
|
|
sizeof(s_ota_snapshot.target_version));
|
|
|
|
|
s_ota_snapshot.forced = firmware->forced;
|
|
|
|
|
s_ota_snapshot.confirmed = firmware->confirmed;
|
|
|
|
|
s_ota_snapshot.bytes_received = 0U;
|
|
|
|
|
s_ota_snapshot.total_bytes = firmware->size_bytes;
|
|
|
|
|
portEXIT_CRITICAL(&s_ota_lock);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void control_plane_ota_clear_candidate(void)
|
|
|
|
|
{
|
|
|
|
|
portENTER_CRITICAL(&s_ota_lock);
|
|
|
|
|
s_ota_snapshot.target_version[0] = '\0';
|
|
|
|
|
s_ota_snapshot.forced = false;
|
|
|
|
|
s_ota_snapshot.confirmed = false;
|
|
|
|
|
s_ota_snapshot.bytes_received = 0U;
|
|
|
|
|
s_ota_snapshot.total_bytes = 0U;
|
|
|
|
|
portEXIT_CRITICAL(&s_ota_lock);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool control_plane_ota_wait_interruptible(uint32_t wait_ms)
|
|
|
|
|
{
|
|
|
|
|
uint32_t waited_ms = 0U;
|
|
|
|
|
while (waited_ms < wait_ms && !control_plane_ota_stop_is_requested()) {
|
|
|
|
|
uint32_t slice_ms = wait_ms - waited_ms;
|
|
|
|
|
if (slice_ms > CONTROL_PLANE_OTA_SLEEP_SLICE_MS) {
|
|
|
|
|
slice_ms = CONTROL_PLANE_OTA_SLEEP_SLICE_MS;
|
|
|
|
|
}
|
|
|
|
|
TickType_t ticks = pdMS_TO_TICKS(slice_ms);
|
|
|
|
|
vTaskDelay(ticks > 0 ? ticks : 1);
|
|
|
|
|
waited_ms += slice_ms;
|
|
|
|
|
}
|
|
|
|
|
return !control_plane_ota_stop_is_requested();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static uint32_t control_plane_ota_retry_delay(uint32_t failures)
|
|
|
|
|
{
|
|
|
|
|
uint64_t delay_ms = CONFIG_TQ_OTA_RETRY_BASE_MS;
|
|
|
|
|
for (uint32_t i = 1U; i < failures && delay_ms < CONFIG_TQ_OTA_RETRY_MAX_MS; ++i) {
|
|
|
|
|
delay_ms *= 2U;
|
|
|
|
|
}
|
|
|
|
|
return delay_ms > CONFIG_TQ_OTA_RETRY_MAX_MS ?
|
|
|
|
|
CONFIG_TQ_OTA_RETRY_MAX_MS : (uint32_t)delay_ms;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void control_plane_ota_note_failure(esp_err_t error, int http_status)
|
|
|
|
|
{
|
|
|
|
|
portENTER_CRITICAL(&s_ota_lock);
|
|
|
|
|
if (s_ota_snapshot.consecutive_failures < UINT32_MAX) {
|
|
|
|
|
s_ota_snapshot.consecutive_failures++;
|
|
|
|
|
}
|
|
|
|
|
s_ota_snapshot.state = CONTROL_PLANE_OTA_STATE_ERROR;
|
|
|
|
|
s_ota_snapshot.last_error = error;
|
|
|
|
|
s_ota_snapshot.last_http_status = http_status;
|
|
|
|
|
portEXIT_CRITICAL(&s_ota_lock);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void control_plane_ota_clear_failures(void)
|
|
|
|
|
{
|
|
|
|
|
portENTER_CRITICAL(&s_ota_lock);
|
|
|
|
|
s_ota_snapshot.consecutive_failures = 0U;
|
|
|
|
|
s_ota_snapshot.last_error = ESP_OK;
|
|
|
|
|
portEXIT_CRITICAL(&s_ota_lock);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static uint32_t control_plane_ota_failure_count(void)
|
|
|
|
|
{
|
|
|
|
|
uint32_t failures;
|
|
|
|
|
portENTER_CRITICAL(&s_ota_lock);
|
|
|
|
|
failures = s_ota_snapshot.consecutive_failures;
|
|
|
|
|
portEXIT_CRITICAL(&s_ota_lock);
|
|
|
|
|
return failures;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void control_plane_ota_progress(uint64_t received,
|
|
|
|
|
uint64_t total,
|
|
|
|
|
void *user_data)
|
|
|
|
|
{
|
|
|
|
|
(void)user_data;
|
|
|
|
|
uint32_t old_percent;
|
|
|
|
|
uint32_t new_percent = total > 0U ? (uint32_t)((received * 100U) / total) : 0U;
|
|
|
|
|
portENTER_CRITICAL(&s_ota_lock);
|
|
|
|
|
old_percent = s_ota_snapshot.total_bytes > 0U ?
|
|
|
|
|
(uint32_t)((s_ota_snapshot.bytes_received * 100U) /
|
|
|
|
|
s_ota_snapshot.total_bytes) : 0U;
|
|
|
|
|
s_ota_snapshot.bytes_received = received;
|
|
|
|
|
s_ota_snapshot.total_bytes = total;
|
|
|
|
|
portEXIT_CRITICAL(&s_ota_lock);
|
|
|
|
|
if (new_percent == 100U || new_percent / 10U != old_percent / 10U) {
|
|
|
|
|
ESP_LOGI(TAG, "OTA download progress: %u%%", (unsigned)new_percent);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void control_plane_ota_log_stack_headroom(const char *phase)
|
|
|
|
|
{
|
|
|
|
|
UBaseType_t high_water_bytes = uxTaskGetStackHighWaterMark(NULL) * sizeof(StackType_t);
|
|
|
|
|
ESP_LOGI(TAG,
|
|
|
|
|
"OTA task stack phase=%s headroom=%uB configured=%uB",
|
|
|
|
|
phase != NULL ? phase : "unknown",
|
|
|
|
|
(unsigned)high_water_bytes,
|
|
|
|
|
(unsigned)CONFIG_TQ_OTA_TASK_STACK_BYTES);
|
|
|
|
|
if (high_water_bytes < CONTROL_PLANE_OTA_TASK_STACK_WARN_BYTES) {
|
|
|
|
|
ESP_LOGW(TAG,
|
|
|
|
|
"OTA task stack margin low: headroom=%uB required=%uB configured=%uB",
|
|
|
|
|
(unsigned)high_water_bytes,
|
|
|
|
|
(unsigned)CONTROL_PLANE_OTA_TASK_STACK_WARN_BYTES,
|
|
|
|
|
(unsigned)CONFIG_TQ_OTA_TASK_STACK_BYTES);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool control_plane_ota_cancel(void *user_data)
|
|
|
|
|
{
|
|
|
|
|
(void)user_data;
|
|
|
|
|
return control_plane_ota_stop_is_requested();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool control_plane_ota_runtime_is_idle(void)
|
|
|
|
|
{
|
|
|
|
|
if (!control_plane_voice_turn_is_idle()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
control_plane_multimodal_runtime_state_t state = {0};
|
|
|
|
|
if (control_plane_multimodal_get_runtime_state(&state) != ESP_OK) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return !state.has_active_task && !state.client_speech_active &&
|
|
|
|
|
!state.remote_responding &&
|
|
|
|
|
!state.local_responding_started;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void control_plane_ota_set_voice_services_reserved(bool reserved)
|
|
|
|
|
{
|
|
|
|
|
portENTER_CRITICAL(&s_ota_lock);
|
|
|
|
|
s_ota_voice_services_reserved = reserved;
|
|
|
|
|
portEXIT_CRITICAL(&s_ota_lock);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static BaseType_t control_plane_ota_create_task(TaskHandle_t *out_task)
|
|
|
|
|
{
|
|
|
|
|
if (out_task == NULL) {
|
|
|
|
|
return pdFAIL;
|
|
|
|
|
}
|
|
|
|
|
*out_task = NULL;
|
|
|
|
|
const BaseType_t core_id = CONFIG_TQ_NETWORK_TASK_CORE < 0 ?
|
|
|
|
|
tskNO_AFFINITY : CONFIG_TQ_NETWORK_TASK_CORE;
|
|
|
|
|
const uint32_t internal_caps = MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT;
|
|
|
|
|
ESP_LOGI(TAG,
|
|
|
|
|
"OTA task create: static_stack=%u internal_free=%u internal_largest=%u",
|
|
|
|
|
(unsigned)CONFIG_TQ_OTA_TASK_STACK_BYTES,
|
|
|
|
|
(unsigned)heap_caps_get_free_size(internal_caps),
|
|
|
|
|
(unsigned)heap_caps_get_largest_free_block(internal_caps));
|
|
|
|
|
|
|
|
|
|
TaskHandle_t task = xTaskCreateStaticPinnedToCore(
|
|
|
|
|
control_plane_ota_worker,
|
|
|
|
|
"cp_ota",
|
|
|
|
|
CONFIG_TQ_OTA_TASK_STACK_BYTES,
|
|
|
|
|
NULL,
|
|
|
|
|
4,
|
|
|
|
|
s_ota_task_stack,
|
|
|
|
|
&s_ota_task_buffer,
|
|
|
|
|
core_id);
|
|
|
|
|
if (task == NULL) {
|
|
|
|
|
return pdFAIL;
|
|
|
|
|
}
|
|
|
|
|
*out_task = task;
|
|
|
|
|
return pdPASS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static esp_err_t control_plane_ota_acquire_voice_window(
|
|
|
|
|
bool initial_window,
|
|
|
|
|
bool allow_active_teardown,
|
|
|
|
|
control_plane_ota_voice_window_t *window)
|
|
|
|
|
{
|
|
|
|
|
if (window == NULL) {
|
|
|
|
|
return ESP_ERR_INVALID_ARG;
|
|
|
|
|
}
|
|
|
|
|
memset(window, 0, sizeof(*window));
|
|
|
|
|
|
|
|
|
|
if (!initial_window && !allow_active_teardown && !control_plane_ota_runtime_is_idle()) {
|
|
|
|
|
return ESP_ERR_INVALID_STATE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
portENTER_CRITICAL(&s_ota_lock);
|
|
|
|
|
if (!initial_window && s_ota_voice_services_reserved) {
|
|
|
|
|
portEXIT_CRITICAL(&s_ota_lock);
|
|
|
|
|
return ESP_ERR_INVALID_STATE;
|
|
|
|
|
}
|
|
|
|
|
s_ota_voice_services_reserved = true;
|
|
|
|
|
portEXIT_CRITICAL(&s_ota_lock);
|
|
|
|
|
|
|
|
|
|
if ((!allow_active_teardown && !control_plane_ota_runtime_is_idle())) {
|
|
|
|
|
control_plane_ota_set_voice_services_reserved(false);
|
|
|
|
|
return ESP_ERR_INVALID_STATE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
control_plane_multimodal_runtime_state_t multimodal_state = {0};
|
|
|
|
|
bool have_multimodal_state =
|
|
|
|
|
control_plane_multimodal_get_runtime_state(&multimodal_state) == ESP_OK;
|
|
|
|
|
window->restore_multimodal = initial_window ||
|
|
|
|
|
(have_multimodal_state &&
|
|
|
|
|
(multimodal_state.connected || multimodal_state.starting));
|
|
|
|
|
/* Every OTA HTTPS window stops the local wake worker, so restore it after
|
|
|
|
|
* both the initial check and later idle-time checks. */
|
|
|
|
|
window->restore_local_wake = true;
|
|
|
|
|
window->acquired = true;
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
esp_err_t wake_stop_err = control_plane_local_wake_stop(1000);
|
|
|
|
|
if (wake_stop_err != ESP_OK) {
|
|
|
|
|
ESP_LOGW(TAG,
|
|
|
|
|
"OTA voice window deferred until local wake teardown completes: %s",
|
|
|
|
|
esp_err_to_name(wake_stop_err));
|
|
|
|
|
control_plane_ota_set_voice_services_reserved(false);
|
|
|
|
|
window->acquired = false;
|
|
|
|
|
return wake_stop_err;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (have_multimodal_state &&
|
|
|
|
|
(multimodal_state.connected || multimodal_state.starting ||
|
|
|
|
|
multimodal_state.stopping || multimodal_state.has_active_task)) {
|
|
|
|
|
esp_err_t stop_err = control_plane_stop_multimodal_client();
|
|
|
|
|
if (stop_err != ESP_OK) {
|
|
|
|
|
ESP_LOGW(TAG,
|
|
|
|
|
"OTA voice window deferred until multimodal teardown completes: %s",
|
|
|
|
|
esp_err_to_name(stop_err));
|
|
|
|
|
control_plane_ota_set_voice_services_reserved(false);
|
|
|
|
|
window->acquired = false;
|
|
|
|
|
return stop_err;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!control_plane_ota_wait_interruptible(
|
|
|
|
|
CONTROL_PLANE_OTA_TRANSPORT_RELEASE_WAIT_MS)) {
|
|
|
|
|
return ESP_ERR_INVALID_STATE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
domain_ota_memory_headroom_t headroom = {0};
|
|
|
|
|
esp_err_t err = domain_runtime_ota_get_memory_headroom(&headroom);
|
|
|
|
|
if (err != ESP_OK) {
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
ESP_LOGI(TAG,
|
|
|
|
|
"OTA HTTPS window memory: free_internal=%u largest_internal=%u largest_dma=%u",
|
|
|
|
|
(unsigned)headroom.free_internal,
|
|
|
|
|
(unsigned)headroom.largest_internal,
|
|
|
|
|
(unsigned)headroom.largest_dma);
|
|
|
|
|
if (headroom.free_internal < CONFIG_TQ_OTA_MIN_FREE_INTERNAL_BYTES ||
|
|
|
|
|
headroom.largest_dma < CONFIG_TQ_OTA_MIN_LARGEST_DMA_BYTES) {
|
|
|
|
|
ESP_LOGW(TAG,
|
|
|
|
|
"OTA HTTPS window deferred: need free_internal>=%u largest_dma>=%u",
|
|
|
|
|
(unsigned)CONFIG_TQ_OTA_MIN_FREE_INTERNAL_BYTES,
|
|
|
|
|
(unsigned)CONFIG_TQ_OTA_MIN_LARGEST_DMA_BYTES);
|
|
|
|
|
return ESP_ERR_NO_MEM;
|
|
|
|
|
}
|
|
|
|
|
return ESP_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void control_plane_ota_release_voice_window(control_plane_ota_voice_window_t *window)
|
|
|
|
|
{
|
|
|
|
|
if (window == NULL || !window->acquired) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
control_plane_ota_set_voice_services_reserved(false);
|
|
|
|
|
window->acquired = false;
|
|
|
|
|
|
|
|
|
|
ESP_LOGI(TAG,
|
|
|
|
|
"OTA HTTPS window released: restore_multimodal=%d restore_local_wake=%d",
|
|
|
|
|
window->restore_multimodal ? 1 : 0,
|
|
|
|
|
window->restore_local_wake ? 1 : 0);
|
|
|
|
|
|
|
|
|
|
if (control_plane_runtime_is_stopping() || !control_plane_is_sta_connected()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (window->restore_local_wake && control_plane_voice_turn_is_idle() &&
|
|
|
|
|
control_plane_ota_start_default_local_wake()) {
|
|
|
|
|
ESP_LOGI(TAG, "Local wake restore requested after OTA HTTPS window");
|
|
|
|
|
}
|
|
|
|
|
if (window->restore_multimodal || !control_plane_voice_turn_is_idle()) {
|
|
|
|
|
(void)control_plane_schedule_websocket_wait_task();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void control_plane_ota_restore_default_voice_services(void)
|
|
|
|
|
{
|
|
|
|
|
if (control_plane_runtime_is_stopping() || !control_plane_is_sta_connected()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (control_plane_voice_turn_is_idle() &&
|
|
|
|
|
control_plane_ota_start_default_local_wake()) {
|
|
|
|
|
ESP_LOGI(TAG, "Local wake restore requested as the default voice listener");
|
|
|
|
|
}
|
|
|
|
|
(void)control_plane_schedule_websocket_wait_task();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static esp_err_t control_plane_ota_prepare_report(domain_ota_report_request_t *request,
|
|
|
|
|
char *device_id,
|
|
|
|
|
size_t device_id_len,
|
|
|
|
|
char *serial_number,
|
|
|
|
|
size_t serial_number_len,
|
|
|
|
|
char *version,
|
|
|
|
|
size_t version_len,
|
|
|
|
|
char *jwt,
|
|
|
|
|
size_t jwt_len,
|
|
|
|
|
bool force_jwt_refresh)
|
|
|
|
|
{
|
|
|
|
|
domain_runtime_read_device_identity(device_id,
|
|
|
|
|
device_id_len,
|
|
|
|
|
serial_number,
|
|
|
|
|
serial_number_len);
|
|
|
|
|
if (device_id[0] == '\0') {
|
|
|
|
|
return ESP_ERR_NOT_FOUND;
|
|
|
|
|
}
|
|
|
|
|
esp_err_t err = domain_runtime_read_firmware_version(version, version_len);
|
|
|
|
|
if (err != ESP_OK || version[0] == '\0') {
|
|
|
|
|
return err != ESP_OK ? err : ESP_ERR_INVALID_STATE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
control_plane_device_auth_result_t auth_result =
|
|
|
|
|
control_plane_multimodal_device_auth_ensure(
|
|
|
|
|
CONTROL_PLANE_DEVICE_AUTH_SCOPE_OTA_REPORT,
|
|
|
|
|
force_jwt_refresh);
|
|
|
|
|
if (auth_result != CONTROL_PLANE_DEVICE_AUTH_OK) {
|
|
|
|
|
return auth_result == CONTROL_PLANE_DEVICE_AUTH_FATAL ?
|
|
|
|
|
ESP_FAIL : ESP_ERR_INVALID_STATE;
|
|
|
|
|
}
|
|
|
|
|
err = control_plane_multimodal_device_auth_copy_jwt(jwt, jwt_len);
|
|
|
|
|
if (err != ESP_OK) {
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*request = (domain_ota_report_request_t) {
|
|
|
|
|
.report_url = CONFIG_TQ_OTA_REPORT_URL,
|
|
|
|
|
.device_id = device_id,
|
|
|
|
|
.serial_number = serial_number,
|
|
|
|
|
.current_version = version,
|
|
|
|
|
.device_jwt = jwt,
|
|
|
|
|
.internal_auth_secret = CONFIG_TQ_OTA_INTERNAL_AUTH_SECRET,
|
|
|
|
|
.timeout_ms = CONFIG_TQ_OTA_HTTP_TIMEOUT_MS,
|
|
|
|
|
};
|
|
|
|
|
return ESP_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static esp_err_t control_plane_ota_check_once(domain_ota_report_response_t *response,
|
|
|
|
|
bool force_initial_jwt_refresh)
|
|
|
|
|
{
|
|
|
|
|
if (response == NULL) {
|
|
|
|
|
return ESP_ERR_INVALID_ARG;
|
|
|
|
|
}
|
|
|
|
|
control_plane_ota_report_context_t *ctx = NULL;
|
|
|
|
|
#if SOC_SPIRAM_SUPPORTED
|
|
|
|
|
ctx = (control_plane_ota_report_context_t *)heap_caps_calloc(
|
|
|
|
|
1,
|
|
|
|
|
sizeof(*ctx),
|
|
|
|
|
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
|
|
|
|
#endif
|
|
|
|
|
if (ctx == NULL) {
|
|
|
|
|
ctx = (control_plane_ota_report_context_t *)calloc(1, sizeof(*ctx));
|
|
|
|
|
}
|
|
|
|
|
if (ctx == NULL) {
|
|
|
|
|
return ESP_ERR_NO_MEM;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
esp_err_t err = ESP_FAIL;
|
|
|
|
|
for (int attempt = 0; attempt < 2; ++attempt) {
|
|
|
|
|
bool force_jwt_refresh = force_initial_jwt_refresh || attempt > 0;
|
|
|
|
|
err = control_plane_ota_prepare_report(&ctx->request,
|
|
|
|
|
ctx->device_id,
|
|
|
|
|
sizeof(ctx->device_id),
|
|
|
|
|
ctx->serial_number,
|
|
|
|
|
sizeof(ctx->serial_number),
|
|
|
|
|
ctx->version,
|
|
|
|
|
sizeof(ctx->version),
|
|
|
|
|
ctx->jwt,
|
|
|
|
|
sizeof(ctx->jwt),
|
|
|
|
|
force_jwt_refresh);
|
|
|
|
|
if (err != ESP_OK) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = domain_runtime_ota_report(&ctx->request, response);
|
|
|
|
|
bool unauthorized = err == ESP_OK &&
|
|
|
|
|
(response->http_status == 401 || response->http_status == 403);
|
|
|
|
|
if (!unauthorized) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
control_plane_multimodal_device_auth_invalidate(response->http_status);
|
|
|
|
|
if (attempt == 0) {
|
|
|
|
|
ESP_LOGW(TAG,
|
|
|
|
|
"OTA report authorization rejected (http=%d); refreshing JWT and retrying once",
|
|
|
|
|
response->http_status);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
|
|
|
heap_caps_free(ctx);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void control_plane_ota_worker(void *arg)
|
|
|
|
|
{
|
|
|
|
|
(void)arg;
|
|
|
|
|
if (ulTaskNotifyTake(pdTRUE,
|
|
|
|
|
pdMS_TO_TICKS(CONTROL_PLANE_OTA_START_GATE_MS)) == 0U) {
|
|
|
|
|
ESP_LOGE(TAG, "OTA worker start gate timed out");
|
|
|
|
|
portENTER_CRITICAL(&s_ota_lock);
|
|
|
|
|
s_ota_snapshot.running = false;
|
|
|
|
|
s_ota_snapshot.state = CONTROL_PLANE_OTA_STATE_ERROR;
|
|
|
|
|
s_ota_snapshot.last_error = ESP_ERR_TIMEOUT;
|
|
|
|
|
s_ota_voice_services_reserved = false;
|
|
|
|
|
s_ota_task = NULL;
|
|
|
|
|
portEXIT_CRITICAL(&s_ota_lock);
|
|
|
|
|
control_plane_ota_restore_default_voice_services();
|
|
|
|
|
vTaskDelete(NULL);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
control_plane_ota_set_state(CONTROL_PLANE_OTA_STATE_IDLE, ESP_OK, -1);
|
|
|
|
|
bool initial_check_pending = true;
|
|
|
|
|
|
|
|
|
|
if (!control_plane_ota_wait_interruptible(CONTROL_PLANE_OTA_INITIAL_GATE_DELAY_MS)) {
|
|
|
|
|
goto stopped;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (!control_plane_ota_stop_is_requested()) {
|
|
|
|
|
if (!control_plane_is_sta_connected()) {
|
|
|
|
|
control_plane_ota_set_state(CONTROL_PLANE_OTA_STATE_WAITING_FOR_NETWORK,
|
|
|
|
|
ESP_OK,
|
|
|
|
|
-1);
|
|
|
|
|
control_plane_ota_wait_interruptible(1000U);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
control_plane_ota_voice_window_t voice_window = {0};
|
|
|
|
|
esp_err_t err = control_plane_ota_acquire_voice_window(initial_check_pending,
|
|
|
|
|
false,
|
|
|
|
|
&voice_window);
|
|
|
|
|
if (err == ESP_ERR_INVALID_STATE) {
|
|
|
|
|
control_plane_ota_release_voice_window(&voice_window);
|
|
|
|
|
control_plane_ota_set_state(CONTROL_PLANE_OTA_STATE_WAITING_FOR_RESOURCES,
|
|
|
|
|
ESP_OK,
|
|
|
|
|
-1);
|
|
|
|
|
control_plane_ota_wait_interruptible(1000U);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (err != ESP_OK) {
|
|
|
|
|
initial_check_pending = false;
|
|
|
|
|
control_plane_ota_release_voice_window(&voice_window);
|
|
|
|
|
control_plane_ota_note_failure(err, -1);
|
|
|
|
|
control_plane_ota_wait_interruptible(
|
|
|
|
|
control_plane_ota_retry_delay(control_plane_ota_failure_count()));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
control_plane_ota_set_state(CONTROL_PLANE_OTA_STATE_CHECKING, ESP_OK, -1);
|
|
|
|
|
domain_ota_report_response_t *response = NULL;
|
|
|
|
|
#if SOC_SPIRAM_SUPPORTED
|
|
|
|
|
response = (domain_ota_report_response_t *)heap_caps_calloc(
|
|
|
|
|
1,
|
|
|
|
|
sizeof(*response),
|
|
|
|
|
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
|
|
|
|
#endif
|
|
|
|
|
if (response == NULL) {
|
|
|
|
|
response = (domain_ota_report_response_t *)calloc(1, sizeof(*response));
|
|
|
|
|
}
|
|
|
|
|
if (response == NULL) {
|
|
|
|
|
initial_check_pending = false;
|
|
|
|
|
control_plane_ota_release_voice_window(&voice_window);
|
|
|
|
|
control_plane_ota_note_failure(ESP_ERR_NO_MEM, -1);
|
|
|
|
|
control_plane_ota_wait_interruptible(
|
|
|
|
|
control_plane_ota_retry_delay(control_plane_ota_failure_count()));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = control_plane_ota_check_once(response, initial_check_pending);
|
|
|
|
|
control_plane_ota_log_stack_headroom("version_report");
|
|
|
|
|
if (err != ESP_OK || response->outcome == DOMAIN_OTA_REPORT_SERVER_ERROR) {
|
|
|
|
|
initial_check_pending = false;
|
|
|
|
|
control_plane_ota_release_voice_window(&voice_window);
|
|
|
|
|
esp_err_t report_err = err != ESP_OK ? err : ESP_ERR_INVALID_RESPONSE;
|
|
|
|
|
control_plane_ota_note_failure(report_err, response->http_status);
|
|
|
|
|
ESP_LOGW(TAG,
|
|
|
|
|
"OTA version report failed: err=%s http=%d business=%d",
|
|
|
|
|
esp_err_to_name(report_err),
|
|
|
|
|
response->http_status,
|
|
|
|
|
response->business_code);
|
|
|
|
|
heap_caps_free(response);
|
|
|
|
|
control_plane_ota_wait_interruptible(
|
|
|
|
|
control_plane_ota_retry_delay(control_plane_ota_failure_count()));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
control_plane_ota_clear_failures();
|
|
|
|
|
if (response->outcome == DOMAIN_OTA_REPORT_NO_UPDATE) {
|
|
|
|
|
initial_check_pending = false;
|
|
|
|
|
control_plane_ota_release_voice_window(&voice_window);
|
|
|
|
|
control_plane_ota_clear_candidate();
|
|
|
|
|
control_plane_ota_set_state(CONTROL_PLANE_OTA_STATE_IDLE,
|
|
|
|
|
ESP_OK,
|
|
|
|
|
response->http_status);
|
|
|
|
|
ESP_LOGI(TAG,
|
|
|
|
|
"OTA version report complete: running firmware is current (http=%d)",
|
|
|
|
|
response->http_status);
|
|
|
|
|
heap_caps_free(response);
|
|
|
|
|
control_plane_ota_wait_interruptible(CONFIG_TQ_OTA_POLL_INTERVAL_MS);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
control_plane_ota_set_candidate(&response->firmware);
|
|
|
|
|
if (!response->firmware.forced && !response->firmware.confirmed) {
|
|
|
|
|
initial_check_pending = false;
|
|
|
|
|
control_plane_ota_release_voice_window(&voice_window);
|
|
|
|
|
ESP_LOGI(TAG,
|
|
|
|
|
"OTA %s available; waiting for mini-program confirmation",
|
|
|
|
|
response->firmware.version);
|
|
|
|
|
control_plane_ota_set_state(CONTROL_PLANE_OTA_STATE_WAITING_FOR_CONFIRMATION,
|
|
|
|
|
ESP_OK,
|
|
|
|
|
response->http_status);
|
|
|
|
|
heap_caps_free(response);
|
|
|
|
|
control_plane_ota_wait_interruptible(CONFIG_TQ_OTA_CONFIRMATION_POLL_MS);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!control_plane_ota_runtime_is_idle()) {
|
|
|
|
|
ESP_LOGI(TAG, "OTA approved; waiting for active interaction to finish");
|
|
|
|
|
initial_check_pending = false;
|
|
|
|
|
control_plane_ota_release_voice_window(&voice_window);
|
|
|
|
|
heap_caps_free(response);
|
|
|
|
|
control_plane_ota_wait_interruptible(2000U);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ESP_LOGI(TAG,
|
|
|
|
|
"Starting %s OTA update to %s",
|
|
|
|
|
response->firmware.forced ? "forced" : "confirmed",
|
|
|
|
|
response->firmware.version);
|
|
|
|
|
control_plane_ota_set_state(CONTROL_PLANE_OTA_STATE_DOWNLOADING,
|
|
|
|
|
ESP_OK,
|
|
|
|
|
response->http_status);
|
|
|
|
|
domain_runtime_behavior_set_keep_awake(true);
|
|
|
|
|
err = domain_runtime_ota_install(&response->firmware,
|
|
|
|
|
CONFIG_TQ_OTA_INSTALL_TIMEOUT_MS,
|
|
|
|
|
control_plane_ota_progress,
|
|
|
|
|
control_plane_ota_cancel,
|
|
|
|
|
NULL);
|
|
|
|
|
control_plane_ota_log_stack_headroom("firmware_install");
|
|
|
|
|
domain_runtime_behavior_set_keep_awake(false);
|
|
|
|
|
if (err != ESP_OK) {
|
|
|
|
|
initial_check_pending = false;
|
|
|
|
|
control_plane_ota_release_voice_window(&voice_window);
|
|
|
|
|
control_plane_ota_note_failure(err, response->http_status);
|
|
|
|
|
ESP_LOGE(TAG, "OTA install failed: %s", esp_err_to_name(err));
|
|
|
|
|
heap_caps_free(response);
|
|
|
|
|
if (!control_plane_ota_stop_is_requested()) {
|
|
|
|
|
control_plane_ota_wait_interruptible(
|
|
|
|
|
control_plane_ota_retry_delay(control_plane_ota_failure_count()));
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
control_plane_ota_set_state(CONTROL_PLANE_OTA_STATE_READY_TO_REBOOT,
|
|
|
|
|
ESP_OK,
|
|
|
|
|
response->http_status);
|
|
|
|
|
ESP_LOGI(TAG,
|
|
|
|
|
"OTA image ready; restarting into version %s",
|
|
|
|
|
response->firmware.version);
|
|
|
|
|
heap_caps_free(response);
|
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
|
|
|
|
domain_runtime_restart();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stopped:
|
|
|
|
|
control_plane_ota_set_voice_services_reserved(false);
|
|
|
|
|
control_plane_ota_restore_default_voice_services();
|
|
|
|
|
portENTER_CRITICAL(&s_ota_lock);
|
|
|
|
|
s_ota_snapshot.running = false;
|
|
|
|
|
s_ota_snapshot.state = CONTROL_PLANE_OTA_STATE_STOPPED;
|
|
|
|
|
s_ota_task = NULL;
|
|
|
|
|
portEXIT_CRITICAL(&s_ota_lock);
|
|
|
|
|
vTaskDelete(NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
esp_err_t control_plane_start_ota_service(void)
|
|
|
|
|
{
|
|
|
|
|
#if !CONFIG_TQ_OTA_ENABLE
|
|
|
|
|
control_plane_ota_set_state(CONTROL_PLANE_OTA_STATE_DISABLED, ESP_OK, -1);
|
|
|
|
|
return ESP_OK;
|
|
|
|
|
#else
|
|
|
|
|
esp_err_t confirm_err = domain_runtime_ota_confirm_running_image();
|
|
|
|
|
if (confirm_err != ESP_OK) {
|
|
|
|
|
ESP_LOGE(TAG, "Unable to confirm running OTA image: %s", esp_err_to_name(confirm_err));
|
|
|
|
|
return confirm_err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
portENTER_CRITICAL(&s_ota_lock);
|
|
|
|
|
if (s_ota_task != NULL) {
|
|
|
|
|
portEXIT_CRITICAL(&s_ota_lock);
|
|
|
|
|
return ESP_OK;
|
|
|
|
|
}
|
|
|
|
|
s_ota_stop_requested = false;
|
|
|
|
|
/* The initial version report owns the startup network window. Voice is
|
|
|
|
|
* released after the first OTA result (including a bounded failure). */
|
|
|
|
|
s_ota_voice_services_reserved = true;
|
|
|
|
|
s_ota_snapshot.running = true;
|
|
|
|
|
s_ota_snapshot.state = CONTROL_PLANE_OTA_STATE_IDLE;
|
|
|
|
|
s_ota_snapshot.last_error = ESP_OK;
|
|
|
|
|
portEXIT_CRITICAL(&s_ota_lock);
|
|
|
|
|
|
|
|
|
|
TaskHandle_t task = NULL;
|
|
|
|
|
BaseType_t created = control_plane_ota_create_task(&task);
|
|
|
|
|
if (created != pdPASS) {
|
|
|
|
|
portENTER_CRITICAL(&s_ota_lock);
|
|
|
|
|
s_ota_snapshot.running = false;
|
|
|
|
|
s_ota_snapshot.state = CONTROL_PLANE_OTA_STATE_ERROR;
|
|
|
|
|
s_ota_snapshot.last_error = ESP_ERR_NO_MEM;
|
|
|
|
|
s_ota_voice_services_reserved = false;
|
|
|
|
|
portEXIT_CRITICAL(&s_ota_lock);
|
|
|
|
|
return ESP_ERR_NO_MEM;
|
|
|
|
|
}
|
|
|
|
|
portENTER_CRITICAL(&s_ota_lock);
|
|
|
|
|
s_ota_task = task;
|
|
|
|
|
portEXIT_CRITICAL(&s_ota_lock);
|
|
|
|
|
xTaskNotifyGive(task);
|
|
|
|
|
ESP_LOGI(TAG, "OTA service started");
|
|
|
|
|
return ESP_OK;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
esp_err_t control_plane_stop_ota_service(int timeout_ms)
|
|
|
|
|
{
|
|
|
|
|
if (timeout_ms < 0) {
|
|
|
|
|
return ESP_ERR_INVALID_ARG;
|
|
|
|
|
}
|
|
|
|
|
portENTER_CRITICAL(&s_ota_lock);
|
|
|
|
|
s_ota_stop_requested = true;
|
|
|
|
|
TaskHandle_t task = s_ota_task;
|
|
|
|
|
portEXIT_CRITICAL(&s_ota_lock);
|
|
|
|
|
if (task == NULL || timeout_ms == 0) {
|
|
|
|
|
return ESP_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TickType_t start = xTaskGetTickCount();
|
|
|
|
|
TickType_t timeout_ticks = pdMS_TO_TICKS(timeout_ms);
|
|
|
|
|
while (true) {
|
|
|
|
|
portENTER_CRITICAL(&s_ota_lock);
|
|
|
|
|
task = s_ota_task;
|
|
|
|
|
portEXIT_CRITICAL(&s_ota_lock);
|
|
|
|
|
if (task == NULL) {
|
|
|
|
|
return ESP_OK;
|
|
|
|
|
}
|
|
|
|
|
if ((xTaskGetTickCount() - start) >= timeout_ticks) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(CONTROL_PLANE_OTA_STOP_POLL_MS));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ESP_LOGW(TAG, "OTA service stop timeout; forcing worker teardown");
|
|
|
|
|
vTaskDelete(task);
|
|
|
|
|
portENTER_CRITICAL(&s_ota_lock);
|
|
|
|
|
if (s_ota_task == task) {
|
|
|
|
|
s_ota_task = NULL;
|
|
|
|
|
}
|
|
|
|
|
s_ota_snapshot.running = false;
|
|
|
|
|
s_ota_snapshot.state = CONTROL_PLANE_OTA_STATE_STOPPED;
|
|
|
|
|
s_ota_voice_services_reserved = false;
|
|
|
|
|
portEXIT_CRITICAL(&s_ota_lock);
|
|
|
|
|
control_plane_ota_restore_default_voice_services();
|
|
|
|
|
return ESP_ERR_TIMEOUT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
esp_err_t control_plane_get_ota_snapshot(control_plane_ota_snapshot_t *out)
|
|
|
|
|
{
|
|
|
|
|
if (out == NULL) {
|
|
|
|
|
return ESP_ERR_INVALID_ARG;
|
|
|
|
|
}
|
|
|
|
|
portENTER_CRITICAL(&s_ota_lock);
|
|
|
|
|
*out = s_ota_snapshot;
|
|
|
|
|
portEXIT_CRITICAL(&s_ota_lock);
|
|
|
|
|
return ESP_OK;
|
|
|
|
|
}
|