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

ethernet.c

源文件路径:midware/ethernt/ethernet.c 所属模块:midware/ethernt(有线/无线网络接入层) 代码行数:1746

1. 文件职责

本文件是 EMS 设备与云端服务器之间唯一的网络收发实现,同时管理有线(W5500 以太网)和无线(易联 WIFI/4G 通讯棒)两条物理通道。

它向下直接驱动 W5500 芯片(SPI 读写、DHCP、DNS、Socket 状态机),向上通过 uCOS-II 消息队列 g_pEthernetQ 接收应用层(net/server.c)封装好的待发报文并在通道就绪时发出,收到数据后回调 handle_tcp_recv_data() 交给应用层解析。

文件还承担两项运维职责:一是网络自愈(DHCP/DNS/Socket/心跳任一环节长时间失败即复位 W5500 或整个系统),二是"服务器指向"策略的落地(固定域名表、用户自定义域名、非固定临时指向三级优先)。

它由两个 uCOS-II 任务驱动:task_ethernet(主收发循环,由 net/ems.h 的消息生产者喂数据)和 task_ethernet_monitor(每 500ms 巡检、故障判定与系统复位)。

2. 关键数据结构与宏

名称类型(结构体/枚举/宏/联合体)说明
g_stNetSysInfo全局变量(STEthernetSys)网络运行时状态总表:DHCP/DNS/Socket 成功标志、各阶段 tick、Socket/链路状态、非固定指向缓存、u16_skip_port/u8_skip_encryption/u8PortSel
IP_Address全局变量(IP_ADDR_TAB_T)掉电保存的网参:MAC、IP 方式、IP/网关/掩码、u8_server_point、非固定指向 1/2、临时指向开关与倒计时、CRC
g_stNetDataSend全局变量(STEthernetData)网络发送数据描述(本文件定义、未见本文件内使用)
g_pEthernetQ全局变量(OS_EVENT*)网络发送消息队列,创建长度 ETHERNET_Q_LEN(20)
g_pUpdateSem全局变量(OS_EVENT*)升级用信号量,本文件创建值为 0,仅创建未在此使用
g_pEthernetMsg[ETHERNET_Q_LEN]全局变量(void*[])消息队列的存储体,供 OSQCreate 使用
g_u8DHCPBuf[MAX_DHCP_BUF_SIZE]全局变量(u8[])DHCP 协议栈缓冲区,548 字节
g_u8DNSBuf[MAX_DNS_BUF_SIZE]全局变量(u8[])DNS 协议栈缓冲区
netinfo全局变量(wiz_NetInfo)W5500 芯片内当前生效网参(由 CN_GET_NETINFO 读回)
g_stNetInfo全局变量(wiz_NetInfo)软件侧待写入 W5500 的网参
g_stGetNetToutInfo全局变量(wiz_NetTimeout)读取到的 W5500 当前超时参数
g_stSetNetToutInfo全局变量(wiz_NetTimeout,带初值)要写入 W5500 的超时参数:retry_cnt=8、time_100us=2000(即重发 400ms,注释称总超时 102s)
g_u8linkstatus全局变量(u8)链路状态缓存,初值 FALSE
s_serverInfo[]static 数组(ServerInfo_t[])固定域名指向表,14 项,含域名/端口 1/加密方式 1/端口 2/加密方式 2,与 SERVER_POINT_E 逐项对应
SOCK_TCP_CLIENT宏(来自 ethernet.h)W5500 的 socket 2,云端 TCP 客户端固定使用
SOCK_DHCP / SOCK_DNS宏W5500 的 socket 1 / socket 0
WIRELESS_SEND_SIZE宏无线通道单次分片 5*1024 字节
MY_MAX_DHCP_RETRY宏DHCP 失败重试上限 3
DHCP_PRO / DNS_PRO宏流程号 1 / 2(本文件未使用)
NET_PRIVATE_ENCRYPTED / NET_TLS_ENCRYPTED / NET_NOT_ENCRYPTED枚举(ETHERNET_ENCRYPTION_TYPE)数据加密方式判定用
SENCOND_* 系列宏(来自 net/ems.h)时间常量,注意单位为 tick:SENCOND_1S=1000、SENCOND_300S=1000*300、SENCOND_30M=1000*1800(即 30 分钟)、SENCOND_1H、SENCOND_3H
DHCP_FAIL_NUM=12 / LOGIN_NO_ACK_NUM=18 / LOGIN_NO_ACK_RST_NUM=3 / HEART_RST_FAIL=3宏(来自 net/ems.h)各类连续失败阈值
PHY_LINK_ON宏(W5500 协议栈)物理链路已连接判定值
SOCK_INIT/SOCK_ESTABLISHED/SOCK_CLOSE_WAIT/SOCK_CLOSED宏(midware/ethernt/lan/w5500.h:1045/1079/1111/1037)Socket 状态值 0x13/0x17/0x1C/0x00,驱动 ems_server_event_pro 状态机
SOCK_OK/SOCK_ERROR宏(midware/ethernt/lan/socket.h:88/92)Socket 操作结果:1 / 0
W5500 / WIRELESS枚举(NET_CONNECT_TYPE,来自 net/ems.h:79-83)通道标识 0x01/0x02,作为 get_ethernet_server_port 的入参

