|
18 | 18 | #include <string.h> |
19 | 19 |
|
20 | 20 | #define DBG_TAG "xz.ws" |
21 | | -#define DBG_LVL DBG_INFO |
| 21 | +#define DBG_LVL DBG_LOG |
22 | 22 | #include <rtdbg.h> |
23 | 23 |
|
24 | 24 | /* Configuration constants */ |
|
29 | 29 | #define NETWORK_CHECK_DELAY_MS 500 |
30 | 30 | #define RETRY_DELAY_BASE_MS 1000 |
31 | 31 | #define RETRY_DELAY_INCREMENT_MS 200 |
32 | | -#define TTS_STOP_DELAY_MS 10 |
| 32 | +#define TTS_STOP_DELAY_MS 200 |
33 | 33 | #define BUTTON_DEBOUNCE_MS 20 |
34 | 34 | #define WAKEWORD_INIT_FLAG_RESET 0 |
35 | | -#define TTS_SENTENCE_TIMEOUT_MS 8500 |
| 35 | +#define TTS_SENTENCE_TIMEOUT_MS 6000 |
36 | 36 |
|
37 | 37 | /* Global application state */ |
38 | 38 | xiaozhi_app_t g_app = |
39 | | -{ |
40 | | - .xiaozhi_tid = RT_NULL, |
41 | | - .client_id = "af7ac552-9991-4b31-b660-683b210ae95f", |
42 | | - .websocket_reconnect_flag = 0, |
43 | | - .iot_initialized = 0, |
44 | | - .last_reconnect_time = 0, |
45 | | - .mac_address_string = {0}, |
46 | | - .client_id_string = {0}, |
47 | | - .ws = {0}, |
48 | | - .state = kDeviceStateUnknown, |
49 | | - .button_event = RT_NULL, |
50 | | - .wakeword_initialized_session = 0, |
51 | | - .multi_turn_conversation_enabled = RT_TRUE, |
52 | | - .tts_sentence_end_timer = RT_NULL |
| 39 | + { |
| 40 | + .xiaozhi_tid = RT_NULL, |
| 41 | + .client_id = "af7ac552-9991-4b31-b660-683b210ae95f", |
| 42 | + .websocket_reconnect_flag = 0, |
| 43 | + .iot_initialized = 0, |
| 44 | + .last_reconnect_time = 0, |
| 45 | + .mac_address_string = {0}, |
| 46 | + .client_id_string = {0}, |
| 47 | + .ws = {0}, |
| 48 | + .state = kDeviceStateUnknown, |
| 49 | + .button_event = RT_NULL, |
| 50 | + .wakeword_initialized_session = 0, |
| 51 | + .multi_turn_conversation_enabled = RT_TRUE, |
| 52 | + .tts_sentence_end_timer = RT_NULL, |
| 53 | + .tts_stop_workqueue = RT_NULL |
53 | 54 | }; |
54 | 55 |
|
55 | 56 | #include "ui/xiaozhi_ui.h" |
@@ -134,10 +135,65 @@ void xz_wakeword_detected_callback(const char *wake_word, float confidence) |
134 | 135 | /* TTS sentence end timeout handler */ |
135 | 136 | static void tts_sentence_end_timeout(void *parameter) |
136 | 137 | { |
137 | | - LOG_D("TTS sentence end timeout, sending event to restart listening for multi-turn conversation"); |
| 138 | + uint32_t tick = rt_tick_get(); |
| 139 | + LOG_D("TTS sentence end timeout at tick=%u, sending event to restart listening for multi-turn conversation", tick); |
138 | 140 |
|
139 | 141 | /* Send event to button thread to handle the restart in non-ISR context */ |
140 | | - rt_event_send(g_app.button_event, TIMEOUT_EVENT); |
| 142 | + rt_err_t ev_ret = rt_event_send(g_app.button_event, TIMEOUT_EVENT); |
| 143 | + if (ev_ret != RT_EOK) |
| 144 | + { |
| 145 | + LOG_W("rt_event_send returned %d from timer callback", ev_ret); |
| 146 | + } |
| 147 | +} |
| 148 | + |
| 149 | +/* TTS stop delayed restart work function */ |
| 150 | +static void tts_stop_restart_listening(struct rt_work *work, void *work_data) |
| 151 | +{ |
| 152 | + LOG_D("TTS stop delayed restart: restarting listening mode"); |
| 153 | + |
| 154 | + /* Multi-turn conversation: keep listening without restarting wake word detection */ |
| 155 | + g_app.state = kDeviceStateListening; |
| 156 | + xz_mic(1); |
| 157 | + |
| 158 | + /* Try to send listen start */ |
| 159 | + if (ws_send_listen_start(&g_app.ws.clnt, g_app.ws.session_id, kListeningModeAutoStop)) |
| 160 | + { |
| 161 | + xiaozhi_ui_chat_status(" 聆听中"); |
| 162 | + xiaozhi_ui_chat_output("聆听中..."); |
| 163 | + } |
| 164 | + else |
| 165 | + { |
| 166 | + LOG_W("Listen start failed in TTS stop delayed restart"); |
| 167 | + /* Reset state if failed */ |
| 168 | + g_app.state = kDeviceStateIdle; |
| 169 | + xz_mic(0); |
| 170 | + xiaozhi_ui_chat_status(" 就绪"); |
| 171 | + xiaozhi_ui_chat_output("就绪"); |
| 172 | + } |
| 173 | +} |
| 174 | + |
| 175 | +/* Static work item for TTS stop restart */ |
| 176 | +static struct rt_work tts_stop_work; |
| 177 | + |
| 178 | +/* Static timer for TTS stop delay */ |
| 179 | +static rt_timer_t tts_stop_delay_timer = RT_NULL; |
| 180 | + |
| 181 | +/* TTS stop delay timer callback */ |
| 182 | +static void tts_stop_delay_timeout(void *parameter) |
| 183 | +{ |
| 184 | + LOG_D("TTS stop delay timeout, submitting work to restart listening"); |
| 185 | + |
| 186 | + /* Submit work to restart listening */ |
| 187 | + if (g_app.tts_stop_workqueue) |
| 188 | + { |
| 189 | + rt_workqueue_submit_work(g_app.tts_stop_workqueue, &tts_stop_work, 0); |
| 190 | + } |
| 191 | + else |
| 192 | + { |
| 193 | + LOG_W("Workqueue not available in timer callback"); |
| 194 | + /* Fallback */ |
| 195 | + tts_stop_restart_listening(&tts_stop_work, RT_NULL); |
| 196 | + } |
141 | 197 | } |
142 | 198 |
|
143 | 199 | /* State consistency check function */ |
@@ -623,8 +679,12 @@ err_t my_wsapp_fn(int code, char *buf, size_t len) |
623 | 679 | /* Stop TTS sentence end timer if running */ |
624 | 680 | if (g_app.tts_sentence_end_timer) |
625 | 681 | { |
626 | | - rt_timer_stop(g_app.tts_sentence_end_timer); |
627 | | - LOG_D("Stopped TTS sentence end timer due to disconnection"); |
| 682 | + rt_err_t stop_ret = rt_timer_stop(g_app.tts_sentence_end_timer); |
| 683 | + LOG_D("rt_timer_stop returned %d when stopping timer due to disconnection", stop_ret); |
| 684 | + if (stop_ret == RT_EOK) |
| 685 | + { |
| 686 | + LOG_D("Stopped TTS sentence end timer due to disconnection"); |
| 687 | + } |
628 | 688 | } |
629 | 689 |
|
630 | 690 | xiaozhi_ui_chat_status(" 休眠中"); |
@@ -813,6 +873,59 @@ void xz_ws_audio_init(void) |
813 | 873 | /* Wake word detection will be initialized after WebSocket connection */ |
814 | 874 | LOG_I("Audio system initialized successfully"); |
815 | 875 |
|
| 876 | + /* Create TTS sentence end timer once here to avoid race conditions |
| 877 | + * Timer is one-shot and will be started on sentence_end events */ |
| 878 | + if (!g_app.tts_sentence_end_timer) |
| 879 | + { |
| 880 | + g_app.tts_sentence_end_timer = rt_timer_create("tts_end_timer", |
| 881 | + tts_sentence_end_timeout, |
| 882 | + RT_NULL, |
| 883 | + rt_tick_from_millisecond(TTS_SENTENCE_TIMEOUT_MS), |
| 884 | + RT_TIMER_FLAG_ONE_SHOT); |
| 885 | + if (g_app.tts_sentence_end_timer) |
| 886 | + { |
| 887 | + LOG_D("Created TTS sentence end timer (%d ms)", TTS_SENTENCE_TIMEOUT_MS); |
| 888 | + } |
| 889 | + else |
| 890 | + { |
| 891 | + LOG_E("Failed to create TTS sentence end timer"); |
| 892 | + } |
| 893 | + } |
| 894 | + |
| 895 | + /* Create workqueue for TTS stop delayed restart */ |
| 896 | + if (!g_app.tts_stop_workqueue) |
| 897 | + { |
| 898 | + g_app.tts_stop_workqueue = rt_workqueue_create("tts_stop_wq", 2048, RT_THREAD_PRIORITY_MAX - 1); |
| 899 | + if (g_app.tts_stop_workqueue) |
| 900 | + { |
| 901 | + LOG_D("Created TTS stop workqueue"); |
| 902 | + /* Initialize the work item */ |
| 903 | + rt_work_init(&tts_stop_work, tts_stop_restart_listening, RT_NULL); |
| 904 | + } |
| 905 | + else |
| 906 | + { |
| 907 | + LOG_E("Failed to create TTS stop workqueue"); |
| 908 | + } |
| 909 | + } |
| 910 | + |
| 911 | + /* Create TTS stop delay timer */ |
| 912 | + if (!tts_stop_delay_timer) |
| 913 | + { |
| 914 | + tts_stop_delay_timer = rt_timer_create("tts_stop_delay", |
| 915 | + tts_stop_delay_timeout, |
| 916 | + RT_NULL, |
| 917 | + rt_tick_from_millisecond(TTS_STOP_DELAY_MS), |
| 918 | + RT_TIMER_FLAG_ONE_SHOT); |
| 919 | + if (tts_stop_delay_timer) |
| 920 | + { |
| 921 | + LOG_D("Created TTS stop delay timer (%d ms)", TTS_STOP_DELAY_MS); |
| 922 | + } |
| 923 | + else |
| 924 | + { |
| 925 | + LOG_E("Failed to create TTS stop delay timer"); |
| 926 | + } |
| 927 | + } |
| 928 | + |
816 | 929 | init_flag = 0; |
817 | 930 | } |
818 | 931 | } |
@@ -1042,25 +1155,31 @@ void Message_handle(const uint8_t *data, uint16_t len) |
1042 | 1155 | /* Check if multi-turn conversation is enabled */ |
1043 | 1156 | if (g_app.multi_turn_conversation_enabled) |
1044 | 1157 | { |
1045 | | - /* Multi-turn conversation: keep listening without restarting wake word detection */ |
1046 | | - LOG_I("Multi-turn conversation enabled, restarting listening mode"); |
1047 | | - g_app.state = kDeviceStateListening; |
1048 | | - xz_mic(1); |
1049 | | - |
1050 | | - /* Try to send listen start */ |
1051 | | - if (ws_send_listen_start(&g_app.ws.clnt, g_app.ws.session_id, kListeningModeAutoStop)) |
| 1158 | + /* Multi-turn conversation: start delay timer to restart listening after audio finishes */ |
| 1159 | + if (tts_stop_delay_timer) |
1052 | 1160 | { |
1053 | | - xiaozhi_ui_chat_status(" 聆听中"); |
1054 | | - xiaozhi_ui_chat_output("聆听中..."); |
| 1161 | + LOG_D("Starting TTS stop delay timer (%d ms)", TTS_STOP_DELAY_MS); |
| 1162 | + rt_timer_start(tts_stop_delay_timer); |
1055 | 1163 | } |
1056 | 1164 | else |
1057 | 1165 | { |
1058 | | - LOG_W("Listen start failed in TTS stop handler"); |
1059 | | - /* Reset state if failed */ |
1060 | | - g_app.state = kDeviceStateIdle; |
1061 | | - xz_mic(0); |
1062 | | - xiaozhi_ui_chat_status(" 就绪"); |
1063 | | - xiaozhi_ui_chat_output("就绪"); |
| 1166 | + LOG_W("Delay timer not available, restarting listening immediately"); |
| 1167 | + /* Fallback: restart immediately */ |
| 1168 | + g_app.state = kDeviceStateListening; |
| 1169 | + xz_mic(1); |
| 1170 | + if (ws_send_listen_start(&g_app.ws.clnt, g_app.ws.session_id, kListeningModeAutoStop)) |
| 1171 | + { |
| 1172 | + xiaozhi_ui_chat_status(" 聆听中"); |
| 1173 | + xiaozhi_ui_chat_output("聆听中..."); |
| 1174 | + } |
| 1175 | + else |
| 1176 | + { |
| 1177 | + LOG_W("Listen start failed in TTS stop handler"); |
| 1178 | + g_app.state = kDeviceStateIdle; |
| 1179 | + xz_mic(0); |
| 1180 | + xiaozhi_ui_chat_status(" 就绪"); |
| 1181 | + xiaozhi_ui_chat_output("就绪"); |
| 1182 | + } |
1064 | 1183 | } |
1065 | 1184 | } |
1066 | 1185 | else |
@@ -1093,29 +1212,40 @@ void Message_handle(const uint8_t *data, uint16_t len) |
1093 | 1212 | /* For multi-turn conversation, start a timeout timer to restart listening after sentence end */ |
1094 | 1213 | if (g_app.multi_turn_conversation_enabled && g_app.state == kDeviceStateSpeaking) |
1095 | 1214 | { |
1096 | | - /* Stop any existing timer first */ |
| 1215 | + /* Use the pre-created timer: stop then start to reset timeout */ |
1097 | 1216 | if (g_app.tts_sentence_end_timer) |
1098 | 1217 | { |
1099 | | - rt_timer_stop(g_app.tts_sentence_end_timer); |
| 1218 | + rt_err_t stop_ret = rt_timer_stop(g_app.tts_sentence_end_timer); |
| 1219 | + if (stop_ret != RT_EOK) |
| 1220 | + { |
| 1221 | + LOG_W("rt_timer_stop returned %d when resetting TTS timer", stop_ret); |
| 1222 | + } |
| 1223 | + rt_err_t start_ret = rt_timer_start(g_app.tts_sentence_end_timer); |
| 1224 | + if (start_ret == RT_EOK) |
| 1225 | + { |
| 1226 | + LOG_D("Started TTS sentence end timer (%d ms), stop_ret=%d start_ret=%d", TTS_SENTENCE_TIMEOUT_MS, stop_ret, start_ret); |
| 1227 | + } |
| 1228 | + else |
| 1229 | + { |
| 1230 | + LOG_E("Failed to start existing TTS sentence end timer, start_ret=%d", start_ret); |
| 1231 | + } |
1100 | 1232 | } |
1101 | 1233 | else |
1102 | 1234 | { |
1103 | | - /* Create timer if it doesn't exist */ |
| 1235 | + /* Fallback: try to create and start the timer if it wasn't created earlier */ |
1104 | 1236 | g_app.tts_sentence_end_timer = rt_timer_create("tts_end_timer", |
1105 | | - tts_sentence_end_timeout, |
1106 | | - RT_NULL, |
1107 | | - rt_tick_from_millisecond(TTS_SENTENCE_TIMEOUT_MS), |
1108 | | - RT_TIMER_FLAG_ONE_SHOT); |
1109 | | - } |
1110 | | - |
1111 | | - if (g_app.tts_sentence_end_timer) |
1112 | | - { |
1113 | | - rt_timer_start(g_app.tts_sentence_end_timer); |
1114 | | - LOG_D("Started TTS sentence end timer (3s timeout)"); |
1115 | | - } |
1116 | | - else |
1117 | | - { |
1118 | | - LOG_E("Failed to create TTS sentence end timer"); |
| 1237 | + tts_sentence_end_timeout, |
| 1238 | + RT_NULL, |
| 1239 | + rt_tick_from_millisecond(TTS_SENTENCE_TIMEOUT_MS), |
| 1240 | + RT_TIMER_FLAG_ONE_SHOT); |
| 1241 | + if (g_app.tts_sentence_end_timer && rt_timer_start(g_app.tts_sentence_end_timer) == RT_EOK) |
| 1242 | + { |
| 1243 | + LOG_D("Created and started fallback TTS sentence end timer (%d ms)", TTS_SENTENCE_TIMEOUT_MS); |
| 1244 | + } |
| 1245 | + else |
| 1246 | + { |
| 1247 | + LOG_E("Failed to create/start fallback TTS sentence end timer"); |
| 1248 | + } |
1119 | 1249 | } |
1120 | 1250 | } |
1121 | 1251 | } |
@@ -1265,8 +1395,8 @@ char *get_xiaozhi_ws(void) |
1265 | 1395 | { |
1266 | 1396 | bytes_read = webclient_read(session, buffer + content_pos, |
1267 | 1397 | content_length - content_pos > GET_RESP_BUFSZ |
1268 | | - ? GET_RESP_BUFSZ |
1269 | | - : content_length - content_pos); |
| 1398 | + ? GET_RESP_BUFSZ |
| 1399 | + : content_length - content_pos); |
1270 | 1400 | if (bytes_read <= 0) |
1271 | 1401 | { |
1272 | 1402 | break; |
|
0 commit comments