feat(ota): defer updates when battery is low

This commit is contained in:
chenjintao
2026-08-20 17:26:11 +08:00
parent 8d3d66c45f
commit 0542aadaa4
6 changed files with 369 additions and 0 deletions
@@ -69,6 +69,7 @@ typedef enum {
CONTROL_PLANE_OTA_STATE_WAITING_FOR_RESOURCES,
CONTROL_PLANE_OTA_STATE_CHECKING,
CONTROL_PLANE_OTA_STATE_WAITING_FOR_CONFIRMATION,
CONTROL_PLANE_OTA_STATE_WAITING_FOR_POWER,
CONTROL_PLANE_OTA_STATE_DOWNLOADING,
CONTROL_PLANE_OTA_STATE_READY_TO_REBOOT,
CONTROL_PLANE_OTA_STATE_ERROR,
@@ -16,6 +16,7 @@
#include "esp_attr.h"
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
@@ -57,6 +58,12 @@
#ifndef CONFIG_TQ_OTA_MIN_LARGEST_DMA_BYTES
#define CONFIG_TQ_OTA_MIN_LARGEST_DMA_BYTES 4096
#endif
#ifndef CONFIG_TQ_OTA_MIN_BATTERY_PERCENT
#define CONFIG_TQ_OTA_MIN_BATTERY_PERCENT 20
#endif
#ifndef CONFIG_TQ_OTA_POWER_RETRY_MS
#define CONFIG_TQ_OTA_POWER_RETRY_MS 60000
#endif
#ifndef CONFIG_TQ_NETWORK_TASK_CORE
#define CONFIG_TQ_NETWORK_TASK_CORE 0
#endif
@@ -87,6 +94,12 @@ typedef struct {
domain_ota_report_request_t request;
} control_plane_ota_report_context_t;
typedef enum {
CONTROL_PLANE_OTA_POWER_READY = 0,
CONTROL_PLANE_OTA_POWER_LOW,
CONTROL_PLANE_OTA_POWER_UNAVAILABLE,
} control_plane_ota_power_state_t;
static const char *TAG = "cp_ota";
static portMUX_TYPE s_ota_lock = portMUX_INITIALIZER_UNLOCKED;
static TaskHandle_t s_ota_task;
@@ -310,6 +323,49 @@ static bool control_plane_ota_runtime_is_idle(void)
!state.local_responding_started;
}
static control_plane_ota_power_state_t control_plane_ota_get_power_state(
domain_battery_status_t *out_status,
int64_t *out_age_ms,
esp_err_t *out_error)
{
if (out_status == NULL || out_age_ms == NULL || out_error == NULL) {
return CONTROL_PLANE_OTA_POWER_UNAVAILABLE;
}
memset(out_status, 0, sizeof(*out_status));
*out_age_ms = -1;
*out_error = ESP_OK;
#if CONFIG_TQ_BATTERY_ENABLE
esp_err_t err = domain_battery_get_status(out_status);
if (err != ESP_OK) {
*out_error = err;
return CONTROL_PLANE_OTA_POWER_UNAVAILABLE;
}
int64_t now_ms = esp_timer_get_time() / 1000;
if (out_status->last_valid_sample_time_ms > 0 &&
now_ms >= out_status->last_valid_sample_time_ms) {
*out_age_ms = now_ms - out_status->last_valid_sample_time_ms;
}
int64_t max_age_ms = (int64_t)CONFIG_TQ_BATTERY_SAMPLE_INTERVAL_SEC * 3000LL;
if (max_age_ms < 30000LL) {
max_age_ms = 30000LL;
}
if (!out_status->valid || *out_age_ms < 0 || *out_age_ms > max_age_ms) {
return CONTROL_PLANE_OTA_POWER_UNAVAILABLE;
}
if (out_status->percentage < CONFIG_TQ_OTA_MIN_BATTERY_PERCENT) {
return CONTROL_PLANE_OTA_POWER_LOW;
}
#else
(void)out_status;
#endif
return CONTROL_PLANE_OTA_POWER_READY;
}
static void control_plane_ota_set_voice_services_reserved(bool reserved)
{
portENTER_CRITICAL(&s_ota_lock);
@@ -720,6 +776,39 @@ static void control_plane_ota_worker(void *arg)
continue;
}
domain_battery_status_t battery = {0};
int64_t battery_age_ms = -1;
esp_err_t battery_err = ESP_OK;
control_plane_ota_power_state_t power_state =
control_plane_ota_get_power_state(&battery,
&battery_age_ms,
&battery_err);
if (power_state != CONTROL_PLANE_OTA_POWER_READY) {
initial_check_pending = false;
control_plane_ota_release_voice_window(&voice_window);
control_plane_ota_set_state(CONTROL_PLANE_OTA_STATE_WAITING_FOR_POWER,
ESP_OK,
response->http_status);
if (power_state == CONTROL_PLANE_OTA_POWER_LOW) {
ESP_LOGW(TAG,
"OTA %s deferred: battery=%u%% minimum=%u%% charging=%d",
response->firmware.version,
(unsigned)battery.percentage,
(unsigned)CONFIG_TQ_OTA_MIN_BATTERY_PERCENT,
battery.charge_state == DOMAIN_BATTERY_CHARGE_CHARGING ? 1 : 0);
} else {
ESP_LOGW(TAG,
"OTA %s deferred: battery status unavailable valid=%d age_ms=%lld err=%s",
response->firmware.version,
battery.valid ? 1 : 0,
(long long)battery_age_ms,
esp_err_to_name(battery_err));
}
heap_caps_free(response);
control_plane_ota_wait_interruptible(CONFIG_TQ_OTA_POWER_RETRY_MS);
continue;
}
if (!control_plane_ota_runtime_is_idle()) {
ESP_LOGI(TAG, "OTA approved; waiting for active interaction to finish");
initial_check_pending = false;