653 lines
22 KiB
C
653 lines
22 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include "control_plane_internal.h"
|
|
|
|
#include "domain.h"
|
|
#include "driver/gpio.h"
|
|
#include "esp_attr.h"
|
|
#include "esp_log.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "sdkconfig.h"
|
|
|
|
#if CONFIG_TQ_BATTERY_ENABLE
|
|
|
|
#define BATTERY_WORKER_STACK_BYTES 4096
|
|
#define BATTERY_WORKER_PRIORITY 2
|
|
#define BATTERY_SUPPRESSED_RETRY_MS 1000
|
|
#define BATTERY_STOP_POLL_MS 10
|
|
#define BATTERY_CHARGE_DEBOUNCE_MS 30
|
|
#define BATTERY_FULL_CONFIRM_RETRY_MS 1000
|
|
#define BATTERY_FULL_CONFIRM_TIME_MS 5000
|
|
#define BATTERY_FULL_CONFIRM_SAMPLES 3U
|
|
#define BATTERY_FULL_MIN_VOLTAGE_MV 4050
|
|
#define BATTERY_SHUTDOWN_CONFIRM_SAMPLES 3U
|
|
#define BATTERY_EMERGENCY_CONFIRM_SAMPLES 2U
|
|
#define BATTERY_SHUTDOWN_RETRY_MS 1000
|
|
#define BATTERY_SHUTDOWN_TASK_STACK_BYTES 6144
|
|
#define BATTERY_SHUTDOWN_TASK_PRIORITY 5
|
|
#define BATTERY_SHUTDOWN_MOTOR_STOP_MS 2000
|
|
#define BATTERY_LOW_PROMPT_FILE "/littlefs/audio_prompts/battery_low_20.opus"
|
|
#define BATTERY_FULL_PROMPT_FILE "/littlefs/audio_prompts/charge_complete.opus"
|
|
|
|
#ifndef CONFIG_TQ_BATTERY_CONTROLLED_SHUTDOWN_ENABLE
|
|
#define CONFIG_TQ_BATTERY_CONTROLLED_SHUTDOWN_ENABLE 0
|
|
#endif
|
|
#ifndef CONFIG_TQ_BATTERY_SHUTDOWN_PROMPT_FILE
|
|
#define CONFIG_TQ_BATTERY_SHUTDOWN_PROMPT_FILE "/littlefs/audio_prompts/shutdown_power_empty.opus"
|
|
#endif
|
|
|
|
#if CONFIG_TQ_BATTERY_CONTROLLED_SHUTDOWN_ENABLE
|
|
#if CONFIG_TQ_BATTERY_EMERGENCY_VOLTAGE_MV >= CONFIG_TQ_BATTERY_SHUTDOWN_VOLTAGE_MV
|
|
#error "Emergency battery voltage must be below the controlled shutdown threshold"
|
|
#endif
|
|
#if CONFIG_TQ_BATTERY_WAKE_RECOVERY_VOLTAGE_MV <= CONFIG_TQ_BATTERY_SHUTDOWN_VOLTAGE_MV
|
|
#error "Battery wake recovery voltage must exceed the shutdown threshold"
|
|
#endif
|
|
#endif
|
|
|
|
#ifndef CONFIG_TQ_LIGHT_STANDBY_BATTERY_INTERVAL_SEC
|
|
#define CONFIG_TQ_LIGHT_STANDBY_BATTERY_INTERVAL_SEC 300
|
|
#endif
|
|
|
|
static const char *TAG = "cp_battery";
|
|
static TaskHandle_t s_battery_task;
|
|
static bool s_stop_requested;
|
|
static bool s_starting;
|
|
static bool s_charge_gpio_isr_registered;
|
|
static bool s_light_standby;
|
|
static TaskHandle_t s_shutdown_task;
|
|
static uint8_t s_shutdown_confirmations;
|
|
static uint8_t s_emergency_confirmations;
|
|
|
|
typedef enum {
|
|
BATTERY_POWER_STATE_RUNNING = 0,
|
|
BATTERY_POWER_STATE_SHUTDOWN_REQUESTED,
|
|
BATTERY_POWER_STATE_SHUTTING_DOWN,
|
|
} battery_power_state_t;
|
|
|
|
static battery_power_state_t s_power_state = BATTERY_POWER_STATE_RUNNING;
|
|
static portMUX_TYPE s_battery_lock = portMUX_INITIALIZER_UNLOCKED;
|
|
|
|
static void IRAM_ATTR battery_charge_gpio_isr(void *arg)
|
|
{
|
|
(void)arg;
|
|
TaskHandle_t task = s_battery_task;
|
|
if (task == NULL) {
|
|
return;
|
|
}
|
|
|
|
BaseType_t higher_priority_task_woken = pdFALSE;
|
|
vTaskNotifyGiveFromISR(task, &higher_priority_task_woken);
|
|
if (higher_priority_task_woken == pdTRUE) {
|
|
portYIELD_FROM_ISR();
|
|
}
|
|
}
|
|
|
|
static esp_err_t battery_charge_interrupt_start(void)
|
|
{
|
|
if (CONFIG_TQ_BATTERY_CHARGE_GPIO < 0) {
|
|
return ESP_ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
esp_err_t err = gpio_set_intr_type((gpio_num_t)CONFIG_TQ_BATTERY_CHARGE_GPIO,
|
|
GPIO_INTR_ANYEDGE);
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
err = gpio_isr_handler_add((gpio_num_t)CONFIG_TQ_BATTERY_CHARGE_GPIO,
|
|
battery_charge_gpio_isr,
|
|
NULL);
|
|
if (err != ESP_OK) {
|
|
(void)gpio_set_intr_type((gpio_num_t)CONFIG_TQ_BATTERY_CHARGE_GPIO,
|
|
GPIO_INTR_DISABLE);
|
|
return err;
|
|
}
|
|
|
|
s_charge_gpio_isr_registered = true;
|
|
return ESP_OK;
|
|
}
|
|
|
|
static void battery_charge_interrupt_stop(void)
|
|
{
|
|
if (!s_charge_gpio_isr_registered || CONFIG_TQ_BATTERY_CHARGE_GPIO < 0) {
|
|
return;
|
|
}
|
|
|
|
(void)gpio_intr_disable((gpio_num_t)CONFIG_TQ_BATTERY_CHARGE_GPIO);
|
|
(void)gpio_isr_handler_remove((gpio_num_t)CONFIG_TQ_BATTERY_CHARGE_GPIO);
|
|
(void)gpio_set_intr_type((gpio_num_t)CONFIG_TQ_BATTERY_CHARGE_GPIO,
|
|
GPIO_INTR_DISABLE);
|
|
s_charge_gpio_isr_registered = false;
|
|
}
|
|
|
|
static const char *charge_state_name(domain_battery_charge_state_t state)
|
|
{
|
|
switch (state) {
|
|
case DOMAIN_BATTERY_CHARGE_CHARGING:
|
|
return "charging";
|
|
case DOMAIN_BATTERY_CHARGE_NOT_CHARGING:
|
|
return "not_charging";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
static const char *level_state_name(domain_battery_level_state_t state)
|
|
{
|
|
switch (state) {
|
|
case DOMAIN_BATTERY_LEVEL_NORMAL:
|
|
return "normal";
|
|
case DOMAIN_BATTERY_LEVEL_LOW:
|
|
return "low";
|
|
case DOMAIN_BATTERY_LEVEL_CRITICAL:
|
|
return "critical";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
static bool battery_stop_requested(void)
|
|
{
|
|
bool requested;
|
|
portENTER_CRITICAL(&s_battery_lock);
|
|
requested = s_stop_requested;
|
|
portEXIT_CRITICAL(&s_battery_lock);
|
|
return requested;
|
|
}
|
|
|
|
static bool battery_light_standby(void)
|
|
{
|
|
bool standby;
|
|
portENTER_CRITICAL(&s_battery_lock);
|
|
standby = s_light_standby;
|
|
portEXIT_CRITICAL(&s_battery_lock);
|
|
return standby;
|
|
}
|
|
|
|
static void battery_worker_finish(void)
|
|
{
|
|
portENTER_CRITICAL(&s_battery_lock);
|
|
s_battery_task = NULL;
|
|
s_stop_requested = false;
|
|
s_starting = false;
|
|
portEXIT_CRITICAL(&s_battery_lock);
|
|
}
|
|
|
|
static void battery_shutdown_task(void *arg)
|
|
{
|
|
(void)arg;
|
|
portENTER_CRITICAL(&s_battery_lock);
|
|
s_power_state = BATTERY_POWER_STATE_SHUTTING_DOWN;
|
|
portEXIT_CRITICAL(&s_battery_lock);
|
|
|
|
ESP_LOGW(TAG, "Controlled low-battery shutdown started");
|
|
control_plane_set_runtime_stop_requested(true);
|
|
(void)control_plane_local_wake_stop(BATTERY_SHUTDOWN_MOTOR_STOP_MS);
|
|
(void)control_plane_stop_ota_service(BATTERY_SHUTDOWN_MOTOR_STOP_MS);
|
|
(void)control_plane_stop_multimodal_client();
|
|
control_plane_stop_key_input_task();
|
|
control_plane_stop_system_event_task();
|
|
|
|
esp_err_t motor_err = domain_direction_release_motor_hold(
|
|
BATTERY_SHUTDOWN_MOTOR_STOP_MS);
|
|
if (motor_err != ESP_OK) {
|
|
ESP_LOGW(TAG, "Release stepper before shutdown failed: %s",
|
|
esp_err_to_name(motor_err));
|
|
}
|
|
|
|
(void)domain_runtime_multimodal_audio_stop_downstream_playback();
|
|
for (;;) {
|
|
esp_err_t audio_init_err = domain_runtime_multimodal_audio_init();
|
|
esp_err_t prompt_err = audio_init_err == ESP_OK
|
|
? domain_runtime_behavior_play_prompt_file(
|
|
CONFIG_TQ_BATTERY_SHUTDOWN_PROMPT_FILE)
|
|
: audio_init_err;
|
|
if (prompt_err == ESP_OK) {
|
|
break;
|
|
}
|
|
|
|
/* The product requires the complete prompt before entering deep sleep. */
|
|
ESP_LOGE(TAG,
|
|
"Shutdown prompt did not complete; retrying: %s",
|
|
esp_err_to_name(prompt_err));
|
|
vTaskDelay(pdMS_TO_TICKS(BATTERY_SHUTDOWN_RETRY_MS));
|
|
}
|
|
|
|
(void)domain_runtime_multimodal_audio_set_speaker_output_enabled(false);
|
|
domain_runtime_multimodal_audio_deinit();
|
|
(void)domain_runtime_set_lcd_backlight_brightness(0U);
|
|
(void)domain_runtime_wifi_portal_stop_sta();
|
|
|
|
ESP_LOGW(TAG, "Shutdown prompt completed; entering deep sleep");
|
|
esp_err_t sleep_err = domain_battery_prepare_shutdown();
|
|
ESP_LOGE(TAG, "Enter deep sleep failed: %s", esp_err_to_name(sleep_err));
|
|
|
|
portENTER_CRITICAL(&s_battery_lock);
|
|
s_power_state = BATTERY_POWER_STATE_RUNNING;
|
|
s_shutdown_task = NULL;
|
|
portEXIT_CRITICAL(&s_battery_lock);
|
|
vTaskDelete(NULL);
|
|
}
|
|
|
|
static void battery_consider_controlled_shutdown(
|
|
const domain_battery_status_t *status)
|
|
{
|
|
#if CONFIG_TQ_BATTERY_CONTROLLED_SHUTDOWN_ENABLE
|
|
if (status == NULL) {
|
|
return;
|
|
}
|
|
|
|
portENTER_CRITICAL(&s_battery_lock);
|
|
bool already_requested = s_power_state != BATTERY_POWER_STATE_RUNNING;
|
|
portEXIT_CRITICAL(&s_battery_lock);
|
|
if (already_requested) {
|
|
return;
|
|
}
|
|
|
|
if (status->charge_state == DOMAIN_BATTERY_CHARGE_CHARGING) {
|
|
s_shutdown_confirmations = 0U;
|
|
s_emergency_confirmations = 0U;
|
|
return;
|
|
}
|
|
|
|
bool normal_sample = status->valid && !status->measurement_suppressed &&
|
|
status->percentage <= CONFIG_TQ_BATTERY_CRITICAL_ENTER_PERCENT &&
|
|
status->voltage_mv <= CONFIG_TQ_BATTERY_SHUTDOWN_VOLTAGE_MV;
|
|
bool emergency_sample = status->latest_sample_valid &&
|
|
status->latest_sample_mv <= CONFIG_TQ_BATTERY_EMERGENCY_VOLTAGE_MV;
|
|
|
|
if (normal_sample) {
|
|
if (s_shutdown_confirmations < UINT8_MAX) {
|
|
++s_shutdown_confirmations;
|
|
}
|
|
} else {
|
|
s_shutdown_confirmations = 0U;
|
|
}
|
|
if (emergency_sample) {
|
|
if (s_emergency_confirmations < UINT8_MAX) {
|
|
++s_emergency_confirmations;
|
|
}
|
|
} else {
|
|
s_emergency_confirmations = 0U;
|
|
}
|
|
|
|
if (s_shutdown_confirmations < BATTERY_SHUTDOWN_CONFIRM_SAMPLES &&
|
|
s_emergency_confirmations < BATTERY_EMERGENCY_CONFIRM_SAMPLES) {
|
|
return;
|
|
}
|
|
|
|
portENTER_CRITICAL(&s_battery_lock);
|
|
if (s_power_state != BATTERY_POWER_STATE_RUNNING || s_shutdown_task != NULL) {
|
|
portEXIT_CRITICAL(&s_battery_lock);
|
|
return;
|
|
}
|
|
s_power_state = BATTERY_POWER_STATE_SHUTDOWN_REQUESTED;
|
|
portEXIT_CRITICAL(&s_battery_lock);
|
|
|
|
TaskHandle_t task = NULL;
|
|
BaseType_t created = xTaskCreate(battery_shutdown_task,
|
|
"cp_bat_shutdown",
|
|
BATTERY_SHUTDOWN_TASK_STACK_BYTES,
|
|
NULL,
|
|
BATTERY_SHUTDOWN_TASK_PRIORITY,
|
|
&task);
|
|
portENTER_CRITICAL(&s_battery_lock);
|
|
if (created == pdPASS && task != NULL) {
|
|
s_shutdown_task = task;
|
|
} else {
|
|
s_power_state = BATTERY_POWER_STATE_RUNNING;
|
|
}
|
|
portEXIT_CRITICAL(&s_battery_lock);
|
|
if (created != pdPASS || task == NULL) {
|
|
ESP_LOGE(TAG, "Create controlled-shutdown task failed");
|
|
}
|
|
#else
|
|
(void)status;
|
|
#endif
|
|
}
|
|
|
|
static void control_plane_battery_low_hook(
|
|
const domain_battery_status_t *status)
|
|
{
|
|
if (status == NULL) {
|
|
return;
|
|
}
|
|
|
|
ESP_LOGI(TAG,
|
|
"Low-battery prompt start level=%u%% file=%s",
|
|
(unsigned)status->percentage,
|
|
BATTERY_LOW_PROMPT_FILE);
|
|
esp_err_t prompt_err = domain_runtime_behavior_play_prompt_file(
|
|
BATTERY_LOW_PROMPT_FILE);
|
|
if (prompt_err != ESP_OK) {
|
|
ESP_LOGW(TAG,
|
|
"Low-battery prompt failed: %s",
|
|
esp_err_to_name(prompt_err));
|
|
}
|
|
}
|
|
|
|
static void control_plane_battery_full_hint_hook(
|
|
const domain_battery_status_t *status)
|
|
{
|
|
if (status == NULL) {
|
|
return;
|
|
}
|
|
|
|
ESP_LOGI(TAG,
|
|
"Inferred charge-complete prompt start voltage=%dmV file=%s",
|
|
status->voltage_mv,
|
|
BATTERY_FULL_PROMPT_FILE);
|
|
esp_err_t prompt_err = domain_runtime_behavior_play_prompt_file(
|
|
BATTERY_FULL_PROMPT_FILE);
|
|
if (prompt_err != ESP_OK) {
|
|
ESP_LOGW(TAG,
|
|
"Charge-complete prompt failed: %s",
|
|
esp_err_to_name(prompt_err));
|
|
}
|
|
}
|
|
|
|
static void battery_worker(void *arg)
|
|
{
|
|
(void)arg;
|
|
(void)ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
|
domain_battery_level_state_t previous_level = DOMAIN_BATTERY_LEVEL_UNKNOWN;
|
|
domain_battery_charge_state_t previous_charge = DOMAIN_BATTERY_CHARGE_UNKNOWN;
|
|
bool full_hint_candidate = false;
|
|
TickType_t full_hint_started_at = 0;
|
|
uint8_t full_hint_confirmations = 0U;
|
|
|
|
esp_err_t init_err = domain_battery_init();
|
|
if (init_err != ESP_OK) {
|
|
ESP_LOGE(TAG, "Battery service initialization failed: %s", esp_err_to_name(init_err));
|
|
battery_worker_finish();
|
|
vTaskDelete(NULL);
|
|
return;
|
|
}
|
|
|
|
esp_err_t interrupt_err = battery_charge_interrupt_start();
|
|
if (interrupt_err == ESP_OK) {
|
|
ESP_LOGI(TAG,
|
|
"TP4057 charge-state interrupt ready gpio=%d debounce=%dms",
|
|
CONFIG_TQ_BATTERY_CHARGE_GPIO,
|
|
BATTERY_CHARGE_DEBOUNCE_MS);
|
|
} else if (interrupt_err != ESP_ERR_NOT_SUPPORTED) {
|
|
ESP_LOGW(TAG,
|
|
"TP4057 charge-state interrupt unavailable; using polling: %s",
|
|
esp_err_to_name(interrupt_err));
|
|
}
|
|
|
|
ESP_LOGI(TAG, "Battery monitoring worker started");
|
|
while (!battery_stop_requested()) {
|
|
esp_err_t sample_err = domain_battery_sample();
|
|
domain_battery_status_t status = {0};
|
|
esp_err_t status_err = domain_battery_get_status(&status);
|
|
|
|
if (sample_err != ESP_OK) {
|
|
ESP_LOGW(TAG, "Battery sample rejected: %s", esp_err_to_name(sample_err));
|
|
}
|
|
if (status_err == ESP_OK) {
|
|
esp_err_t overlay_err = domain_runtime_update_battery_overlay(&status);
|
|
if (overlay_err != ESP_OK && overlay_err != ESP_ERR_NOT_SUPPORTED) {
|
|
ESP_LOGD(TAG, "Battery LCD overlay update skipped: %s", esp_err_to_name(overlay_err));
|
|
}
|
|
|
|
if (status.valid && !status.measurement_suppressed) {
|
|
ESP_LOGI(TAG,
|
|
"Battery adc=%dmV voltage=%dmV level=%u%% charge=%s state=%s",
|
|
status.latest_adc_mv,
|
|
status.voltage_mv,
|
|
(unsigned)status.percentage,
|
|
charge_state_name(status.charge_state),
|
|
level_state_name(status.level_state));
|
|
} else if (status.measurement_suppressed) {
|
|
ESP_LOGD(TAG,
|
|
"Battery SOC update suppressed adc=%dmV raw=%dmV valid=%d load=%d cooldown=%d",
|
|
status.latest_adc_mv,
|
|
status.latest_sample_mv,
|
|
status.latest_sample_valid,
|
|
status.high_load_active,
|
|
status.cooldown_active);
|
|
}
|
|
|
|
if (status.level_state != previous_level) {
|
|
ESP_LOGW(TAG,
|
|
"Battery level state changed: %s -> %s",
|
|
level_state_name(previous_level),
|
|
level_state_name(status.level_state));
|
|
if (status.level_state == DOMAIN_BATTERY_LEVEL_LOW &&
|
|
(previous_level == DOMAIN_BATTERY_LEVEL_NORMAL ||
|
|
previous_level == DOMAIN_BATTERY_LEVEL_UNKNOWN)) {
|
|
control_plane_battery_low_hook(&status);
|
|
}
|
|
previous_level = status.level_state;
|
|
}
|
|
if (status.charge_state != previous_charge) {
|
|
ESP_LOGI(TAG,
|
|
"Battery charge state changed: %s -> %s",
|
|
charge_state_name(previous_charge),
|
|
charge_state_name(status.charge_state));
|
|
if (previous_charge == DOMAIN_BATTERY_CHARGE_CHARGING &&
|
|
status.charge_state == DOMAIN_BATTERY_CHARGE_NOT_CHARGING) {
|
|
/*
|
|
* TP4057 exposes the same high level for charge termination
|
|
* and charger removal. Voltage can only make this a useful
|
|
* UI/audio hint, not a rigorous full-charge measurement.
|
|
* Never use this inference for safety, shutdown, or power
|
|
* switching decisions.
|
|
*/
|
|
full_hint_candidate = true;
|
|
full_hint_started_at = xTaskGetTickCount();
|
|
full_hint_confirmations = 0U;
|
|
} else if (status.charge_state != DOMAIN_BATTERY_CHARGE_NOT_CHARGING) {
|
|
full_hint_candidate = false;
|
|
full_hint_confirmations = 0U;
|
|
}
|
|
previous_charge = status.charge_state;
|
|
}
|
|
|
|
if (full_hint_candidate && status.valid &&
|
|
!status.measurement_suppressed) {
|
|
if (status.charge_state != DOMAIN_BATTERY_CHARGE_NOT_CHARGING ||
|
|
status.voltage_mv < BATTERY_FULL_MIN_VOLTAGE_MV) {
|
|
ESP_LOGI(TAG,
|
|
"Charge-complete inference cancelled charge=%s voltage=%dmV",
|
|
charge_state_name(status.charge_state),
|
|
status.voltage_mv);
|
|
full_hint_candidate = false;
|
|
full_hint_confirmations = 0U;
|
|
} else {
|
|
if (full_hint_confirmations < UINT8_MAX) {
|
|
++full_hint_confirmations;
|
|
}
|
|
TickType_t elapsed_ticks =
|
|
xTaskGetTickCount() - full_hint_started_at;
|
|
if (full_hint_confirmations >= BATTERY_FULL_CONFIRM_SAMPLES &&
|
|
elapsed_ticks >= pdMS_TO_TICKS(BATTERY_FULL_CONFIRM_TIME_MS)) {
|
|
control_plane_battery_full_hint_hook(&status);
|
|
full_hint_candidate = false;
|
|
full_hint_confirmations = 0U;
|
|
}
|
|
}
|
|
}
|
|
|
|
battery_consider_controlled_shutdown(&status);
|
|
}
|
|
|
|
uint32_t delay_ms;
|
|
if (status_err == ESP_OK && status.measurement_suppressed) {
|
|
delay_ms = BATTERY_SUPPRESSED_RETRY_MS;
|
|
} else if (full_hint_candidate || s_shutdown_confirmations > 0U ||
|
|
s_emergency_confirmations > 0U) {
|
|
delay_ms = BATTERY_FULL_CONFIRM_RETRY_MS;
|
|
} else if (battery_light_standby()) {
|
|
delay_ms = (uint32_t)CONFIG_TQ_LIGHT_STANDBY_BATTERY_INTERVAL_SEC * 1000U;
|
|
} else {
|
|
delay_ms = (uint32_t)CONFIG_TQ_BATTERY_SAMPLE_INTERVAL_SEC * 1000U;
|
|
}
|
|
uint32_t notifications = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(delay_ms));
|
|
if (notifications > 0U && !battery_stop_requested()) {
|
|
vTaskDelay(pdMS_TO_TICKS(BATTERY_CHARGE_DEBOUNCE_MS));
|
|
/* Collapse contact bounce into one fresh read of the final level. */
|
|
(void)ulTaskNotifyTake(pdTRUE, 0U);
|
|
}
|
|
}
|
|
|
|
battery_charge_interrupt_stop();
|
|
ESP_LOGI(TAG, "Battery monitoring worker stopped");
|
|
battery_worker_finish();
|
|
vTaskDelete(NULL);
|
|
}
|
|
|
|
esp_err_t control_plane_battery_apply_boot_guard(void)
|
|
{
|
|
#if CONFIG_TQ_BATTERY_CONTROLLED_SHUTDOWN_ENABLE
|
|
if (!domain_battery_woke_from_deep_sleep()) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t sample_err = domain_battery_sample();
|
|
if (sample_err != ESP_OK) {
|
|
return sample_err;
|
|
}
|
|
domain_battery_status_t status = {0};
|
|
esp_err_t status_err = domain_battery_get_status(&status);
|
|
if (status_err != ESP_OK) {
|
|
return status_err;
|
|
}
|
|
|
|
ESP_LOGW(TAG,
|
|
"Deep-sleep wake battery check voltage=%dmV charge=%s valid=%d",
|
|
status.voltage_mv,
|
|
charge_state_name(status.charge_state),
|
|
status.valid);
|
|
if (status.charge_state == DOMAIN_BATTERY_CHARGE_CHARGING ||
|
|
(status.valid &&
|
|
status.voltage_mv >= CONFIG_TQ_BATTERY_WAKE_RECOVERY_VOLTAGE_MV)) {
|
|
domain_battery_complete_wakeup();
|
|
return ESP_OK;
|
|
}
|
|
|
|
ESP_LOGW(TAG, "Battery has not recovered; returning to deep sleep");
|
|
return domain_battery_prepare_shutdown();
|
|
#else
|
|
return ESP_ERR_NOT_SUPPORTED;
|
|
#endif
|
|
}
|
|
|
|
esp_err_t control_plane_battery_service_start(void)
|
|
{
|
|
portENTER_CRITICAL(&s_battery_lock);
|
|
if (s_battery_task != NULL || s_starting) {
|
|
portEXIT_CRITICAL(&s_battery_lock);
|
|
return ESP_OK;
|
|
}
|
|
s_stop_requested = false;
|
|
s_starting = true;
|
|
s_light_standby = false;
|
|
portEXIT_CRITICAL(&s_battery_lock);
|
|
|
|
TaskHandle_t task = NULL;
|
|
BaseType_t created = xTaskCreate(battery_worker,
|
|
"cp_battery",
|
|
BATTERY_WORKER_STACK_BYTES,
|
|
NULL,
|
|
BATTERY_WORKER_PRIORITY,
|
|
&task);
|
|
if (created != pdPASS || task == NULL) {
|
|
portENTER_CRITICAL(&s_battery_lock);
|
|
s_starting = false;
|
|
portEXIT_CRITICAL(&s_battery_lock);
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
portENTER_CRITICAL(&s_battery_lock);
|
|
s_battery_task = task;
|
|
s_starting = false;
|
|
portEXIT_CRITICAL(&s_battery_lock);
|
|
xTaskNotifyGive(task);
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t control_plane_battery_service_stop(int timeout_ms)
|
|
{
|
|
if (timeout_ms < 0) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
TaskHandle_t task;
|
|
portENTER_CRITICAL(&s_battery_lock);
|
|
task = s_battery_task;
|
|
s_stop_requested = task != NULL;
|
|
portEXIT_CRITICAL(&s_battery_lock);
|
|
if (task == NULL) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
xTaskNotifyGive(task);
|
|
int waited_ms = 0;
|
|
while (waited_ms < timeout_ms) {
|
|
portENTER_CRITICAL(&s_battery_lock);
|
|
task = s_battery_task;
|
|
portEXIT_CRITICAL(&s_battery_lock);
|
|
if (task == NULL) {
|
|
return ESP_OK;
|
|
}
|
|
vTaskDelay(pdMS_TO_TICKS(BATTERY_STOP_POLL_MS));
|
|
waited_ms += BATTERY_STOP_POLL_MS;
|
|
}
|
|
|
|
portENTER_CRITICAL(&s_battery_lock);
|
|
task = s_battery_task;
|
|
s_battery_task = NULL;
|
|
s_stop_requested = false;
|
|
portEXIT_CRITICAL(&s_battery_lock);
|
|
if (task != NULL) {
|
|
ESP_LOGW(TAG, "Battery worker stop timeout; forcing teardown");
|
|
vTaskDelete(task);
|
|
battery_charge_interrupt_stop();
|
|
return ESP_ERR_TIMEOUT;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
void control_plane_battery_service_set_light_standby(bool standby)
|
|
{
|
|
TaskHandle_t task;
|
|
portENTER_CRITICAL(&s_battery_lock);
|
|
s_light_standby = standby;
|
|
task = s_battery_task;
|
|
portEXIT_CRITICAL(&s_battery_lock);
|
|
if (task != NULL) {
|
|
xTaskNotifyGive(task);
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
esp_err_t control_plane_battery_service_start(void)
|
|
{
|
|
return ESP_ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
esp_err_t control_plane_battery_apply_boot_guard(void)
|
|
{
|
|
return ESP_ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
esp_err_t control_plane_battery_service_stop(int timeout_ms)
|
|
{
|
|
return timeout_ms < 0 ? ESP_ERR_INVALID_ARG : ESP_OK;
|
|
}
|
|
|
|
void control_plane_battery_service_set_light_standby(bool standby)
|
|
{
|
|
(void)standby;
|
|
}
|
|
|
|
#endif
|