3. 函数清单

函数名作用域说明(一句话)
void w5500_spi_configuration(void)全局初始化 SPI1(SPI1_Init()),W5500 的物理层入口
static u8 SPI1_ReadByte(u8 TxData)static注册给 WIZCHIP 的 SPI 读回调,转发 SPI1_ReadWriteByte
static void SPI1_WriteByte(u8 TxData)static注册给 WIZCHIP 的 SPI 写回调,丢弃返回值
void Net_Para_Init(void)全局把 IP_Address 里的 MAC/IP/掩码/网关/DNS 灌入 g_stNetInfo,并按 IP 方式决定 DHCP/静态
void net_init(void)全局仅置位 g_stNetSysInfo.u8EMSInit = TRUE,请求重新初始化网络
void network_init(void)全局通过 ctlnetwork 向 W5500 下发网参和超时参数,并读回校验/打印
void my_ip_assign(void)全局DHCP 回调:从 DHCP 协议栈取回 IP/网关/掩码/DNS 后调 network_init() 生效
void W5500_Init(void)全局W5500 完整初始化:SPI、电源上下电、硬件复位、SPI 回调注册、socket buffer 分配、网参下发、DHCP/DNS 初始化
void dhcp_pro_fun(void)全局单步推进 DHCP_run() 状态机,成功置 u8DHCPOk 并写网络事件位,失败超 3 次则 DHCP_stop()
void dhcp_function(void)全局每 1s 调一次 dhcp_pro_fun(),DHCP 成功后把结果回写 IP_Address 并置网络事件位
void dns_pro_fun(void)全局每 500ms 用轮换的 DNS 服务器(本机/8.8.8.8/1.1.1.1/9.9.9.9/185.222.222.222/223.5.5.5/119.29.29.29/77.88.8.8)解析当前域名
void ems_server_data_comm(INT8U err, STEthernetData *msg)全局有线通道的报文发送:加密封装 + 3 次重试的分片累加 send()
void lan_receive_event(void)全局查询 socket 2 的 RECV 中断与接收字节数,有数据则调 handle_tcp_recv_data(W5500)
void ems_server_event_pro(INT8U err, STEthernetData *msg)全局有线 TCP 客户端 socket 状态机:INIT→connect,ESTABLISHED→收发,CLOSE_WAIT→disconnect,CLOSED→重开 socket
void wireless_event_pro(INT8U err, STEthernetData *msg)全局无线通道的报文发送:加密封装 + 按 5KB 分片 + 每片 3 次重试的 wireless_send()
void wireless_receive_event(void)全局占用/释放 WIFI 串口标志,若有数据则调 handle_tcp_recv_data(WIRELESS)
void handle_net_abnormal(void)全局响应三个复位请求标志(W5500 初始化、无线重启、EMS 初始化),执行 W5500_Init / wireless_init / socket 断开
void task_ethernet(void *p_arg)全局网络主任务:初始化 W5500 与无线、创建队列与信号量,循环收发 + 推进有线状态机 + 调 modbus TCP
void internet_time_handle(void)全局每 1s 调 DHCP_time_handler() 与 DNS_time_handler() 推进协议栈定时
void wireless_abnormal_detect(void)全局无线异常检测:长时间未连路由(3h)或连上路由但 30min 连不上服务器时置无线重启标志
static void server_skip_support_check(void)static非固定指向的功能验证与临时指向倒计时管理,超时后清空指向并触发保存
void net_abnormal_detect(void)全局网络总异常判定:DHCP/DNS/Socket/登录/心跳/任务超时/升级超时等多种条件,最终决定是否复位系统
void task_ethernet_monitor(void *p_arg)全局网络监控任务主体,每 500ms 顺序调无线检测、无线异常检测、网络异常检测
void ip_add_skip_change_clear(void)全局清空非固定指向 1/2 的地址、端口、加密、变更标志及临时指向,并置网参保存标志
static u8 trySkip(u8 wantTLS, u16 *outPort, u8 *outEncryptionFlag, u8 *outPortSel)static在非固定指向 1/2 中按是否需要 TLS 匹配一项,匹配失败则清空指向并返回 FALSE
u16 get_ethernet_server_port(u8 networkType, u8 tlsSupport)全局按三级优先级计算应连接端口,并同步刷新 u8_skip_encryption
char* get_ethernet_server_point(void)全局按三级优先级返回应连接域名
u8 get_server_data_encryption_flag(void)全局返回 g_stNetSysInfo.u8_skip_encryption
char* wireless_get_ethernet_server_point(char *s_skip_addr, u8 protocol_type)全局拼出 tcp://域名 或 tcps://域名 字符串给 WIFI 棒下发

