fix: improve field diagnostics and mirror logs to USB CDC

This commit is contained in:
chenjintao
2026-10-20 14:46:08 +08:00
parent 8bff1f569e
commit 07094d4a4e
20 changed files with 1036 additions and 200 deletions
@@ -68,6 +68,9 @@ SRC_FILES+= \
http_aofei.c \
net_mgt.c \
net_diag.c \
net_diag_probe.c \
net_diag_voice.c \
field_log.c \
ws_talkingq.c \
talkingq_wss.c \
talkingq_root_ca.c \
@@ -114,7 +117,7 @@ INC_DIRS+= \
#-------------------------------------------------------------------------------
# Configure compile flag for C
#-------------------------------------------------------------------------------
U_FLAGS:=
U_FLAGS:= -Dprintf=field_log_printf
#-------------------------------------------------------------------------------
# Configure compile flag for C++
@@ -26,7 +26,8 @@
#include "ql_uart.h"
#include "ql_application.h"
#include "ql_log.h"
#include "ql_log.h"
#include "field_log.h"
#include "hal_api.h"
#include "nfc.h"
#include "ota_baseline.h"
@@ -78,35 +79,8 @@ int app_get_app_sleep_flag(void)
}
void quec_usbcdc_callback(QL_UART_PORT_NUMBER_E port, void *para)
{
int read_len = 0;
char r_data[1024] = {0};
read_len = ql_uart_read(port, r_data, sizeof(r_data));
printf("uart[%d] callback read data, data:%s, read_len:%d \n", port, r_data, read_len);
}
void quec_usbcdc_write(unsigned char *data, int len)
{
ql_uart_write(QL_USB_CDC_PORT, data, len);
}
static void quec_usbcdc_init(void)
{
ql_uart_config_t dcb;
ql_uart_open(QL_USB_CDC_PORT, QL_UART_BAUD_115200, QL_FC_NONE);
dcb.baudrate = QL_UART_BAUD_115200;
dcb.data_bit = 8;
dcb.stop_bit = 1;
dcb.parity_bit = 0;
dcb.flow_ctrl = 0;
ql_uart_set_dcbconfig(QL_USB_CDC_PORT, &dcb);
ql_uart_register_cb(QL_USB_CDC_PORT, quec_usbcdc_callback); //use callback to read uart data
}
/* USB CDC is owned by field_log.c; no receive/echo callback is installed. */
#define AT_CMD_SLEPP_SUFFIX "\r\n"
void ql_atcmd_sleep(void)
{
@@ -135,6 +109,8 @@ static void custom_app_task(void * argv)
uint8_t flag = 0;
u32 app_start_time_record = 0;
field_log_init();
// 安全修复:初始化互斥锁
ql_rtos_mutex_create(&g_app_op_mutex);
@@ -162,7 +138,6 @@ static void custom_app_task(void * argv)
//app_uartsh_init();
// quec_usbcdc_init();
//ql_rtos_task_sleep_s(15);
@@ -0,0 +1,120 @@
/* Mirror application printf calls to plain-text USB CDC without waiting
* for the USB host in producers. UART still uses the SDK's original printf. */
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "ql_rtos.h"
#include "ql_uart.h"
#include "field_log.h"
/* This module is also compiled with -Dprintf=field_log_printf. */
#undef printf
extern int printf(const char *format, ...);
#define LOG_RECORD_BYTES 512
#define LOG_QUEUE_DEPTH 16
struct log_record { unsigned int length; char text[LOG_RECORD_BYTES]; };
static ql_queue_t usb_queue;
static ql_task_t usb_task;
static volatile int ready;
static unsigned long dropped;
static void note_drop(void)
{
ql_rtos_task_sched_lock();
++dropped;
ql_rtos_task_sched_unlock();
}
int field_log_printf(const char *format, ...)
{
struct log_record record;
va_list args;
int n, ret;
va_start(args, format);
n = vsnprintf(record.text, sizeof(record.text), format, args);
va_end(args);
if (n < 0) return n;
if (n >= (int)sizeof(record.text)) {
const char suffix[] = " [TRUNCATED]\r\n";
size_t cut = sizeof(record.text) - sizeof(suffix);
/* Keep the UTF-8 prefix valid if a multi-byte character crosses cut. */
while (cut && ((unsigned char)record.text[cut] & 0xc0) == 0x80) --cut;
memcpy(record.text + cut, suffix, sizeof(suffix));
}
record.length = (unsigned int)strlen(record.text);
ret = printf("%s", record.text);
if (ready && record.length &&
ql_rtos_queue_release(usb_queue, sizeof(record), (u8 *)&record, QL_NO_WAIT) != 0)
note_drop();
return ret;
}
/* SDK variants return zero on success or a positive byte count. */
static int usb_write_all(const char *text, int length)
{
int offset = 0;
while (offset < length) {
int n = ql_uart_write(QL_USB_CDC_PORT, (unsigned char *)text + offset, length - offset);
if (n == 0) return 0;
if (n < 0 || n > length - offset) return -1;
offset += n;
}
return 0;
}
static void usb_worker(void *arg)
{
struct log_record record;
int opened = 0;
unsigned long last_notice = 0, reported = 0;
(void)arg;
for (;;) {
if (!opened) {
int open_result = ql_uart_open(QL_USB_CDC_PORT, QL_UART_BAUD_115200, QL_FC_NONE);
if (open_result == QL_UART_SUCCESS || open_result == QL_UART_ALREAD_OPEN) {
const char banner[] = "\r\n[USB日志] 板板现场日志 CDC 已开启;UTF-8 / 115200 8N1;短按原报警键开始网络诊断\r\n";
opened = usb_write_all(banner, sizeof(banner) - 1) == 0;
if (!opened) ql_uart_close(QL_USB_CDC_PORT);
}
if (!opened) { ql_rtos_task_sleep_ms(1000); continue; }
}
if (ql_rtos_queue_wait(usb_queue, (u8 *)&record, sizeof(record), 1000) == 0) {
if (usb_write_all(record.text, record.length) != 0) {
note_drop();
ql_uart_close(QL_USB_CDC_PORT);
opened = 0;
ql_rtos_task_sleep_ms(1000);
}
}
/* Beacons identify the correct serial interface even when app is idle.
* Drop totals are never reset: concurrent producers cannot lose them. */
if (opened && (u32)(ql_rtos_get_systicks() - last_notice) >= 1000) {
char status[160];
unsigned long total;
ql_rtos_task_sched_lock(); total = dropped; ql_rtos_task_sched_unlock();
snprintf(status, sizeof(status), "\r\n[USB日志] 在线;累计丢弃=%lu 条%s\r\n", total,
total != reported ? ";USB拥塞或未连接,记录可能不完整" : "");
if (usb_write_all(status, (int)strlen(status)) != 0) {
ql_uart_close(QL_USB_CDC_PORT); opened = 0;
} else reported = total;
last_notice = ql_rtos_get_systicks();
}
}
}
int field_log_init(void)
{
int ret;
if (ready) return 0;
ret = ql_rtos_queue_create(&usb_queue, sizeof(struct log_record), LOG_QUEUE_DEPTH);
if (ret != 0) { printf("[USB日志] 队列创建失败=%d;仅保留UART日志\r\n", ret); return ret; }
ret = ql_rtos_task_create(&usb_task, 4096, 130, "usb_log", usb_worker, NULL);
if (ret != 0) {
ql_rtos_queue_delete(usb_queue); usb_queue = NULL;
printf("[USB日志] 转发任务创建失败=%d;仅保留UART日志\r\n", ret);
return ret;
}
ready = 1;
field_log_printf("[USB日志] 应用日志已同步到USB CDC;内核/基带日志不在此转发范围\r\n");
return 0;
}
@@ -16,6 +16,18 @@
#include "netdb.h"
#include "app_inc.h"
/* Temporary field-debug switch: set to 1 to restore GNSS text/NMEA logs.
* The quiet function deliberately evaluates arguments (some close the GNSS). */
#ifndef BANBAN_GNSS_LOG_ENABLED
#define BANBAN_GNSS_LOG_ENABLED 0
#endif
#if BANBAN_GNSS_LOG_ENABLED
#define GNSS_LOG printf
#else
static int gnss_log_quiet(const char *format, ...) { (void)format; return 0; }
#define GNSS_LOG gnss_log_quiet
#endif
// 新增宏定义(原代码中未定义)
#define GNSS_FAST_MODE_HOLD_TIME (5 * 60 * 1000) // FAST模式持续5分钟后切SLOW
@@ -213,7 +225,7 @@ void quec_gnss_callback(char *data, int len)
// 打印原始 NMEA 数据
for (i = 0; i < len; i++) {
printf("%c", data[i]);
GNSS_LOG("%c", data[i]);
}
// 在 data 中定位 GGA 句子的起止位置
@@ -267,7 +279,7 @@ void quec_gnss_callback(char *data, int len)
g_gps_location.is_valid = (fix_status >= 1) ? 1 : 0;
} else {
g_gps_location.is_valid = 0;
printf("\n[GNSS Parse failed] FS=%d, %s\n",
GNSS_LOG("\n[GNSS Parse failed] FS=%d, %s\n",
fix_status,
(fix_status == 0) ? "No Fix (Invalid)" : "Format error");
}
@@ -321,7 +333,7 @@ void quec_gnss_callback(char *data, int len)
void set_gnss_mode(gnss_mode_t mode)
{
g_gnss_mode = mode;
printf("[GNSSMode] Set to %s Mode\n",
GNSS_LOG("[GNSSMode] Set to %s Mode\n",
(mode == GNSS_MODE_FAST) ? "Fast (30s)" : "Slow (5min)");
}
@@ -353,11 +365,11 @@ gps_location_info_t* get_gps_location_info(void)
void print_gps_location_info(void)
{
if (g_gps_location.is_valid) {
printf("[GPSinfo] Latitude: %.8f, Longitude: %.8f\n",
GNSS_LOG("[GPSinfo] Latitude: %.8f, Longitude: %.8f\n",
g_gps_location.latitude, g_gps_location.longitude);
printf("[GPSinfo] rawNMEA: %s\n", g_gps_location.raw_nmea);
GNSS_LOG("[GPSinfo] rawNMEA: %s\n", g_gps_location.raw_nmea);
} else {
printf("[GPSinfo] No valid position data\n");
GNSS_LOG("[GPSinfo] No valid position data\n");
}
}
@@ -395,26 +407,26 @@ static void quec_gnss_test(void * argv)
/* ========== FAST MODE ========== */
if (fast_mode_start_time == 0) {
fast_mode_start_time = hal_os_up_time_ms();
printf("[GNSS] Enter FAST mode\n");
GNSS_LOG("[GNSS] Enter FAST mode\n");
}
if (!gnss_is_open) {
gnss_is_open = 1;
app_set_app_operation(1);
ql_gnss_open(quec_gnss_callback);
printf("[GNSS] FAST: GPS OPEN\n");
GNSS_LOG("[GNSS] FAST: GPS OPEN\n");
}
// 5分钟超时切SLOW
if (hal_os_up_time_ms() - fast_mode_start_time >= GNSS_FAST_MODE_HOLD_TIME) {
printf("[GNSS] FAST 5min timeout -> SLOW\n");
GNSS_LOG("[GNSS] FAST 5min timeout -> SLOW\n");
g_gnss_mode = GNSS_MODE_SLOW;
fast_mode_start_time = 0;
slow_mode_start_time = 0;
slow_next_wake_time = 0;
gnss_is_open = 0;
printf("[GNSS] SLOW: GPS CLOSED ret:%d\n", ql_gnss_close());
GNSS_LOG("[GNSS] SLOW: GPS CLOSED ret:%d\n", ql_gnss_close());
}
}
else
@@ -426,12 +438,12 @@ static void quec_gnss_test(void * argv)
if (slow_mode_start_time == 0) {
slow_mode_start_time = now;
slow_next_wake_time = now; // 立即尝试第一次定位
printf("[GNSS] Enter SLOW mode (fixed 15min interval)\n");
GNSS_LOG("[GNSS] Enter SLOW mode (fixed 15min interval)\n");
}
// 检查是否到达唤醒时间
if (now < slow_next_wake_time) {
printf("[GNSS] SLOW: next wake in %d s\n",
GNSS_LOG("[GNSS] SLOW: next wake in %d s\n",
(slow_next_wake_time - now) / 1000);
}
else if (!gnss_is_open) {
@@ -444,7 +456,7 @@ static void quec_gnss_test(void * argv)
app_set_app_operation(1);
ql_gnss_open(quec_gnss_callback);
printf("[GNSS] SLOW: GPS OPEN, search start\n");
GNSS_LOG("[GNSS] SLOW: GPS OPEN, search start\n");
/* ---- 第一阶段:60秒卫星检测 ---- */
int sat_found = 0;
@@ -454,7 +466,7 @@ static void quec_gnss_test(void * argv)
elapsed++;
if (g_gnss_sat_count > 1) {
sat_found = 1;
printf("[GNSS] SLOW: Sat found! count=%d, after %ds\n",
GNSS_LOG("[GNSS] SLOW: Sat found! count=%d, after %ds\n",
g_gnss_sat_count, elapsed);
break;
}
@@ -462,10 +474,10 @@ static void quec_gnss_test(void * argv)
if (!sat_found) {
// 60秒无卫星 → 立即关闭,等待15分钟
printf("[GNSS] SLOW: No satellite in 30s, stop ret:%d.\n", ql_gnss_close());
GNSS_LOG("[GNSS] SLOW: No satellite in 30s, stop ret:%d.\n", ql_gnss_close());
gnss_is_open = 0;
slow_next_wake_time = search_start_time + (GNSS_SEARCH_INTERVAL * 1000);
printf("[GNSS] SLOW: GPS CLOSED, next search at +%ds\n", GNSS_SEARCH_INTERVAL);
GNSS_LOG("[GNSS] SLOW: GPS CLOSED, next search at +%ds\n", GNSS_SEARCH_INTERVAL);
continue;
}
@@ -478,17 +490,17 @@ static void quec_gnss_test(void * argv)
// 1.5分钟时刻检查卫星数
int sat_count_now = g_gnss_sat_count;
printf("[GNSS] SLOW: 1min check, satellite count = %d\n", sat_count_now);
GNSS_LOG("[GNSS] SLOW: 1min check, satellite count = %d\n", sat_count_now);
if (sat_count_now < GNSS_SAT_MIN_COUNT) {
printf("[GNSS] SLOW: Sat count < %d, stop search ret:%d.\n", GNSS_SAT_MIN_COUNT,ql_gnss_close());
GNSS_LOG("[GNSS] SLOW: Sat count < %d, stop search ret:%d.\n", GNSS_SAT_MIN_COUNT,ql_gnss_close());
gnss_is_open = 0;
slow_next_wake_time = search_start_time + (GNSS_SEARCH_INTERVAL * 1000);
printf("[GNSS] SLOW: GPS CLOSED, next search at +%ds\n", GNSS_SEARCH_INTERVAL);
GNSS_LOG("[GNSS] SLOW: GPS CLOSED, next search at +%ds\n", GNSS_SEARCH_INTERVAL);
continue;
}
/* ---- 第三阶段:卫星足够,继续定位,但总时间不超过3分钟 ---- */
printf("[GNSS] SLOW: Sat count OK, continue for fix (max %ds total)\n",
GNSS_LOG("[GNSS] SLOW: Sat count OK, continue for fix (max %ds total)\n",
GNSS_MAX_SEARCH_TIME);
// 继续等待直到总时间达到 GNSS_MAX_SEARCH_TIME (180秒)
@@ -498,28 +510,28 @@ static void quec_gnss_test(void * argv)
// 如果获得有效定位,打印提示(但继续等待至3分钟)
if (g_gps_location.is_valid) {
printf("[GNSS] SLOW: Fix obtained at %ds!\n", elapsed);
GNSS_LOG("[GNSS] SLOW: Fix obtained at %ds!\n", elapsed);
print_gps_location_info();
// 若希望在定位成功后立即关闭,可在此 break,但此处按“最长3分钟”处理
}
}
// 3分钟时间到,关闭GPS
printf("[GNSS] SLOW: Max search time reached, stop.\n");
GNSS_LOG("[GNSS] SLOW: Max search time reached, stop.\n");
if (g_gps_location.is_valid) {
printf("[GNSS] SLOW: Final valid position:\n");
GNSS_LOG("[GNSS] SLOW: Final valid position:\n");
print_gps_location_info();
} else {
printf("[GNSS] SLOW: No valid fix within 3min.\n");
GNSS_LOG("[GNSS] SLOW: No valid fix within 3min.\n");
}
printf("[GNSS] SLOW: GPS CLOSED ret:%d",ql_gnss_close());
GNSS_LOG("[GNSS] SLOW: GPS CLOSED ret:%d",ql_gnss_close());
gnss_is_open = 0;
g_gps_location.is_valid = 0; // 重置状态
// 设置下次唤醒时间为 15分钟 后
slow_next_wake_time = search_start_time + (GNSS_SEARCH_INTERVAL * 1000);
printf("[GNSS] SLOW: GPS CLOSED, next search at +%ds\n", GNSS_SEARCH_INTERVAL);
GNSS_LOG("[GNSS] SLOW: GPS CLOSED, next search at +%ds\n", GNSS_SEARCH_INTERVAL);
}
// else: GPS正在运行中(理论上不会到这里)
}
@@ -0,0 +1,6 @@
#ifndef BANBAN_FIELD_LOG_H
#define BANBAN_FIELD_LOG_H
/* Init once from the application startup task, before other app workers. */
int field_log_init(void);
int field_log_printf(const char *format, ...);
#endif
@@ -1,5 +1,7 @@
#ifndef BANBAN_NET_DIAG_H
#define BANBAN_NET_DIAG_H
int net_diag_is_enabled(void);
int net_diag_can_probe(void);
void net_diag_start(void);
void net_diag_event(const char *event, int code);
#endif
@@ -0,0 +1,11 @@
#ifndef BANBAN_NET_DIAG_PROBE_H
#define BANBAN_NET_DIAG_PROBE_H
/* Transport failures are distinct from local setup/resource failures. */
enum { PROBE_OK, PROBE_DNS, PROBE_LOCAL, PROBE_CONNECT, PROBE_TIMEOUT, PROBE_CANCELLED };
typedef struct {
int outcome, error, dns_result, tcp_started;
unsigned long dns_ms, tcp_ms;
char address[48];
} net_probe_result;
void net_diag_probe(const char *host, unsigned short port, net_probe_result *result);
#endif
@@ -0,0 +1,15 @@
#ifndef BANBAN_NET_DIAG_VOICE_H
#define BANBAN_NET_DIAG_VOICE_H
enum {
VOICE_SPEECH, VOICE_RECORD_STOP, VOICE_END_QUEUED, VOICE_END_ACK,
VOICE_TTS_START, VOICE_FIRST_URL, VOICE_DOWNLOAD_BEGIN,
VOICE_DOWNLOAD_DONE, VOICE_FIRST_PCM, VOICE_STAGE_COUNT
};
void net_diag_voice_begin(unsigned int round);
/* round=0 means the currently recording round (for the VAD task). */
void net_diag_voice_mark(unsigned int round, int stage);
void net_diag_voice_url(unsigned int round, const char *url);
unsigned int net_diag_audio_begin(const char *url);
void net_diag_audio_done(unsigned int round, int bytes, int result);
void net_diag_vad_stats(unsigned int frames, unsigned int speech, unsigned int peak);
#endif
@@ -26,7 +26,8 @@
#include "rws_string.h"
#include <netdb.h>
#include "sockets.h"
#include "ql_rtos.h"
#include "ql_rtos.h"
#include "net_diag.h"
#include <time.h>
#include "rws_handshake.h"
#ifdef LIBRWS_USING_MBED_TLS
@@ -212,13 +213,14 @@ static int rws_tls_send_all(_rws_socket *s, const unsigned char *data, size_t si
// need close socket on error
rws_bool rws_socket_send(_rws_socket *s, const void *data, const size_t data_size)
{
int sended = -1;
int sended = -1;
unsigned int diag_write_start = ql_rtos_get_systicks();
// rws_error_delete_clean(&s->error);
#ifdef LIBRWS_USING_MBED_TLS
if (s->scheme && strcmp(s->scheme, "wss") == 0)
{
LOG_D("[T:%.8d, L:%.5d, %*.*s] try send data with tls", ql_rtos_get_systicks(), __LINE__, DBG_FUNCTION_NUM, DBG_FUNCTION_NUM, __FUNCTION__);
/* Individual frame tracing overwhelms field logs; report slow writes below. */
sended = rws_tls_send_all(s, (const unsigned char *)data, data_size);
}
else
@@ -243,7 +245,13 @@ rws_bool rws_socket_send(_rws_socket *s, const void *data, const size_t data_siz
}
#endif
if (sended == (int)data_size)
if (net_diag_is_enabled()) {
unsigned long elapsed = (unsigned int)(ql_rtos_get_systicks() - diag_write_start) * 5UL;
if (elapsed >= 200UL || sended != (int)data_size)
printf("[现场诊断][%lu ms][底层发送] 请求=%lu 字节 已写=%d 耗时=%lu ms;未代表服务端已收到\r\n",
(unsigned long)ql_rtos_get_systicks() * 5UL, (unsigned long)data_size, sended, elapsed);
}
if (sended == (int)data_size)
{
rws_socket_error_cnt = 0;
rws_socket_error_staus = 0;
@@ -13,7 +13,8 @@
#include <netdb.h>
#include "sockets.h"
#include "mp3dec.h"
#include "test_mp3.h"
#include "test_mp3.h"
#include "net_diag_voice.h"
#define RLOGD printf
struct webclient_session *session = NULL;
@@ -24,6 +25,7 @@ static Mp3PlayConfigInfo mp3_configInfo;
static unsigned char mp3_player_status = 0;
QFILE *local_mp3_file_fd=NULL;
static unsigned int diag_player_round;
static int mp3_player_end_frame = 0;
static int mp3_player_last_result = 0;
@@ -252,7 +254,13 @@ int mp3_decoder_run(struct mp3_decoder* decoder)
current_sample_rate = decoder->frame_info.samprate;
}
ql_audio_track_write(track_hd, (char*)pcm_buff, 1152);
{
int pcm_ret = ql_audio_track_write(track_hd, (char*)pcm_buff, 1152);
if (diag_player_round && pcm_ret >= 0) {
net_diag_voice_mark(diag_player_round, VOICE_FIRST_PCM);
diag_player_round = 0;
}
}
}
return 0;
@@ -396,6 +404,7 @@ void aofei_mp3_player(char *file_name)
{
int ret = 0;
uint8_t *mp3_buff = NULL;
diag_player_round = 0;
mp3_player_last_result = -1;
if(file_name == NULL){
@@ -413,7 +422,9 @@ void aofei_mp3_player(char *file_name)
//直接下载到一个100k的buffer
if(strncmp(file_name, "http://", strlen("http://")) == 0 ||
strncmp(file_name, "https://", strlen("https://")) == 0){//播放url
diag_player_round = net_diag_audio_begin(file_name);
ret = url_mp3_data_fetch(file_name, mp3_buff, 100*1024);
net_diag_audio_done(diag_player_round, total_length, ret);
}else if(strstr(file_name, "test.mp3")){//播放本地音频
total_length = test_data_size;
memcpy(mp3_buff, test_data, total_length);
@@ -5,7 +5,8 @@
#include "hal_api.h"
#include "ql_nw.h"
#include "ql_data_call.h"
#include "ql_ping.h"
#include "ql_func.h"
#include "net_diag_probe.h"
#include "net_diag.h"
extern uint8_t get_sleep_mode(void);
@@ -16,10 +17,10 @@ extern u8 power_down_status;
static volatile int enabled;
static ql_task_t sample_task, probe_task;
static ql_ping_stat_t ping_result;
static int ping_seen, ping_error;
static int sinr_unavailable;
static const char *targets[] = {"223.5.5.5", "banban.api.talkingq.com"};
static const char *labels[] = {"参照节点", "业务域名"};
static const char *labels[] = {"参照DNS服务", "业务WSS入口"};
static const unsigned short ports[] = {53, 443};
static unsigned long attempts[2], received[2], streak[2], errors[2];
static const char *registration_name(int state)
@@ -51,6 +52,9 @@ static int paused(void)
return fota_start == 1 || power_down_status != 0 || get_sleep_mode();
}
int net_diag_is_enabled(void) { return enabled; }
int net_diag_can_probe(void) { return enabled && !paused(); }
static void sample_worker(void *arg)
{
int previous_reg = -1, previous_pdp = -1, previous_pause = -1;
@@ -84,10 +88,16 @@ static void sample_worker(void *arg)
pdp == 1 ? "已连接" : (pdp == -1 ? "读取失败" : "未连接"),
get_mqtt_connect_status() == 1 ? "已连接" : "未连接",
get_xiaoice_connected_status() ? "已连接" : "未连接");
if (sr == QL_NW_SUCCESS && (registered == 1 || registered == 5)) {
printf("[现场诊断][%lu ms][信号原始值] RSSI=%d RSRP=%d RSRQ=%d CQI=%d;单位/无效值以模组为准\r\n",
if (sr == QL_NW_SUCCESS && rr == QL_NW_SUCCESS &&
(reg.data_reg.rat == QL_NW_ACCESS_TECH_E_UTRAN ||
reg.data_reg.rat == QL_NW_ACCESS_TECH_E_UTRAN_CA)) {
char cqi[32];
if (signal.LTE_SignalStrength.cqi >= 0 && signal.LTE_SignalStrength.cqi <= 15)
snprintf(cqi, sizeof(cqi), "%d", signal.LTE_SignalStrength.cqi);
else snprintf(cqi, sizeof(cqi), "不可用(raw=%d)", signal.LTE_SignalStrength.cqi);
printf("[现场诊断][%lu ms][信号原始值] RSSI=%d RSRP=%d RSRQ=%d CQI=%s;单位/无效值以模组为准\r\n",
now_ms(), signal.LTE_SignalStrength.rssi, signal.LTE_SignalStrength.rsrp,
signal.LTE_SignalStrength.rsrq, signal.LTE_SignalStrength.cqi);
signal.LTE_SignalStrength.rsrq, cqi);
} else {
printf("[现场诊断][%lu ms][信号] 暂不可判读;接口返回=%d 注册=%d\r\n", now_ms(), sr, registered);
}
@@ -107,10 +117,20 @@ static void sample_worker(void *arg)
if ((unsigned long)(now_ms() - next_cell) >= 10000UL || next_cell == 0) {
int sinr, result;
next_cell = now_ms();
result = ql_nw_get_sinr(&sinr);
if (result == QL_NW_SUCCESS)
printf("[现场诊断][%lu ms][信噪比原始值] SINR=%d;编码以模组为准\r\n", now_ms(), sinr);
else net_diag_event("SINR读取失败", result);
if (!sinr_unavailable) {
if (!m_get_api_ptr || !m_get_api_ptr("ql_nw_get_sinr")) {
sinr_unavailable = 1;
net_diag_event("当前内核未导出SINR接口;本次开机跳过该项", 0);
} else {
result = ql_nw_get_sinr(&sinr);
if (result == QL_NW_SUCCESS && sinr != 255)
printf("[现场诊断][%lu ms][信噪比原始值] SINR=%d;编码以模组为准\r\n", now_ms(), sinr);
else if (result == 255 || (result == QL_NW_SUCCESS && sinr == 255)) {
sinr_unavailable = 1;
net_diag_event("SINR接口/数值不可用;本次开机跳过该项", result);
} else net_diag_event("SINR暂时读取失败", result);
}
}
memset(&cells, 0, sizeof(cells));
result = ql_nw_get_cell_info(&cells);
if (result == QL_NW_SUCCESS && cells.lte_info_valid) {
@@ -118,7 +138,7 @@ static void sample_worker(void *arg)
for (i = 0; i < cells.lte_info_num && i < QL_NW_CELL_MAX_NUM; ++i) {
QL_MCM_NW_LTE_CELL_INFO_T *c = &cells.lte_info[i];
if (c->flag != 0 || c->cid == 0) continue;
printf("[现场诊断][%lu ms][驻留小区] CID=%lu PCI=%lu EARFCN=%lu MCC=%lu MNC=%lu%s\r\n",
printf("[现场诊断][%lu ms][驻留小区] CID=%lu PCI=%lu EARFCN=%lu MCC原始编码=0x%lX MNC原始编码=0x%lX%s\r\n",
now_ms(), (unsigned long)c->cid, (unsigned long)c->pci,
(unsigned long)c->earfcn, (unsigned long)c->mcc, (unsigned long)c->mnc,
had_cell && (cell_id != c->cid || pci != c->pci) ? ";驻留小区发生变化" : "");
@@ -135,46 +155,34 @@ static void sample_worker(void *arg)
}
}
static void ping_callback(ql_ping_state_e state, int err, ql_ping_stat_t *stat)
{
(void)state;
if (err) ping_error = err;
if (stat) { ping_result = *stat; ping_seen = 1; }
}
static void probe_once(int index)
{
ql_ping_cfg_t cfg;
ql_ping_ctx_t *ctx;
int ret;
memset(&cfg, 0, sizeof(cfg));
cfg.pdpCid = 1; cfg.host = (char *)targets[index];
cfg.timeout = 2000; cfg.dataSize = 32; cfg.pingNum = 1;
ctx = ql_ping_init(cfg, ping_callback);
if (!ctx) { ++errors[index]; net_diag_event("探测初始化失败,下轮重试", index); return; }
memset(&ping_result, 0, sizeof(ping_result));
ping_seen = 0; ping_error = 0;
/* SDK declares ql_ping synchronous: callbacks complete before return. */
ret = ql_ping(ctx);
ql_ping_deinit(ctx);
if (ping_seen && ping_result.sentNum) {
attempts[index] += ping_result.sentNum;
received[index] += ping_result.rcvdNum;
if (ping_result.rcvdNum) {
streak[index] = 0;
printf("[现场诊断][%lu ms][探测成功] %s %s;往返=%lu ms\r\n",
now_ms(), labels[index], targets[index], (unsigned long)ping_result.rtt);
net_probe_result result;
printf("[现场诊断][%lu ms][探测开始] %s %s:%u;解析后TCP连接,连接超时3000ms\r\n",
now_ms(), labels[index], targets[index], (unsigned int)ports[index]);
net_diag_probe(targets[index], ports[index], &result);
if (result.outcome == PROBE_CANCELLED) {
net_diag_event("状态变化,已取消本轮新连接探测", 0); return;
}
if (result.outcome == PROBE_OK || result.outcome == PROBE_CONNECT || result.outcome == PROBE_TIMEOUT) {
++attempts[index];
if (result.outcome == PROBE_OK) {
++received[index]; streak[index] = 0;
printf("[现场诊断][%lu ms][探测成功] %s;IP=%s 解析=%lu ms TCP建连=%lu ms(非AI响应时间)\r\n",
now_ms(), labels[index], result.address, result.dns_ms, result.tcp_ms);
} else {
++streak[index];
printf("[现场诊断][%lu ms][探测未回应] %s;连续=%lu 次;可能超时或节点禁Ping,不能单独判定断网\r\n",
now_ms(), labels[index], streak[index]);
printf("[现场诊断][%lu ms][探测失败] %s;阶段=%s IP=%s 解析=%lu ms TCP等待=%lu ms 错误=%d 连续失败=%lu\r\n",
now_ms(), labels[index], result.outcome == PROBE_TIMEOUT ? "TCP超时" : "TCP拒绝/连接错误",
result.address, result.dns_ms, result.tcp_ms, result.error, streak[index]);
}
} else {
++errors[index];
printf("[现场诊断][%lu ms][探测执行异常] %s;返回=%d 回调错误=%d;无有效发包统计,不计入未回应率\r\n",
now_ms(), labels[index], ret, ping_error);
printf("[现场诊断][%lu ms][探测执行异常] 目标=%s 阶段=%s 错误=%d 解析返回=%d 解析=%lu ms;不计入TCP失败率\r\n",
now_ms(), labels[index], result.outcome == PROBE_DNS ? "域名解析" : "本地套接字/数据连接",
result.error, result.dns_result, result.dns_ms);
}
printf("[现场诊断][%lu ms][累计统计] %s;已发送=%lu 收到=%lu 未回应=%lu 未回应率=%lu%% 执行异常=%lu(本次开机累计)\r\n",
printf("[现场诊断][%lu ms][累计统计] %s;TCP有效尝试=%lu 成功=%lu 失败=%lu 失败率=%lu%% 执行异常=%lu;非业务丢包率\r\n",
now_ms(), labels[index], attempts[index], received[index], attempts[index] - received[index],
attempts[index] ? (attempts[index] - received[index]) * 100UL / attempts[index] : 0, errors[index]);
}
@@ -212,10 +220,10 @@ void net_diag_start(void)
ret = ql_rtos_task_create(&probe_task, 4096, 120, "net_probe", probe_worker, NULL);
if (ret != 0) { probe_task = NULL; printf("[现场诊断][错误] 探测任务创建失败=%d,请短按重试\r\n", ret); return; }
}
printf("\r\n[现场诊断] ===== 主动模式开启:重启退出 =====\r\n"
"[现场诊断] 时间=开机毫秒;串口请选择UTF-8并保存完整日志\r\n"
"[现场诊断] 信号/连接约2秒一条;小区约10秒一次;两节点依次探测后间隔10秒\r\n"
"[现场诊断] 每节点32字节,ICMP超时2000ms;业务域名探测可能另有解析耗时,不等于WSS/AI处理耗时\r\n"
"[现场诊断] 主动流量可能改变空闲态、耗电及故障表现;休眠/升级/关机时暂停新探测\r\n");
printf("\r\n[现场诊断] ===== 主动模式开启:重启退出 =====\r\n");
printf("[现场诊断] 时间=开机毫秒;UART与USB CDC同步,UTF-8;下一轮录音开始记录分段耗时\r\n");
printf("[现场诊断] 信号/连接约2秒一条;小区约10秒一次;两节点依次探测后间隔10秒\r\n");
printf("[现场诊断] 主动探测改用DNS/TCP,不依赖可选Ping接口;3000ms连接超时不含DNS解析耗时\r\n");
printf("[现场诊断] 主动流量可能改变空闲态、耗电及故障表现;休眠/升级/关机时暂停新探测\r\n");
enabled = 1;
}
@@ -0,0 +1,91 @@
/* Use the same PDP 1 socket path as the working WSS client. No raw ICMP
* or optional Ping API; select + SO_ERROR distinguish timeout/refusal/success. */
#include <string.h>
#include <stdio.h>
#include "ql_rtos.h"
#include "ql_data_call.h"
#include "sockets.h"
#include "netdb.h"
#include "hal_api.h"
#include "net_diag_probe.h"
#include "net_diag.h"
static unsigned long probe_ms(void) { return (unsigned long)hal_os_up_time_ms(); }
void net_diag_probe(const char *host, unsigned short port, net_probe_result *r)
{
struct addrinfo hints, *addresses = NULL, *address;
struct sockaddr_in local;
struct ql_data_call_info data;
char service[8];
unsigned long started;
int fd = -1, nonblocking = 1, ret, connect_error;
socklen_t error_size;
memset(r, 0, sizeof(*r)); r->outcome = PROBE_LOCAL;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM;
snprintf(service, sizeof(service), "%u", (unsigned int)port);
if (!net_diag_can_probe()) { r->outcome = PROBE_CANCELLED; return; }
started = probe_ms();
ret = getaddrinfo_with_pcid(host, service, &hints, &addresses, 1);
r->dns_ms = probe_ms() - started; r->dns_result = ret;
if (ret != 0 || !addresses) {
r->outcome = PROBE_DNS; r->error = ret ? ret : -1;
if (addresses) freeaddrinfo(addresses);
return;
}
/* One address per round: avoid multiplying timeout/load by DNS answer count. */
for (address = addresses; address; address = address->ai_next)
if (address->ai_family == AF_INET && address->ai_addr &&
address->ai_addrlen >= sizeof(struct sockaddr_in)) break;
if (!address) { r->outcome = PROBE_DNS; r->error = -1; goto done; }
inet_ntop(AF_INET, &((struct sockaddr_in *)address->ai_addr)->sin_addr, r->address, sizeof(r->address));
if (!net_diag_can_probe()) { r->outcome = PROBE_CANCELLED; goto done; }
memset(&data, 0, sizeof(data));
ret = ql_get_data_call_info(1, 0, &data);
if (ret != 0 || data.v4.state != 1) { r->error = ret ? ret : -1; goto done; }
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) { r->error = ql_get_soc_errno(); goto done; }
if (ioctl(fd, FIONBIO, &nonblocking) != 0) { r->error = ql_get_soc_errno(); goto done; }
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET; local.sin_port = htons(ql_soc_generate_port());
local.sin_addr = data.v4.addr.ip;
if (bind(fd, (struct sockaddr *)&local, sizeof(local)) != 0) { r->error = ql_get_soc_errno(); goto done; }
started = probe_ms();
ret = connect(fd, address->ai_addr, address->ai_addrlen);
r->tcp_started = 1;
if (ret == 0) { r->outcome = PROBE_OK; goto connected; }
connect_error = ql_get_soc_errno();
if (connect_error != EINPROGRESS && connect_error != EWOULDBLOCK && connect_error != EALREADY) {
r->outcome = PROBE_CONNECT; r->error = connect_error; goto connected;
}
/* Short slices allow OTA/sleep/shutdown to cancel within one select slice. */
r->outcome = PROBE_TIMEOUT;
while ((unsigned long)(probe_ms() - started) < 3000UL) {
fd_set writable, exceptional;
struct timeval wait;
int error = 0;
if (!net_diag_can_probe()) { r->outcome = PROBE_CANCELLED; break; }
FD_ZERO(&writable); FD_ZERO(&exceptional);
FD_SET(fd, &writable); FD_SET(fd, &exceptional);
{
unsigned long remaining = 3000UL - (unsigned long)(probe_ms() - started);
if (remaining > 3000UL) break;
wait.tv_sec = 0; wait.tv_usec = (remaining < 200UL ? remaining : 200UL) * 1000UL;
}
ret = select(fd + 1, NULL, &writable, &exceptional, &wait);
if (ret < 0) { r->outcome = PROBE_LOCAL; r->error = ql_get_soc_errno(); break; }
if (ret == 0) continue;
if (!FD_ISSET(fd, &writable) && !FD_ISSET(fd, &exceptional)) continue;
error_size = sizeof(error);
ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &error_size);
if (ret != 0) { r->outcome = PROBE_LOCAL; r->error = ql_get_soc_errno(); }
else { r->error = error; r->outcome = error == 0 ? PROBE_OK : PROBE_CONNECT; }
break;
}
connected:
r->tcp_ms = probe_ms() - started;
done:
if (fd >= 0) close(fd);
freeaddrinfo(addresses);
}
@@ -0,0 +1,99 @@
#include <stdio.h>
#include <string.h>
#include "ql_rtos.h"
#include "hal_api.h"
#include "net_diag.h"
#include "net_diag_voice.h"
static struct {
unsigned int round, seen;
unsigned long started, stamp[VOICE_STAGE_COUNT];
char first_url[128];
} voice;
static const char *stages[] = {
"确认用户语音", "录音结束", "结束包已入发送队列(不代表服务端收到)",
"收到END_ACK", "收到TTS_START", "收到首段音频URL", "首段开始下载",
"首段下载完成", "首PCM提交播放接口(不等于实际出声)"
};
void net_diag_voice_begin(unsigned int round)
{
if (!net_diag_is_enabled()) return;
ql_rtos_task_sched_lock();
memset(&voice, 0, sizeof(voice));
voice.round = round; voice.started = (unsigned long)hal_os_up_time_ms();
ql_rtos_task_sched_unlock();
printf("[现场诊断][%lu ms][对话#%u] 开始录音;分段计时起点\r\n", voice.started, round);
}
void net_diag_voice_mark(unsigned int round, int stage)
{
unsigned long now, since_start, total = 0, tail = 0, server = 0, wait_url = 0, queue = 0, download = 0, pcm = 0;
unsigned int bit, seen;
if (!net_diag_is_enabled() || stage < 0 || stage >= VOICE_STAGE_COUNT) return;
now = (unsigned long)hal_os_up_time_ms(); bit = 1U << stage;
ql_rtos_task_sched_lock();
if (!voice.round || (round && round != voice.round) || (voice.seen & bit)) {
ql_rtos_task_sched_unlock(); return;
}
round = voice.round; voice.seen |= bit; voice.stamp[stage] = now;
since_start = now - voice.started; seen = voice.seen;
#define HAVE(s) ((seen & (1U << (s))) != 0)
if (stage == VOICE_FIRST_PCM && HAVE(VOICE_RECORD_STOP) && HAVE(VOICE_END_QUEUED) &&
HAVE(VOICE_TTS_START) && HAVE(VOICE_FIRST_URL) && HAVE(VOICE_DOWNLOAD_BEGIN) && HAVE(VOICE_DOWNLOAD_DONE)) {
total = now - voice.stamp[VOICE_RECORD_STOP];
tail = voice.stamp[VOICE_END_QUEUED] - voice.stamp[VOICE_RECORD_STOP];
server = voice.stamp[VOICE_TTS_START] - voice.stamp[VOICE_END_QUEUED];
wait_url = voice.stamp[VOICE_FIRST_URL] - voice.stamp[VOICE_TTS_START];
queue = voice.stamp[VOICE_DOWNLOAD_BEGIN] - voice.stamp[VOICE_FIRST_URL];
download = voice.stamp[VOICE_DOWNLOAD_DONE] - voice.stamp[VOICE_DOWNLOAD_BEGIN];
pcm = now - voice.stamp[VOICE_DOWNLOAD_DONE];
}
ql_rtos_task_sched_unlock();
printf("[现场诊断][%lu ms][对话#%u] %s;距录音开始=%lu ms\r\n", now, round, stages[stage], since_start);
/* Nonstandard server ordering is printed as events, never unsigned deltas. */
if (total && tail <= total && server <= total && wait_url <= total && queue <= total && download <= total && pcm <= total)
printf("[现场诊断][%lu ms][对话#%u][耗时汇总] 录音结束到首PCM=%lu ms;收尾入队=%lu 等TTS通知=%lu 等首URL=%lu 播放队列=%lu 下载=%lu 解码提交=%lu(均ms;含网络与服务端处理,尚未分离)\r\n",
now, round, total, tail, server, wait_url, queue, download, pcm);
#undef HAVE
}
void net_diag_voice_url(unsigned int round, const char *url)
{
int accepted = 0;
if (!net_diag_is_enabled() || !url) return;
ql_rtos_task_sched_lock();
if (round == voice.round && !voice.first_url[0] && strlen(url) < sizeof(voice.first_url)) {
strcpy(voice.first_url, url); accepted = 1;
}
ql_rtos_task_sched_unlock();
if (accepted) net_diag_voice_mark(round, VOICE_FIRST_URL);
}
unsigned int net_diag_audio_begin(const char *url)
{
unsigned int round = 0;
if (!net_diag_is_enabled() || !url) return 0;
ql_rtos_task_sched_lock();
if (voice.first_url[0] && strcmp(url, voice.first_url) == 0) round = voice.round;
ql_rtos_task_sched_unlock();
if (round) net_diag_voice_mark(round, VOICE_DOWNLOAD_BEGIN);
return round;
}
void net_diag_audio_done(unsigned int round, int bytes, int result)
{
if (!round || !net_diag_is_enabled()) return;
printf("[现场诊断][%lu ms][对话#%u][首段下载] 字节=%d 返回=%d\r\n",
(unsigned long)hal_os_up_time_ms(), round, bytes, result);
if (result >= 0 && bytes > 0) net_diag_voice_mark(round, VOICE_DOWNLOAD_DONE);
}
void net_diag_vad_stats(unsigned int frames, unsigned int speech, unsigned int peak)
{
unsigned int round;
if (!net_diag_is_enabled()) return;
ql_rtos_task_sched_lock(); round = voice.round; ql_rtos_task_sched_unlock();
printf("[现场诊断][%lu ms][对话#%u][语音检测] 总帧=%u 活跃录音帧=%u 首次确认前连续语音峰值=%u/20;帧长20ms\r\n",
(unsigned long)hal_os_up_time_ms(), round, frames, speech, peak);
}
@@ -2,7 +2,9 @@
#include <string.h>
#include <librws.h>
#include "cJSON.h"
#include "app_inc.h"
#include "app_inc.h"
#include "net_diag.h"
#include "net_diag_voice.h"
#include "ql_rtos.h"
#include "ql_application.h"
#include "ql_audio.h"
@@ -27,6 +29,7 @@
// 在文件顶部添加状态变量
static uint8_t speech_detect_peak = 0;
static uint8_t speech_detect_count = 0; // 连续语音帧计数器
#define SPEECH_DETECT_THRESHOLD 20 // 连续检测到语音的阈值
#define PLAYBACK_VAD_GUARD_MS 800 // 屏蔽播放结束后的尾音和音频通路切换噪声
@@ -209,16 +212,19 @@ int vad_audio_start(void) {
vad_audio_init();
frame_count = 0;
speech_frame_count = 0;
speech_frame_count = 0;
speech_detect_peak = 0;
printf("Audio recording started with FVAD");
printf("Audio recording started with FVAD\r\n");
return 0;
}
// 停止录音
int vad_audio_stop(void) {
printf("Audio recording stopped, frames: %d, speech: %d",
net_diag_voice_mark(0, VOICE_RECORD_STOP);
net_diag_vad_stats(frame_count, speech_frame_count, speech_detect_peak);
printf("Audio recording stopped, frames: %d, speech: %d\r\n",
frame_count, speech_frame_count);
vad_audio_deinit();
@@ -416,7 +422,7 @@ void vad_audio_process(void) {
}
if (current_vad == VAD_STATE_ACTIVE) {
speech_detect_count++;
printf("Speech frame count: %d\r\n", speech_detect_count);
if (speech_detect_count > speech_detect_peak) speech_detect_peak = speech_detect_count;
// 连续达到阈值才确认检测到声音
if (speech_detect_count >= SPEECH_DETECT_THRESHOLD) {
@@ -447,12 +453,13 @@ void vad_audio_process(void) {
vad_preroll_count = 0;
}
set_state(STATE_RECORDING_ACTIVE);
printf("First speech confirmed by FVAD (continuous %d frames)", SPEECH_DETECT_THRESHOLD);
net_diag_voice_mark(0, VOICE_SPEECH);
printf("First speech confirmed by FVAD (continuous %d frames)\r\n", SPEECH_DETECT_THRESHOLD);
}
} else {
// 出现静音帧,计数器清零
if (speech_detect_count > 0) {
printf("Speech interrupted, reset count\r\n");
/* Per-frame chatter replaced by the final peak statistic. */
speech_detect_count = 0;
}
@@ -5,6 +5,7 @@
#include "app_inc.h"
#include "talkingq_wss.h"
#include "net_diag.h"
#include "net_diag_voice.h"
#include "ql_rtos.h"
#include "ql_application.h"
#include "ql_audio.h"
@@ -102,6 +103,7 @@ void conversation_record_begin(void)
conversation_lock();
if (!g_mic_working) conversation_begin_round();
g_mic_working = 1;
net_diag_voice_begin(conversation_round);
conversation_unlock();
}
@@ -118,7 +120,8 @@ void conversation_no_speech(void)
{
conversation_lock();
wait_round = conversation_round;
conversation_wait = WAIT_NO_SPEECH;
conversation_wait = WAIT_NO_SPEECH;
net_diag_event("VAD未确认用户语音,本轮等待超时提示;不是AI回复耗时", 0);
conversation_unlock();
}
static volatile int player_mq_couter = 0;
@@ -504,7 +507,8 @@ static void onmessage_text_locked(rws_socket socket, const char *text, const uns
char url_buff_bak[128]={0x00};
if(strstr(text, "_START")){
printf("TTS_START\r\n");
printf("TTS_START\r\n");
if (active_session_valid) net_diag_voice_mark(active_session_round, VOICE_TTS_START);
//语音交互音频开始
tts_playing = 1;
tts_play_status = 1;
@@ -514,7 +518,9 @@ static void onmessage_text_locked(rws_socket socket, const char *text, const uns
//语音交互音频结束
// tts_playing = 0;
tts_play_status = 2;
}else if(strstr(text, "END_ACK")||strstr(text, "START_ACK")){
}else if(strstr(text, "END_ACK")||strstr(text, "START_ACK")){
if (active_session_valid && strstr(text, "END_ACK"))
net_diag_voice_mark(active_session_round, VOICE_END_ACK);
}else{//TTS_URL:audio_url
char *ret_str1 = strstr(text, "https://");
@@ -584,6 +590,9 @@ static void onmessage_text_locked(rws_socket socket, const char *text, const uns
/* 对话超时提示使用 type=3,云端播放失败时由播放线程切换到本地兜底。 */
if (active_session_valid && !is_tts_error_url && !is_message_ok_url &&
strstr(url_buff_bak, sessionId) != NULL)
net_diag_voice_url(active_session_round, url_buff);
add_file_to_play_player_queue(url_buff, is_tts_error_url == 1 ? 3 : 0);
}
}
@@ -1056,7 +1065,9 @@ void sendEndPacket(void) {
conversation_lock();
if (sender_round == conversation_round) {
int end_ret = sendRecordingAudioPacket(&endPacket);
int end_ret = sendRecordingAudioPacket(&endPacket);
if (end_ret == 0) net_diag_voice_mark(sender_round, VOICE_END_QUEUED);
else net_diag_event("结束包入队失败;不能按等待AI回复解释", end_ret);
if (!round_response_received) {
wait_round = sender_round;
reply_wait_start_ms = hal_os_up_time_ms();