ethernet.c
ems_server_event_pro,ethernet.c:665-738)4.2 DHCP 流程(dhcp_function → dhcp_pro_fun,ethernet.c:306-408)4.3 DNS 解析与 8 个备选服务器轮换(dns_pro_fun,ethernet.c:417-537)4.4 报文发送:有线路径(ems_server_data_comm,ethernet.c:550-630)4.5 报文发送:无线路径(wireless_event_pro,ethernet.c:747-881)4.6 手动指定域名/端口:三级优先级(get_ethernet_server_port,ethernet.c:1658-1691)4.7 无线域名下发格式(wireless_get_ethernet_server_point,ethernet.c:1736-1745)4.8 网络自愈体系4.9 net_abnormal_detect 判定表(ethernet.c:1234-1529)4.10 非固定指向与临时指向管理(server_skip_support_check,ethernet.c:1151-1226)4.11 W5500 初始化时序(W5500_Init,ethernet.c:251-297)5. 对外接口6. 依赖关系7. 备注与风险点源文件路径:
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_RUNNING/DHCP_IP_ASSIGN/DHCP_IP_CHANGED:空分支,依赖注册的回调my_ip_assign完成网参更新;DHCP_IP_LEASED:置g_stNetSysInfo.u8DHCPOk = TRUE;DHCP_FAILED:my_dhcp_retry累加,超过MY_MAX_DHCP_RETRY(3) 后DHCP_stop()并network_init()回退到静态网参;DHCP_STOPPED:空。
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()。
候选顺序(注释给出用途):
case 0:g_stNetInfo.dns[](网关/DHCP 下发)case 1:8.8.8.8 Google Public DNScase 2:1.1.1.1 CloudFlarecase 3:9.9.9.9 Quad9case 4:185.222.222.222 DNS.SBcase 5:223.5.5.5 阿里公共 DNScase 6:119.29.29.29 DNSPodcase 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)
err != OS_ERR_NONE时按错误类型分派:OS_ERR_EVENT_TYPE/OS_ERR_PEVENT_NULL使g_stEMSInfo.u16OSQDealFailCnt++(队列处理失败计数,后续用于判系统复位),其它错误清零;- 正常取到消息后:
msg->u8lock = TRUE上锁(防同一条报文被重入发送); - 刷新
g_stNetSysInfo.u16_skip_port = get_ethernet_server_port(W5500, tls支持标志); - 私有加密:当
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 字节; - 发送循环:外层
for (i = 0; i < 1000; i++),内层最多 3 次send();send()返回正值则前移指针、扣减剩余长度并break内层;剩余长度为 0 或send()返回负值则break外层。即"最多 1000 段 × 每段最多 3 次重试"的累积发送; - 解锁
msg->u8lock = FALSE并把msg->u8Type = STATUS_NONE标记为已处理。
4.5 报文发送:无线路径(wireless_event_pro,ethernet.c:747-881)
与有线不同的三点:
- 前置守卫:
g_t_wireless_info.u8_wireless_uart_flag == TRUE时直接return——WIFI 串口是独占资源,P1 抄表、AT 状态查询、数据发送三者互斥。原代码还有一段"距上次发送不足 30ms 则 return"的节流,已被注释掉(ethernet.c:762-767),改为在wireless_driver.c与elink_parse.c里做 30ms/100ms 节流。 - 通道就绪判据:
(u8tcpstatus == TRUE) || (g_t_app_info.u8_app_connect_flag == TRUE),即"WIFI 棒已连上云端"或"APP 已直连"任一成立即可发。就绪时置g_stEMSInfo.bIsBuiltSocket = TRUE,否则置 FALSE。 - 5KB 分片:
l_u32_loop_cnt = msg->u32Len / WIRELESS_SEND_SIZE、l_u32_remainder = msg->u32Len % WIRELESS_SEND_SIZE;先循环发loop_cnt个满片,每片最多重试 3 次、每次发送后刷新u32_WIFI_uart_tick并IWDG_Feed()喂狗;若有余数再发一次余数片。任一片 3 次全失败即return(注意此时u8lock仍为 TRUE、u8Type未清、u8_wireless_uart_flag仍为 TRUE,见风险点)。
加密/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 超时)取消息,然后:
- 无条件调
wireless_receive_event()保证 APP 查询帧能及时响应; - 通道选择:
g_t_app_info.u8_app_connect_flag == TRUE或(u8linkstatus == TRUE且有线 socket 未 OK)时走wireless_event_pro——即 APP 直连优先于有线,有线上线后无线发送会被跳过; - 读物理链路
ctlwizchip(CW_GET_PHYLINK, &l_u8linkstatus),PHY_LINK_ON时按 DHCP→DNS→TCP 顺序推进,并每次都调ems_to_modbus_tcp();同时置bHardLink事件位; - 链路断开时:若上一轮是 ON 则置
g_stEMSInfo.b_modbus_Socket = FALSE与u8W5500Init = TRUE(触发 W5500 重新初始化);清bIsLogin/bIsBuiltSocket; - 每轮写
g_stNetSysInfo.u32EthTaskTick = OSTimeGet()作为任务存活心跳; - 最后
handle_net_abnormal()。
(2) handle_net_abnormal 执行复位请求(ethernet.c:912-977)
u8W5500Init == TRUE:清 DHCP/DNS/Socket 标志、重置三个 tick,若b_modbus_Socket == FALSE(modbus TCP 未占用)则W5500_Init();wireless_query_restart_flag() == TRUE:wireless_init();u8EMSInit == TRUE:清 DHCP/DNS/Socket/登录/建链标志,socket_abnormal_handle(),再按b_modbus_Socket决定W5500_Init()还是只disconnect(SOCK_TCP_CLIENT),最后wireless_init();- 末尾无条件
W5500_Power_ON()(网络初始化后不再断电)。
(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 == FALSE | SENCOND_300S | u8DHCPRstCnt++、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_30M | u8W5500Init = TRUE、刷新 u32DNSTick |
| DNS 成功 / tick 回卷 | — | 刷新 u32DNSTick |
| DNS 成功但 socket 未 OK | SENCOND_300S | u8W5500Init = TRUE、刷新 u32SOCKTick |
| Socket OK / tick 回卷 | — | 刷新 u32SOCKTick |
| 链路 OFF 且无线未上线,且链路从 ON→OFF | SENCOND_10S | u8EMSInit = TRUE,置 s_u8LinkRstFlag = TRUE 防止重复触发 |
| 链路 OFF 且有线无线皆未连 | SENCOND_3H | u8EMSInit = 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_180S | u8EMSInit = TRUE、u16HeartRstCnt++、置心跳失败事件位;达 HEART_RST_FAIL(3) 且无线类型 NONE → s_u8ResetFlag = TRUE + 置心跳复位事件位 |
bIsLogin == FALSE 且长时间无交互 | SENCOND_30M | 若电表全丢则置无线重启标志 + u8EMSInit = TRUE |
u16OSQDealFailCnt >= 50 | 50 | s_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)
三段逻辑:
- 临时指向倒计时:
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()。 - 非固定指向功能验证:若当前选中的指向(
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 分钟即认为验证通过"。 - 变更落地:
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. 对外接口
导出的函数与全局变量
导出的全局变量(供其它模块直接引用):
OS_EVENT *g_pEthernetQ、OS_EVENT *g_pUpdateSem(在ethernet.h有 extern 声明)STEthernetData g_stNetDataSend、STEthernetSys g_stNetSysInfo、IP_ADDR_TAB_T IP_Address、wiz_NetInfo g_stNetInfo、u8 g_u8linkstatus- 仅本文件可见(无 extern):
g_pEthernetMsg、g_u8DHCPBuf、g_u8DNSBuf、netinfo、g_stGetNetToutInfo、g_stSetNetToutInfo
导出的函数:
- 任务入口(
user/main.c创建):task_ethernet、task_ethernet_monitor - 头文件已声明:
net_init、internet_time_handle、get_ethernet_server_point、get_ethernet_server_port、get_server_data_encryption_flag、wireless_get_ethernet_server_point、ip_add_skip_change_clear - 未在头文件声明但非 static(隐式外部可见,属接口泄漏):
w5500_spi_configuration、Net_Para_Init、network_init、my_ip_assign、W5500_Init、dhcp_pro_fun、dhcp_function、dns_pro_fun、ems_server_data_comm、lan_receive_event、ems_server_event_pro、wireless_event_pro、wireless_receive_event、handle_net_abnormal、wireless_abnormal_detect、net_abnormal_detect
调用的外部函数/变量
| 来源模块 | 被调用内容 |
|---|---|
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.h | g_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/modbus | ems_to_modbus_tcp、get_flag_modbus_force_update、set_flag_modbus_force_update |
midware/ethernt/wireless | wireless_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/meter | wireless_meter_is_all_lost |
net/ems_download.h | IsSystemDownload、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)。
被依赖:
user/main.c:266-275创建task_ethernet与task_ethernet_monitor任务net/server.c通过g_pEthernetQ投递待发报文(g_stNetDataSend作为描述符),并调用handle_tcp_recv_data的接收侧net/ems.h:15包含ethernet.h,几乎所有净层文件间接依赖midware/ethernt/wireless/wifi/e_linker_wifi.c:17直接包含ethernet.h,调用get_ethernet_server_port、get_ethernet_server_point、get_server_data_encryption_flag、wireless_get_ethernet_server_point、ip_add_skip_change_clearmidware/auxiliary/system_aux.c、midware/meter/meter.c引用网络状态
7. 备注与风险点
真实缺陷 / 隐患(按严重程度排序)
- 无线发送失败后状态机不一致(
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无论成败都会解锁。 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,但该判空完全失效。dns_pro_fun的 DNS 旁路分支(ethernet.c:521-531):else分支(u8DNSOk == TRUE时)会把Domain_IP写成192.168.8.7。若u8DNSOk曾短暂为真而域名变更未重置该标志,会把服务器指向改成内网地址。同函数if((TRUE) && (...))的恒真判断也是可疑写法。static u16 s_u16_last_port类 static 状态无初始化保护:ems_server_event_pro的 static 变量初值为 0,首次进SOCK_CLOSED时s_u16_now_port != 0通常成立,但如果 W5500 恰好分配端口 0 则会误判走close()分支——概率极低。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)都会产生这些副作用,阅读代码时易被误导。trySkip中wantTLS自赋值(ethernet.c:1636):wantTLS = wantTLS && (!IP_Address.u8_server_temp_switchover);位于for循环体内,第一轮之后wantTLS可能已被改写。逻辑上由于右侧条件不变,结果稳定,但把"循环不变量"写在循环体内是易错写法。remote分片发送缺少响应确认:wireless_event_pro只判断wireless_send的返回值(串口是否发出),并不等待服务器 ACK,5KB 分片之间也无协议层确认,丢片只能靠上层超时重传。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 标志。ip_add_skip_change_clear的#if/else空分支(ethernet.c:1568-1572):EMS_18031_DEBUG == 1时该#if块为空,s_skip_addr不会被清零(调试期行为差异),量产路径正常。server_skip_support_check的重复 else-if 分支(ethernet.c:1208-1215):两个分支体相同,疑似漏写。
魔法数字
EMS_18031_DEBUG下的阈值SENCOND_60S;u8_logInCnt阈值 18(有宏LOGIN_NO_ACK_NUM);u16OSQDealFailCnt >= 50(无宏,且net/ems.h:61定义了OSQ_DEAL_FAIL (3)却未被使用);s_u8_timeout_count <= 15(P1 超时计数);retry_cnt = 8/time_100us = 2000(TCP 超时);SO_KEEPALIVEAUTO = 6;memsize {{2,2,8,2,0,0,0,0},...}中的 2/8(KB);发送重试3次与1000轮;l_u8KeepAliveCnt = 6。SENCOND_30M的宏定义为1000 * 1800(1800 秒 = 30 分钟),命名与数值的关系依赖读者自行换算,语义上正确但易被误读为 30 秒。
编码/遗留
- 第 279 行注释
// ×¢²á¶Áдº¯Êý是 GBK 中文"注册读写函数"被以 UTF-8 保存后的乱码,属源文件编码污染残留。 - 第 763-767 行的 30ms 发送节流代码被整段注释保留。
w5500_spi_configuration、net_init等 16 个函数未在ethernet.h声明却非 static,形成隐式外部接口,缺乏编译期检查(改签名不会报错)。
并发与竞态
wireless_event_pro(task_ethernet)与wireless_read_P1_data(meter.c:5879,另一个任务上下文)通过g_t_wireless_info.u8_wireless_uart_flag争夺 WIFI 串口:两者都是"读-改-写"非原子操作,且都无锁保护。若两个任务同时通过标志检查,AT 指令与云数据会在串口上交错。wireless_read_P1_data中还有一段if(...)/{}空语句块和if分支直接 fallthrough 到公共释放点(wireless_driver.c:495-498),逻辑上依赖后续g_t_wireless_info.u8_wireless_uart_flag = FALSE统一释放。g_stNetSysInfo被task_ethernet、task_ethernet_monitor、e_linker_wifi.c(由 monitor 任务调用)共享读写,无临界区保护。