/* * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "control_plane_internal.h" #include #include #include "control_plane_events.h" #include "esp_err.h" #include "esp_log.h" #include "esp_timer.h" #include "freertos/FreeRTOS.h" #include "freertos/queue.h" #include "freertos/task.h" #include "sdkconfig.h" #ifndef CONFIG_TQ_SYSTEM_EVENT_ENABLE #define CONFIG_TQ_SYSTEM_EVENT_ENABLE 1 #endif #ifndef CONFIG_TQ_SYSTEM_EVENT_QUEUE_LEN #define CONFIG_TQ_SYSTEM_EVENT_QUEUE_LEN 32 #endif #ifndef CONFIG_TQ_SYSTEM_EVENT_TASK_STACK #define CONFIG_TQ_SYSTEM_EVENT_TASK_STACK 4096 #endif #if !defined(CONFIG_TQ_PTT_MODE_HOLD_TO_TALK) && !defined(CONFIG_TQ_PTT_MODE_CLICK_TO_TALK) #define CONFIG_TQ_PTT_MODE_CLICK_TO_TALK 1 #endif #define CONTROL_PLANE_PTT_HOLD_STEP_MS 80 #ifndef CONFIG_TQ_LOCAL_TASK_CORE #define CONFIG_TQ_LOCAL_TASK_CORE 1 #endif #if CONFIG_TQ_LOCAL_TASK_CORE < 0 #define CONTROL_PLANE_SYSTEM_EVENT_TASK_CORE tskNO_AFFINITY #else #define CONTROL_PLANE_SYSTEM_EVENT_TASK_CORE ((BaseType_t)CONFIG_TQ_LOCAL_TASK_CORE) #endif static const char *TAG = "cp_system_event"; static QueueHandle_t s_system_event_queue; static TaskHandle_t s_system_event_task; static volatile bool s_stop_requested; static const char *control_plane_event_family_name(control_plane_event_family_t family) { switch (family) { case CONTROL_PLANE_EVENT_FAMILY_KEY: return "KEY"; case CONTROL_PLANE_EVENT_FAMILY_WIFI: return "WIFI"; case CONTROL_PLANE_EVENT_FAMILY_MULTIMODAL: return "MULTIMODAL"; case CONTROL_PLANE_EVENT_FAMILY_LCD: return "LCD"; case CONTROL_PLANE_EVENT_FAMILY_DIRECTION: return "DIRECTION"; case CONTROL_PLANE_EVENT_FAMILY_CALIBRATION: return "CALIBRATION"; case CONTROL_PLANE_EVENT_FAMILY_RUNTIME: return "RUNTIME"; default: return "UNKNOWN"; } } const char *control_plane_key_id_name(control_plane_key_id_t key_id) { switch (key_id) { case CONTROL_PLANE_KEY_ID_PTT: return "PTT"; case CONTROL_PLANE_KEY_ID_SW1: return "SW1"; case CONTROL_PLANE_KEY_ID_SW2: return "SW2"; default: return "UNKNOWN"; } } const char *control_plane_key_event_type_name(control_plane_key_event_type_t type) { switch (type) { case CONTROL_PLANE_KEY_EVENT_PRESSED: return "PRESSED"; case CONTROL_PLANE_KEY_EVENT_RELEASED: return "RELEASED"; case CONTROL_PLANE_KEY_EVENT_SHORT_PRESS: return "SHORT_PRESS"; case CONTROL_PLANE_KEY_EVENT_LONG_PRESS_START: return "LONG_PRESS_START"; case CONTROL_PLANE_KEY_EVENT_LONG_PRESS_RELEASE: return "LONG_PRESS_RELEASE"; case CONTROL_PLANE_KEY_EVENT_DOUBLE_CLICK: return "DOUBLE_CLICK"; case CONTROL_PLANE_KEY_EVENT_REPEAT: return "REPEAT"; case CONTROL_PLANE_KEY_EVENT_CANCELLED: return "CANCELLED"; default: return "UNKNOWN"; } } static void control_plane_dispatch_key_event(const control_plane_key_event_t *event) { if (event == NULL) { return; } ESP_LOGI(TAG, "key event key=%s type=%s gpio=%d level=%d duration=%" PRIu32 "ms clicks=%" PRIu32, control_plane_key_id_name(event->key_id), control_plane_key_event_type_name(event->type), (int)event->gpio_num, event->level, event->press_duration_ms, event->click_count); if (event->key_id == CONTROL_PLANE_KEY_ID_SW2) { if (event->type == CONTROL_PLANE_KEY_EVENT_SHORT_PRESS) { esp_err_t request_err = control_plane_request_cloud_chouqian(); if (request_err == ESP_ERR_INVALID_STATE) { ESP_LOGI(TAG, "Cloud chouqian request skipped: multimodal session unavailable or request pending"); } else if (request_err != ESP_OK) { ESP_LOGW(TAG, "Cloud chouqian request failed: %s", esp_err_to_name(request_err)); } } if (event->type == CONTROL_PLANE_KEY_EVENT_DOUBLE_CLICK) { const domain_expression_request_t request = { .id = DOMAIN_EXPRESSION_WUXING, .trigger = DOMAIN_EXPRESSION_TRIGGER_SW2_DOUBLE_CLICK, }; esp_err_t expr_err = domain_runtime_request_expression(&request); if (expr_err != ESP_OK && expr_err != ESP_ERR_INVALID_STATE) { ESP_LOGW(TAG, "SW2 double-click wuxing expression failed: %s", esp_err_to_name(expr_err)); } } return; } if (event->key_id != CONTROL_PLANE_KEY_ID_PTT) { return; } switch (event->type) { case CONTROL_PLANE_KEY_EVENT_PRESSED: { #if CONFIG_TQ_PTT_MODE_CLICK_TO_TALK control_plane_multimodal_runtime_state_t state = {0}; if (control_plane_multimodal_get_runtime_state(&state) == ESP_OK && state.remote_responding) { esp_err_t err = control_plane_multimodal_send_request_to_speak(); if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { ESP_LOGW(TAG, "PTT barge-in request failed: %s", esp_err_to_name(err)); } } #else control_plane_set_ptt_active(true); #endif break; } case CONTROL_PLANE_KEY_EVENT_RELEASED: { #if !CONFIG_TQ_PTT_MODE_CLICK_TO_TALK control_plane_set_ptt_active(false); #endif break; } case CONTROL_PLANE_KEY_EVENT_SHORT_PRESS: { #if CONFIG_TQ_PTT_MODE_CLICK_TO_TALK control_plane_set_ptt_active(true); #endif break; } case CONTROL_PLANE_KEY_EVENT_DOUBLE_CLICK: { const domain_expression_request_t request = { .id = DOMAIN_EXPRESSION_MUYU, .trigger = DOMAIN_EXPRESSION_TRIGGER_PTT_DOUBLE_CLICK, }; esp_err_t expr_err = domain_runtime_request_expression(&request); if (expr_err != ESP_OK && expr_err != ESP_ERR_INVALID_STATE) { ESP_LOGW(TAG, "PTT double-click expression failed: %s", esp_err_to_name(expr_err)); } break; } default: break; } } static void control_plane_dispatch_system_event(const control_plane_system_event_t *event) { if (event == NULL) { return; } ESP_LOGI(TAG, "dispatch family=%s", control_plane_event_family_name(event->family)); switch (event->family) { case CONTROL_PLANE_EVENT_FAMILY_KEY: control_plane_dispatch_key_event(&event->data.key); break; default: ESP_LOGW(TAG, "unhandled event family=%s", control_plane_event_family_name(event->family)); break; } } static void control_plane_system_event_task(void *arg) { (void)arg; ESP_LOGI(TAG, "System event task started queue_len=%d", CONFIG_TQ_SYSTEM_EVENT_QUEUE_LEN); while (!s_stop_requested) { control_plane_system_event_t event = {0}; if (xQueueReceive(s_system_event_queue, &event, pdMS_TO_TICKS(200)) == pdTRUE) { control_plane_dispatch_system_event(&event); } } ESP_LOGI(TAG, "System event task stopped"); s_system_event_task = NULL; vTaskDelete(NULL); } esp_err_t control_plane_post_system_event(const control_plane_system_event_t *event, uint32_t timeout_ms) { #if CONFIG_TQ_SYSTEM_EVENT_ENABLE if (event == NULL) { return ESP_ERR_INVALID_ARG; } if (s_system_event_queue == NULL) { return ESP_ERR_INVALID_STATE; } TickType_t timeout_ticks = pdMS_TO_TICKS(timeout_ms); if (xQueueSend(s_system_event_queue, event, timeout_ticks) != pdTRUE) { return ESP_ERR_TIMEOUT; } return ESP_OK; #else (void)event; (void)timeout_ms; return ESP_ERR_NOT_SUPPORTED; #endif } int64_t control_plane_event_timestamp_ms(void) { return esp_timer_get_time() / 1000; } esp_err_t control_plane_start_system_event_task(void) { #if CONFIG_TQ_SYSTEM_EVENT_ENABLE if (s_system_event_task != NULL) { return ESP_OK; } if (s_system_event_queue == NULL) { s_system_event_queue = xQueueCreate(CONFIG_TQ_SYSTEM_EVENT_QUEUE_LEN, sizeof(control_plane_system_event_t)); if (s_system_event_queue == NULL) { return ESP_ERR_NO_MEM; } } s_stop_requested = false; BaseType_t created = xTaskCreatePinnedToCore(control_plane_system_event_task, "cp_system_event", CONFIG_TQ_SYSTEM_EVENT_TASK_STACK, NULL, 4, &s_system_event_task, CONTROL_PLANE_SYSTEM_EVENT_TASK_CORE); if (created != pdPASS) { s_system_event_task = NULL; return ESP_ERR_NO_MEM; } return ESP_OK; #else ESP_LOGI(TAG, "System event task disabled by configuration"); return ESP_OK; #endif } void control_plane_stop_system_event_task(void) { #if CONFIG_TQ_SYSTEM_EVENT_ENABLE s_stop_requested = true; control_plane_set_ptt_active(false); for (int i = 0; i < 20 && s_system_event_task != NULL; ++i) { vTaskDelay(pdMS_TO_TICKS(50)); } if (s_system_event_task != NULL) { vTaskDelete(s_system_event_task); s_system_event_task = NULL; } if (s_system_event_queue != NULL) { vQueueDelete(s_system_event_queue); s_system_event_queue = NULL; } #endif }