/* net_wifi.c - WiFi 后端(网络层下的通道实现之一) * * 组成:esp8266.c (ESP-01S AT驱动/TCP透传) + mqtt_client.c (软件MQTT 3.1.1)。 * 连接顺序:硬件复位 → 等已存AP自动连接(20s) → 连固定SSID(60s) → * Smart Config 智能配网(60s) → 失败返回,由 network.c 切回 4G。 * 运行阶段:mqttc_process 解析下行报文,30s 心跳保活, * 断链(CLOSED/心跳超时) 清 ready,由 network.c 调度重连或切回 4G。 */ #include "net_wifi.h" #include "esp8266.h" #include "mqtt_client.h" #include "network.h" #include "air780e.h" /* bg_delay */ #include "uart.h" #include "log.h" #include "reset_log.h" #include "stdio.h" #include "string.h" volatile uint8_t g_wifi_mqtt_ready = 0; volatile uint8_t g_wifi_first_publish_ok = 0; /* WiFi 模块恢复出厂请求标志:由蓝牙 WIFIRESET 命令(sys任务)置位, * net 任务在 net_wifi_process 里执行——所有 UART4 操作都收口在 net 任务, * 避免与 4G 切换/建链并发冲突 */ volatile uint8_t g_wifi_restore_req = 0; static uint32_t s_ping_tick = 0; /* 上行发布主题暂存 */ static char s_topic_up[48]; /*==== 阻塞建链:成功返回 0 并置 g_wifi_mqtt_ready ==== * 子顺序由 EEPROM 记忆的"上次成功联网方式"决定: * 上次是配网热点 → 先查已存 AP 自动连接(CIPSTATUS, 10s) * 然后统一:固定 SSID(60s 倒计时) → SmartConfig(60s 倒计时) * 成功后按实际使用的方式更新 EEPROM 记忆(NET_SaveMode) */ int net_wifi_init(void) { log_info("> WiFi: 初始化..."); g_wifi_mqtt_ready = 0; g_wifi_first_publish_ok = 0; esp_gpio_init(); UART4_StartRx(); if (esp_reset() != 0) return -1; if (esp_send_cmd("AT", "OK", 2000) != 0) return -1; esp_send_cmd("ATE0", "OK", 1000); /* 关回显,失败不致命 */ if (esp_send_cmd("AT+CWMODE=1", "OK", 2000) != 0) return -1; int joined = 0; uint8_t via_smart = 0; /* 1=本次用的是配网热点(已存AP或SmartConfig) */ /* 按键选定模式限制连接方式(V1.7.1 起): 配网模式只走已存AP+SmartConfig, 固定模式只连固定热点 */ uint8_t fmode = NET_GetForceMode(); /* 配网模式:模块里存着上次配网热点密码,优先查自动连接 */ if (fmode == NET_MODE_WIFI_SMART) { if (esp_saved_ap_connected(10) == 0) { joined = 1; via_smart = 1; } } /* 固定热点模式:连代码里固定/EEPROM 配置的 SSID/PASS */ if (!joined && fmode == NET_MODE_WIFI_FIXED && esp_join_fixed_ap() == 0) { joined = 1; via_smart = 0; } /* 配网模式:已存 AP 连不上则进 SmartConfig 智能配网(ESP-Touch APP 广播密码) */ if (!joined && fmode == NET_MODE_WIFI_SMART) { if (esp_smartconfig() == 0) { joined = 1; via_smart = 1; } else { log_warn("> WiFi: 所有 AP 连接方式均失败"); return -1; } } /* 已连上热点:打印信号强度(dBm),便于现场判断摆放位置 */ esp_log_rssi(); /* TCP 透传到 MQTT 服务器 */ if (esp_enter_transparent(MqttInfoStr.ServerIP, MqttInfoStr.ServerPort) != 0) return -1; /* 软件 MQTT 建链 + 订阅下行主题 */ mqttc_init(MqttInfoStr.ClientID, MqttInfoStr.Username, MqttInfoStr.Passward); if (mqttc_connect() != 0) return -1; { char topic_down[48]; snprintf(topic_down, sizeof(topic_down), "%s%s", MqttInfoStr.Topic, MqttInfoStr.ClientID); if (mqttc_subscribe(topic_down) != 0) return -1; snprintf(s_topic_up, sizeof(s_topic_up), "/iot/data/up/%s", MqttInfoStr.ClientID); } g_wifi_mqtt_ready = 1; s_ping_tick = HAL_GetTick(); log_info("> WiFi: Ready"); /* 更新联网方式记忆(仅变化时写 EEPROM) */ NET_SaveMode(via_smart ? NET_MODE_WIFI_SMART : NET_MODE_WIFI_FIXED); /* 就绪后上报一次电量,与 4G 就绪行为对齐(同时标记首次发布成功) */ NET_publish_power(1); return 0; } /*==== 非阻塞轮询:下行报文解析 + 心跳保活 ====*/ void net_wifi_process(void) { /* 恢复出厂请求(蓝牙 WIFIRESET):在 net 任务上下文执行, * 不与本任务的建链/收发并发 */ if (g_wifi_restore_req) { g_wifi_restore_req = 0; log_warn("> WiFi: 收到恢复出厂请求,执行 AT+RESTORE 并重启"); g_net_tx_busy = 1; g_wifi_mqtt_ready = 0; reset_log_mark(RSN_CMD_RECOVERY); if (esp_factory_restore() != 0) { log_warn("> WiFi: RESTORE 失败,仍强制重启"); } HAL_Delay(100); NVIC_SystemReset(); } if (!g_wifi_mqtt_ready) return; int r; while ((r = mqttc_process()) == 1) { } if (r == -1) { /* 收到 CLOSED:服务器主动断开 TCP(被平台踢下线),直接复位重连 */ log_error("> WiFi: 连接被服务器关闭(被平台踢下线),系统复位"); reset_log_mark(RSN_KICKED); HAL_Delay(100); NVIC_SystemReset(); } /* 心跳阶梯(参考 V3.0 工程 TIM6 设计): * 正常 30s 一次 PINGREQ;未收到 PINGRESP 立即升级 3s 快探测(容忍单包丢失); * 连续 5 次无响应(约 42s)判链路死亡,分两级原地恢复,不整机复位: * 第一级:原 TCP 上重发 CONNECT(路由/服务器抖动,1~2s 恢复) * 第二级:完整重建 WiFi 链路(ESP 断电重启+重连热点+透传+MQTT) */ static uint8_t ping_miss = 0; uint32_t interval = (ping_miss == 0) ? 30000u : 3000u; if (HAL_GetTick() - s_ping_tick >= interval) { if (g_mqttc_ping_outstanding) { ping_miss++; log_warn("> WiFi: PINGRESP 超时 (%d/5)", ping_miss); if (ping_miss >= 5) { ping_miss = 0; log_error("> WiFi: 心跳超时(服务器无响应),原地重连 MQTT"); if (mqttc_connect() == 0) { char topic_down[48]; snprintf(topic_down, sizeof(topic_down), "%s%s", MqttInfoStr.Topic, MqttInfoStr.ClientID); if (mqttc_subscribe(topic_down) == 0) { log_info("> WiFi: MQTT 原地重连成功"); s_ping_tick = HAL_GetTick(); mqttc_ping(); return; } } log_warn("> WiFi: 原地重连失败,重建链路"); g_wifi_mqtt_ready = 0; if (net_wifi_init() == 0) { log_info("> WiFi: 链路重建成功"); } else { log_warn("> WiFi: 链路重建失败,交由状态机重试/切 4G"); } } } else { if (ping_miss) log_info("> WiFi: 心跳已恢复"); ping_miss = 0; } s_ping_tick = HAL_GetTick(); mqttc_ping(); } } /*==== 上行发布 ====*/ /* QoS1 发布(仅平台指令应答用): 带报文标识符, 调用方自行等 PUBACK */ int net_wifi_publish_qos1(const char *json, uint16_t pkt_id) { if (!g_wifi_mqtt_ready) return -1; return mqttc_publish_qos1(s_topic_up, json, pkt_id); } void net_wifi_publish_up(const char *json) { if (!g_wifi_mqtt_ready) return; if (mqttc_publish(s_topic_up, json) == 0 && !g_wifi_first_publish_ok) { g_wifi_first_publish_ok = 1; log_info("> WiFi: 首次发布成功"); } }