shuichan_old/V1.5/STM32F103_App1/HardWare/NETWORK/net_wifi_ota.c

277 lines
10 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* net_wifi_ota.c - WiFi 通道 OTA 固件下载
*
* ESP-01S 是单连接透传CIPMUX=0OTA 期间必须拆掉 MQTT 连接:
* 退透传("+++") → 连固件服务器 → 透传发 HTTP GET → 流式接收固件体
* → 每 3072B 写 W25Q64 槽 B + 回读校验 + 累计 CRC16
* → 整包 CRC + 固件头校验 → 重连 MQTT 上报结果 → 写 OTA 信息区 → 复位进 BL
*
* 参考工程《4路继电器控制带WIFI和4G V3.4 WIFI无回弹》的透传整包下载思路
* 但复用本工程 W25Q64 双槽 + CRC + 向量校验 + BootLoader 回退体系(比参考工程
* 写内部 Flash APP2 的方案可靠得多)。吞吐靠 TCP 流控自适应MCU 消费慢时
* 接收窗口收缩,服务器自然降速,不会丢字节。
*/
#include "net_wifi_ota.h"
#include "net_wifi.h"
#include "esp8266.h"
#include "network.h"
#include "air780e.h" /* bg_delay / U2_CopyBuff / Verify_Firmware_External / crc16_modbus_update */
#include "ota_app.h" /* Application_Size */
#include "uart.h"
#include "relay.h"
#include "feed_scale.h"
#include "led.h"
#include "log.h"
#include "reset_log.h"
#include "../W25Q64/w25q64.h"
#include "stdio.h"
#include "string.h"
#include <stdlib.h>
#define WIFI_OTA_CHUNK 3072u /* 与 4G OTA 相同的分块粒度 */
#define WIFI_OTA_HDR_TIMEOUT 15000u /* 等 HTTP 响应头超时 */
#define WIFI_OTA_IDLE_TIMEOUT 20000u /* 数据流静默超时TCP 流控可能主动降速) */
static uint8_t s_blk[WIFI_OTA_CHUNK]; /* 分块暂存写flash/校验/CRC 都按块处理 */
/* 二进制安全子串查找 */
static uint8_t *ota_memfind(uint8_t *hay, uint16_t hay_len, const char *needle)
{
uint16_t nlen = strlen(needle);
if (hay_len < nlen) return NULL;
for (uint16_t i = 0; i <= hay_len - nlen; i++) {
if (memcmp(hay + i, needle, nlen) == 0) return hay + i;
}
return NULL;
}
/* 大小写不敏感查找Content-Length 头各服务器大小写不一) */
static uint8_t *ota_memfind_i(uint8_t *hay, uint16_t hay_len, const char *needle)
{
uint16_t nlen = strlen(needle);
if (hay_len < nlen) return NULL;
for (uint16_t i = 0; i <= hay_len - nlen; i++) {
uint16_t j;
for (j = 0; j < nlen; j++) {
char a = hay[i + j], b = needle[j];
if (a >= 'A' && a <= 'Z') a += 32;
if (b >= 'A' && b <= 'Z') b += 32;
if (a != b) break;
}
if (j == nlen) return hay + i;
}
return NULL;
}
/* 消费接收缓冲前 n 字节 */
static void ota_consume(uint16_t n)
{
if (n >= U2_CopyIndex) {
U2_CopyIndex = 0;
} else {
memmove(U2_CopyBuff, U2_CopyBuff + n, U2_CopyIndex - n);
U2_CopyIndex -= n;
}
U2_CopyBuff[U2_CopyIndex] = '\0';
}
/*==== HTTP Range 分块下载固件到 W25Q64 槽 B成功返回 0 且 *out_size=固件大小 ====
* 每块 3072B 一个 GETRange: bytes=off-endMCU 自定节奏:
* 服务器一次只发一块,不存在大流量灌爆接收缓冲的问题(实测整包下载时
* 服务器 6 秒灌完 57KBMCU 写 flash 消费不动,缓冲溢出丢字节)。 */
static int wifi_ota_download(const char *host, int port, const char *path,
uint16_t crc16, uint32_t *out_size)
{
char req[224];
uint32_t offset = 0, total_size = 0;
uint16_t crc_acc = 0xFFFF;
int last_pct = -1;
/* 拆掉 MQTT 连接,换连固件服务器(同为透传模式) */
esp_exit_transparent();
if (esp_enter_transparent(host, (uint16_t)port) != 0) {
log_warn("> WiFi OTA: 连接固件服务器失败");
return -1;
}
while (1) {
/* 最后一块按总大小截断total_size 第一块响应头里才拿到) */
uint32_t want_end = offset + WIFI_OTA_CHUNK - 1;
if (total_size > 0 && want_end >= total_size) want_end = total_size - 1;
/* HTTP/1.1 默认 keep-alive同一 TCP 连接上顺序发多个 Range 请求 */
snprintf(req, sizeof(req),
"GET %s HTTP/1.1\r\nHost: %s:%d\r\nUser-Agent: esp8266-ota\r\nAccept: */*\r\nRange: bytes=%u-%u\r\n\r\n",
path, host, port, (unsigned)offset, (unsigned)want_end);
esp_rx_clear();
esp_send((uint8_t *)req, strlen(req));
/* 等响应头完整(\r\n\r\n 结束) */
uint32_t hdr_end = 0;
uint32_t tick = HAL_GetTick();
while (HAL_GetTick() - tick < WIFI_OTA_HDR_TIMEOUT) {
bg_delay(50);
uint8_t *p = ota_memfind(U2_CopyBuff, U2_CopyIndex, "\r\n\r\n");
if (p) { hdr_end = (uint32_t)(p - U2_CopyBuff) + 4; break; }
if (ota_memfind(U2_CopyBuff, U2_CopyIndex, "CLOSED")) {
log_warn("> WiFi OTA: 服务器关闭连接 @%u", (unsigned)offset);
return -1;
}
}
if (hdr_end == 0) {
log_warn("> WiFi OTA: 响应头超时 @%u", (unsigned)offset);
return -1;
}
/* 必须 206服务器支持 Range200 说明忽略了 Range整包灌流量消费不动放弃 */
if (strncmp((char *)U2_CopyBuff, "HTTP/", 5) != 0) {
log_warn("> WiFi OTA: 响应异常 @%u", (unsigned)offset);
return -1;
}
if (!ota_memfind(U2_CopyBuff, 16, " 206")) {
U2_CopyBuff[hdr_end < 120 ? hdr_end : 120] = '\0';
log_warn("> WiFi OTA: 非 206 响应(服务器不支持 Range?: %.60s", U2_CopyBuff);
return -1;
}
/* 第一块:从 Content-Range: bytes 0-x/TOTAL 拿总大小,然后擦槽 B */
if (total_size == 0) {
uint8_t *cr = ota_memfind_i(U2_CopyBuff, hdr_end, "content-range:");
uint8_t *slash = cr ? (uint8_t *)strchr((char *)cr, '/') : NULL;
if (slash) total_size = (uint32_t)atoi((char *)slash + 1);
if (total_size == 0 || total_size > Application_Size) {
log_warn("> WiFi OTA: 固件总大小异常 %u", (unsigned)total_size);
return -1;
}
log_info("**************************************");
log_info("* WiFi OTA 升级开始 *");
log_info("* 固件大小: %u 字节 *", (unsigned)total_size);
log_info("**************************************");
log_info("> 正在擦除 W25Q64 新槽位...");
W25Q64_EraseRegion(W25Q64_SLOT_NEW_ADDR, W25Q64_SLOT_SIZE);
log_info("> W25Q64 新槽位擦除完成");
}
/* 本块长度 = Content-Length≤3072最后一块可能更小 */
uint32_t clen = 0;
{
uint8_t *cl = ota_memfind_i(U2_CopyBuff, hdr_end, "content-length:");
if (cl) clen = (uint32_t)atoi((char *)cl + 15);
}
if (clen == 0 || clen > WIFI_OTA_CHUNK) {
log_warn("> WiFi OTA: 块长度异常 %u @%u", (unsigned)clen, (unsigned)offset);
return -1;
}
ota_consume((uint16_t)hdr_end);
/* 精确收 clen 字节固件体到暂存块 */
uint16_t got = 0;
tick = HAL_GetTick();
while (got < clen) {
if (U2_CopyIndex > 0) {
tick = HAL_GetTick();
uint16_t n = U2_CopyIndex;
if (n > (uint16_t)(clen - got)) n = (uint16_t)(clen - got);
memcpy(s_blk + got, U2_CopyBuff, n);
got += n;
ota_consume(n);
} else {
if (HAL_GetTick() - tick > WIFI_OTA_IDLE_TIMEOUT) {
log_warn("> WiFi OTA: 数据接收超时 @%u 已收 %u/%u",
(unsigned)offset, got, (unsigned)clen);
log_warn("> OTA 诊断: CNDTR=%u RxState=%u rxcplt=%u ore=%u pending=%u",
(unsigned)huart4.hdmarx->Instance->CNDTR,
(unsigned)huart4.RxState,
(unsigned)g_uart4_rxcplt_cnt,
(unsigned)g_uart4_ore_cnt,
(unsigned)U2_CopyIndex);
return -1;
}
bg_delay(20);
}
}
/* 写 W25Q64 槽 B + 回读校验 + 累计 CRC */
W25Q64_WriteBuffer(W25Q64_SLOT_NEW_ADDR + offset, s_blk, clen);
if (!W25Q64_Verify(W25Q64_SLOT_NEW_ADDR + offset, s_blk, clen)) {
log_error("> WiFi OTA: W25Q64 回读校验失败 offset=%u", (unsigned)offset);
return -1;
}
for (uint16_t i = 0; i < clen; i++) {
crc_acc = crc16_modbus_update(crc_acc, s_blk[i]);
}
offset += clen;
int pct = (int)(offset * 100 / total_size);
if (pct > 100) pct = 100;
if (pct / 10 > last_pct / 10) {
last_pct = pct;
log_info("> [%3d%%] %u/%uB", pct, (unsigned)offset, (unsigned)total_size);
LED2_TOGGLE;
}
if (offset >= total_size) break;
}
/* 整包 CRC16 校验 */
log_info("> W25Q64 CRC16=0x%04X (服务器=0x%04X)", crc_acc, crc16);
if (crc16 != 0 && crc_acc != crc16) {
log_error("> WiFi OTA: CRC16 校验不匹配!中止下载");
return -1;
}
/* 固件头合法性校验(栈顶/复位向量) */
if (!Verify_Firmware_External(W25Q64_SLOT_NEW_ADDR, total_size)) {
log_error("> WiFi OTA: 固件头校验无效");
return -1;
}
*out_size = total_size;
log_info("> WiFi OTA: 下载完成,%uB 校验通过", (unsigned)total_size);
return 0;
}
/*==== WiFi OTA 入口:与 air780e_trigger_ota 对齐的语义 ====
* 成功:上报 result=1 后写信息区直接复位进 BootLoader
* 失败:上报 result=0恢复 MQTT 链路继续正常业务 */
void net_wifi_ota(const char *host, int port, const char *path,
const char *ota_id, uint16_t new_ver, uint16_t crc16)
{
uint32_t file_size = 0;
log_info("> WiFi OTA: 开始");
feedScaleStop(); /* 称重模式下停止投喂状态机 */
relaySet(1, 0); /* 无秤模式下直接断开投喂继电器 */
/* MQTT 还在线先上报开始progress 0再屏蔽业务发布 */
NET_mqtt_report("iot.ota.progress.post", ota_id, 0);
bg_delay(500);
g_net_tx_busy = 1;
g_wifi_mqtt_ready = 0; /* MQTT 链路即将被 OTA 连接顶替 */
int ret = wifi_ota_download(host, port, path, crc16, &file_size);
/* 重连 MQTT走完整 WiFi 建链复位模块→连AP→透传→CONNECT/SUB上报结果 */
if (net_wifi_init() == 0) {
g_net_tx_busy = 0;
NET_mqtt_report("iot.ota.result.post", ota_id, ret == 0 ? 1 : 0);
bg_delay(2000); /* 等结果消息发完 */
} else {
g_net_tx_busy = 0;
log_warn("> WiFi OTA: MQTT 重连失败,结果上报丢失");
}
if (ret == 0) {
log_info("**************************************");
log_info("* WiFi OTA 下载完成! *");
log_info("* 正在重启进入 BootLoader... *");
log_info("**************************************");
/* 只把固件信息写入 W25Q64版本号在 APP 确认成功后再写入 EEPROM */
W25Q64_OTA_SetNewInfo(new_ver, file_size, crc16, ota_id);
reset_log_mark(RSN_OTA_UPDATE);
bg_delay(100);
NVIC_SystemReset();
}
log_warn("> WiFi OTA: 已中止,恢复正常运行");
}