共 30 个函数(4 个 static,26 个全局)。

4. 核心逻辑解析

4.1 有线 TCP 客户端状态机(ems_server_event_pro,ethernet.c:665-738)

每次 task_ethernet 循环调用一次,先读 getSn_SR(SOCK_TCP_CLIENT),状态变化时置 g_stNetEventLogInfo.stNewNetEvent.stBit.bSocketStatus = TRUE 供网络事件日志记录,然后分派:

当前状态动作迁移到
SOCK_INIT (0x13)connect(SOCK_TCP_CLIENT, g_stNetSysInfo.Domain_IP, get_ethernet_server_port(W5500, FALSE)) 发出连接请求等待 SOCK_ESTABLISHED
SOCK_ESTABLISHED (0x17)首次进入时(s8SocketOk == SOCK_ERROR)设 SO_KEEPALIVEAUTO=6、bIsBuiltSocket=TRUE、bIsLogin=FALSE;随后 ems_server_data_comm() 发数据 + lan_receive_event() 收数据保持或转 CLOSE_WAIT/CLOSED
SOCK_CLOSE_WAIT (0x1C)disconnect(SOCK_TCP_CLIENT)SOCK_CLOSED
SOCK_CLOSED (0x00)若之前是 SOCK_OK,则调 socket_abnormal_handle()、OSQFlush(g_pEthernetQ) 清空发送队列、set_info_status(SOCKET_STATUS);置 s8SocketOk=SOCK_ERROR、u8DNSOk=FALSE;socket(SOCK_TCP_CLIENT, Sn_MR_TCP, LOCAL_PORT, Sn_MR_ND) 重开SOCK_INIT

重连防抖(ethernet.c:724-732):用 static 变量 s_u16_last_port / s_u16_now_port 比较新旧本地端口号,若相同则主动 close() 强制换端口,避免 W5500 复用同一端口导致连接无法建立。这是 W5500 应用层的常见坑。

