feat(ux): improve wifi, voice, and print feedback flows
This commit is contained in:
@@ -278,6 +278,12 @@ esp_err_t control_plane_lifecycle_start(void) {
|
|||||||
voice_started = true;
|
voice_started = true;
|
||||||
|
|
||||||
image_generation_schedule_prewarm();
|
image_generation_schedule_prewarm();
|
||||||
|
esp_err_t voice_prewarm_rc = voice_interaction_schedule_session_prewarm();
|
||||||
|
if (voice_prewarm_rc != ESP_OK && voice_prewarm_rc != ESP_ERR_INVALID_STATE) {
|
||||||
|
ESP_LOGW(TAG,
|
||||||
|
"voice session prewarm schedule failed: rc=0x%x",
|
||||||
|
(unsigned)voice_prewarm_rc);
|
||||||
|
}
|
||||||
|
|
||||||
if (lifecycle_lock_take(1000)) {
|
if (lifecycle_lock_take(1000)) {
|
||||||
lifecycle_set_state_locked(CONTROL_PLANE_LIFECYCLE_STATE_RUNNING, ESP_OK, "running");
|
lifecycle_set_state_locked(CONTROL_PLANE_LIFECYCLE_STATE_RUNNING, ESP_OK, "running");
|
||||||
|
|||||||
@@ -153,6 +153,20 @@ typedef struct {
|
|||||||
int64_t finished_ms;
|
int64_t finished_ms;
|
||||||
} print_job_info_t;
|
} 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 {
|
typedef struct {
|
||||||
// Printer-only option: bypass paper/temp/battery precheck for this print job.
|
// Printer-only option: bypass paper/temp/battery precheck for this print job.
|
||||||
bool ignore_precheck;
|
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_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);
|
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);
|
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);
|
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_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_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);
|
void voice_interaction_get_status(voice_interaction_status_t *out_status);
|
||||||
const char *voice_interaction_dialog_state_str(voice_dialog_state_t state);
|
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_find_job_idx_locked(uint32_t id);
|
||||||
int printer_protocol_alloc_job_slot_locked(void);
|
int printer_protocol_alloc_job_slot_locked(void);
|
||||||
bool printer_protocol_is_terminal_state(print_job_state_t state);
|
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);
|
void printer_protocol_worker_task(void *arg);
|
||||||
bool printer_protocol_is_stopping(void);
|
bool printer_protocol_is_stopping(void);
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ bool s_protocol_initialized;
|
|||||||
bool s_protocol_stopping;
|
bool s_protocol_stopping;
|
||||||
|
|
||||||
static const char *TAG = "printer_protocol";
|
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
|
#if CONFIG_FREERTOS_UNICORE
|
||||||
#define PRINT_WORKER_CORE_ID 0
|
#define PRINT_WORKER_CORE_ID 0
|
||||||
@@ -46,6 +49,47 @@ static esp_err_t ensure_printer_driver_ready(void) {
|
|||||||
return platform_printer_init();
|
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) {
|
static void refresh_printer_status_locked(void) {
|
||||||
platform_printer_sensors_t sensors = {0};
|
platform_printer_sensors_t sensors = {0};
|
||||||
if (platform_printer_get_sensors(&sensors) == ESP_OK) {
|
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);
|
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;
|
s_jobs[idx].data = copy;
|
||||||
strlcpy(s_jobs[idx].density, density != NULL ? density : "中等", sizeof(s_jobs[idx].density));
|
strlcpy(s_jobs[idx].density, density != NULL ? density : "中等", sizeof(s_jobs[idx].density));
|
||||||
bool ignore_precheck = s_jobs[idx].ignore_precheck;
|
bool ignore_precheck = s_jobs[idx].ignore_precheck;
|
||||||
|
job_slot_t queued_snapshot = s_jobs[idx];
|
||||||
|
|
||||||
xSemaphoreGive(s_mutex);
|
xSemaphoreGive(s_mutex);
|
||||||
|
|
||||||
@@ -177,6 +178,7 @@ esp_err_t printer_protocol_submit_raster_job_ex(const uint8_t *raster,
|
|||||||
density != NULL ? density : "中等",
|
density != NULL ? density : "中等",
|
||||||
ignore_precheck,
|
ignore_precheck,
|
||||||
(unsigned)queue_depth);
|
(unsigned)queue_depth);
|
||||||
|
printer_protocol_emit_job_event(PRINTER_JOB_EVENT_QUEUED, &queued_snapshot);
|
||||||
|
|
||||||
return ESP_OK;
|
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) {
|
esp_err_t printer_protocol_cancel_job(uint32_t job_id, char *err, size_t err_len) {
|
||||||
bool canceled_finalized = false;
|
bool canceled_finalized = false;
|
||||||
|
job_slot_t canceled_snapshot = {0};
|
||||||
|
|
||||||
if (job_id == 0) {
|
if (job_id == 0) {
|
||||||
if (err != NULL && err_len > 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].state = PRINT_JOB_STATE_CANCELED;
|
||||||
s_jobs[idx].finished_ms = esp_timer_get_time() / 1000;
|
s_jobs[idx].finished_ms = esp_timer_get_time() / 1000;
|
||||||
s_jobs[idx].progress = 0;
|
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);
|
free(s_jobs[idx].data);
|
||||||
s_jobs[idx].data = NULL;
|
s_jobs[idx].data = NULL;
|
||||||
canceled_finalized = true;
|
canceled_finalized = true;
|
||||||
|
canceled_snapshot = s_jobs[idx];
|
||||||
} else if (s_jobs[idx].state == PRINT_JOB_STATE_RUNNING) {
|
} 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);
|
xSemaphoreGive(s_mutex);
|
||||||
if (canceled_finalized) {
|
if (canceled_finalized) {
|
||||||
|
printer_protocol_emit_job_event(PRINTER_JOB_EVENT_FINISHED, &canceled_snapshot);
|
||||||
}
|
}
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,38 @@
|
|||||||
|
|
||||||
static const char *TAG = "printer_protocol";
|
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) {
|
static uint16_t density_to_hot_time(const char *density) {
|
||||||
if (density == NULL) {
|
if (density == NULL) {
|
||||||
return 2000;
|
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) {
|
static bool run_print_job_printer_driver(job_slot_t *job) {
|
||||||
char check_err[96];
|
char check_err[96];
|
||||||
if (!precheck_printer_ready(job->ignore_precheck, check_err, sizeof(check_err))) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,30 +118,38 @@ static bool run_print_job_printer_driver(job_slot_t *job) {
|
|||||||
.cancel_flag = &job->cancel_requested,
|
.cancel_flag = &job->cancel_requested,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool emit_progress = false;
|
||||||
|
job_slot_t progress_snapshot = {0};
|
||||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) == pdTRUE) {
|
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) == pdTRUE) {
|
||||||
job->progress = 10;
|
job->progress = 10;
|
||||||
|
progress_snapshot = *job;
|
||||||
|
emit_progress = true;
|
||||||
xSemaphoreGive(s_mutex);
|
xSemaphoreGive(s_mutex);
|
||||||
}
|
}
|
||||||
|
if (emit_progress) {
|
||||||
|
printer_protocol_emit_job_event(PRINTER_JOB_EVENT_PROGRESS, &progress_snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
char print_err[96] = {0};
|
char print_err[96] = {0};
|
||||||
esp_err_t rc = platform_printer_print(&req, print_err, sizeof(print_err));
|
esp_err_t rc = platform_printer_print(&req, print_err, sizeof(print_err));
|
||||||
if (rc != ESP_OK) {
|
if (rc != ESP_OK) {
|
||||||
if (print_err[0] != 0) {
|
strlcpy(job->error,
|
||||||
snprintf(job->error, sizeof(job->error), "%s", print_err);
|
printer_user_error_message(print_err, rc, (rc == ESP_ERR_INVALID_STATE && job->cancel_requested)),
|
||||||
} else if (rc == ESP_ERR_TIMEOUT) {
|
sizeof(job->error));
|
||||||
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");
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emit_progress = false;
|
||||||
|
memset(&progress_snapshot, 0, sizeof(progress_snapshot));
|
||||||
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) == pdTRUE) {
|
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(200)) == pdTRUE) {
|
||||||
job->progress = 95;
|
job->progress = 95;
|
||||||
|
progress_snapshot = *job;
|
||||||
|
emit_progress = true;
|
||||||
xSemaphoreGive(s_mutex);
|
xSemaphoreGive(s_mutex);
|
||||||
}
|
}
|
||||||
|
if (emit_progress) {
|
||||||
|
printer_protocol_emit_job_event(PRINTER_JOB_EVENT_PROGRESS, &progress_snapshot);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +164,7 @@ static bool run_print_job(job_slot_t *job) {
|
|||||||
job->ignore_precheck);
|
job->ignore_precheck);
|
||||||
|
|
||||||
if (printer_protocol_is_stopping()) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,6 +222,7 @@ void printer_protocol_worker_task(void *arg) {
|
|||||||
job->started_ms = esp_timer_get_time() / 1000;
|
job->started_ms = esp_timer_get_time() / 1000;
|
||||||
job->progress = 1;
|
job->progress = 1;
|
||||||
s_busy_refcnt = 1;
|
s_busy_refcnt = 1;
|
||||||
|
job_slot_t running_snapshot = *job;
|
||||||
int64_t queue_wait_ms = 0;
|
int64_t queue_wait_ms = 0;
|
||||||
if (job->started_ms >= job->created_ms) {
|
if (job->started_ms >= job->created_ms) {
|
||||||
queue_wait_ms = 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,
|
(unsigned)job->height,
|
||||||
job->density);
|
job->density);
|
||||||
xSemaphoreGive(s_mutex);
|
xSemaphoreGive(s_mutex);
|
||||||
|
printer_protocol_emit_job_event(PRINTER_JOB_EVENT_RUNNING, &running_snapshot);
|
||||||
|
|
||||||
bool ok = run_print_job(job);
|
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()) {
|
} else if (s_jobs[idx].cancel_requested || printer_protocol_is_stopping()) {
|
||||||
s_jobs[idx].state = PRINT_JOB_STATE_CANCELED;
|
s_jobs[idx].state = PRINT_JOB_STATE_CANCELED;
|
||||||
if (s_jobs[idx].error[0] == 0) {
|
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 {
|
} else {
|
||||||
s_jobs[idx].state = PRINT_JOB_STATE_FAILED;
|
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;
|
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 : "-");
|
s_jobs[idx].error[0] != 0 ? s_jobs[idx].error : "-");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
job_slot_t finished_snapshot = s_jobs[idx];
|
||||||
free(s_jobs[idx].data);
|
free(s_jobs[idx].data);
|
||||||
s_jobs[idx].data = NULL;
|
s_jobs[idx].data = NULL;
|
||||||
xSemaphoreGive(s_mutex);
|
xSemaphoreGive(s_mutex);
|
||||||
|
printer_protocol_emit_job_event(PRINTER_JOB_EVENT_FINISHED, &finished_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s_evt != NULL) {
|
if (s_evt != NULL) {
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
|
|
||||||
static const char *TAG = "system_runtime";
|
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_wifi_hint_shown;
|
||||||
static bool s_power_key_ready;
|
static bool s_power_key_ready;
|
||||||
@@ -59,6 +60,24 @@ static esp_err_t system_runtime_power_key_init(void) {
|
|||||||
return ESP_OK;
|
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) {
|
static void system_runtime_show_wifi_hint(void) {
|
||||||
if (s_wifi_hint_shown) {
|
if (s_wifi_hint_shown) {
|
||||||
return;
|
return;
|
||||||
@@ -76,17 +95,7 @@ static void system_runtime_show_wifi_hint(void) {
|
|||||||
"未检测到Wi-Fi配置\n连接热点: %s\n打开: http://192.168.4.1",
|
"未检测到Wi-Fi配置\n连接热点: %s\n打开: http://192.168.4.1",
|
||||||
ap_ssid);
|
ap_ssid);
|
||||||
|
|
||||||
char preview_err[96] = {0};
|
system_runtime_show_status_text(hint, SYSTEM_RUNTIME_WIFI_HINT_TIMEOUT_MS);
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void system_runtime_try_show_wifi_hint_for_unprovisioned(void) {
|
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) {
|
static void system_runtime_wifi_event_handler(wifi_manager_event_t event, void *user_data) {
|
||||||
(void)user_data;
|
(void)user_data;
|
||||||
if (event == WIFI_MANAGER_EVENT_PROVISIONING_STARTED) {
|
switch (event) {
|
||||||
|
case WIFI_MANAGER_EVENT_PROVISIONING_STARTED:
|
||||||
system_runtime_show_wifi_hint();
|
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"
|
#include "esp_log.h"
|
||||||
|
|
||||||
static const char *TAG = "voice_interaction";
|
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) {
|
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;
|
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;
|
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) {
|
const char *voice_interaction_dialog_state_str(voice_dialog_state_t state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case VOICE_DIALOG_STATE_IDLE:
|
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.dialog_state = VOICE_DIALOG_STATE_IDLE;
|
||||||
s_voice.last_event_ms = voice_now_ms();
|
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();
|
esp_err_t mic_key_err = voice_interaction_mic_key_init();
|
||||||
if (mic_key_err != ESP_OK) {
|
if (mic_key_err != ESP_OK) {
|
||||||
@@ -115,6 +208,43 @@ esp_err_t voice_interaction_init(void) {
|
|||||||
return ESP_OK;
|
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) {
|
esp_err_t voice_interaction_start_with_timeout(uint32_t timeout_ms, char *err, size_t err_len) {
|
||||||
uint32_t connect_timeout_ms =
|
uint32_t connect_timeout_ms =
|
||||||
clamp_timeout_ms(timeout_ms, VOICE_START_CONNECT_TIMEOUT_MS, 1000, 30000);
|
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;
|
return ESP_ERR_INVALID_STATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
voice_interaction_stop_prewarm_task();
|
||||||
|
|
||||||
if (!voice_lock(VOICE_STATUS_LOCK_TIMEOUT_MS)) {
|
if (!voice_lock(VOICE_STATUS_LOCK_TIMEOUT_MS)) {
|
||||||
voice_fill_err(err, err_len, "voice lock timeout");
|
voice_fill_err(err, err_len, "voice lock timeout");
|
||||||
ESP_LOGW(TAG, "session_stop_abort: 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_MARKER_REPEAT_SUPPRESS_MS 45000
|
||||||
#define VOICE_IMAGE_PRINT_THRESHOLD 160
|
#define VOICE_IMAGE_PRINT_THRESHOLD 160
|
||||||
#define VOICE_IMAGE_PRINT_MAX_HEIGHT 2200
|
#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 \
|
#define VOICE_IMAGE_MARKER_TEMPLATE \
|
||||||
"%s,线稿风格,白色背景,边缘清晰,细节简洁,高对比,避免大面积阴影和灰度渐变,适配热敏打印机打印。"
|
"%s,线稿风格,白色背景,边缘清晰,细节简洁,高对比,避免大面积阴影和灰度渐变,适配热敏打印机打印。"
|
||||||
#define VOICE_IMAGE_MARKER_TEMPLATE_FALLBACK_SUBJECT "简洁卡通图案"
|
#define VOICE_IMAGE_MARKER_TEMPLATE_FALLBACK_SUBJECT "简洁卡通图案"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#define VOICE_MIC_KEY_QUEUE_LEN 8
|
#define VOICE_MIC_KEY_QUEUE_LEN 8
|
||||||
#define VOICE_MIC_KEY_SESSION_READY_WAIT_MS 2000
|
#define VOICE_MIC_KEY_SESSION_READY_WAIT_MS 2000
|
||||||
#define VOICE_MIC_KEY_SESSION_READY_POLL_MS 30
|
#define VOICE_MIC_KEY_SESSION_READY_POLL_MS 30
|
||||||
|
#define VOICE_MIC_KEY_STATUS_PREVIEW_TIMEOUT_MS 3000
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
VOICE_MIC_KEY_CMD_PRESS_DOWN = 0,
|
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_task_with_caps;
|
||||||
static bool s_mic_key_ready;
|
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,
|
static bool voice_mic_key_round_busy(const voice_interaction_status_t *voice,
|
||||||
const printer_runtime_status_t *printer) {
|
const printer_runtime_status_t *printer) {
|
||||||
if (voice == NULL || printer == NULL) {
|
if (voice == NULL || printer == NULL) {
|
||||||
@@ -56,6 +77,7 @@ static void voice_mic_key_handle_press_down(void) {
|
|||||||
|
|
||||||
if (voice.tap_active) {
|
if (voice.tap_active) {
|
||||||
ESP_LOGI(TAG, "MIC_KEY press ignored: tap already active");
|
ESP_LOGI(TAG, "MIC_KEY press ignored: tap already active");
|
||||||
|
voice_mic_key_show_status("语音已在录制中");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,10 +90,12 @@ static void voice_mic_key_handle_press_down(void) {
|
|||||||
voice.marker_image_gen_running,
|
voice.marker_image_gen_running,
|
||||||
printer.busy,
|
printer.busy,
|
||||||
printer.queue_depth);
|
printer.queue_depth);
|
||||||
|
voice_mic_key_show_status("设备忙碌中\n请等待当前任务完成");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!voice_mic_key_session_ready(&voice)) {
|
if (!voice_mic_key_session_ready(&voice)) {
|
||||||
|
voice_mic_key_show_status("语音连接中...\n请稍候");
|
||||||
char start_err[128] = {0};
|
char start_err[128] = {0};
|
||||||
esp_err_t start_rc = voice_interaction_start_with_timeout(0, start_err, sizeof(start_err));
|
esp_err_t start_rc = voice_interaction_start_with_timeout(0, start_err, sizeof(start_err));
|
||||||
if (start_rc != ESP_OK) {
|
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)",
|
"MIC_KEY auto session start failed: %s (%s)",
|
||||||
esp_err_to_name(start_rc),
|
esp_err_to_name(start_rc),
|
||||||
start_err[0] != '\0' ? start_err : "-");
|
start_err[0] != '\0' ? start_err : "-");
|
||||||
|
voice_mic_key_show_status("语音连接失败\n请稍后重试");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,6 +115,7 @@ static void voice_mic_key_handle_press_down(void) {
|
|||||||
voice.session_active,
|
voice.session_active,
|
||||||
voice.ws_connected,
|
voice.ws_connected,
|
||||||
voice.started);
|
voice.started);
|
||||||
|
voice_mic_key_show_status("语音服务暂不可用\n请稍后重试");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,9 +126,11 @@ static void voice_mic_key_handle_press_down(void) {
|
|||||||
"MIC_KEY tap start failed: %s (%s)",
|
"MIC_KEY tap start failed: %s (%s)",
|
||||||
esp_err_to_name(tap_rc),
|
esp_err_to_name(tap_rc),
|
||||||
tap_err[0] != '\0' ? tap_err : "-");
|
tap_err[0] != '\0' ? tap_err : "-");
|
||||||
|
voice_mic_key_show_status("开始录音失败\n请重试");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
voice_mic_key_show_status("请开始说话");
|
||||||
ESP_LOGI(TAG, "MIC_KEY tap started");
|
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)",
|
"MIC_KEY tap stop failed: %s (%s)",
|
||||||
esp_err_to_name(tap_rc),
|
esp_err_to_name(tap_rc),
|
||||||
tap_err[0] != '\0' ? tap_err : "-");
|
tap_err[0] != '\0' ? tap_err : "-");
|
||||||
|
voice_mic_key_show_status("结束录音失败\n请重试");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
voice_mic_key_show_status("已结束录音\n正在处理...");
|
||||||
ESP_LOGI(TAG, "MIC_KEY tap stopped");
|
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_STACK 6144
|
||||||
#define VOICE_RESPONSE_FINALIZE_TASK_RETRY_COUNT 3
|
#define VOICE_RESPONSE_FINALIZE_TASK_RETRY_COUNT 3
|
||||||
#define VOICE_RESPONSE_FINALIZE_TASK_RETRY_DELAY_MS 120
|
#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 portMUX_TYPE s_response_finalize_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||||
static bool s_response_finalize_running;
|
static bool s_response_finalize_running;
|
||||||
|
|||||||
@@ -337,6 +337,21 @@
|
|||||||
status_connect_ok: '连接成功,正在关闭配网页面...',
|
status_connect_ok: '连接成功,正在关闭配网页面...',
|
||||||
status_connect_failed: '连接失败:{error}。请重试。',
|
status_connect_failed: '连接失败:{error}。请重试。',
|
||||||
status_request_failed: '请求失败,请重试。',
|
status_request_failed: '请求失败,请重试。',
|
||||||
|
error_invalid_payload: '提交内容无效',
|
||||||
|
error_read_failed: '设备读取请求失败',
|
||||||
|
error_ssid_required: 'SSID 不能为空',
|
||||||
|
error_password_too_short: '密码至少需要 8 位',
|
||||||
|
error_wrong_password: '密码错误',
|
||||||
|
error_no_ap_found: '未找到该 Wi-Fi',
|
||||||
|
error_signal_too_weak: '信号太弱,请靠近路由器',
|
||||||
|
error_ap_not_responding: '路由器无响应',
|
||||||
|
error_association_failed: '连接被路由器拒绝',
|
||||||
|
error_ap_security_unsupported: '路由器加密方式暂不支持',
|
||||||
|
error_connect_timeout: '连接超时',
|
||||||
|
error_wifi_unavailable: '设备 Wi-Fi 暂不可用',
|
||||||
|
error_save_failed: '连接成功但保存失败,请重试',
|
||||||
|
error_connect_failed: '无法连接到该 Wi-Fi',
|
||||||
|
error_unknown: '未知错误',
|
||||||
signal_unit: 'dBm'
|
signal_unit: 'dBm'
|
||||||
},
|
},
|
||||||
'en-US': {
|
'en-US': {
|
||||||
@@ -359,6 +374,21 @@
|
|||||||
status_connect_ok: 'Connected successfully. Closing this provisioning page...',
|
status_connect_ok: 'Connected successfully. Closing this provisioning page...',
|
||||||
status_connect_failed: 'Connection failed: {error}. Please try again.',
|
status_connect_failed: 'Connection failed: {error}. Please try again.',
|
||||||
status_request_failed: 'Request failed. Please try again.',
|
status_request_failed: 'Request failed. Please try again.',
|
||||||
|
error_invalid_payload: 'Invalid request payload',
|
||||||
|
error_read_failed: 'Device failed to read request',
|
||||||
|
error_ssid_required: 'SSID is required',
|
||||||
|
error_password_too_short: 'Password must be at least 8 characters',
|
||||||
|
error_wrong_password: 'Wrong password',
|
||||||
|
error_no_ap_found: 'Wi-Fi network not found',
|
||||||
|
error_signal_too_weak: 'Signal is too weak',
|
||||||
|
error_ap_not_responding: 'Router is not responding',
|
||||||
|
error_association_failed: 'Router rejected the connection',
|
||||||
|
error_ap_security_unsupported: 'Wi-Fi security mode is unsupported',
|
||||||
|
error_connect_timeout: 'Connection timed out',
|
||||||
|
error_wifi_unavailable: 'Device Wi-Fi is unavailable',
|
||||||
|
error_save_failed: 'Connected, but failed to save credentials',
|
||||||
|
error_connect_failed: 'Unable to connect to this Wi-Fi',
|
||||||
|
error_unknown: 'Unknown error',
|
||||||
signal_unit: 'dBm'
|
signal_unit: 'dBm'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -402,6 +432,18 @@
|
|||||||
setStatusText(trWithParams(key, params), type);
|
setStatusText(trWithParams(key, params), type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mapProvisionError(code) {
|
||||||
|
if (!code) {
|
||||||
|
return tr('error_unknown');
|
||||||
|
}
|
||||||
|
const key = 'error_' + String(code);
|
||||||
|
const mapped = tr(key);
|
||||||
|
if (mapped === key) {
|
||||||
|
return tr('error_connect_failed');
|
||||||
|
}
|
||||||
|
return mapped;
|
||||||
|
}
|
||||||
|
|
||||||
function decodeSsid(value) {
|
function decodeSsid(value) {
|
||||||
try {
|
try {
|
||||||
return decodeURIComponent(value);
|
return decodeURIComponent(value);
|
||||||
@@ -562,7 +604,9 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setStatusKey('status_connect_failed', 'err', { error: data.error || 'unknown' });
|
setStatusKey('status_connect_failed', 'err', {
|
||||||
|
error: mapProvisionError(data.error)
|
||||||
|
});
|
||||||
} catch (_err) {
|
} catch (_err) {
|
||||||
setStatusKey('status_request_failed', 'err');
|
setStatusKey('status_request_failed', 'err');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ esp_err_t platform_bootstrap_init(void);
|
|||||||
// ---------- wifi_manager ----------
|
// ---------- wifi_manager ----------
|
||||||
typedef enum {
|
typedef enum {
|
||||||
WIFI_MANAGER_EVENT_PROVISIONING_STARTED = 0,
|
WIFI_MANAGER_EVENT_PROVISIONING_STARTED = 0,
|
||||||
|
WIFI_MANAGER_EVENT_STA_CONNECTING,
|
||||||
|
WIFI_MANAGER_EVENT_STA_CONNECTED,
|
||||||
|
WIFI_MANAGER_EVENT_STA_CONNECT_FAILED,
|
||||||
|
WIFI_MANAGER_EVENT_STA_DISCONNECTED,
|
||||||
} wifi_manager_event_t;
|
} wifi_manager_event_t;
|
||||||
|
|
||||||
typedef void (*wifi_manager_event_cb_t)(wifi_manager_event_t event, void *user_data);
|
typedef void (*wifi_manager_event_cb_t)(wifi_manager_event_t event, void *user_data);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
static const char *TAG = "lcd_module";
|
static const char *TAG = "lcd_module";
|
||||||
|
|
||||||
#define ST7789_SPI_HOST SPI2_HOST
|
#define ST7789_SPI_HOST SPI2_HOST
|
||||||
#define LCD_PREVIEW_TIMEOUT_MS_DEFAULT 200
|
#define LCD_PREVIEW_TIMEOUT_MS_DEFAULT 3000
|
||||||
#define LCD_LOCK_TIMEOUT_MS 1000
|
#define LCD_LOCK_TIMEOUT_MS 1000
|
||||||
#define LCD_DEFAULT_DRAW_LINES 20
|
#define LCD_DEFAULT_DRAW_LINES 20
|
||||||
#define LCD_PREVIEW_FADE_DURATION_MS 150
|
#define LCD_PREVIEW_FADE_DURATION_MS 150
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ static esp_netif_t *s_ap_netif;
|
|||||||
static httpd_handle_t s_prov_httpd;
|
static httpd_handle_t s_prov_httpd;
|
||||||
static esp_event_handler_instance_t s_wifi_event_inst;
|
static esp_event_handler_instance_t s_wifi_event_inst;
|
||||||
static esp_event_handler_instance_t s_ip_event_inst;
|
static esp_event_handler_instance_t s_ip_event_inst;
|
||||||
|
static uint8_t s_last_sta_disconnect_reason;
|
||||||
|
static bool s_last_sta_disconnect_reason_valid;
|
||||||
|
|
||||||
static void wifi_manager_notify_event(wifi_manager_event_t event) {
|
static void wifi_manager_notify_event(wifi_manager_event_t event) {
|
||||||
wifi_manager_event_cb_t cb = s_event_cb;
|
wifi_manager_event_cb_t cb = s_event_cb;
|
||||||
@@ -75,6 +77,87 @@ static void wifi_manager_notify_event(wifi_manager_event_t event) {
|
|||||||
cb(event, s_event_cb_user_data);
|
cb(event, s_event_cb_user_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *wifi_manager_error_from_disconnect_reason(uint8_t reason) {
|
||||||
|
#ifdef WIFI_REASON_AUTH_EXPIRE
|
||||||
|
if (reason == WIFI_REASON_AUTH_EXPIRE) {
|
||||||
|
return "wrong_password";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef WIFI_REASON_AUTH_FAIL
|
||||||
|
if (reason == WIFI_REASON_AUTH_FAIL) {
|
||||||
|
return "wrong_password";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT
|
||||||
|
if (reason == WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT) {
|
||||||
|
return "wrong_password";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef WIFI_REASON_HANDSHAKE_TIMEOUT
|
||||||
|
if (reason == WIFI_REASON_HANDSHAKE_TIMEOUT) {
|
||||||
|
return "wrong_password";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef WIFI_REASON_ASSOC_FAIL
|
||||||
|
if (reason == WIFI_REASON_ASSOC_FAIL) {
|
||||||
|
return "association_failed";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef WIFI_REASON_CONNECTION_FAIL
|
||||||
|
if (reason == WIFI_REASON_CONNECTION_FAIL) {
|
||||||
|
return "association_failed";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef WIFI_REASON_NO_AP_FOUND
|
||||||
|
if (reason == WIFI_REASON_NO_AP_FOUND) {
|
||||||
|
return "no_ap_found";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef WIFI_REASON_NO_AP_FOUND_IN_AUTHMODE_THRESHOLD
|
||||||
|
if (reason == WIFI_REASON_NO_AP_FOUND_IN_AUTHMODE_THRESHOLD) {
|
||||||
|
return "ap_security_unsupported";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef WIFI_REASON_NO_AP_FOUND_IN_RSSI_THRESHOLD
|
||||||
|
if (reason == WIFI_REASON_NO_AP_FOUND_IN_RSSI_THRESHOLD) {
|
||||||
|
return "signal_too_weak";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef WIFI_REASON_BEACON_TIMEOUT
|
||||||
|
if (reason == WIFI_REASON_BEACON_TIMEOUT) {
|
||||||
|
return "ap_not_responding";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef WIFI_REASON_ASSOC_EXPIRE
|
||||||
|
if (reason == WIFI_REASON_ASSOC_EXPIRE) {
|
||||||
|
return "ap_not_responding";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef WIFI_REASON_ASSOC_LEAVE
|
||||||
|
if (reason == WIFI_REASON_ASSOC_LEAVE) {
|
||||||
|
return "ap_not_responding";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return "connect_failed";
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *wifi_manager_resolve_connect_error_code(esp_err_t connect_rc) {
|
||||||
|
if (connect_rc == ESP_ERR_TIMEOUT) {
|
||||||
|
return "connect_timeout";
|
||||||
|
}
|
||||||
|
if (connect_rc == ESP_ERR_INVALID_STATE) {
|
||||||
|
return "wifi_unavailable";
|
||||||
|
}
|
||||||
|
if (connect_rc != ESP_FAIL) {
|
||||||
|
return "connect_failed";
|
||||||
|
}
|
||||||
|
if (s_last_sta_disconnect_reason_valid) {
|
||||||
|
return wifi_manager_error_from_disconnect_reason(s_last_sta_disconnect_reason);
|
||||||
|
}
|
||||||
|
return "connect_failed";
|
||||||
|
}
|
||||||
|
|
||||||
static void wifi_manager_apply_sta_throughput_profile(void) {
|
static void wifi_manager_apply_sta_throughput_profile(void) {
|
||||||
const uint8_t protocol = WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N;
|
const uint8_t protocol = WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N;
|
||||||
esp_err_t proto_rc = esp_wifi_set_protocol(WIFI_IF_STA, protocol);
|
esp_err_t proto_rc = esp_wifi_set_protocol(WIFI_IF_STA, protocol);
|
||||||
@@ -175,11 +258,15 @@ static void wifi_event_handler(void *arg,
|
|||||||
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||||
s_ready = false;
|
s_ready = false;
|
||||||
if (!s_started || !s_sta_reconnect_enabled) {
|
if (!s_started || !s_sta_reconnect_enabled) {
|
||||||
|
wifi_manager_notify_event(WIFI_MANAGER_EVENT_STA_DISCONNECTED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
wifi_event_sta_disconnected_t *disc = (wifi_event_sta_disconnected_t *)event_data;
|
wifi_event_sta_disconnected_t *disc = (wifi_event_sta_disconnected_t *)event_data;
|
||||||
uint8_t reason = disc != NULL ? disc->reason : 0;
|
uint8_t reason = disc != NULL ? disc->reason : 0;
|
||||||
|
s_last_sta_disconnect_reason = reason;
|
||||||
|
s_last_sta_disconnect_reason_valid = (reason != 0);
|
||||||
|
wifi_manager_notify_event(WIFI_MANAGER_EVENT_STA_DISCONNECTED);
|
||||||
|
|
||||||
if (s_retry_num < CONFIG_TQ_WIFI_MAXIMUM_RETRY) {
|
if (s_retry_num < CONFIG_TQ_WIFI_MAXIMUM_RETRY) {
|
||||||
(void)esp_wifi_connect();
|
(void)esp_wifi_connect();
|
||||||
@@ -208,6 +295,7 @@ static void wifi_event_handler(void *arg,
|
|||||||
if (s_wifi_event_group != NULL) {
|
if (s_wifi_event_group != NULL) {
|
||||||
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
|
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
|
||||||
}
|
}
|
||||||
|
wifi_manager_notify_event(WIFI_MANAGER_EVENT_STA_CONNECTED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,6 +564,8 @@ static esp_err_t wifi_manager_connect_sta(const wifi_sta_credentials_t *creds, b
|
|||||||
xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT);
|
xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT);
|
||||||
s_retry_num = 0;
|
s_retry_num = 0;
|
||||||
s_ready = false;
|
s_ready = false;
|
||||||
|
s_last_sta_disconnect_reason = 0;
|
||||||
|
s_last_sta_disconnect_reason_valid = false;
|
||||||
|
|
||||||
s_sta_reconnect_enabled = false;
|
s_sta_reconnect_enabled = false;
|
||||||
esp_err_t err = esp_wifi_disconnect();
|
esp_err_t err = esp_wifi_disconnect();
|
||||||
@@ -486,23 +576,28 @@ static esp_err_t wifi_manager_connect_sta(const wifi_sta_credentials_t *creds, b
|
|||||||
|
|
||||||
err = esp_wifi_set_mode(keep_softap ? WIFI_MODE_APSTA : WIFI_MODE_STA);
|
err = esp_wifi_set_mode(keep_softap ? WIFI_MODE_APSTA : WIFI_MODE_STA);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
|
wifi_manager_notify_event(WIFI_MANAGER_EVENT_STA_CONNECT_FAILED);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
|
err = esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
|
wifi_manager_notify_event(WIFI_MANAGER_EVENT_STA_CONNECT_FAILED);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = esp_wifi_start();
|
err = esp_wifi_start();
|
||||||
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
||||||
|
wifi_manager_notify_event(WIFI_MANAGER_EVENT_STA_CONNECT_FAILED);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = esp_wifi_connect();
|
err = esp_wifi_connect();
|
||||||
if (err != ESP_OK && err != ESP_ERR_WIFI_CONN) {
|
if (err != ESP_OK && err != ESP_ERR_WIFI_CONN) {
|
||||||
|
wifi_manager_notify_event(WIFI_MANAGER_EVENT_STA_CONNECT_FAILED);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
wifi_manager_notify_event(WIFI_MANAGER_EVENT_STA_CONNECTING);
|
||||||
ESP_LOGI(TAG, "trying to connect to SSID:%s", creds->ssid);
|
ESP_LOGI(TAG, "trying to connect to SSID:%s", creds->ssid);
|
||||||
|
|
||||||
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
|
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
|
||||||
@@ -526,10 +621,12 @@ static esp_err_t wifi_manager_connect_sta(const wifi_sta_credentials_t *creds, b
|
|||||||
|
|
||||||
if (bits & WIFI_FAIL_BIT) {
|
if (bits & WIFI_FAIL_BIT) {
|
||||||
ESP_LOGW(TAG, "failed to connect to SSID:%s", creds->ssid);
|
ESP_LOGW(TAG, "failed to connect to SSID:%s", creds->ssid);
|
||||||
|
wifi_manager_notify_event(WIFI_MANAGER_EVENT_STA_CONNECT_FAILED);
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGW(TAG, "Wi-Fi connect timeout, SSID:%s", creds->ssid);
|
ESP_LOGW(TAG, "Wi-Fi connect timeout, SSID:%s", creds->ssid);
|
||||||
|
wifi_manager_notify_event(WIFI_MANAGER_EVENT_STA_CONNECT_FAILED);
|
||||||
return ESP_ERR_TIMEOUT;
|
return ESP_ERR_TIMEOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -583,7 +680,9 @@ static esp_err_t wifi_manager_prov_submit_post_handler(httpd_req_t *req) {
|
|||||||
|
|
||||||
esp_err_t err = wifi_manager_connect_sta(&creds, true);
|
esp_err_t err = wifi_manager_connect_sta(&creds, true);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
return wifi_manager_send_prov_json(req, false, "connect_failed");
|
return wifi_manager_send_prov_json(req,
|
||||||
|
false,
|
||||||
|
wifi_manager_resolve_connect_error_code(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
err = wifi_manager_save_credentials(&creds);
|
err = wifi_manager_save_credentials(&creds);
|
||||||
@@ -789,6 +888,8 @@ esp_err_t wifi_manager_start(void) {
|
|||||||
s_ready = false;
|
s_ready = false;
|
||||||
s_sta_reconnect_enabled = true;
|
s_sta_reconnect_enabled = true;
|
||||||
s_provisioning_active = false;
|
s_provisioning_active = false;
|
||||||
|
s_last_sta_disconnect_reason = 0;
|
||||||
|
s_last_sta_disconnect_reason_valid = false;
|
||||||
|
|
||||||
if (s_wifi_event_group == NULL) {
|
if (s_wifi_event_group == NULL) {
|
||||||
s_wifi_event_group = xEventGroupCreate();
|
s_wifi_event_group = xEventGroupCreate();
|
||||||
@@ -888,6 +989,8 @@ esp_err_t wifi_manager_stop(void) {
|
|||||||
s_ready = false;
|
s_ready = false;
|
||||||
s_retry_num = 0;
|
s_retry_num = 0;
|
||||||
s_sta_reconnect_enabled = false;
|
s_sta_reconnect_enabled = false;
|
||||||
|
s_last_sta_disconnect_reason = 0;
|
||||||
|
s_last_sta_disconnect_reason_valid = false;
|
||||||
|
|
||||||
wifi_manager_stop_softap_provisioning();
|
wifi_manager_stop_softap_provisioning();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user