feat(ux): improve wifi, voice, and print feedback flows
This commit is contained in:
@@ -153,6 +153,20 @@ typedef struct {
|
||||
int64_t finished_ms;
|
||||
} print_job_info_t;
|
||||
|
||||
typedef enum {
|
||||
PRINTER_JOB_EVENT_QUEUED = 0,
|
||||
PRINTER_JOB_EVENT_RUNNING,
|
||||
PRINTER_JOB_EVENT_PROGRESS,
|
||||
PRINTER_JOB_EVENT_FINISHED,
|
||||
} printer_job_event_type_t;
|
||||
|
||||
typedef struct {
|
||||
printer_job_event_type_t type;
|
||||
print_job_info_t job;
|
||||
} printer_job_event_t;
|
||||
|
||||
typedef void (*printer_job_event_cb_t)(const printer_job_event_t *event, void *user_data);
|
||||
|
||||
typedef struct {
|
||||
// Printer-only option: bypass paper/temp/battery precheck for this print job.
|
||||
bool ignore_precheck;
|
||||
@@ -200,6 +214,7 @@ bool printer_protocol_get_job(uint32_t job_id, print_job_info_t *out_info);
|
||||
esp_err_t printer_protocol_list_jobs(print_job_info_t *out_jobs, size_t max_jobs, size_t *out_count);
|
||||
esp_err_t printer_protocol_cancel_job(uint32_t job_id, char *err, size_t err_len);
|
||||
size_t printer_protocol_cleanup_jobs(bool include_success, bool include_failed, bool include_canceled);
|
||||
esp_err_t printer_protocol_set_event_callback(printer_job_event_cb_t cb, void *user_data);
|
||||
|
||||
esp_err_t printer_protocol_gap_move(uint32_t timeout_ms, char *err, size_t err_len);
|
||||
|
||||
@@ -237,6 +252,7 @@ esp_err_t voice_interaction_stop(char *err, size_t err_len);
|
||||
|
||||
esp_err_t voice_interaction_tap_start(char *err, size_t err_len);
|
||||
esp_err_t voice_interaction_tap_cancel(char *err, size_t err_len);
|
||||
esp_err_t voice_interaction_schedule_session_prewarm(void);
|
||||
|
||||
void voice_interaction_get_status(voice_interaction_status_t *out_status);
|
||||
const char *voice_interaction_dialog_state_str(voice_dialog_state_t state);
|
||||
|
||||
@@ -67,6 +67,7 @@ extern bool s_protocol_stopping;
|
||||
int printer_protocol_find_job_idx_locked(uint32_t id);
|
||||
int printer_protocol_alloc_job_slot_locked(void);
|
||||
bool printer_protocol_is_terminal_state(print_job_state_t state);
|
||||
void printer_protocol_emit_job_event(printer_job_event_type_t event_type, const job_slot_t *job);
|
||||
|
||||
void printer_protocol_worker_task(void *arg);
|
||||
bool printer_protocol_is_stopping(void);
|
||||
|
||||
@@ -35,6 +35,9 @@ bool s_protocol_initialized;
|
||||
bool s_protocol_stopping;
|
||||
|
||||
static const char *TAG = "printer_protocol";
|
||||
static printer_job_event_cb_t s_job_event_cb;
|
||||
static void *s_job_event_user_data;
|
||||
static portMUX_TYPE s_job_event_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
#if CONFIG_FREERTOS_UNICORE
|
||||
#define PRINT_WORKER_CORE_ID 0
|
||||
@@ -46,6 +49,47 @@ static esp_err_t ensure_printer_driver_ready(void) {
|
||||
return platform_printer_init();
|
||||
}
|
||||
|
||||
static void printer_protocol_fill_job_info_from_slot(const job_slot_t *slot, print_job_info_t *out_info) {
|
||||
if (slot == NULL || out_info == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(out_info, 0, sizeof(*out_info));
|
||||
out_info->id = slot->id;
|
||||
out_info->state = slot->state;
|
||||
out_info->progress = slot->progress;
|
||||
out_info->width = slot->width;
|
||||
out_info->height = slot->height;
|
||||
out_info->data_len = slot->data_len;
|
||||
out_info->created_ms = slot->created_ms;
|
||||
out_info->started_ms = slot->started_ms;
|
||||
out_info->finished_ms = slot->finished_ms;
|
||||
strlcpy(out_info->density, slot->density, sizeof(out_info->density));
|
||||
strlcpy(out_info->error, slot->error, sizeof(out_info->error));
|
||||
}
|
||||
|
||||
void printer_protocol_emit_job_event(printer_job_event_type_t event_type, const job_slot_t *job) {
|
||||
if (job == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
printer_job_event_cb_t cb = NULL;
|
||||
void *user_data = NULL;
|
||||
portENTER_CRITICAL(&s_job_event_lock);
|
||||
cb = s_job_event_cb;
|
||||
user_data = s_job_event_user_data;
|
||||
portEXIT_CRITICAL(&s_job_event_lock);
|
||||
if (cb == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
printer_job_event_t event = {
|
||||
.type = event_type,
|
||||
};
|
||||
printer_protocol_fill_job_info_from_slot(job, &event.job);
|
||||
cb(&event, user_data);
|
||||
}
|
||||
|
||||
static void refresh_printer_status_locked(void) {
|
||||
platform_printer_sensors_t sensors = {0};
|
||||
if (platform_printer_get_sensors(&sensors) == ESP_OK) {
|
||||
@@ -429,3 +473,11 @@ void printer_protocol_get_runtime_status(printer_runtime_status_t *out_status) {
|
||||
xSemaphoreGive(s_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t printer_protocol_set_event_callback(printer_job_event_cb_t cb, void *user_data) {
|
||||
portENTER_CRITICAL(&s_job_event_lock);
|
||||
s_job_event_cb = cb;
|
||||
s_job_event_user_data = user_data;
|
||||
portEXIT_CRITICAL(&s_job_event_lock);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -140,6 +140,7 @@ esp_err_t printer_protocol_submit_raster_job_ex(const uint8_t *raster,
|
||||
s_jobs[idx].data = copy;
|
||||
strlcpy(s_jobs[idx].density, density != NULL ? density : "中等", sizeof(s_jobs[idx].density));
|
||||
bool ignore_precheck = s_jobs[idx].ignore_precheck;
|
||||
job_slot_t queued_snapshot = s_jobs[idx];
|
||||
|
||||
xSemaphoreGive(s_mutex);
|
||||
|
||||
@@ -177,6 +178,7 @@ esp_err_t printer_protocol_submit_raster_job_ex(const uint8_t *raster,
|
||||
density != NULL ? density : "中等",
|
||||
ignore_precheck,
|
||||
(unsigned)queue_depth);
|
||||
printer_protocol_emit_job_event(PRINTER_JOB_EVENT_QUEUED, &queued_snapshot);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -271,6 +273,7 @@ esp_err_t printer_protocol_list_jobs(print_job_info_t *out_jobs, size_t max_jobs
|
||||
|
||||
esp_err_t printer_protocol_cancel_job(uint32_t job_id, char *err, size_t err_len) {
|
||||
bool canceled_finalized = false;
|
||||
job_slot_t canceled_snapshot = {0};
|
||||
|
||||
if (job_id == 0) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
@@ -308,16 +311,18 @@ esp_err_t printer_protocol_cancel_job(uint32_t job_id, char *err, size_t err_len
|
||||
s_jobs[idx].state = PRINT_JOB_STATE_CANCELED;
|
||||
s_jobs[idx].finished_ms = esp_timer_get_time() / 1000;
|
||||
s_jobs[idx].progress = 0;
|
||||
strlcpy(s_jobs[idx].error, "job canceled", sizeof(s_jobs[idx].error));
|
||||
strlcpy(s_jobs[idx].error, "Job canceled.", sizeof(s_jobs[idx].error));
|
||||
free(s_jobs[idx].data);
|
||||
s_jobs[idx].data = NULL;
|
||||
canceled_finalized = true;
|
||||
canceled_snapshot = s_jobs[idx];
|
||||
} else if (s_jobs[idx].state == PRINT_JOB_STATE_RUNNING) {
|
||||
strlcpy(s_jobs[idx].error, "cancel requested", sizeof(s_jobs[idx].error));
|
||||
strlcpy(s_jobs[idx].error, "Cancel requested.", sizeof(s_jobs[idx].error));
|
||||
}
|
||||
|
||||
xSemaphoreGive(s_mutex);
|
||||
if (canceled_finalized) {
|
||||
printer_protocol_emit_job_event(PRINTER_JOB_EVENT_FINISHED, &canceled_snapshot);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,38 @@
|
||||
|
||||
static const char *TAG = "printer_protocol";
|
||||
|
||||
static const char *printer_user_error_message(const char *raw_err,
|
||||
esp_err_t rc,
|
||||
bool canceled) {
|
||||
if (canceled) {
|
||||
return "Job canceled.";
|
||||
}
|
||||
|
||||
if (raw_err != NULL && raw_err[0] != '\0') {
|
||||
if (strstr(raw_err, "out of paper") != NULL) {
|
||||
return "Out of paper. Load paper and retry.";
|
||||
}
|
||||
if (strstr(raw_err, "battery too low") != NULL) {
|
||||
return "Battery too low. Charge and retry.";
|
||||
}
|
||||
if (strstr(raw_err, "temperature out of range") != NULL) {
|
||||
return "Printer temperature abnormal. Wait and retry.";
|
||||
}
|
||||
if (strstr(raw_err, "sensor read failed") != NULL) {
|
||||
return "Sensor read failed. Retry.";
|
||||
}
|
||||
if (strstr(raw_err, "print timeout") != NULL) {
|
||||
return "Printing timed out. Retry.";
|
||||
}
|
||||
return raw_err;
|
||||
}
|
||||
|
||||
if (rc == ESP_ERR_TIMEOUT) {
|
||||
return "Printing timed out. Retry.";
|
||||
}
|
||||
return "Printing failed. Retry.";
|
||||
}
|
||||
|
||||
static uint16_t density_to_hot_time(const char *density) {
|
||||
if (density == NULL) {
|
||||
return 2000;
|
||||
@@ -66,7 +98,9 @@ static bool precheck_printer_ready(bool ignore_precheck, char *err, size_t err_l
|
||||
static bool run_print_job_printer_driver(job_slot_t *job) {
|
||||
char check_err[96];
|
||||
if (!precheck_printer_ready(job->ignore_precheck, check_err, sizeof(check_err))) {
|
||||
snprintf(job->error, sizeof(job->error), "%s", check_err);
|
||||
strlcpy(job->error,
|
||||
printer_user_error_message(check_err, ESP_FAIL, false),
|
||||
sizeof(job->error));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -84,30 +118,38 @@ static bool run_print_job_printer_driver(job_slot_t *job) {
|
||||
.cancel_flag = &job->cancel_requested,
|
||||
};
|
||||
|
||||
bool emit_progress = false;
|
||||
job_slot_t progress_snapshot = {0};
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) == pdTRUE) {
|
||||
job->progress = 10;
|
||||
progress_snapshot = *job;
|
||||
emit_progress = true;
|
||||
xSemaphoreGive(s_mutex);
|
||||
}
|
||||
if (emit_progress) {
|
||||
printer_protocol_emit_job_event(PRINTER_JOB_EVENT_PROGRESS, &progress_snapshot);
|
||||
}
|
||||
|
||||
char print_err[96] = {0};
|
||||
esp_err_t rc = platform_printer_print(&req, print_err, sizeof(print_err));
|
||||
if (rc != ESP_OK) {
|
||||
if (print_err[0] != 0) {
|
||||
snprintf(job->error, sizeof(job->error), "%s", print_err);
|
||||
} else if (rc == ESP_ERR_TIMEOUT) {
|
||||
snprintf(job->error, sizeof(job->error), "print timeout");
|
||||
} else if (rc == ESP_ERR_INVALID_STATE && job->cancel_requested) {
|
||||
snprintf(job->error, sizeof(job->error), "job canceled");
|
||||
} else {
|
||||
snprintf(job->error, sizeof(job->error), "printer print failed");
|
||||
}
|
||||
strlcpy(job->error,
|
||||
printer_user_error_message(print_err, rc, (rc == ESP_ERR_INVALID_STATE && job->cancel_requested)),
|
||||
sizeof(job->error));
|
||||
return false;
|
||||
}
|
||||
|
||||
emit_progress = false;
|
||||
memset(&progress_snapshot, 0, sizeof(progress_snapshot));
|
||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) == pdTRUE) {
|
||||
job->progress = 95;
|
||||
progress_snapshot = *job;
|
||||
emit_progress = true;
|
||||
xSemaphoreGive(s_mutex);
|
||||
}
|
||||
if (emit_progress) {
|
||||
printer_protocol_emit_job_event(PRINTER_JOB_EVENT_PROGRESS, &progress_snapshot);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -122,7 +164,7 @@ static bool run_print_job(job_slot_t *job) {
|
||||
job->ignore_precheck);
|
||||
|
||||
if (printer_protocol_is_stopping()) {
|
||||
snprintf(job->error, sizeof(job->error), "protocol stopping");
|
||||
strlcpy(job->error, "Service is stopping. Job canceled.", sizeof(job->error));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -180,6 +222,7 @@ void printer_protocol_worker_task(void *arg) {
|
||||
job->started_ms = esp_timer_get_time() / 1000;
|
||||
job->progress = 1;
|
||||
s_busy_refcnt = 1;
|
||||
job_slot_t running_snapshot = *job;
|
||||
int64_t queue_wait_ms = 0;
|
||||
if (job->started_ms >= job->created_ms) {
|
||||
queue_wait_ms = job->started_ms - job->created_ms;
|
||||
@@ -193,6 +236,7 @@ void printer_protocol_worker_task(void *arg) {
|
||||
(unsigned)job->height,
|
||||
job->density);
|
||||
xSemaphoreGive(s_mutex);
|
||||
printer_protocol_emit_job_event(PRINTER_JOB_EVENT_RUNNING, &running_snapshot);
|
||||
|
||||
bool ok = run_print_job(job);
|
||||
|
||||
@@ -214,10 +258,13 @@ void printer_protocol_worker_task(void *arg) {
|
||||
} else if (s_jobs[idx].cancel_requested || printer_protocol_is_stopping()) {
|
||||
s_jobs[idx].state = PRINT_JOB_STATE_CANCELED;
|
||||
if (s_jobs[idx].error[0] == 0) {
|
||||
strlcpy(s_jobs[idx].error, "job canceled", sizeof(s_jobs[idx].error));
|
||||
strlcpy(s_jobs[idx].error, "Job canceled.", sizeof(s_jobs[idx].error));
|
||||
}
|
||||
} else {
|
||||
s_jobs[idx].state = PRINT_JOB_STATE_FAILED;
|
||||
strlcpy(s_jobs[idx].error,
|
||||
printer_user_error_message(s_jobs[idx].error, ESP_FAIL, false),
|
||||
sizeof(s_jobs[idx].error));
|
||||
}
|
||||
|
||||
int64_t duration_ms = 0;
|
||||
@@ -239,9 +286,11 @@ void printer_protocol_worker_task(void *arg) {
|
||||
s_jobs[idx].error[0] != 0 ? s_jobs[idx].error : "-");
|
||||
}
|
||||
|
||||
job_slot_t finished_snapshot = s_jobs[idx];
|
||||
free(s_jobs[idx].data);
|
||||
s_jobs[idx].data = NULL;
|
||||
xSemaphoreGive(s_mutex);
|
||||
printer_protocol_emit_job_event(PRINTER_JOB_EVENT_FINISHED, &finished_snapshot);
|
||||
}
|
||||
|
||||
if (s_evt != NULL) {
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
|
||||
static const char *TAG = "system_runtime";
|
||||
|
||||
#define SYSTEM_RUNTIME_WIFI_HINT_TIMEOUT_MS 220
|
||||
#define SYSTEM_RUNTIME_WIFI_HINT_TIMEOUT_MS 3000
|
||||
#define SYSTEM_RUNTIME_WIFI_STATUS_TIMEOUT_MS 3000
|
||||
|
||||
static bool s_wifi_hint_shown;
|
||||
static bool s_power_key_ready;
|
||||
@@ -59,6 +60,24 @@ static esp_err_t system_runtime_power_key_init(void) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void system_runtime_show_status_text(const char *text, uint32_t timeout_ms) {
|
||||
if (text == NULL || text[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
char preview_err[96] = {0};
|
||||
esp_err_t preview_rc = screen_preview_show_text(text,
|
||||
timeout_ms,
|
||||
preview_err,
|
||||
sizeof(preview_err));
|
||||
if (preview_rc != ESP_OK && preview_rc != ESP_ERR_NOT_SUPPORTED) {
|
||||
ESP_LOGW(TAG,
|
||||
"status_preview_failed: rc=0x%x msg=%s",
|
||||
(unsigned)preview_rc,
|
||||
preview_err[0] != '\0' ? preview_err : "show text failed");
|
||||
}
|
||||
}
|
||||
|
||||
static void system_runtime_show_wifi_hint(void) {
|
||||
if (s_wifi_hint_shown) {
|
||||
return;
|
||||
@@ -76,17 +95,7 @@ static void system_runtime_show_wifi_hint(void) {
|
||||
"未检测到Wi-Fi配置\n连接热点: %s\n打开: http://192.168.4.1",
|
||||
ap_ssid);
|
||||
|
||||
char preview_err[96] = {0};
|
||||
esp_err_t preview_rc = screen_preview_show_text(hint,
|
||||
SYSTEM_RUNTIME_WIFI_HINT_TIMEOUT_MS,
|
||||
preview_err,
|
||||
sizeof(preview_err));
|
||||
if (preview_rc != ESP_OK && preview_rc != ESP_ERR_NOT_SUPPORTED) {
|
||||
ESP_LOGW(TAG,
|
||||
"wifi_hint_preview_failed: rc=0x%x msg=%s",
|
||||
(unsigned)preview_rc,
|
||||
preview_err[0] != '\0' ? preview_err : "show text failed");
|
||||
}
|
||||
system_runtime_show_status_text(hint, SYSTEM_RUNTIME_WIFI_HINT_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
static void system_runtime_try_show_wifi_hint_for_unprovisioned(void) {
|
||||
@@ -97,8 +106,32 @@ static void system_runtime_try_show_wifi_hint_for_unprovisioned(void) {
|
||||
|
||||
static void system_runtime_wifi_event_handler(wifi_manager_event_t event, void *user_data) {
|
||||
(void)user_data;
|
||||
if (event == WIFI_MANAGER_EVENT_PROVISIONING_STARTED) {
|
||||
system_runtime_show_wifi_hint();
|
||||
switch (event) {
|
||||
case WIFI_MANAGER_EVENT_PROVISIONING_STARTED:
|
||||
system_runtime_show_wifi_hint();
|
||||
break;
|
||||
case WIFI_MANAGER_EVENT_STA_CONNECTING:
|
||||
system_runtime_show_status_text("正在连接Wi-Fi...\n请稍候", SYSTEM_RUNTIME_WIFI_STATUS_TIMEOUT_MS);
|
||||
break;
|
||||
case WIFI_MANAGER_EVENT_STA_CONNECTED: {
|
||||
char ip[20] = {0};
|
||||
wifi_manager_get_ip(ip, sizeof(ip));
|
||||
char text[96] = {0};
|
||||
snprintf(text,
|
||||
sizeof(text),
|
||||
"Wi-Fi已连接\nIP: %s",
|
||||
ip[0] != '\0' ? ip : "0.0.0.0");
|
||||
system_runtime_show_status_text(text, SYSTEM_RUNTIME_WIFI_STATUS_TIMEOUT_MS);
|
||||
break;
|
||||
}
|
||||
case WIFI_MANAGER_EVENT_STA_CONNECT_FAILED:
|
||||
system_runtime_show_status_text("Wi-Fi连接失败\n请检查密码/信号后重试", SYSTEM_RUNTIME_WIFI_STATUS_TIMEOUT_MS);
|
||||
break;
|
||||
case WIFI_MANAGER_EVENT_STA_DISCONNECTED:
|
||||
system_runtime_show_status_text("Wi-Fi已断开\n正在重连...", SYSTEM_RUNTIME_WIFI_STATUS_TIMEOUT_MS);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,15 @@
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char *TAG = "voice_interaction";
|
||||
static TaskHandle_t s_voice_prewarm_task;
|
||||
static bool s_voice_prewarm_task_with_caps;
|
||||
static volatile bool s_voice_prewarm_stop_requested;
|
||||
|
||||
#define VOICE_PREWARM_TASK_STACK 6144
|
||||
#define VOICE_PREWARM_CONNECT_TIMEOUT_MS 3000
|
||||
#define VOICE_PREWARM_WIFI_POLL_MS 300
|
||||
#define VOICE_PREWARM_RETRY_DELAY_MS 1500
|
||||
#define VOICE_PREWARM_MAX_ATTEMPTS 3
|
||||
|
||||
static uint32_t clamp_timeout_ms(uint32_t timeout_ms, uint32_t default_ms, uint32_t min_ms, uint32_t max_ms) {
|
||||
uint32_t value = timeout_ms == 0 ? default_ms : timeout_ms;
|
||||
@@ -74,6 +83,87 @@ static bool voice_wait_worker_tasks_exit(uint32_t timeout_ms) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static void voice_session_prewarm_task(void *arg) {
|
||||
(void)arg;
|
||||
|
||||
uint32_t attempts = 0;
|
||||
while (!s_voice_prewarm_stop_requested) {
|
||||
voice_interaction_status_t status = {0};
|
||||
voice_interaction_get_status(&status);
|
||||
if (status.session_active && status.ws_connected && status.started) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!system_runtime_wifi_ready()) {
|
||||
vTaskDelay(pdMS_TO_TICKS(VOICE_PREWARM_WIFI_POLL_MS));
|
||||
continue;
|
||||
}
|
||||
|
||||
char prewarm_err[128] = {0};
|
||||
esp_err_t prewarm_rc = voice_interaction_start_with_timeout(VOICE_PREWARM_CONNECT_TIMEOUT_MS,
|
||||
prewarm_err,
|
||||
sizeof(prewarm_err));
|
||||
if (prewarm_rc == ESP_OK) {
|
||||
ESP_LOGI(TAG, "voice session prewarm ready");
|
||||
break;
|
||||
}
|
||||
|
||||
if (prewarm_rc == ESP_ERR_INVALID_STATE &&
|
||||
strstr(prewarm_err, "config missing") != NULL) {
|
||||
ESP_LOGW(TAG, "voice session prewarm skipped: config missing");
|
||||
break;
|
||||
}
|
||||
|
||||
++attempts;
|
||||
if (attempts >= VOICE_PREWARM_MAX_ATTEMPTS) {
|
||||
ESP_LOGW(TAG,
|
||||
"voice session prewarm failed after retries: rc=0x%x err=%s",
|
||||
(unsigned)prewarm_rc,
|
||||
prewarm_err[0] != '\0' ? prewarm_err : "-");
|
||||
break;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(VOICE_PREWARM_RETRY_DELAY_MS));
|
||||
}
|
||||
|
||||
bool delete_with_caps = false;
|
||||
if (voice_lock(VOICE_STATUS_LOCK_TIMEOUT_MS)) {
|
||||
delete_with_caps = s_voice_prewarm_task_with_caps;
|
||||
s_voice_prewarm_task = NULL;
|
||||
s_voice_prewarm_task_with_caps = false;
|
||||
voice_unlock();
|
||||
}
|
||||
voice_delete_self_task(delete_with_caps);
|
||||
}
|
||||
|
||||
static void voice_interaction_stop_prewarm_task(void) {
|
||||
s_voice_prewarm_stop_requested = true;
|
||||
|
||||
TaskHandle_t prewarm_task = NULL;
|
||||
if (voice_lock(VOICE_STATUS_LOCK_TIMEOUT_MS)) {
|
||||
prewarm_task = s_voice_prewarm_task;
|
||||
voice_unlock();
|
||||
}
|
||||
if (prewarm_task == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
(void)xTaskAbortDelay(prewarm_task);
|
||||
|
||||
int64_t deadline_ms = voice_now_ms() + 500;
|
||||
while (voice_now_ms() < deadline_ms) {
|
||||
bool done = true;
|
||||
if (voice_lock(VOICE_STATUS_LOCK_TIMEOUT_MS)) {
|
||||
done = (s_voice_prewarm_task == NULL);
|
||||
voice_unlock();
|
||||
}
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
}
|
||||
ESP_LOGW(TAG, "voice prewarm task still running during stop, continue shutdown");
|
||||
}
|
||||
|
||||
const char *voice_interaction_dialog_state_str(voice_dialog_state_t state) {
|
||||
switch (state) {
|
||||
case VOICE_DIALOG_STATE_IDLE:
|
||||
@@ -103,6 +193,9 @@ esp_err_t voice_interaction_init(void) {
|
||||
|
||||
s_voice.dialog_state = VOICE_DIALOG_STATE_IDLE;
|
||||
s_voice.last_event_ms = voice_now_ms();
|
||||
s_voice_prewarm_task = NULL;
|
||||
s_voice_prewarm_task_with_caps = false;
|
||||
s_voice_prewarm_stop_requested = false;
|
||||
|
||||
esp_err_t mic_key_err = voice_interaction_mic_key_init();
|
||||
if (mic_key_err != ESP_OK) {
|
||||
@@ -115,6 +208,43 @@ esp_err_t voice_interaction_init(void) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t voice_interaction_schedule_session_prewarm(void) {
|
||||
if (!s_voice.ready) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (!voice_lock(VOICE_STATUS_LOCK_TIMEOUT_MS)) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
bool already_running = (s_voice_prewarm_task != NULL);
|
||||
voice_unlock();
|
||||
if (already_running) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
TaskHandle_t task = NULL;
|
||||
bool with_caps = false;
|
||||
s_voice_prewarm_stop_requested = false;
|
||||
BaseType_t ok = voice_create_task_prefer_psram(voice_session_prewarm_task,
|
||||
"voice_prewarm",
|
||||
VOICE_PREWARM_TASK_STACK,
|
||||
3,
|
||||
&task,
|
||||
&with_caps);
|
||||
if (ok != pdPASS || task == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
if (!voice_lock(VOICE_STATUS_LOCK_TIMEOUT_MS)) {
|
||||
voice_delete_task(task, with_caps);
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
s_voice_prewarm_task = task;
|
||||
s_voice_prewarm_task_with_caps = with_caps;
|
||||
voice_unlock();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t voice_interaction_start_with_timeout(uint32_t timeout_ms, char *err, size_t err_len) {
|
||||
uint32_t connect_timeout_ms =
|
||||
clamp_timeout_ms(timeout_ms, VOICE_START_CONNECT_TIMEOUT_MS, 1000, 30000);
|
||||
@@ -366,6 +496,8 @@ esp_err_t voice_interaction_stop_with_timeout(uint32_t timeout_ms, char *err, si
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
voice_interaction_stop_prewarm_task();
|
||||
|
||||
if (!voice_lock(VOICE_STATUS_LOCK_TIMEOUT_MS)) {
|
||||
voice_fill_err(err, err_len, "voice lock timeout");
|
||||
ESP_LOGW(TAG, "session_stop_abort: lock timeout");
|
||||
|
||||
@@ -17,7 +17,7 @@ static const char *TAG = "voice_interaction";
|
||||
#define VOICE_IMAGE_MARKER_REPEAT_SUPPRESS_MS 45000
|
||||
#define VOICE_IMAGE_PRINT_THRESHOLD 160
|
||||
#define VOICE_IMAGE_PRINT_MAX_HEIGHT 2200
|
||||
#define VOICE_IMAGE_PREVIEW_TIMEOUT_MS 200
|
||||
#define VOICE_IMAGE_PREVIEW_TIMEOUT_MS 3000
|
||||
#define VOICE_IMAGE_MARKER_TEMPLATE \
|
||||
"%s,线稿风格,白色背景,边缘清晰,细节简洁,高对比,避免大面积阴影和灰度渐变,适配热敏打印机打印。"
|
||||
#define VOICE_IMAGE_MARKER_TEMPLATE_FALLBACK_SUBJECT "简洁卡通图案"
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#define VOICE_MIC_KEY_QUEUE_LEN 8
|
||||
#define VOICE_MIC_KEY_SESSION_READY_WAIT_MS 2000
|
||||
#define VOICE_MIC_KEY_SESSION_READY_POLL_MS 30
|
||||
#define VOICE_MIC_KEY_STATUS_PREVIEW_TIMEOUT_MS 3000
|
||||
|
||||
typedef enum {
|
||||
VOICE_MIC_KEY_CMD_PRESS_DOWN = 0,
|
||||
@@ -22,6 +23,26 @@ static TaskHandle_t s_mic_key_task;
|
||||
static bool s_mic_key_task_with_caps;
|
||||
static bool s_mic_key_ready;
|
||||
|
||||
static void voice_mic_key_show_status(const char *text) {
|
||||
if (text == NULL || text[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
char preview_err[96] = {0};
|
||||
esp_err_t preview_rc = screen_preview_show_text(text,
|
||||
VOICE_MIC_KEY_STATUS_PREVIEW_TIMEOUT_MS,
|
||||
preview_err,
|
||||
sizeof(preview_err));
|
||||
if (preview_rc == ESP_OK || preview_rc == ESP_ERR_NOT_SUPPORTED) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG,
|
||||
"mic_key_status_preview_failed: rc=0x%x msg=%s",
|
||||
(unsigned)preview_rc,
|
||||
preview_err[0] != '\0' ? preview_err : "show text failed");
|
||||
}
|
||||
|
||||
static bool voice_mic_key_round_busy(const voice_interaction_status_t *voice,
|
||||
const printer_runtime_status_t *printer) {
|
||||
if (voice == NULL || printer == NULL) {
|
||||
@@ -56,6 +77,7 @@ static void voice_mic_key_handle_press_down(void) {
|
||||
|
||||
if (voice.tap_active) {
|
||||
ESP_LOGI(TAG, "MIC_KEY press ignored: tap already active");
|
||||
voice_mic_key_show_status("语音已在录制中");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -68,10 +90,12 @@ static void voice_mic_key_handle_press_down(void) {
|
||||
voice.marker_image_gen_running,
|
||||
printer.busy,
|
||||
printer.queue_depth);
|
||||
voice_mic_key_show_status("设备忙碌中\n请等待当前任务完成");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!voice_mic_key_session_ready(&voice)) {
|
||||
voice_mic_key_show_status("语音连接中...\n请稍候");
|
||||
char start_err[128] = {0};
|
||||
esp_err_t start_rc = voice_interaction_start_with_timeout(0, start_err, sizeof(start_err));
|
||||
if (start_rc != ESP_OK) {
|
||||
@@ -79,6 +103,7 @@ static void voice_mic_key_handle_press_down(void) {
|
||||
"MIC_KEY auto session start failed: %s (%s)",
|
||||
esp_err_to_name(start_rc),
|
||||
start_err[0] != '\0' ? start_err : "-");
|
||||
voice_mic_key_show_status("语音连接失败\n请稍后重试");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -90,6 +115,7 @@ static void voice_mic_key_handle_press_down(void) {
|
||||
voice.session_active,
|
||||
voice.ws_connected,
|
||||
voice.started);
|
||||
voice_mic_key_show_status("语音服务暂不可用\n请稍后重试");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -100,9 +126,11 @@ static void voice_mic_key_handle_press_down(void) {
|
||||
"MIC_KEY tap start failed: %s (%s)",
|
||||
esp_err_to_name(tap_rc),
|
||||
tap_err[0] != '\0' ? tap_err : "-");
|
||||
voice_mic_key_show_status("开始录音失败\n请重试");
|
||||
return;
|
||||
}
|
||||
|
||||
voice_mic_key_show_status("请开始说话");
|
||||
ESP_LOGI(TAG, "MIC_KEY tap started");
|
||||
}
|
||||
|
||||
@@ -120,9 +148,11 @@ static void voice_mic_key_handle_press_up(void) {
|
||||
"MIC_KEY tap stop failed: %s (%s)",
|
||||
esp_err_to_name(tap_rc),
|
||||
tap_err[0] != '\0' ? tap_err : "-");
|
||||
voice_mic_key_show_status("结束录音失败\n请重试");
|
||||
return;
|
||||
}
|
||||
|
||||
voice_mic_key_show_status("已结束录音\n正在处理...");
|
||||
ESP_LOGI(TAG, "MIC_KEY tap stopped");
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ static const char *TAG = "voice_interaction";
|
||||
#define VOICE_RESPONSE_FINALIZE_TASK_STACK 6144
|
||||
#define VOICE_RESPONSE_FINALIZE_TASK_RETRY_COUNT 3
|
||||
#define VOICE_RESPONSE_FINALIZE_TASK_RETRY_DELAY_MS 120
|
||||
#define VOICE_SCREEN_PREVIEW_TIMEOUT_MS 180
|
||||
#define VOICE_SCREEN_PREVIEW_TIMEOUT_MS 3000
|
||||
|
||||
static portMUX_TYPE s_response_finalize_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
static bool s_response_finalize_running;
|
||||
|
||||
Reference in New Issue
Block a user