超时参数:g_stSetNetToutInfo 在 W5500_Init→network_init 时写入(retry_cnt=8、time_100us=2000),即 TCP 层 400ms 重发一次、最多 8 次。

4.2 DHCP 流程(dhcp_function → dhcp_pro_fun,ethernet.c:306-408)

dhcp_function() 用 static s_u32_dhcp_ticks 做 1s 节流(SENCOND_1S),每次调 dhcp_pro_fun() 单步推进 DHCP_run():

DHCP 成功后第一次(u8DNSOk == FALSE)把 IP/网关/掩码回写 IP_Address 持久化字段;无论成功失败都写 g_stNetEventLogInfo.stNewNetEvent.stBit.bDHCP。

两处都有 tick 回卷保护:else if(s_u32_dhcp_ticks > OSTimeGet()) s_u32_dhcp_ticks = OSTimeGet();(32 位 tick 约 49.7 天溢出)。这个模式在 dns_pro_fun、internet_time_handle 等处反复出现。

4.3 DNS 解析与 8 个备选服务器轮换(dns_pro_fun,ethernet.c:417-537)

每 500ms 执行一次,核心是 switch(s_u8_index % 8) 配合故意的 case 穿透(无 break):从 case 0 开始依次向下穿透,直到某个 if (l_u8_dns[0] == 0x00) 为真才填入候选 DNS 并继续穿透到 default,最终在 default 里真正调用 DNS_run()。

候选顺序(注释给出用途):

  1. case 0:g_stNetInfo.dns[](网关/DHCP 下发)
  2. case 1:8.8.8.8 Google Public DNS
  3. case 2:1.1.1.1 CloudFlare
  4. case 3:9.9.9.9 Quad9
  5. case 4:185.222.222.222 DNS.SB
  6. case 5:223.5.5.5 阿里公共 DNS
  7. case 6:119.29.29.29 DNSPod
  8. case 7:77.88.8.8 Yandex DNS

DNS_run() 返回值语义:1 = 解析成功、2 = 仍在进行(return 直接退出本轮)、其它 = 失败(s_u8_index++ 换下一个 DNS)。解析成功但结果全 0 时也视为失败并换服务器。

强制旁路分支(ethernet.c:521-531):else 分支(即 g_stNetSysInfo.u8DNSOk != FALSE 时)直接把 Domain_IP 硬编码为 192.168.8.7 并置 u8DNSOk = TRUE。注意 if((TRUE) && (g_stNetSysInfo.u8DNSOk == FALSE)) 中 TRUE 是恒真常量,等价于只判断 u8DNSOk == FALSE——这是调试便利写法却留在量产代码里的可疑点。

4.4 报文发送:有线路径(ems_server_data_comm,ethernet.c:550-630)

  1. err != OS_ERR_NONE 时按错误类型分派:OS_ERR_EVENT_TYPE/OS_ERR_PEVENT_NULL 使 g_stEMSInfo.u16OSQDealFailCnt++(队列处理失败计数,后续用于判系统复位),其它错误清零;
  2. 正常取到消息后:msg->u8lock = TRUE 上锁(防同一条报文被重入发送);
  3. 刷新 g_stNetSysInfo.u16_skip_port = get_ethernet_server_port(W5500, tls支持标志);
  4. 私有加密:当 get_server_data_encryption_flag() == NET_PRIVATE_ENCRYPTED 且 msg->u32Len > 9 且当前不是 APP 直连状态(g_t_app_info.u8_app_connect_flag == FALSE)时,对 pu8Info[7] 起、长度 u32Len - 9 的数据段做 Encrypt() 原地加密,然后用 GetCrcCode_Modbus(pu8Info, u32Len - 2) 重算校验并写入最后 2 字节;
  5. 发送循环:外层 for (i = 0; i < 1000; i++),内层最多 3 次 send();send() 返回正值则前移指针、扣减剩余长度并 break 内层;剩余长度为 0 或 send() 返回负值则 break 外层。即"最多 1000 段 × 每段最多 3 次重试"的累积发送;
  6. 解锁 msg->u8lock = FALSE 并把 msg->u8Type = STATUS_NONE 标记为已处理。

