EMS 知识库
首页 / knowledge/代码解析/midware/ethernt/wireless

wireless_driver.c

源文件路径:midware/ethernt/wireless/wireless_driver.c 所属模块:midware/ethernt/wireless(无线通讯棒驱动层) 代码行数:644

1. 文件职责

本文件是 EMS 主控与"无线通讯棒"(易联 E-LINKER WIFI / 4G 棒、Alpha 自研 WIFI 棒)之间的驱动适配层,向下把 UART5 上的 AT 指令收发(elink_parse.c)与具体模块状态解析(wifi/e_linker_wifi.c)封装起来,向上给 midware/ethernt/ethernet.c 的无线收发通道提供统一入口(wireless_send / wireless_recv / wireless_recv_flag)。

它处在数据流的"物理链路之上、TCP 协议栈之下"这一环:云端的 EMS 报文由 ethernet.c 组帧后经 wireless_send() 通过 AT 通道透传给通讯棒,通讯棒再走蜂窝网/路由上网;反向由 wireless_recv_flag() 触发 handle_tcp_recv_data(WIRELESS)。

本文件还是整个无线子系统的"状态机宿主":wireless_status_detect() 负责通讯棒型号探测、连接状态刷新、信号强度采集与异常重启,由 task_ethernet_monitor 任务每 500ms 调用一次(midware/ethernt/ethernet.c:1544)。

另外 wireless_read_P1_data() 走同一条 UART5 通道去读 P1 采集器电表数据,由 task_meter 任务每 300ms 调用一次(midware/meter/meter.c:5879),因此本文件同时承担"电表数据源"这一职责。

2. 关键数据结构与宏

名称类型说明
WIRELESS_TAB_T结构体无线运行时总表(定义在 wireless_driver.h),本文件定义其实例 g_t_wireless_info
E_LINKER_ID_INFO结构体通讯棒身份信息(SN / HW / SW / ICCID / 是否 4G),本文件定义实例 g_e_linker_info
WIRELESS_TYPE_E枚举通讯棒型号:NONE、ZLG、E_LINKER_WIFI、E_LINKER_4G、ALPHA_LINKER_WIFI、MAX_WIRELESS
PROTOCOL_TYPE_E联合体通讯棒上报的协议支持位(纯 TCP / TCP+TLS / MQTT / MQTTS),由 e_linker_wifi.c 填写、ethernet.c 读取
APP_TAB结构体外部应用状态表类型,本文件引用外部实例 g_t_app_info(net/ems.h:351)
METER_RUN_TABLE_T结构体电表运行数据表(midware/meter/meter.h:310),wireless_read_P1_data() 用它承接 P1 数据
METER_DATA_T结构体METER_RUN_TABLE_T 内的实测数据子结构(midware/meter/meter.h:189),含 u16_volt[]/s16_current[]/s32_actpower[]/s32_reactpower[]/s16_factor[]/u16_frequent;该结构体处于 #pragma pack(1) 作用域内,无对齐填充
PHASE_A/PHASE_B/PHASE_C/PHASE_T/MAX_PHASE枚举相位索引,MAX_PHASE = 4;PHASE_T 表示总值
SENCOND_30MS宏30(net/ems.h:21)——因 OS_TICKS_PER_SEC = 1000,实为 30ms,用作串口静默间隔
SENCOND_10S宏10000(net/ems.h:28),通讯棒探测周期
SENCOND_300S宏300000(net/ems.h:36),无线状态周期体检间隔
SENCOND_800MS宏800(net/ems.h:23),P1 抄表最小间隔
UPDT_STS_IDLE枚举值net/server.h:160,升级状态机的"空闲"态
RESUME_STS_NONE / RESUME_STS_Idle枚举值net/server.h:208/209,断点续传状态
SOCK_OK宏midware/ethernt/lan/socket.h:88,值 1,表示有线网 socket 正常
E_LINKER_COMM_DEBUG / E_LINKER_4G_COMM_DEBUG宏调试打印开关;E_LINKER_COMM_DEBUG 定义在 e_Linker_wifi.h:8,E_LINKER_4G_COMM_DEBUG 全工程无定义(见备注)
WIRELESS_DEBUG / WIRELESS_DEBUG_INFO宏本文件调试打印开关,均默认 0
MAX_E_LINKER_SN_LEN / MAX_WIFI_4G_ICCID_LEN宏SN 16、ICCID 25(定义在 wireless_driver.h)
cUPS枚举值逆变器运行模式,用于 P1 掉线时不误报电表丢失

