/* On-demand field diagnostics. No PDP reconfiguration or recovery actions. */ #include #include #include "app_inc.h" #include "hal_api.h" #include "ql_nw.h" #include "ql_data_call.h" #include "ql_func.h" #include "net_diag_probe.h" #include "net_diag.h" extern uint8_t get_sleep_mode(void); extern int get_mqtt_connect_status(void); extern unsigned char get_xiaoice_connected_status(void); extern int fota_start; extern u8 power_down_status; static volatile int enabled; static ql_task_t sample_task, probe_task; static int sinr_unavailable; static const char *targets[] = {"223.5.5.5", "banban.api.talkingq.com"}; 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) { switch (state) { case 0: return "未注册"; case 1: return "已注册本地网络"; case 2: return "正在搜索网络"; case 3: return "注册被拒绝"; case 5: return "已注册漫游网络"; case -1: return "读取失败"; default: return "未知状态"; } } static unsigned long now_ms(void) { return (unsigned long)hal_os_up_time_ms(); } void net_diag_event(const char *event, int code) { if (enabled) printf("[现场诊断][%lu ms][事件] %s;状态码=%d\r\n", now_ms(), event, code); } 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; unsigned long down_since = 0, next_cell = 0; int down = 0, had_cell = 0; unsigned int cell_id = 0, pci = 0; static QL_NW_CELL_INFO_T cells; (void)arg; for (;;) { if (enabled) { int hold = paused(); if (hold != previous_pause) { net_diag_event(hold ? "休眠/升级/关机中,暂停采样与新探测" : "诊断采样运行中", hold); previous_pause = hold; } if (!hold) { QL_NW_SIGNAL_STRENGTH_INFO_T signal; QL_NW_REG_STATUS_INFO_T reg; struct ql_data_call_info data; int sr, rr, dr, registered, pdp; memset(&signal, 0, sizeof(signal)); memset(®, 0, sizeof(reg)); memset(&data, 0, sizeof(data)); sr = ql_nw_get_signal_strength(&signal); rr = ql_nw_get_reg_status(®); dr = ql_get_data_call_info(1, 0, &data); registered = rr == QL_NW_SUCCESS ? (int)reg.data_reg.state : -1; pdp = dr == 0 ? data.v4.state : -1; printf("[现场诊断][%lu ms][连接] 注册=%s(%d) 数据连接=%s MQTT=%s WSS=%s\r\n", now_ms(), registration_name(registered), registered, pdp == 1 ? "已连接" : (pdp == -1 ? "读取失败" : "未连接"), get_mqtt_connect_status() == 1 ? "已连接" : "未连接", get_xiaoice_connected_status() ? "已连接" : "未连接"); 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, cqi); } else { printf("[现场诊断][%lu ms][信号] 暂不可判读;接口返回=%d 注册=%d\r\n", now_ms(), sr, registered); } if (registered != previous_reg || pdp != previous_pdp) { net_diag_event("注册或数据连接状态变化(详见连接行)", registered); previous_reg = registered; previous_pdp = pdp; } /* Unknown readings never become a fabricated disconnect/recovery. */ if (rr == QL_NW_SUCCESS && dr == 0) { int online = (registered == 1 || registered == 5) && pdp == 1; if (!online && !down) { down = 1; down_since = now_ms(); net_diag_event("网络不可用,开始计时", 0); } if (online && down) { printf("[现场诊断][%lu ms][恢复] 本次网络不可用观测时长=%lu ms(含采样误差)\r\n", now_ms(), now_ms() - down_since); down = 0; } } if ((unsigned long)(now_ms() - next_cell) >= 10000UL || next_cell == 0) { int sinr, result; next_cell = now_ms(); 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) { int i; 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原始编码=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) ? ";驻留小区发生变化" : ""); cell_id = c->cid; pci = c->pci; had_cell = 1; break; } if (i == cells.lte_info_num || i == QL_NW_CELL_MAX_NUM) net_diag_event("无有效LTE驻留小区记录", 0); } else net_diag_event("LTE小区信息不可用", result); } } } ql_rtos_task_sleep_ms(2000); } } static void probe_once(int index) { 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;阶段=%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 阶段=%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;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]); } static void probe_worker(void *arg) { (void)arg; for (;;) { if (enabled && !paused()) { int i; for (i = 0; i < 2; ++i) { struct ql_data_call_info data; memset(&data, 0, sizeof(data)); if (paused()) break; if (ql_get_data_call_info(1, 0, &data) != 0 || data.v4.state != 1) { net_diag_event("数据连接未就绪,跳过主动探测", 0); break; } probe_once(i); } } ql_rtos_task_sleep_ms(10000); } } /* Called only by the serialized key worker, never from the GPIO interrupt. */ void net_diag_start(void) { int ret; if (enabled) { net_diag_event("主动模式已开启,继续采集;重启退出", 1); return; } if (!sample_task) { ret = ql_rtos_task_create(&sample_task, 4096, 120, "net_sample", sample_worker, NULL); if (ret != 0) { sample_task = NULL; printf("[现场诊断][错误] 采样任务创建失败=%d,请短按重试\r\n", ret); return; } } if (!probe_task) { 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"); 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; }