4.5 报文发送:无线路径(wireless_event_pro,ethernet.c:747-881)

与有线不同的三点:

加密/CRC 处理与有线路径完全一致(ethernet.c:791-802)。

4.6 手动指定域名/端口:三级优先级(get_ethernet_server_port,ethernet.c:1658-1691)

trySkip(networkType==WIRELESS && tlsSupport, ...)   → 命中则用非固定指向 1 或 2 的端口
  else g_system_custom_info.u8_socket_flag          → 用自定义端口(是否 TLS 决定 1/2 号端口)
  else IP_Address.u8_server_point < MAX_SEVER       → 查 s_serverInfo[],按 useTLS 取 Port2/port1
  else                                              → 兜底 FORMAL_SERVER_PORT(0) = 7777,加密方式 NET_NOT_ENCRYPTED

其中 useTLS = (networkType==WIRELESS && tlsSupport && (IP_Address.u8_server_temp_switchover != TRUE))——临时指向期间强制走非 TLS 端口。

trySkip(ethernet.c:1628-1650)按 i=1、2 顺序尝试:wantTLS 且该项加密方式为 NET_TLS_ENCRYPTED,或 !wantTLS 且该项加密方式非 TLS,则命中并回填 outPort/outEncryptionFlag/outPortSel。注意 wantTLS = wantTLS && (!IP_Address.u8_server_temp_switchover); 写在循环体内,是自赋值式覆盖(第一轮后 wantTLS 可能已变 FALSE,第二轮再与同一条件相与,结果不再改变,逻辑上等价但可读性差)。两项都不命中时,若任一 u8_skip_change_flag 为真则调 ip_add_skip_change_clear() 清空指向。

get_ethernet_server_point(ethernet.c:1699-1715)采用同样的三级顺序返回域名:非固定指向 IP_Address.s_skip_addr → CUSTOM_SERVER_NAME → s_serverInfo[u8_server_point].name → 兜底 FORMAL_SERVER_NAME。

4.7 无线域名下发格式(wireless_get_ethernet_server_point,ethernet.c:1736-1745)

用 static char s_sever_mailing_addr[75] 加 sprintf 拼 "%s://%s":TLS 时前缀 "tcps",否则 "tcp"。返回值指向 static 缓冲,不可重入、返回值在下次调用后失效;调用方(e_linker_wifi.c:419)都是"取值后立即用",但嵌套调用 wireless_get_ethernet_server_point(get_ethernet_server_point(), get_server_data_encryption_flag()) 恰好没有冲突。

4.8 网络自愈体系

三层,从快到慢依次生效:

(1) task_ethernet 主循环内的即时处理(ethernet.c:986-1071)

每轮先 OSQPend(g_pEthernetQ, 10, &l_u8Err)(10 tick 超时)取消息,然后:

(2) handle_net_abnormal 执行复位请求(ethernet.c:912-977)

(3) task_ethernet_monitor 每 500ms 的故障判定(ethernet.c:1538-1556)

顺序调用 wireless_status_detect()(在 wireless_driver.c)、wireless_abnormal_detect()、net_abnormal_detect()。

4.9 net_abnormal_detect 判定表(ethernet.c:1234-1529)

这是全文件最长也最关键的故障判定函数,静态变量承担状态记忆:s_u8LinkRstFlag(初值 TRUE)、s_u32TickLink、s_u8ResetFlag(初值 FALSE)、s_u8_updata_flag/s_u32_updata_tick/s_u32_updata_flag_clear_tick。