3. 函数清单

函数名作用域说明(一句话)
void wireless_set_restart_flag(u8 flag)全局置位通讯棒重启请求标志,供网络层异步请求重启
u8 wireless_query_restart_flag(void)全局读取重启请求标志
u8 wireless_send(u8 *buf, u32 len, u16 timeout)全局直接透传到 e_linker_send() 发数据给通讯棒
u8 wireless_recv_flag(void)全局透传到 e_linker_recv_flag(),告知 UART5 是否有待读数据
u32 wireless_recv(u8 *buf, u32 len)全局透传到 e_linker_recv(),从 UART5 环形缓冲取数据
void wireless_status_refresh(char *buf)全局解析云端下发的 JSON "WiFi info" 字段,刷新无线状态/IP/SN 等
void wireless_status_detect(void)全局无线子系统总巡检:型号探测、状态刷新、信号采集、异常重启
static u8 wireless_module_find(void)static按 4G→AlphaLinker→ELinker 顺序尝试登录,识别通讯棒型号
void wireless_init(void)全局清空无线运行状态与 tick,首次调用时给通讯棒上电
void wireless_restart(void)全局断电/上电三段式硬重启通讯棒,全程 9s 阻塞延时
void wireless_read_P1_data(void)全局通过 AT 通道读取 P1 采集器电表数据并写入电表模块

4. 核心逻辑解析

4.1 串口互斥:u8_wireless_uart_flag

UART5 是"AT 指令通道"与"无线 TCP 透传通道"共用的物理口,三条调用路径可能并发:

该字段在头文件里被声明为 volatile u8,是软互斥而非硬件锁:因为 task_meter(prio 9) 与 task_ethernet_monitor(prio 14) 都是可抢占的 uCOS-II 任务,占用期间若被更高优先级任务抢占并再次进入,靠的是先检查后置位的顺序判断(非原子),理论上存在竞态窗口(见第 7 节)。

4.2 通讯棒型号探测:wireless_module_find()

无独立状态机,而是一条"顺序试探"逻辑,探测周期由调用方控制(10s):

步骤发送的 AT 指令成功后的型号
1AT_GET_4G_COM("AT+4G_COM?\r\n")E_LINKER_4G
2AT_ALPHE_LOGIN_E("AT+LOGIN=ALPHA_LINKER\r\n")ALPHA_LINKER_WIFI
3AT_E_LOGIN_E("AT+LOGIN=E-LINTER\r\n")E_LINKER_WIFI
失败—NONE,并 memset(&g_e_linker_info,0,...)

4.3 巡检状态机:wireless_status_detect()

由 task_ethernet_monitor 每 500ms 调用。整体是"三条并行的时间片分支",不是显式 enum 状态机:

入口闸门(wireless_driver.c:189)

if ((g_t_wireless_info.u8_wireless_uart_flag == TRUE) ||
    ((OSTimeGet() - g_t_wireless_info.u32_WIFI_uart_tick) < SENCOND_30MS))
    return;

即"串口被占用"或"距上次串口活动不足 30ms"时直接退出,避免与 AT 收发抢通道。通过后置位 u8_wireless_uart_flag = TRUE。

分支 A:型号探测(周期 10s)

条件(wireless_driver.c:196): ((tcp 未连 && app 未连) || u8_wireless_type == NONE) && (now - u32_dectect_tick >= SENCOND_10S)

分支 B:周期体检(周期 300s)

条件(wireless_driver.c:243~247): get_updt_status_current() <= UPDT_STS_IDLE 且 IsSystemDownload() == FALSE 且 Get_Resume_Status_Current() == RESUME_STS_Idle 且 u8_wireless_type != NONE 且 now - u32_wireless_check_tick >= SENCOND_300S。

命中后:

  1. u32_wireless_check_tick = now
  2. 按型号调 e_linker_wifi_status_detect()(WIFI/AlphaLinker)或 e_linker_4g_status_detect()(4G)
  3. 信号强度:若 g_4g_state.state.u8_sta_state == TRUE 或 g_wifi_state.state.l_u8_route == TRUE,取 e_linker_sta_eirp()(发 AT+STA_EIRP?),否则固定 -99
  4. 4G 信号质量:若 g_4g_state.state.u8_sim_pin_state == TRUE,取 get_e_linker_4g_csq()(发 AT+CSQ?,解析 "CSQ:%d,%d"),否则 u8_4g_rssi = u8_4g_ber = 99

