From 8bff1f569e6e3100ac18d69b35c0b8b33fefb791 Mon Sep 17 00:00:00 2001 From: chenjintao Date: Tue, 20 Oct 2026 14:12:28 +0800 Subject: [PATCH] feat: add button-activated field network diagnostics --- docs/现场网络诊断.md | 42 ++++ .../threadx/interface/custom_app/Makefile | 3 +- .../threadx/interface/custom_app/hal_key.c | 33 +-- .../interface/custom_app/inc/net_diag.h | 5 + .../threadx/interface/custom_app/net_diag.c | 221 ++++++++++++++++++ .../threadx/interface/custom_app/net_mgt.c | 4 +- .../interface/custom_app/ws_talkingq.c | 5 +- tools/test_net_diag.py | 117 ++++++++++ 8 files changed, 406 insertions(+), 24 deletions(-) create mode 100644 docs/现场网络诊断.md create mode 100644 ql-application/threadx/interface/custom_app/inc/net_diag.h create mode 100644 ql-application/threadx/interface/custom_app/net_diag.c create mode 100644 tools/test_net_diag.py diff --git a/docs/现场网络诊断.md b/docs/现场网络诊断.md new file mode 100644 index 0000000..bcc0a2e --- /dev/null +++ b/docs/现场网络诊断.md @@ -0,0 +1,42 @@ +# 现场网络诊断固件 + +GPIO84 原报警/留言按键改为诊断键。释放时按住超过 50 ms、少于 3 秒,开启主动模式。超过或等于 3 秒不执行动作。重复短按只打印已开启,不创建重复任务;重启恢复关闭。该按键不再报警、留言、播放提示音或停止音频。 + +设备处于定时休眠、OTA 或关机流程时不响应开启。开启之后遇到这些状态,暂停采样和新探测,已有探测可能继续到返回。没有新增常亮、常唤醒或自动重启操作;主动网络流量本身仍可能影响空闲态和耗电。 + +## 查看日志 + +使用原有固件日志串口,终端设置 UTF-8,保存完整串口输出。搜索 `[现场诊断]` 可筛出诊断行。时间为开机毫秒(目标平台计数会回绕),不是北京时间。日志不写 Flash、不上传服务器;断网时电脑仍可留存串口日志。 + +- 每约 2 秒:注册状态、PDP 数据连接、MQTT、WSS,及 LTE 信号原始字段。 +- 每约 10 秒:SINR 原始值、LTE 驻留小区 CID/PCI/EARFCN 和 MCC/MNC。标记小区变化。 +- 主动探测:依次 Ping `223.5.5.5` 和当前业务域名 `banban.api.talkingq.com`,每目标 1 个 32 字节载荷,ICMP 超时 2000 ms;整轮完成后等待 10 秒。域名解析和 SDK 调度可能增加耗时,非严格周期。 +- 即时事件:数据连接回调、当前 WSS 的建立/断开;注册/PDP 的采样变化、不可用和恢复。 +- 每次探测后:本次开机的已发送、收到、未回应、未回应率和执行异常数量。超时连续次数遇到收到回应时清零。未获得有效发包统计的 SDK 错误单独计数。 + +示例(演示数据,并非现场测量): + +```text +[现场诊断][12000 ms][连接] 注册=已注册本地网络(1) 数据连接=已连接 MQTT=已连接 WSS=已连接 +[现场诊断][12000 ms][信号原始值] RSSI=-71 RSRP=-97 RSRQ=-12 CQI=8;单位/无效值以模组为准 +[现场诊断][12500 ms][探测成功] 参照节点 223.5.5.5;往返=123 ms +[现场诊断][12500 ms][累计统计] 参照节点;已发送=20 收到=19 未回应=1 未回应率=5% 执行异常=0(本次开机累计) +``` + +## 判断边界 + +信号值以 SDK 返回的原始字段记录。必须结合模组实际单位、无效值和无线制式确认后解释,暂不直接分档。注册成功不代表有互联网;MQTT/WSS 状态是业务维护的状态,断网瞬间可能滞后。当前按 LTE 项目设计,其他制式不能直接使用 LTE 字段判读。 + +参照节点属于外部公共服务,可能限速或禁 ICMP;业务域名对应入口也可能不回答 Ping。两者均不是 AI 服务健康检查。ICMP 未回应率不等于语音业务丢包率;域名探测结果中的 RTT 是 ICMP 往返时间,不包括 DNS 或 AI 处理时间。 + +网络不可用计时依据注册状态和实际 PDP 查询;查询失败不虚构掉线/恢复。时间包含采样误差和暂停期间,不能当作精确无线断线时间。小区变化表示驻留记录改变,不等同于已确认发生连接态切换。 + +本次实现侧重网络现场日志,尚未新增每轮对话的 VAD/上传/服务端/首音分段计时,也未新增 DNS/TCP/TLS 独立探测。若网络记录正常但回复仍慢,需结合已有 WSS/TLS/音频日志和服务端记录继续定位。 + +## 验证与上板 + +运行 `python3 tools/test_net_diag.py`,以 SDK 头文件和模拟模组接口编译运行真实诊断源码,验证任务创建失败重试、重复开启、成功/无回应/执行异常统计、网络恢复计时和休眠暂停;并检查按键分支没有原报警/留言/音频操作。 + +macOS 测试仅在临时副本中移除 SDK 与宿主不兼容的 32 位 size_t 定义,不改 SDK 文件。该测试不代表 ARM 完整固件编译或硬件验收。正式构建沿用项目 Windows ARM Compiler 5 构建流程。 + +上板后核对:UTF-8 中文是否正确;50 ms 以下抖动、正常短按、重复短按、3 秒以上长按;断网仍可开启诊断;两目标成功和不回应;恢复网络;对话时不因按键中断声音;OTA/定时休眠暂停;重启关闭。核实实际模组信号单位、Ping 回调和内存余量。 diff --git a/ql-application/threadx/interface/custom_app/Makefile b/ql-application/threadx/interface/custom_app/Makefile index b79cab6..c287ff1 100644 --- a/ql-application/threadx/interface/custom_app/Makefile +++ b/ql-application/threadx/interface/custom_app/Makefile @@ -66,7 +66,8 @@ SRC_FILES+= \ lib/libvad/vad_sp.c \ mp3_player.c \ http_aofei.c \ - net_mgt.c \ + net_mgt.c \ + net_diag.c \ ws_talkingq.c \ talkingq_wss.c \ talkingq_root_ca.c \ diff --git a/ql-application/threadx/interface/custom_app/hal_key.c b/ql-application/threadx/interface/custom_app/hal_key.c index 8eb650d..e8a0955 100644 --- a/ql-application/threadx/interface/custom_app/hal_key.c +++ b/ql-application/threadx/interface/custom_app/hal_key.c @@ -3,7 +3,8 @@ #include "ql_rtos.h" #include "ql_gpio.h" #include "ql_uart.h" -#include "app_inc.h" +#include "app_inc.h" +#include "net_diag.h" #define KEY_PIN_WAKEUP_MIC GPIO_PIN_NO_53 #define KEY_PIN_ALARM GPIO_PIN_NO_84 @@ -121,7 +122,7 @@ static void key_thread_entry(void *parameter) unsigned char level = 0; unsigned char volume = 0; unsigned char pre_key_press_mic_counter = 0; - unsigned char pre_key_press_alarm_counter = 0; + unsigned char pre_key_press_alarm_counter = 0; PIN_LEVEL_E value; while (1) @@ -209,30 +210,20 @@ static void key_thread_entry(void *parameter) if(value!=PIN_LEVEL_LOW){//up key_press_alarm_status = 0; - if(ql_rtos_get_systicks() - key_press_alarm_time > KEY_LONG_PRESS_3S){ - build_alarm(); - } - else if(ql_rtos_get_systicks() - key_press_alarm_time > KEY_WAIT_TIME){ - if(!vad_audio_is_recording()&&key_start_leave==0&&get_mqtt_connect_status()==1){ - key_start_leave = 1; - build_leave_to_parent(); - }else{ - key_start_leave = 0; - app_play_tips_file(2); - } - } - + /* GPIO84 is diagnostic-only; long holds have no action. */ + { + u32 held = (u32)(ql_rtos_get_systicks() - key_press_alarm_time); + if (held > KEY_WAIT_TIME && held < KEY_LONG_PRESS_3S) + net_diag_start(); + } + key_press_alarm_time = ql_rtos_get_systicks(); key_press_alarm_counter = 0; - pre_key_press_mic_counter = 0; + pre_key_press_alarm_counter = 0; }else{ //一直按着 if(key_press_alarm_status==0){ key_press_alarm_status = 1;//表示已经按下了 - //先停掉音频播放线程 - if(key_start_leave==0&&!vad_audio_is_recording()){ - stop_player_func(); - } - } + } } } diff --git a/ql-application/threadx/interface/custom_app/inc/net_diag.h b/ql-application/threadx/interface/custom_app/inc/net_diag.h new file mode 100644 index 0000000..0f7eeb1 --- /dev/null +++ b/ql-application/threadx/interface/custom_app/inc/net_diag.h @@ -0,0 +1,5 @@ +#ifndef BANBAN_NET_DIAG_H +#define BANBAN_NET_DIAG_H +void net_diag_start(void); +void net_diag_event(const char *event, int code); +#endif diff --git a/ql-application/threadx/interface/custom_app/net_diag.c b/ql-application/threadx/interface/custom_app/net_diag.c new file mode 100644 index 0000000..c0ce220 --- /dev/null +++ b/ql-application/threadx/interface/custom_app/net_diag.c @@ -0,0 +1,221 @@ +/* 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_ping.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 ql_ping_stat_t ping_result; +static int ping_seen, ping_error; +static const char *targets[] = {"223.5.5.5", "banban.api.talkingq.com"}; +static const char *labels[] = {"参照节点", "业务域名"}; +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(); +} + +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 && (registered == 1 || registered == 5)) { + printf("[现场诊断][%lu ms][信号原始值] RSSI=%d RSRP=%d RSRQ=%d CQI=%d;单位/无效值以模组为准\r\n", + now_ms(), signal.LTE_SignalStrength.rssi, signal.LTE_SignalStrength.rsrp, + signal.LTE_SignalStrength.rsrq, signal.LTE_SignalStrength.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(); + 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); + 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=%lu MNC=%lu%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 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); + } else { + ++streak[index]; + printf("[现场诊断][%lu ms][探测未回应] %s;连续=%lu 次;可能超时或节点禁Ping,不能单独判定断网\r\n", + now_ms(), labels[index], streak[index]); + } + } else { + ++errors[index]; + printf("[现场诊断][%lu ms][探测执行异常] %s;返回=%d 回调错误=%d;无有效发包统计,不计入未回应率\r\n", + now_ms(), labels[index], ret, ping_error); + } + printf("[现场诊断][%lu ms][累计统计] %s;已发送=%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" + "[现场诊断] 时间=开机毫秒;串口请选择UTF-8并保存完整日志\r\n" + "[现场诊断] 信号/连接约2秒一条;小区约10秒一次;两节点依次探测后间隔10秒\r\n" + "[现场诊断] 每节点32字节,ICMP超时2000ms;业务域名探测可能另有解析耗时,不等于WSS/AI处理耗时\r\n" + "[现场诊断] 主动流量可能改变空闲态、耗电及故障表现;休眠/升级/关机时暂停新探测\r\n"); + enabled = 1; +} diff --git a/ql-application/threadx/interface/custom_app/net_mgt.c b/ql-application/threadx/interface/custom_app/net_mgt.c index 8987699..9cb3c8e 100644 --- a/ql-application/threadx/interface/custom_app/net_mgt.c +++ b/ql-application/threadx/interface/custom_app/net_mgt.c @@ -18,6 +18,7 @@ #include "ql_ping.h" #include "ql_nw.h" +#include "net_diag.h" #include "ql_atcmd.h" #include "http_downfile.h" #include "ota_post_update.h" @@ -96,7 +97,8 @@ void app_data_recall(void) } static void ql_data_call_cb(int profile_idx, int nw_status) -{ +{ + net_diag_event("数据连接回调", nw_status); printf("[APP]ql_data_call_cb: profile(%d) status(%d)\r\n", profile_idx, nw_status); if(nw_status==0){ ql_rtos_semaphore_release(net_mgt_datacall_sem); diff --git a/ql-application/threadx/interface/custom_app/ws_talkingq.c b/ql-application/threadx/interface/custom_app/ws_talkingq.c index 7b13919..2711e33 100644 --- a/ql-application/threadx/interface/custom_app/ws_talkingq.c +++ b/ql-application/threadx/interface/custom_app/ws_talkingq.c @@ -3,7 +3,8 @@ #include #include "cJSON.h" #include "app_inc.h" -#include "talkingq_wss.h" +#include "talkingq_wss.h" +#include "net_diag.h" #include "ql_rtos.h" #include "ql_application.h" #include "ql_audio.h" @@ -222,6 +223,7 @@ static void onopen(rws_socket socket) if (socket != app->socket || !app->init) return; wss_connect_counter=0; + net_diag_event("WSS连接建立(尚不代表鉴权或AI回复完成)", 0); LOG_D("websocket connected. "); if(ws_connect_sem){ ql_rtos_semaphore_release(ws_connect_sem); @@ -245,6 +247,7 @@ static void onclose(rws_socket socket) } if (socket != app->socket) return; + net_diag_event("WSS连接断开", error_code); xiaoice_wss_connect_status = 0; app->socket = NULL; /* The worker owns and releases this socket after the callback. */ diff --git a/tools/test_net_diag.py b/tools/test_net_diag.py new file mode 100644 index 0000000..9d0f6cb --- /dev/null +++ b/tools/test_net_diag.py @@ -0,0 +1,117 @@ +"""Run the real diagnostic code with mocked modem APIs (no network traffic).""" +from pathlib import Path +import re +import shutil +import subprocess +import tempfile + +root = Path(__file__).resolve().parents[1] +base = root / 'ql-application/threadx/interface/custom_app' +sdk = root / 'ql-application/threadx/common/include' +harness = r''' +#include +#include +#include "net_diag.c" +static int creates, fail_create, mode, hold, pdp = 1; +static unsigned long clock_ms; +static int sample_steps, step_limit = 1; +static jmp_buf loop_exit; +static ql_ping_ctx_t mock_ctx; +static ql_ping_cb_t callback; +uint64_t hal_os_up_time_ms(void) { return clock_ms; } +uint8_t get_sleep_mode(void) { return hold; } +int get_mqtt_connect_status(void) { return 1; } +unsigned char get_xiaoice_connected_status(void) { return 1; } +int fota_start; +u8 power_down_status; +QlOSStatus ql_rtos_task_create(ql_task_t *task, u32 stack, u8 priority, char *name, + void (*entry)(void *), void *arg, ...) { + (void)name; (void)entry; (void)arg; + assert(stack == 4096 && priority == 120); + ++creates; + if (fail_create == creates) return -9; + *task = (void *)1; return 0; +} +void ql_rtos_task_sleep_ms(u32 ms) { + clock_ms += ms; + if (step_limit > 1) { + ++sample_steps; + if (sample_steps == 1) pdp = 0; + if (sample_steps == 2) pdp = 1; + if (sample_steps < step_limit) return; + } + longjmp(loop_exit, 1); +} +QL_NW_ERROR_CODE ql_nw_get_signal_strength(QL_NW_SIGNAL_STRENGTH_INFO_T *s) { + s->LTE_SignalStrength.rsrp = -97; return QL_NW_SUCCESS; +} +QL_NW_ERROR_CODE ql_nw_get_reg_status(QL_NW_REG_STATUS_INFO_T *r) { + r->data_reg.state = 1; return QL_NW_SUCCESS; +} +int ql_get_data_call_info(int profile, int version, struct ql_data_call_info *d) { + assert(profile == 1 && version == 0); d->v4.state = pdp; return 0; +} +QL_NW_ERROR_CODE ql_nw_get_sinr(int *s) { *s = 13; return QL_NW_SUCCESS; } +QL_NW_ERROR_CODE ql_nw_get_cell_info(QL_NW_CELL_INFO_T *c) { + c->lte_info_valid = 1; c->lte_info_num = 1; + c->lte_info[0].cid = 123; c->lte_info[0].pci = 42; return QL_NW_SUCCESS; +} +ql_ping_ctx_t *ql_ping_init(ql_ping_cfg_t cfg, ql_ping_cb_t cb) { + assert(cfg.timeout == 2000 && cfg.pingNum == 1 && cfg.dataSize == 32 && cfg.pdpCid == 1); + callback = cb; return mode == 3 ? NULL : &mock_ctx; +} +int ql_ping(ql_ping_ctx_t *ctx) { + ql_ping_stat_t s; + assert(ctx == &mock_ctx); memset(&s, 0, sizeof(s)); + if (mode == 2) return -2; + s.sentNum = 1; s.rcvdNum = mode == 0; s.rtt = 123; + callback(QL_PING_FINISHED, mode == 1 ? -1 : 0, &s); return 0; +} +void ql_ping_deinit(ql_ping_ctx_t *ctx) { assert(ctx == &mock_ctx); } +int main(void) { + fail_create = 2; + net_diag_start(); assert(!enabled && sample_task && !probe_task); + fail_create = 0; + net_diag_start(); assert(enabled && creates == 3); + net_diag_start(); assert(creates == 3); + mode = 0; probe_once(0); assert(attempts[0] == 1 && received[0] == 1 && !streak[0]); + mode = 1; probe_once(0); assert(attempts[0] == 2 && received[0] == 1 && streak[0] == 1); + mode = 2; probe_once(0); assert(attempts[0] == 2 && errors[0] == 1); + mode = 3; probe_once(0); assert(attempts[0] == 2 && errors[0] == 2); + mode = 0; probe_once(0); assert(!streak[0]); + step_limit = 3; + if (!setjmp(loop_exit)) sample_worker(NULL); + step_limit = 1; + hold = 1; + if (!setjmp(loop_exit)) probe_worker(NULL); + assert(attempts[1] == 0); + puts("PASS: creation failure/retry, idempotent activation, probe counters, sampling, sleep pause"); + return 0; +} +''' +with tempfile.TemporaryDirectory(prefix='banban-netdiag-') as directory: + tmp = Path(directory) + for name in ['ql_type.h', 'ql_rtos.h', 'ql_nw.h', 'ql_data_call.h', 'ql_ping.h']: + content = (sdk / name).read_bytes() + if name == 'ql_type.h': + # The ARM SDK defines 32-bit size_t unconditionally. Host tests use libc's. + content, count = re.subn(rb'typedef unsigned int\s+size_t;', b'/* host size_t */', content) + assert count == 1 + (tmp / name).write_bytes(content) + (tmp / 'test.c').write_text(harness) + command = [shutil.which('clang') or 'cc', '-std=c99', '-Werror=implicit-function-declaration', + '-Wno-typedef-redefinition'] + for include in [tmp, base, base / 'inc', base / 'lib/hal', sdk, sdk / 'lwipv4v6']: + command.extend(['-I', str(include)]) + subprocess.run(command + [str(tmp / 'test.c'), '-o', str(tmp / 'test')], check=True) + result = subprocess.run([str(tmp / 'test')], check=True, capture_output=True, text=True) + print(result.stdout, end='') + assert '本次网络不可用观测时长=2000 ms' in result.stdout + assert '未回应率=50%' in result.stdout + +key = (base / 'hal_key.c').read_text() +alarm = key[key.index('if(key_press_alarm_counter!=0'):key.index('ql_rtos_task_sleep_ms(10);')] +assert 'net_diag_start();' in alarm +for forbidden in ['build_alarm(', 'build_leave_to_parent(', 'stop_player_func(', 'app_play_tips_file(']: + assert forbidden not in alarm, forbidden +print('PASS: diagnostic key has no former alarm/message/audio actions')