条件阈值动作
链路 ON 且 DHCP 模式且 u8DHCPOk == FALSESENCOND_300Su8DHCPRstCnt++、u8W5500Init = TRUE、b_modbus_Socket = FALSE、重记 u32DHCPTick;累计达 DHCP_FAIL_NUM(12) 且无线未上线 → s_u8ResetFlag = TRUE
DHCP 成功 / 静态 IP / tick 回卷—清零 u8DHCPRstCnt 并刷新 u32DHCPTick
modbus TCP 未占用,且(静态 IP 或 DHCP 成功)且 DNS 失败SENCOND_30Mu8W5500Init = TRUE、刷新 u32DNSTick
DNS 成功 / tick 回卷—刷新 u32DNSTick
DNS 成功但 socket 未 OKSENCOND_300Su8W5500Init = TRUE、刷新 u32SOCKTick
Socket OK / tick 回卷—刷新 u32SOCKTick
链路 OFF 且无线未上线,且链路从 ON→OFFSENCOND_10Su8EMSInit = TRUE,置 s_u8LinkRstFlag = TRUE 防止重复触发
链路 OFF 且有线无线皆未连SENCOND_3Hu8EMSInit = TRUE
bIsBuiltSocket == TRUE 且 u8LogInCnt >= LOGIN_NO_ACK_NUM(18)18 次若所有电表都丢了则 u8EMSInit = TRUE;u8LogInCnt 清零、u8LogInNoAckRstCnt++;累计达 LOGIN_NO_ACK_RST_NUM(3) 且无线类型为 NONE → s_u8ResetFlag = TRUE
bIsLogin == TRUE 且距 u64TickHeartRcv 超时SENCOND_180Su8EMSInit = TRUE、u16HeartRstCnt++、置心跳失败事件位;达 HEART_RST_FAIL(3) 且无线类型 NONE → s_u8ResetFlag = TRUE + 置心跳复位事件位
bIsLogin == FALSE 且长时间无交互SENCOND_30M若电表全丢则置无线重启标志 + u8EMSInit = TRUE
u16OSQDealFailCnt >= 5050s_u8ResetFlag = TRUE + 置 OSQ 处理失败事件位
task_ethernet 心跳超时SENCOND_180S刷新 tick、s_u8ResetFlag = TRUE + 置任务超时事件位
task_net_app 心跳超时SENCOND_60S同上,s_u8ResetFlag = TRUE
正在下载固件或 modbus 强制升级SENCOND_30M清 g_stEMSInfo.stUpt、set_updt_status_current(UPDT_STS_NONE)、set_flag_modbus_force_update(FALSE)、置 g_t_upgrade_run_info.u16_upgrade_error.BIT.timeout = 1
u8_forceupdt_flag 置位SENCOND_600S自动清除该标志
s_u8ResetFlag == TRUE 且 !IsSystemUpgrade()—置 bSeriousFaultRst 事件位、OSTimeDlyHMSM(0,0,10,0) 延时 10s、system_hard_reset() 硬复位

关键设计:所有"致命"判定最终都收敛到 s_u8ResetFlag,而这个标志只在 task_ethernet_monitor 里被消费一次,且会被 IsSystemUpgrade() 拦截(正在升级固件时不复位)。wireless_meter_is_all_lost() == 1 作为附加条件,避免"只断了云连接但本地抄表正常"时轻易重启无线棒。

4.10 非固定指向与临时指向管理(server_skip_support_check,ethernet.c:1151-1226)

三段逻辑:

  1. 临时指向倒计时:u8_server_temp_switchover_tim != 0 && u8_server_temp_switchover != FALSE 时,每 SENCOND_1H(EMS_18031_DEBUG 下为 SENCOND_60S)减 1;减到 0 后关闭临时指向、置 g_t_config_save_flag.u8_set_net_para_flag = TRUE、调 net_init()。
  2. 非固定指向功能验证:若当前选中的指向(u8PortSel 为 1 或 2)的 u8_skip_support_check_state 不等于 2(未完成)且 u8_skip_change_flag 为 TRUE,则每 SENCOND_600S(调试下 SENCOND_60S)把对应的 u8_skip_support_check_state 置 1 并置 s_u8_change_flag = TRUE。语义是"客户临时指向存活超过 10 分钟即认为验证通过"。
  3. 变更落地:s_u8_change_flag == TRUE 时清零、set_info_status(ICDC_UPDATE_STATUS)(通知 ICDC 上报状态)、ip_add_skip_change_clear() 清指向、net_init() 触发重连。