分支 C:延迟重启(wireless_driver.c:289)

u8_wireless_rst_flag == TRUE → wireless_restart() → 清标志。注意这一步在释放 u8_wireless_uart_flag 之前,所以 9s 重启期间串口一直被占用。

出口:u32_WIFI_uart_tick = OSTimeGet(); u8_wireless_uart_flag = FALSE;

4.4 硬重启时序:wireless_restart()

IWDG_Feed() → OSTimeDlyHMSM(0,0,3,0)   // 等 3s,让正在进行的串口收发自然结束
IWDG_Feed() → wifi_power_off()          // 拉 GPIOH_PIN_7 掉电
IWDG_Feed() → OSTimeDlyHMSM(0,0,3,0)   // 断电保持 3s
IWDG_Feed() → wifi_power_on()           // 上电
IWDG_Feed() → OSTimeDlyHMSM(0,0,3,0)   // 等待模块启动
IWDG_Feed()

共 9s 阻塞延时,全过程中穿插 4 次 IWDG_Feed() 喂狗,避免看门狗复位。OSTimeDlyHMSM 会让出 CPU,同优先级/低优先级任务仍能运行,但本任务自身 9s 不返回,期间 wireless_status_detect() 不再巡检。wireless_init() 里的 wifi_power_on() 用 static u8 s_u8_reset_flag 保证全程只上电一次。

4.5 P1 抄表:wireless_read_P1_data()

前置闸门(顺序判断,任一不满足即 return)

  1. meter_get_meter_or_ct(GRID_METER) != P1_SAMPLING → meter_set_grid_wifi_P1_fault(0)(清告警)后返回

—— 非 P1 电表场景不做任何事。

  1. u8_wifi_support_P1_falg != TRUE 或型号不在 {E_LINKER_WIFI, E_LINKER_4G} 中 → meter_set_grid_wifi_P1_fault(1) 后返回

—— 只有支持 18031 版协议的易联棒才支持 P1,Alpha 自研棒不参与;注释明确"再发 P1 的 AT 指令会影响设备上线"。

  1. 时间闸门:(now - u32_P1_read_tick) < SENCOND_800MS 或 (now - u32_WIFI_uart_tick) < SENCOND_30MS 或 u8_wireless_uart_flag == TRUE → 返回。

通过后置 u8_wireless_uart_flag = TRUE 占用串口。

采集主体(wireless_driver.c:495~607)

条件:型号为 E_LINKER_WIFI 且 !is_goto_get_update_file_status()(不在取升级文件)且 check_force_upgradable_flag() 且 Get_Resume_Status_Current() == RESUME_STS_NONE。 即"升级/断点续传期间不抄 P1"。

P1 数据格式(sscanf 格式串,wireless_driver.c:509)

"%d,%12[^,],%14[^,],%11[^,],%d,%d,%d,%d,%d,%d,%d,%d,%hu,%hu,%hu,%hd,%hd,%hd,%d,%d,%d,%d,%d,%d"

共 24 个转换项,逗号分隔:

#目标字段含义
1l_u8_p1_link_sta采集器链路状态,1 = 在线
2l_p1_data.u8_meter_sn电表 SN(≤12 字符)
3l_u8_p1_hw_verP1 硬件版本(≤14)
4l_u8_p1_sf_verP1 软件版本(≤11)
5l_p1_data.u32_p1_cycle刷新周期
6l_u32_p1_type电表类型
7~10l_u32_energy_input_1/2、l_u32_energy_output_1/2双向电能(买/卖各 2 相或 2 段)
11~12l_u32_in_actpower、l_u32_out_actpower输入/输出有功功率
13~15t_data.u16_volt[PHASE_A/B/C]三相电压(0.1V)
16~18t_data.s16_current[PHASE_A/B/C]三相电流(0.01A)
19~21t_data.s32_actpower[PHASE_A/B/C]三相有功功率(0.001kW)
22t_data.u16_frequent频率(格式项写成 %d,见第 7 节风险 2)
23t_data.s16_factor[PHASE_T]总功率因数(格式项写成 %d,见第 7 节风险 2)
24l_u32_cost电费(当前未使用)

