feat(control_plane): apply light standby hardware policy

This commit is contained in:
chenjintao
2026-08-21 13:55:32 +08:00
parent 6a11aca835
commit baad9dcfa0
15 changed files with 405 additions and 41 deletions
@@ -142,9 +142,9 @@ esp_err_t control_plane_prepare_runtime_storage(void);
/**
* @brief Start the control-plane owned light-standby coordinator.
*
* The coordinator tracks one inactivity deadline for the full runtime. Phase
* one records state transitions only; hardware power policies are attached in
* later phases. Repeated starts are idempotent.
* The coordinator tracks one inactivity deadline for the full runtime and
* applies configured local hardware power policies. Repeated starts are
* idempotent.
*/
esp_err_t control_plane_light_standby_start(void);
@@ -159,8 +159,9 @@ esp_err_t control_plane_light_standby_stop(int timeout_ms);
/**
* @brief Record user activity and return the coordinator to ACTIVE state.
*
* This call is non-blocking and safe from control-plane worker tasks. It must
* not be called directly from an ISR.
* If hardware is in light standby, this call restores configured hardware
* before returning. Transition serialization is bounded; the API must not be
* called directly from an ISR.
*/
esp_err_t control_plane_light_standby_record_activity(
control_plane_light_standby_activity_t activity);
@@ -13,6 +13,7 @@
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "sdkconfig.h"
@@ -25,19 +26,41 @@
#endif
#ifndef CONFIG_TQ_LIGHT_STANDBY_TASK_STACK_BYTES
#define CONFIG_TQ_LIGHT_STANDBY_TASK_STACK_BYTES 3072
#define CONFIG_TQ_LIGHT_STANDBY_TASK_STACK_BYTES 4096
#endif
#ifndef CONFIG_TQ_LIGHT_STANDBY_TASK_PRIORITY
#define CONFIG_TQ_LIGHT_STANDBY_TASK_PRIORITY 3
#endif
#ifndef CONFIG_TQ_LIGHT_STANDBY_DIM_BACKLIGHT
#define CONFIG_TQ_LIGHT_STANDBY_DIM_BACKLIGHT 0
#endif
#ifndef CONFIG_TQ_LIGHT_STANDBY_BACKLIGHT_PERCENT
#define CONFIG_TQ_LIGHT_STANDBY_BACKLIGHT_PERCENT 10
#endif
#ifndef CONFIG_TQ_LIGHT_STANDBY_DISABLE_SPEAKER_OUTPUT
#define CONFIG_TQ_LIGHT_STANDBY_DISABLE_SPEAKER_OUTPUT 0
#endif
#ifndef CONFIG_TQ_LIGHT_STANDBY_RELEASE_MOTOR_HOLD
#define CONFIG_TQ_LIGHT_STANDBY_RELEASE_MOTOR_HOLD 0
#endif
#ifndef CONFIG_TQ_SCREEN_BACKLIGHT_BRIGHTNESS_PERCENT
#define CONFIG_TQ_SCREEN_BACKLIGHT_BRIGHTNESS_PERCENT 100
#endif
#ifndef CONFIG_TQ_LOCAL_TASK_CORE
#define CONFIG_TQ_LOCAL_TASK_CORE 1
#endif
#define CONTROL_PLANE_LIGHT_STANDBY_ELIGIBILITY_RECHECK_MS 1000U
#define CONTROL_PLANE_LIGHT_STANDBY_MOTOR_RELEASE_WAIT_MS 200U
#define CONTROL_PLANE_LIGHT_STANDBY_STOP_POLL_MS 20U
#define CONTROL_PLANE_LIGHT_STANDBY_TRANSITION_WAIT_MS 1000U
#if CONFIG_TQ_LOCAL_TASK_CORE < 0
#define CONTROL_PLANE_LIGHT_STANDBY_TASK_CORE tskNO_AFFINITY
@@ -59,6 +82,8 @@ typedef struct {
static const char *TAG = "cp_light_standby";
static control_plane_light_standby_runtime_t s_standby;
static portMUX_TYPE s_standby_lock = portMUX_INITIALIZER_UNLOCKED;
static StaticSemaphore_t s_transition_lock_storage;
static SemaphoreHandle_t s_transition_lock;
static uint64_t control_plane_light_standby_now_ms(void)
{
@@ -139,10 +164,103 @@ static bool control_plane_light_standby_runtime_is_eligible(void)
!state.local_responding_started;
}
static bool control_plane_light_standby_lock_transition(void)
{
return s_transition_lock != NULL &&
xSemaphoreTake(
s_transition_lock,
pdMS_TO_TICKS(CONTROL_PLANE_LIGHT_STANDBY_TRANSITION_WAIT_MS)) == pdTRUE;
}
static void control_plane_light_standby_unlock_transition(void)
{
if (s_transition_lock != NULL) {
(void)xSemaphoreGive(s_transition_lock);
}
}
static esp_err_t control_plane_light_standby_apply_wake_policy(void)
{
esp_err_t first_error = ESP_OK;
#if CONFIG_TQ_LIGHT_STANDBY_DIM_BACKLIGHT
esp_err_t backlight_err = domain_runtime_set_lcd_backlight_brightness(
CONFIG_TQ_SCREEN_BACKLIGHT_BRIGHTNESS_PERCENT);
if (backlight_err != ESP_OK) {
ESP_LOGE(TAG,
"Restore backlight failed: %s",
esp_err_to_name(backlight_err));
first_error = backlight_err;
}
#endif
#if CONFIG_TQ_LIGHT_STANDBY_DISABLE_SPEAKER_OUTPUT
esp_err_t speaker_err =
domain_runtime_multimodal_audio_set_speaker_output_enabled(true);
if (speaker_err != ESP_OK) {
ESP_LOGE(TAG,
"Restore speaker output failed: %s",
esp_err_to_name(speaker_err));
if (first_error == ESP_OK) {
first_error = speaker_err;
}
}
#endif
return first_error;
}
static esp_err_t control_plane_light_standby_apply_entry_policy(void)
{
#if CONFIG_TQ_LIGHT_STANDBY_RELEASE_MOTOR_HOLD
esp_err_t motor_err = domain_direction_release_motor_hold(
CONTROL_PLANE_LIGHT_STANDBY_MOTOR_RELEASE_WAIT_MS);
if (motor_err != ESP_OK) {
ESP_LOGE(TAG,
"Release motor hold failed: %s",
esp_err_to_name(motor_err));
return motor_err;
}
#endif
#if CONFIG_TQ_LIGHT_STANDBY_DISABLE_SPEAKER_OUTPUT
esp_err_t speaker_err =
domain_runtime_multimodal_audio_set_speaker_output_enabled(false);
if (speaker_err != ESP_OK) {
ESP_LOGE(TAG,
"Disable speaker output failed: %s",
esp_err_to_name(speaker_err));
return speaker_err;
}
#endif
#if CONFIG_TQ_LIGHT_STANDBY_DIM_BACKLIGHT
esp_err_t backlight_err = domain_runtime_set_lcd_backlight_brightness(
CONFIG_TQ_LIGHT_STANDBY_BACKLIGHT_PERCENT);
if (backlight_err != ESP_OK) {
ESP_LOGE(TAG,
"Dim backlight failed: %s",
esp_err_to_name(backlight_err));
#if CONFIG_TQ_LIGHT_STANDBY_DISABLE_SPEAKER_OUTPUT
(void)domain_runtime_multimodal_audio_set_speaker_output_enabled(true);
#endif
return backlight_err;
}
#endif
return ESP_OK;
}
static void control_plane_light_standby_enter(uint64_t now_ms)
{
if (!control_plane_light_standby_lock_transition()) {
ESP_LOGW(TAG, "Enter transition lock timed out");
return;
}
control_plane_light_standby_state_t from = CONTROL_PLANE_LIGHT_STANDBY_STATE_ACTIVE;
bool entering = false;
uint64_t entry_activity_ms = 0U;
portENTER_CRITICAL(&s_standby_lock);
if (s_standby.running && !s_standby.stop_requested &&
@@ -150,6 +268,7 @@ static void control_plane_light_standby_enter(uint64_t now_ms)
(now_ms - s_standby.last_activity_ms) >=
(uint64_t)CONFIG_TQ_LIGHT_STANDBY_IDLE_TIMEOUT_MS) {
from = s_standby.state;
entry_activity_ms = s_standby.last_activity_ms;
s_standby.state = CONTROL_PLANE_LIGHT_STANDBY_STATE_ENTERING;
s_standby.transition_count++;
entering = true;
@@ -157,20 +276,37 @@ static void control_plane_light_standby_enter(uint64_t now_ms)
portEXIT_CRITICAL(&s_standby_lock);
if (!entering) {
control_plane_light_standby_unlock_transition();
return;
}
control_plane_light_standby_log_transition(from,
CONTROL_PLANE_LIGHT_STANDBY_STATE_ENTERING,
"idle_timeout");
esp_err_t policy_err = control_plane_light_standby_apply_entry_policy();
bool entered = false;
bool restore_required = false;
const char *completion_reason = "hardware_policy";
portENTER_CRITICAL(&s_standby_lock);
if (s_standby.state == CONTROL_PLANE_LIGHT_STANDBY_STATE_ENTERING &&
!s_standby.stop_requested) {
!s_standby.stop_requested &&
s_standby.last_activity_ms == entry_activity_ms &&
policy_err == ESP_OK) {
s_standby.state = CONTROL_PLANE_LIGHT_STANDBY_STATE_STANDBY;
s_standby.standby_since_ms = now_ms;
s_standby.transition_count++;
entered = true;
} else if (s_standby.state == CONTROL_PLANE_LIGHT_STANDBY_STATE_ENTERING) {
s_standby.state = CONTROL_PLANE_LIGHT_STANDBY_STATE_WAKING;
s_standby.transition_count++;
restore_required = true;
if (policy_err != ESP_OK) {
completion_reason = "hardware_policy_failed";
} else if (s_standby.stop_requested) {
completion_reason = "stop_requested";
} else {
completion_reason = "activity_during_entry";
}
}
portEXIT_CRITICAL(&s_standby_lock);
@@ -178,8 +314,38 @@ static void control_plane_light_standby_enter(uint64_t now_ms)
control_plane_light_standby_log_transition(
CONTROL_PLANE_LIGHT_STANDBY_STATE_ENTERING,
CONTROL_PLANE_LIGHT_STANDBY_STATE_STANDBY,
"phase1_state_only");
completion_reason);
control_plane_light_standby_unlock_transition();
return;
}
control_plane_light_standby_log_transition(
CONTROL_PLANE_LIGHT_STANDBY_STATE_ENTERING,
CONTROL_PLANE_LIGHT_STANDBY_STATE_WAKING,
completion_reason);
if (restore_required) {
(void)control_plane_light_standby_apply_wake_policy();
}
bool activated = false;
portENTER_CRITICAL(&s_standby_lock);
if (s_standby.state == CONTROL_PLANE_LIGHT_STANDBY_STATE_WAKING) {
s_standby.state = CONTROL_PLANE_LIGHT_STANDBY_STATE_ACTIVE;
s_standby.standby_since_ms = 0U;
if (policy_err != ESP_OK) {
s_standby.last_activity_ms = now_ms;
}
s_standby.transition_count++;
activated = true;
}
portEXIT_CRITICAL(&s_standby_lock);
if (activated) {
control_plane_light_standby_log_transition(
CONTROL_PLANE_LIGHT_STANDBY_STATE_WAKING,
CONTROL_PLANE_LIGHT_STANDBY_STATE_ACTIVE,
completion_reason);
}
control_plane_light_standby_unlock_transition();
}
static void control_plane_light_standby_task(void *arg)
@@ -232,6 +398,38 @@ static void control_plane_light_standby_task(void *arg)
(void)ulTaskNotifyTake(pdTRUE, wait_ticks);
}
if (control_plane_light_standby_lock_transition()) {
control_plane_light_standby_state_t from =
CONTROL_PLANE_LIGHT_STANDBY_STATE_ACTIVE;
bool restore_required = false;
portENTER_CRITICAL(&s_standby_lock);
if (s_standby.state == CONTROL_PLANE_LIGHT_STANDBY_STATE_STANDBY ||
s_standby.state == CONTROL_PLANE_LIGHT_STANDBY_STATE_ENTERING ||
s_standby.state == CONTROL_PLANE_LIGHT_STANDBY_STATE_WAKING) {
from = s_standby.state;
s_standby.state = CONTROL_PLANE_LIGHT_STANDBY_STATE_WAKING;
s_standby.transition_count++;
restore_required = true;
}
portEXIT_CRITICAL(&s_standby_lock);
if (restore_required) {
control_plane_light_standby_log_transition(
from,
CONTROL_PLANE_LIGHT_STANDBY_STATE_WAKING,
"coordinator_stop");
esp_err_t wake_err = control_plane_light_standby_apply_wake_policy();
if (wake_err != ESP_OK) {
ESP_LOGW(TAG,
"Hardware restore during stop failed: %s",
esp_err_to_name(wake_err));
}
}
control_plane_light_standby_unlock_transition();
} else {
ESP_LOGW(TAG, "Stop transition lock timed out");
}
portENTER_CRITICAL(&s_standby_lock);
s_standby.task = NULL;
s_standby.running = false;
@@ -247,6 +445,13 @@ esp_err_t control_plane_light_standby_start(void)
#if !CONFIG_TQ_LIGHT_STANDBY_ENABLE
return ESP_ERR_NOT_SUPPORTED;
#else
if (s_transition_lock == NULL) {
s_transition_lock = xSemaphoreCreateMutexStatic(&s_transition_lock_storage);
if (s_transition_lock == NULL) {
return ESP_ERR_NO_MEM;
}
}
uint64_t now_ms = control_plane_light_standby_now_ms();
portENTER_CRITICAL(&s_standby_lock);
if (s_standby.running) {
@@ -348,9 +553,8 @@ esp_err_t control_plane_light_standby_record_activity(
#if !CONFIG_TQ_LIGHT_STANDBY_ENABLE
return ESP_ERR_NOT_SUPPORTED;
#else
control_plane_light_standby_state_t from = CONTROL_PLANE_LIGHT_STANDBY_STATE_ACTIVE;
TaskHandle_t task = NULL;
bool waking = false;
bool wake_required = false;
uint64_t now_ms = control_plane_light_standby_now_ms();
portENTER_CRITICAL(&s_standby_lock);
@@ -363,6 +567,27 @@ esp_err_t control_plane_light_standby_record_activity(
task = s_standby.task;
if (s_standby.state == CONTROL_PLANE_LIGHT_STANDBY_STATE_STANDBY ||
s_standby.state == CONTROL_PLANE_LIGHT_STANDBY_STATE_ENTERING) {
wake_required = true;
}
portEXIT_CRITICAL(&s_standby_lock);
if (task != NULL) {
xTaskNotifyGive(task);
}
if (!wake_required) {
return ESP_OK;
}
if (!control_plane_light_standby_lock_transition()) {
ESP_LOGE(TAG, "Wake transition lock timed out");
return ESP_ERR_TIMEOUT;
}
control_plane_light_standby_state_t from = CONTROL_PLANE_LIGHT_STANDBY_STATE_ACTIVE;
bool waking = false;
portENTER_CRITICAL(&s_standby_lock);
if (s_standby.running && !s_standby.stop_requested &&
(s_standby.state == CONTROL_PLANE_LIGHT_STANDBY_STATE_STANDBY ||
s_standby.state == CONTROL_PLANE_LIGHT_STANDBY_STATE_ENTERING)) {
from = s_standby.state;
s_standby.state = CONTROL_PLANE_LIGHT_STANDBY_STATE_WAKING;
s_standby.transition_count++;
@@ -370,32 +595,36 @@ esp_err_t control_plane_light_standby_record_activity(
}
portEXIT_CRITICAL(&s_standby_lock);
if (waking) {
const char *reason = control_plane_light_standby_activity_name(activity);
control_plane_light_standby_log_transition(
from,
CONTROL_PLANE_LIGHT_STANDBY_STATE_WAKING,
reason);
bool activated = false;
portENTER_CRITICAL(&s_standby_lock);
if (s_standby.state == CONTROL_PLANE_LIGHT_STANDBY_STATE_WAKING) {
s_standby.state = CONTROL_PLANE_LIGHT_STANDBY_STATE_ACTIVE;
s_standby.standby_since_ms = 0U;
s_standby.transition_count++;
activated = true;
}
portEXIT_CRITICAL(&s_standby_lock);
if (activated) {
control_plane_light_standby_log_transition(
CONTROL_PLANE_LIGHT_STANDBY_STATE_WAKING,
CONTROL_PLANE_LIGHT_STANDBY_STATE_ACTIVE,
reason);
}
if (!waking) {
control_plane_light_standby_unlock_transition();
return ESP_OK;
}
if (task != NULL) {
xTaskNotifyGive(task);
const char *reason = control_plane_light_standby_activity_name(activity);
control_plane_light_standby_log_transition(
from,
CONTROL_PLANE_LIGHT_STANDBY_STATE_WAKING,
reason);
esp_err_t wake_err = control_plane_light_standby_apply_wake_policy();
bool activated = false;
portENTER_CRITICAL(&s_standby_lock);
if (s_standby.state == CONTROL_PLANE_LIGHT_STANDBY_STATE_WAKING) {
s_standby.state = CONTROL_PLANE_LIGHT_STANDBY_STATE_ACTIVE;
s_standby.standby_since_ms = 0U;
s_standby.transition_count++;
activated = true;
}
portEXIT_CRITICAL(&s_standby_lock);
if (activated) {
control_plane_light_standby_log_transition(
CONTROL_PLANE_LIGHT_STANDBY_STATE_WAKING,
CONTROL_PLANE_LIGHT_STANDBY_STATE_ACTIVE,
wake_err == ESP_OK ? reason : "hardware_restore_failed");
}
control_plane_light_standby_unlock_transition();
if (wake_err != ESP_OK) {
return wake_err;
}
return ESP_OK;
#endif