注意第 2 段的 else if 链(ethernet.c:1208-1215)里两个分支体完全相同(都是 s_u32TickNetSkipChange = OSTimeGet()),属冗余但无害的写法。

4.11 W5500 初始化时序(W5500_Init,ethernet.c:251-297)

顺序为:SPI1 初始化 → W5500_Power_OFF() + 延时 1s → W5500_Power_ON() + 延时 1s → W5500_Hardware_Reset_On() + 延时 1s → W5500_Hardware_Reset_Off() + 延时 1s → 注册 CS/SPI 回调 → ctlwizchip(CW_INIT_WIZCHIP, memsize) 分配 socket buffer({{2,2,8,2,0,0,0,0},{2,2,8,2,0,0,0,0}},即 socket2 给 8KB TX/RX,其余各 2KB)→ Net_Para_Init() → network_init() → 若为 DHCP 模式则注册 reg_dhcp_cbfunc(my_ip_assign, my_ip_assign, net_init) 并 DHCP_init(SOCK_DHCP, g_u8DHCPBuf) → DNS_init(SOCK_DNS, g_u8DNSBuf)。

整个过程中有 4 处 1 秒 OSTimeDlyHMSM 阻塞,共 4 秒。由于 W5500_Init 在 task_ethernet 主循环和 handle_net_abnormal 里都可能被调用,这段时间任务无法处理队列消息——但看门狗由其它任务喂。

5. 对外接口

导出的函数与全局变量

导出的全局变量(供其它模块直接引用):

导出的函数:

调用的外部函数/变量

来源模块被调用内容
W5500 ioLibrary(midware/ethernt/lan)SPI1_Init、SPI1_ReadWriteByte、SPI1_CS_Select/Deselect、ctlwizchip、ctlnetwork、reg_wizchip_cs_cbfunc、reg_wizchip_spi_cbfunc、reg_dhcp_cbfunc、DHCP_init/DHCP_run/DHCP_stop/DHCP_time_handler、DNS_init/DNS_run/DNS_time_handler、getIPfromDHCP 等、socket/close/connect/disconnect/send、getSn_SR/getSn_IR/setSn_IR/getSn_RX_RSR/getSn_PORT/setsockopt、W5500_Power_ON/OFF、W5500_Hardware_Reset_On/Off
net/ems.h / net/server.hg_stEMSInfo(u16OSQDealFailCnt、bIsBuiltSocket、bIsLogin、u8LogInCnt、u8LogInNoAckRstCnt、u16HeartRstCnt、u64TickHeartRcv、u32EMSTaskTick、b_modbus_Socket、u8_forceupdt_flag、stUpt)、handle_tcp_recv_data、socket_abnormal_handle、set_info_status、IsSystemUpgrade
data/(system_config.h、net_event_log.h)g_system_custom_info、g_t_config_save_flag.u8_set_net_para_flag、g_t_app_info、g_stNetEventLogInfo
midware/interface/modbusems_to_modbus_tcp、get_flag_modbus_force_update、set_flag_modbus_force_update
midware/ethernt/wirelesswireless_send、wireless_recv_flag、wireless_init、wireless_status_detect、wireless_query_restart_flag、wireless_set_restart_flag、g_t_wireless_info
common/Encrypt(simple_encryption)、GetCrcCode_Modbus(crc.h)、json 相关
midware/meterwireless_meter_is_all_lost
net/ems_download.hIsSystemDownload、is_goto_get_update_file_status 相关
其它IWDG_Feed、system_hard_reset、OSTimeGet/OSTimeDlyHMSM/OSQCreate/OSQPend/OSQFlush、g_t_upgrade_run_info

6. 依赖关系

