feat: add button-activated field network diagnostics

This commit is contained in:
chenjintao
2026-10-20 14:12:28 +08:00
parent b2c78208bf
commit 8bff1f569e
8 changed files with 406 additions and 24 deletions
@@ -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 \
@@ -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();
}
}
}
}
}
@@ -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
@@ -0,0 +1,221 @@
/* On-demand field diagnostics. No PDP reconfiguration or recovery actions. */
#include <stdio.h>
#include <string.h>
#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(&reg, 0, sizeof(reg));
memset(&data, 0, sizeof(data));
sr = ql_nw_get_signal_strength(&signal);
rr = ql_nw_get_reg_status(&reg);
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;
}
@@ -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);
@@ -3,7 +3,8 @@
#include <librws.h>
#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. */