sscanf 返回值必须等于 24(比较结果存入 l_u8_res,为 1 时才进后处理)。

注意第 22/23 两项的转换说明符与目标字段宽度不匹配(%d 写 u16/s16),且 METER_DATA_T 是 #pragma pack(1) 紧凑布局,两者会互相覆盖,详见第 7 节风险 2。

派生计算(wireless_driver.c:570~572)

t_data.u32_energy_input[PHASE_T]  = (u32_energy_input_1  + u32_energy_input_2 ) / 10;
t_data.u32_energy_output[PHASE_T] = (u32_energy_output_1 + u32_energy_output_2) / 10;
t_data.s32_actpower[PHASE_T]      = u32_in_actpower - u32_out_actpower;   // 有符号差值

除以 10 是把 P1 的 0.001kWh 口径归一到电表模块约定的 0.01kWh 口径。

软件版本截取(防越界,wireless_driver.c:573~583)

P1 的软件版本号比电表原有版本号长,而目标 t_data.u8_software_version[9] 只能放 9 字节(8 字符 + NUL):

if (len < sizeof(u8_software_version))  index = 0;
else                                    index = len - sizeof(...) + 1;
memcpy(u8_software_version, &p1_sf_ver[index], sizeof(u8_software_version));

即取后 8 位字符。

入库(wireless_driver.c:585~594)

l_u8_p1_link_sta == 1 时:meter_set_run_data(&l_p1_data) → meter_set_grid_hw_ver(l_u8_p1_hw_ver) → u32_P1_refresh_tick = OSTimeGet() → meter_set_P1_lose_flag(0)。 无论链路状态如何都执行 s_u8_timeout_count = 0 与 meter_set_grid_wifi_P1_fault(0)。

掉线判定(wireless_driver.c:612~633)

以 u32_P1_refresh_tick 为心跳基准:

出口 u8_wireless_uart_flag = FALSE,注意此分支不受初始闸门 return 的影响,即使本次没抄表也会做掉线判定(这是有意设计:在 E_LINKER_WIFI 之外的型号上也能维持"P1 已离线"标志)。

4.6 云端状态回写:wireless_status_refresh(char *buf)

由 net/server.c:7054 在收到命令字 CMD_RXD_WIFI_INFO 时调用,buf 是 TCP 载荷偏移 7 字节后的 JSON 串。

  1. json_get_value(buf, "WiFi info", str) 取出内嵌字符串(长度上限 MAX_LEN_JSON_CONNENT),失败直接返回;str 为 char[128]。
  2. 复位 g_e_linker_info.u8_is4G = 0。
  3. 单次 sscanf 解析 14 个字段(wireless_driver.c:116):
"%16[^,],%14[^,],%14[^,],%u,%u,%hhu,%hu.%hu.%hu.%hu,%hhu,%hhu,%hhu,%24[^,]"

字段顺序:SN, SW, HW, is_connect_router, is_connect_sta, tcpstatus, ip1.ip2.ip3.ip4, 4g_rssi, 4g_ber, is4G, ICCID。

  1. 返回项数校验放宽为 10 || 13 || 14(wireless_driver.c:126),以兼容旧固件:10 项 = 只到 IP;13 项 = 到 is4G;14 项 = 含 ICCID。
  2. 变更检测:SN、SW、ICCID 任一与 g_e_linker_info 中缓存不同 → strncpy 更新并置 is_need_report_status = 1。
  3. 状态落位:
    • g_wifi_state.state.l_u8_APP_login = is_connect_sta
    • g_wifi_state.state.l_u8_route = is_connect_router
    • g_t_wireless_info.u8linkstatus = g_wifi_state.state.l_u8_route
  4. IP 覆盖:仅当 g_stNetSysInfo.s8SocketOk != SOCK_OK(有线 LAN 未建链)时才把 4 个 wIpAddress 写进全局 IP_Address;有线优先,注释"优先使用有线lan通讯,不覆盖有线lan的ip"。IP 变化也置 is_need_report_status = 1。
  5. 连接标志:g_t_app_info.u8_app_connect_flag == FALSE && u8tcpstatus == FALSE 时,用 l_u8_APP_login 赋值并刷新 u32_app_connect_tick(带 30 分钟超时语义的时间戳)。
  6. is_need_report_status == 1 → Set_Setup_Config_Status(FALSE) + meter_set_saveConfigFlag(),触发云端重新上报配置。