依赖(include):ethernet.h、spi1.h、socket.h、dhcp.h、dns.h、ems.h、system_config.h、net_event_log.h、crc.h、iwdg.h、ems_download.h、modbus.h、simple_encryption.h、meter.h(ethernet.c:13-26)。

被依赖:

7. 备注与风险点

真实缺陷 / 隐患(按严重程度排序)

  1. 无线发送失败后状态机不一致(ethernet.c:820-826, 845-851):l_u8SendRes == FALSE 时直接 return,但此时 msg->u8lock 仍为 TRUE、msg->u8Type 未清为 STATUS_NONE、g_t_wireless_info.u8_wireless_uart_flag 仍为 TRUE。同一条报文后续会被反复取到并重试(未清 type),而 u8lock 一直是 TRUE 会导致上层的锁判定失效;u8_wireless_uart_flag 不被释放则 WIFI 串口会被永久占用,直到 wireless_status_detect 下一次把它清掉(wireless_driver.c:295)。相比之下有线路径 ems_server_data_comm 无论成败都会解锁。
  2. msg != NULL 判空位置错误(ethernet.c:571、785):l_p_temp = (u8 *)msg->pu8Info;、l_u32_length = msg->u32Len;(有线,567-569)以及 l_u32_loop_cnt = msg->u32Len / WIRELESS_SEND_SIZE;(无线,782-783)都在 if(msg != NULL) 之前执行,msg 为 NULL 时已经解引用。虽然在 OSQPend 返回 OS_ERR_NONE 时理论上不会是 NULL,但该判空完全失效。
  3. dns_pro_fun 的 DNS 旁路分支(ethernet.c:521-531):else 分支(u8DNSOk == TRUE 时)会把 Domain_IP 写成 192.168.8.7。若 u8DNSOk 曾短暂为真而域名变更未重置该标志,会把服务器指向改成内网地址。同函数 if((TRUE) && (...)) 的恒真判断也是可疑写法。
  4. static u16 s_u16_last_port 类 static 状态无初始化保护:ems_server_event_pro 的 static 变量初值为 0,首次进 SOCK_CLOSED 时 s_u16_now_port != 0 通常成立,但如果 W5500 恰好分配端口 0 则会误判走 close() 分支——概率极低。
  5. get_ethernet_server_port 的副作用:该函数名义上"获取端口号",实际会修改 g_stNetSysInfo.u8_skip_encryption、u8PortSel、u16_skip_port,并在 trySkip 失败时调用 ip_add_skip_change_clear()(会置 Flash 保存标志)。多处调用(ethernet.c:575/688/790、e_linker_wifi.c:283/397)都会产生这些副作用,阅读代码时易被误导。
  6. trySkip 中 wantTLS 自赋值(ethernet.c:1636):wantTLS = wantTLS && (!IP_Address.u8_server_temp_switchover); 位于 for 循环体内,第一轮之后 wantTLS 可能已被改写。逻辑上由于右侧条件不变,结果稳定,但把"循环不变量"写在循环体内是易错写法。
  7. remote 分片发送缺少响应确认:wireless_event_pro 只判断 wireless_send 的返回值(串口是否发出),并不等待服务器 ACK,5KB 分片之间也无协议层确认,丢片只能靠上层超时重传。
  8. net_abnormal_detect 中 s_u8_updata_flag 分支的 else-if 嵌套(ethernet.c:1477-1488):if ((OSTimeGet() - s_u32_updata_tick) > SENCOND_30M) {...} else if (s_u32_updata_tick > OSTimeGet()) {...} —— 超时分支没有重置 s_u8_updata_flag,下一轮会重新进入超时分支并重复清 stUpt/置 timeout 标志。
  9. ip_add_skip_change_clear 的 #if/else 空分支(ethernet.c:1568-1572):EMS_18031_DEBUG == 1 时该 #if 块为空,s_skip_addr 不会被清零(调试期行为差异),量产路径正常。
  10. server_skip_support_check 的重复 else-if 分支(ethernet.c:1208-1215):两个分支体相同,疑似漏写。

魔法数字

编码/遗留

并发与竞态