4.7 tick 字段与时间基准

OSTimeGet() 在 OS_TICKS_PER_SEC = 1000 下返回毫秒计数,全部超时判断都是 now - tick 的无符号回绕安全写法。

字段作用初值位置
u32_dectect_tick型号探测节流(10s)wireless_init()
u32_link_tick链路相关节流(本文件只初始化,未使用)wireless_init()
u32_tcp_tickTCP 相关节流(本文件只初始化,未使用)wireless_init()
u32_wireless_check_tick300s 周期体检节流wireless_init()
u32_P1_read_tickP1 抄表节流(800ms)wireless_init(),抄表时更新
u32_P1_refresh_tickP1 数据心跳,>3s 判掉线抄表成功且链路在线时更新
u32_WIFI_uart_tick上次串口活动时刻(30ms 静默)wireless_init(),巡检出口、发送、抄表成功时更新

5. 对外接口

本文件导出(供外部调用)

名称调用方
WIRELESS_TAB_T g_t_wireless_infoethernet.c、net/ems.c、net/server.c、hmi/led.c、wifi/e_linker_wifi.c
E_LINKER_ID_INFO g_e_linker_infonet/ems.c:1896(上报 SN/HW/SW/ICCID)、midware/interface/modbus/modbus.c:3244 起(SN 拆字节读寄存器)
wireless_set_restart_flag()ethernet.c:1114/1135/1366/1415、net/server.c:1455
wireless_query_restart_flag()ethernet.c:936
wireless_send()ethernet.c:810/835
wireless_recv_flag()ethernet.c:897
wireless_recv()net/server.c:6353/6381
wireless_status_refresh()net/server.c:7054(CMD_RXD_WIFI_INFO)
wireless_status_detect()ethernet.c:1544(task_ethernet_monitor)
wireless_init()ethernet.c:941/973/993(异常恢复、任务启动)
wireless_restart()wifi/e_linker_wifi.c:463(e_linker_wifi_init() 内部失败重启)
wireless_read_P1_data()midware/meter/meter.c:5879(task_meter)

本文件调用的外部函数/变量

来源模块具体项
elink_parse.ce_linker_send、e_linker_recv、e_linker_recv_flag
wifi/e_linker_wifi.ce_linker_wifi_login、e_linker_wifi_init、e_linker_wifi_status_detect、e_linker_4g_status_detect、get_e_linker_wifi_info、e_linker_sta_eirp、get_e_linker_4g_csq、e_linker_P1_data;全局量 g_wifi_state、g_4g_state
midware/meter/meter.cmeter_get_meter_or_ct、meter_set_run_data、meter_set_grid_hw_ver、meter_set_grid_com_sta、meter_set_clear_grid_data、meter_set_grid_wifi_P1_fault、meter_set_P1_lose_flag、wireless_meter_is_all_lost、meter_set_saveConfigFlag
midware/inverter/inverter.cinverter_read_run_mode(cUPS)
net/ems.cSet_Setup_Config_Status、g_t_app_info
net/server.cget_updt_status_current、IsSystemDownload、Get_Resume_Status_Current、check_force_upgradable_flag、is_goto_get_update_file_status
midware/ethernt/ethernet.cg_stNetSysInfo.s8SocketOk、IP_Address
hardware/gpio/gpio.cwifi_power_on、wifi_power_off
hardware/iwdg/iwdg.cIWDG_Feed
common/json.cjson_get_value
uCOS-IIOSTimeGet、OSTimeDlyHMSM
libcsscanf、strcmp、strncpy、strlen、memcpy、memset、printf

6. 依赖关系

依赖(include / 使用)

被依赖

7. 备注与风险点

  1. wireless_init() 不重置型号字段。 该函数复位了 u8_wireless_found、信号强度、各类 tick,但没有清 u8_wireless_type。若通讯棒已被判为 E_LINKER_4G 而实际换成 WIFI 棒,wireless_module_find() 会在 u8_wireless_found 为真时直接返回且跳过试探,型号可能与实机不符。型号的实际清零点在 wireless_module_find() 的失败分支和 e_linker_wifi.c 的连续 6 次失败分支。
  1. sscanf 第 22/23 项长度不匹配(真实缺陷,会造成字段互相覆盖)。 wireless_driver.c:532 把 u16_frequent 写成 %d 配 (int *),wireless_driver.c:533 把 s16_factor[PHASE_T] 写成 %d 配 (int *),两处都是 4 字节写 2 字节字段。METER_DATA_T 声明在 #pragma pack(1) 下(midware/meter/meter.h:188),无对齐填充,字节偏移为:
字段偏移长度
u16_volt[4]08
s16_current[4]88
s32_actpower[4]1616
s32_reactpower[4]3216
s16_factor[4]488
u16_frequent562
u32_total_phase_energy_input584

于是按 sscanf 的写入顺序:第 22 项(应为频率)在偏移 56 写 4 字节,顺带把 u32_total_phase_energy_input 的低 16 位冲掉;紧接着第 23 项(应为功率因数)在偏移 54 写 4 字节,其高 16 位又覆盖偏移 56~57,即最终 u16_frequent 里存的是功率因数的高 16 位,而频率本身丢失。正确写法应为 %hu(频率)与 %hd(功率因数)。

   (int *)&l_p1_data.t_data.u16_frequent,      // 第 22 项,应为 %hu
   (int *)&l_p1_data.t_data.s16_factor[PHASE_T] // 第 23 项,应为 %hd
  1. recv_wifi_log() 无边界写入(见 elink_parse.c)。 l_u8_pdata[170] 由调用方传入,但解析器不校验长度,超长应答会溢出该栈数组;调用方注释也只写"数据长度 162",余量仅 8 字节。
  1. u8_wireless_uart_flag 是非原子软锁。 检查与置位之间可被任务切换打断(wireless_status_detect() 在 wireless_driver.c:189 检查、194 置位;wireless_read_P1_data() 在 484 检查、493 置位),ethernet.c:804/896 亦同。当前靠"各调用点错开周期"规避,没有 OSSemPend 之类硬互斥。
  1. wireless_restart() 在持锁状态下阻塞 9s。 该调用位于 wireless_status_detect() 释放 u8_wireless_uart_flag 之前(wireless_driver.c:289~293 早于 295),期间所有无线收发路径(含 ethernet.c 的上报)都会被 30ms 静默/占用判断挡回,最长约 9s 无法上云。另外该函数内有 4 次 IWDG_Feed(),属于"用喂狗换长延时"的写法,一旦路径被改坏会掩盖看门狗保护。
  1. E_LINKER_4G_COMM_DEBUG 全工程无定义。 elink_parse.c:230 用 #if (E_LINKER_4G_COMM_DEBUG == 1) 控制打印,该宏既不在 elink_parse.h 也不在其它头文件定义;在 C 预处理下未定义标识符按 0 处理,语句被裁掉,属于永远关闭的死代码。e_linker_wifi.c:153/593 同样如此。
  1. P1 掉线判定与 u32_P1_refresh_tick 的初值耦合。 该字段在 wireless_init() 被设为当前 tick,之后只有"P1 在线且 l_u8_p1_link_sta == 1"才更新。因此设备刚启动即处于非 P1 场景(第一个闸门 return)时,代码仍会走到 3s 判定分支;因为 wireless_init() 刚刷过 tick,前 3s 内不会误报,但 u8_wifi_support_P1_falg == FALSE 时同样会累积推进——逻辑上依赖 wireless_init() 被及时调用。
  1. u32_link_tick / u32_tcp_tick 疑似遗留。 两者在 wireless_init() 中赋值,本文件其余位置从未读写;实际链路/tcp 节流由 wifi/e_linker_wifi.c 内部的 static 计数器与 e_linker_tcp_state() 承担。
  1. 调试打印复制粘贴错误。 wireless_driver.c:550/551 打印 B 相、C 相电压时仍使用 PHASE_A 作为数组下标,三行输出完全相同;属仅影响日志的瑕疵(该块由 WIRELESS_DEBUG_INFO 控制,默认 0)。
  1. wireless_send() 的超时是忙等。 最终落到 usart5_send_data(),其内部用 while(...) 轮询 DMA 完成标志而非 OSTimeDly,ethernet.c 传入的 timeout = 5000(ms) 一旦触发,将以忙等方式霸占 CPU 最长 5s。
  1. is_need_report_status 的 IP 比较在 %hu 截断下工作。 wIpAddress1..4 用 %hu 解析到 u16,若云端给的是标准点分十进制(0~255)没有问题;但一旦某段超过 65535,%hu 的溢出行为未定义。