shuichan_old/V1.1/STM32F103_App1/HardWare/CAR/air780e.c

758 lines
30 KiB
C
Raw Normal View History

#include "air780e.h"
#include "ota_app.h"
#include "relay.h"
#include "led.h"
#include "network.h"
#include "uart.h"
#include "main.h"
#include "system.h"
#include "24c02.h"
#include "proto.h"
#include "feed_scale.h"
#include "app_loop.h"
#include "../W25Q64/w25q64.h"
#include "stdio.h"
#include "string.h"
#include <stdarg.h>
/* AT指令发送: 格式化后通过USART3发出, 末尾自动加\r\n */
void CAT1_printf(const char *fmt, ...)
{
static uint32_t last_tx_tick = 0;
char buf[384];
int n;
va_list ap;
/* 任意两条 AT 指令之间至少间隔 30ms保证 4G 模块不丢包 */
while (HAL_GetTick() - last_tx_tick < 30) {
HAL_Delay(1);
}
va_start(ap, fmt);
n = vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
HAL_UART_Transmit(&huart3, (uint8_t *)buf, strlen(buf), 500);
HAL_UART_Transmit(&huart3, (uint8_t *)"\r\n", 2, 500);
last_tx_tick = HAL_GetTick();
if (n >= (int)sizeof(buf)) {
log_warn("> CAT1: tx truncated! need %d bytes", n);
}
/* 任意 MPUB 发送成功即认为首次发布成功,作为 OTA 业务健康标志之一 */
if (g_air780e_mqtt_ready && !g_air780e_first_publish_ok && strstr(buf, "AT+MPUB")) {
g_air780e_first_publish_ok = 1;
log_info("> 4G: first publish ok");
}
}
uint8_t U2_CopyBuff[U2_COPY_SIZE];
volatile uint16_t U2_CopyIndex = 0;
volatile uint8_t U2_CopyFlag = 0;
volatile uint8_t g_air780e_mqtt_ready = 0;
volatile uint8_t g_air780e_first_publish_ok = 0;
static MqttConfig mqtt_cfg;
/*==== 外部 Flash 校验:读取固件头 8 字节判断合法性 ====*/
static int Verify_Firmware_External(uint32_t ext_addr, uint32_t size)
{
uint8_t hdr[8];
W25Q64_Read(ext_addr, hdr, 8);
uint32_t sp = (uint32_t)hdr[0] | ((uint32_t)hdr[1] << 8) | ((uint32_t)hdr[2] << 16) | ((uint32_t)hdr[3] << 24);
uint32_t rv = (uint32_t)hdr[4] | ((uint32_t)hdr[5] << 8) | ((uint32_t)hdr[6] << 16) | ((uint32_t)hdr[7] << 24);
if ((sp & 0x2FFE0000ul) != 0x20000000ul) { log_info("> VF ext: SP=0x%X", (unsigned)sp); return 0; }
if (rv < Application_1_Addr || rv > (Application_1_Addr + size)) { log_info("> VF ext: RV=0x%X", (unsigned)rv); return 0; }
return 1;
}
/*==== 4G Reset Control ====*/
/*==== 4G Reset Control ====*/
static void cat_reset(void) {
log_info("> 4G: Reset...");
/* 关闭 NVIC 中断, 防止模块复位期间乱码触发中断风暴 */
HAL_NVIC_DisableIRQ(USART3_IRQn);
HAL_NVIC_DisableIRQ(DMA1_Channel3_IRQn);
HAL_UART_DMAStop(&huart3);
/* 确保 DMA 通道关闭, 清除标志 */
DMA1_Channel3->CCR &= ~DMA_CCR_EN;
while (DMA1_Channel3->CCR & DMA_CCR_EN);
DMA1->IFCR = DMA_IFCR_CGIF3;
__HAL_UART_CLEAR_IDLEFLAG(&huart3);
__HAL_UART_CLEAR_OREFLAG(&huart3);
__HAL_UART_CLEAR_PEFLAG(&huart3);
__HAL_UART_CLEAR_FEFLAG(&huart3);
__HAL_UART_CLEAR_NEFLAG(&huart3);
U2_CopyIndex = 0;
U2_CopyBuff[0] = 0;
U2_CopyFlag = 0;
GPIO_InitTypeDef g = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
g.Pin = CAT_RST_PIN; g.Mode = GPIO_MODE_OUTPUT_PP; g.Pull = GPIO_NOPULL; g.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(CAT_RST_PORT, &g);
CAT_RST_HIGH(); HAL_Delay(1000); CAT_RST_LOW();
for (int i = 0; i < 40; i++) { IWDG_Feed(); HAL_Delay(100); } /* 等4s期间喂狗 */
/* 模块复位完成后重新启动 DMA+IDLE 接收 */
U2_CopyIndex = 0;
U2_CopyBuff[0] = 0;
U2_CopyFlag = 0;
/* 清可能存在的 UART 错误/溢出标志 */
__HAL_UART_CLEAR_PEFLAG(&huart3);
__HAL_UART_CLEAR_FEFLAG(&huart3);
__HAL_UART_CLEAR_NEFLAG(&huart3);
__HAL_UART_CLEAR_OREFLAG(&huart3);
__HAL_UART_CLEAR_IDLEFLAG(&huart3);
HAL_UART_Receive_DMA(&huart3, Rx_Buf, Rx_Max);
__HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE);
HAL_NVIC_EnableIRQ(DMA1_Channel3_IRQn);
HAL_NVIC_EnableIRQ(USART3_IRQn);
log_info("> 4G: Reset done");
}
/* OTA 前直接硬件复位 4G 模块, 与初始化时一致 */
static void ota_module_reset(void) {
cat_reset();
}
/*==== AT Command: 带重试, 参考代码风格 ====*/
/* 切片等待: 每 20ms 让出一次后台任务(蓝牙命令/秤轮询/LED/电量),
* AT */
static void bg_delay(uint32_t ms)
{
while (ms >= 20) {
HAL_Delay(20);
app_bg_service();
ms -= 20;
}
if (ms) HAL_Delay(ms);
}
/* cmd: AT指令, expect: 期望应答关键字, retry: 重试次数, timeout: 超时(×100ms) */
/* 返回 0=成功, 非0=失败 */
uint8_t catSendCmd(const char *cmd, const char *expect, uint8_t retry, uint8_t timeout) {
uint8_t result = 1;
while (retry > 0 && result != 0) {
/* 只复位已消费的部分, 不擦除整个20KB缓冲区 */
U2_CopyIndex = 0;
U2_CopyBuff[0] = 0;
U2_CopyFlag = 0;
CAT1_printf("%s", cmd);
uint8_t t = timeout;
while (--t) {
bg_delay(100);
if (U2_CopyFlag) {
U2_CopyFlag = 0;
if (strstr((char *)U2_CopyBuff, expect)) {
result = 0;
break;
}
}
}
if (t == 0) {
log_warn("> AT t/o [r%u]: %s", retry, cmd);
}
retry--;
}
return result;
}
/* 兼容旧接口: 单次发送, timeout 毫秒 (不会自动追加重试) */
int air780e_send_at(const char *cmd, const char *expect, int timeout_ms) {
return catSendCmd(cmd, expect, 1, (uint8_t)((timeout_ms + 99) / 100));
}
/* 等待模块网络附着: 轮询 AT+CGATT?, 直到 +CGATT: 1 或 PDN 激活 */
static int cat_wait_cgatt_attached(int max_wait_s) {
int waited = 0;
while (waited < max_wait_s * 10) {
U2_CopyIndex = 0;
U2_CopyBuff[0] = 0;
U2_CopyFlag = 0;
CAT1_printf("AT+CGATT?");
uint8_t t = 30; /* 单次查询等 3 秒 */
while (--t) {
bg_delay(100);
if (U2_CopyFlag) {
U2_CopyFlag = 0;
if (strstr((char *)U2_CopyBuff, "+CGATT: 1")) {
log_info("> 4G: CGATT attached");
return 0;
}
if (strstr((char *)U2_CopyBuff, "+CGEV: ME PDN ACT 1")) {
log_info("> 4G: PDN activated");
return 0;
}
}
}
waited += 30; /* 每次轮询约 3 秒 */
log_info("> 4G: CGATT not attached, retry %d/%d", waited / 10, max_wait_s);
}
log_warn("> 4G: CGATT attach timeout");
return -1;
}
/*==== Network ====*/
/* 4G 模块目标波特率:模块出厂默认 115200AT+IPR 配置后掉电保存 */
#define CAT_BAUD_TARGET 921600u
#define CAT_BAUD_DEFAULT 115200u
static int cat_connect_network(void) {
log_info("> 4G: Net...");
/* 波特率同步:永远先按 115200 探测(出厂/自适应模式的模块不能被
921600
921600 */
USART3_SetBaudRate(CAT_BAUD_DEFAULT);
if (catSendCmd("AT", "OK", 1, 5) == 0) {
if (catSendCmd("AT+IPR=921600", "OK", 1, 30) != 0) return -1;
USART3_SetBaudRate(CAT_BAUD_TARGET);
bg_delay(100);
catSendCmd("AT&W", "OK", 1, 20); /* 保存配置,模块复位后保持 921600 */
if (catSendCmd("AT", "OK", 3, 20) != 0) return -1;
log_info("> 4G: baud -> 921600");
} else {
USART3_SetBaudRate(CAT_BAUD_TARGET);
if (catSendCmd("AT", "OK", 3, 20) != 0) return -1;
}
if (catSendCmd("AT", "OK", 3, 20) != 0) return -1;
if (catSendCmd("ATE0", "OK", 1, 10) != 0) return -1;
/* 检测 SIM 卡:多次查询 AT+CPIN?,识别无 SIM 卡场景 */
int sim_retry = 3;
while (sim_retry-- > 0) {
U2_CopyIndex = 0; U2_CopyBuff[0] = 0; U2_CopyFlag = 0;
CAT1_printf("AT+CPIN?");
uint8_t t = 30;
while (--t) {
bg_delay(100);
if (U2_CopyFlag) {
U2_CopyFlag = 0;
if (strstr((char *)U2_CopyBuff, "READY")) {
break;
}
if (strstr((char *)U2_CopyBuff, "NO SIM") ||
strstr((char *)U2_CopyBuff, "NOSIM") ||
strstr((char *)U2_CopyBuff, "SIM NOT INSERTED") ||
strstr((char *)U2_CopyBuff, "+CME ERROR: 10")) {
log_warn("> 4G: no SIM card detected");
g_no_sim_detected = 1;
return -2;
}
}
}
if (strstr((char *)U2_CopyBuff, "READY")) break;
HAL_Delay(1000);
}
if (!strstr((char *)U2_CopyBuff, "READY")) {
log_warn("> 4G: SIM not ready");
return -1;
}
if (cat_wait_cgatt_attached(30) != 0) return -1;
if (catSendCmd("AT+SAPBR=3,1,\"CONTYPE\",\"GPRS\"", "OK", 1, 20) != 0) return -1;
if (catSendCmd("AT+SAPBR=3,1,\"APN\",\"\"", "OK", 1, 20) != 0) return -1;
if (catSendCmd("AT+SAPBR=1,1", "OK", 3, 150) != 0) return -1;
log_info("> 4G: Net OK"); return 0;
}
/*==== MQTT Connect ====*/
static int cat_connect_mqtt(void) {
char b[120]; log_info("> 4G: MQTT...");
/* 复用 catSendCmd 等待 MCONNECT 的真正连接确认, 不清空已有缓冲区 */
sprintf(b, "AT+MCONFIG=\"%s\",\"%s\",\"%s\",0,0,0,0", mqtt_cfg.ClientID, mqtt_cfg.Username, mqtt_cfg.Passward);
if (catSendCmd(b, "OK", 1, 15) != 0) return -1;
/* 等待 TCP 建立: "CONNECT OK" 或 "OK" */
sprintf(b, "AT+MIPSTART=\"%s\",%d", mqtt_cfg.ServerIP, mqtt_cfg.ServerPort);
if (catSendCmd(b, "OK", 1, 60) != 0) return -1;
/* 参考代码也仅等待 OK, 所以兼容处理: 发完后再等一下 CONNECT OK */
if (!strstr((char *)U2_CopyBuff, "CONNECT OK")) {
int tcp_t = 30; /* 3s */
U2_CopyFlag = 0;
while (--tcp_t >= 0) {
bg_delay(100);
if (U2_CopyFlag) {
U2_CopyFlag = 0;
if (strstr((char *)U2_CopyBuff, "CONNECT OK") ||
strstr((char *)U2_CopyBuff, "+MIPCALLBACK")) break;
}
}
if (tcp_t < 0) {
log_warn("> 4G: TCP connect t/o, last[%d]: %.80s", U2_CopyIndex, U2_CopyBuff);
}
}
/* 建立 MQTT 连接 */
if (catSendCmd("AT+MCONNECT=1,60", "OK", 1, 60) != 0) return -1;
/* 等待 MQTT 连接完成Air780E 返回 CONNACK OK 表示成功 */
if (!strstr((char *)U2_CopyBuff, "CONNACK OK") && !strstr((char *)U2_CopyBuff, "CONNECT OK")) {
int mqtt_t = 30; /* 3s */
U2_CopyFlag = 0;
while (--mqtt_t >= 0) {
bg_delay(100);
if (U2_CopyFlag) {
U2_CopyFlag = 0;
if (strstr((char *)U2_CopyBuff, "CONNACK OK") ||
strstr((char *)U2_CopyBuff, "CONNECT OK") ||
strstr((char *)U2_CopyBuff, "+MCONNECTED")) break;
}
}
if (mqtt_t < 0) {
log_warn("> 4G: MQTT connect t/o, last[%d]: %.80s", U2_CopyIndex, U2_CopyBuff);
return -1;
}
}
sprintf(b, "AT+MSUB=\"%s%s\",0", mqtt_cfg.Topic, mqtt_cfg.ClientID);
if (catSendCmd(b, "SUBACK", 1, 50) != 0) {
/* 如果没等到 SUBACK 但等到了 OK, 也算订阅命令已接受 */
if (strstr((char *)U2_CopyBuff, "OK")) {
log_info("> 4G: MSUB accepted (OK)");
} else {
log_warn("> 4G: MSUB fail, buff[%d]: %.80s", U2_CopyIndex, U2_CopyBuff);
return -1;
}
}
log_info("> 4G: MQTT OK"); return 0;
}
/*==== OTA: 版本管理 ====*/
/* 版本号存于外置 EEPROM (MqttInfoStr.Ver), 断电保持 */
uint16_t ota_get_version(void) {
return MqttInfoStr.Ver;
}
static int ota_ver_save(uint16_t ver) {
MqttInfo_Str tmp;
int retry = 3;
while (retry--) {
MqttInfoStr.Ver = ver;
uint8_t err = eepromWriteData(0, &MqttInfoStr, MQTT_STRUCT_LEN);
if (err == 0) {
/* 写入后立刻回读验证 */
memset(&tmp, 0, MQTT_STRUCT_LEN);
eepromReadData(0, &tmp, MQTT_STRUCT_LEN);
if (tmp.Ver == ver) {
log_info("> OTA ver saved & verified: %u", ver);
return 0;
}
log_warn("> OTA ver verify mismatch: wrote=%u, read=%u", ver, tmp.Ver);
}
HAL_Delay(20);
}
log_warn("> OTA ver save FAILED: %u", ver);
return -1;
}
/*==== MQTT Publish: OTA进度/结果上报 ====*/
void air780e_mqtt_report(const char *header, const char *ota_id, int val) {
if (!header || !ota_id || !ota_id[0]) return;
static char cmd[300];
if (strstr(header, "progress")) {
/* 进度上报:允许丢包,发了就走 */
sprintf(cmd, "AT+MPUB=\"/iot/data/up/%s\",0,0,\"{\\22header\\22:\\22%s\\22,\\22body\\22:{\\22id\\22:\\22%.16s\\22,\\22progress\\22:%d}}\"",
MqttInfoStr.ClientID, header, ota_id, val);
CAT1_printf("%s", cmd);
} else {
/* 结果上报:平台据此判定升级完成,必须可靠送达——等 OK 并重试 */
sprintf(cmd, "AT+MPUB=\"/iot/data/up/%s\",0,0,\"{\\22header\\22:\\22%s\\22,\\22body\\22:{\\22id\\22:\\22%.16s\\22,\\22success\\22:%s}}\"",
MqttInfoStr.ClientID, header, ota_id, val ? "true" : "false");
if (catSendCmd(cmd, "OK", 3, 30) != 0) {
log_warn("> OTA result report FAILED (no OK after 3 retries)");
} else {
log_info("> OTA result reported: success=%d", val);
}
}
}
/*==== MQTT Dispatch (with dedup) ====*/
/* 处理 U2_CopyBuff 中的第一条 MQTT 消息, 如果有下一条则移动到缓冲区开头 */
static void dispatch_one_mqtt_msg(void) {
char *d = (char *)U2_CopyBuff;
char *sub = strstr(d, "+MSUB:");
char *next = NULL;
int has_next = 0;
if (!sub && !strstr(d, "CLOSED") && !strstr(d, "+NO Service") && !strstr(d, "boot.rom")) {
U2_CopyIndex = 0;
U2_CopyBuff[0] = 0;
return;
}
/* 找下一条 +MSUB */
if (sub) {
char *p = sub + 6;
next = strstr(p, "+MSUB:");
if (next) has_next = 1;
}
/* 临时截断只处理第一条 */
char saved = 0;
if (next) {
saved = *next;
*next = '\0';
}
log_info("> MQTT: %.200s", d);
if (strstr(d, "+NO Service")) {
if (next) *next = saved;
log_error("> 4G exception: NO Service, reset");
HAL_Delay(1000);
NVIC_SystemReset();
}
if (strstr(d, "CLOSED")) {
if (next) *next = saved;
log_error("> 4G exception: MQTT/TCP CLOSED, reset");
HAL_Delay(1000);
NVIC_SystemReset();
}
if (strstr(d, "boot.rom")) {
if (next) *next = saved;
log_error("> 4G exception: module reboot (boot.rom), reset");
HAL_Delay(1000);
NVIC_SystemReset();
}
/* dedup by message id */
static char last_id[20] = {0};
char cur_id[20] = {0};
proto_get_id((char*)d, cur_id, sizeof(cur_id));
if (cur_id[0] && strcmp(cur_id, last_id) == 0) {
log_info("> MQTT: dup skip, id=%.16s", cur_id);
} else {
strncpy(last_id, cur_id, sizeof(last_id)-1);
/* relay control: 执行后再消费缓冲区, 让 relayAction 里的 AT 发送完成 */
if (strstr(d, "iot.prop.set")) { relay_action((uint8_t *)d); }
/* OTA upgrade */
if (strstr(d, "iot.ota.upgrade.post")) {
log_info("> OTA cmd!");
char host[30]={0}, path[80]={0}, ota_id[20]={0}; int port = 81;
uint16_t new_ver = 0;
uint16_t crc16 = 0;
/* 解析版本号与 crc16 */
{ uint32_t v = 0; if (proto_get_u32((char*)d, "ver", &v)) new_ver = (uint16_t)v; }
proto_get_hex16((char*)d, "crc16", &crc16);
log_info("> OTA ver: cur=%u, new=%u, crc16=0x%04X", MqttInfoStr.Ver, new_ver, crc16);
/* 版本号 ≤ 当前版本 → 跳过下载 (防止重复或降级) */
if (new_ver > 0 && new_ver <= MqttInfoStr.Ver) {
proto_get_id((char*)d, ota_id, sizeof(ota_id));
log_info("> OTA: same or lower ver, skip (cur=%u, new=%u)", MqttInfoStr.Ver, new_ver);
air780e_mqtt_report("iot.ota.progress.post", ota_id, 10);
} else {
strncpy(host, mqtt_cfg.ServerIP, sizeof(host)-1); strcpy(path, "/firmware.bin");
proto_get_id((char*)d, ota_id, sizeof(ota_id));
{ char fu[100]={0};
if (proto_get_str((char*)d, "url", fu, sizeof(fu))) {
log_info("> OTA url: %s", fu); char *p=fu;
if(strncmp(p,"http://",7)==0)p+=7;
char *sl=strchr(p,'/'),*co=strchr(p,':');
if(co&&(!sl||co<sl)){ int hl=co-p; memcpy(host,p,hl); host[hl]=0;
co++; port=0; while(*co>='0'&&*co<='9'){port=port*10+(*co-'0');co++;}
if(sl)strncpy(path,sl,sizeof(path)-1); }
else if(sl){ int hl=sl-p; memcpy(host,p,hl); host[hl]=0; strncpy(path,sl,sizeof(path)-1); }
else strncpy(host,p,sizeof(host)-1);
}}
log_info("> OTA: %s:%d%s", host, port, path);
air780e_trigger_ota(host, port, path, ota_id, new_ver, crc16);
}
}
}
/* 恢复截断 */
if (next) *next = saved;
/* 移动剩余内容到缓冲区开头 */
if (has_next && next) {
int remain = U2_CopyIndex - (int)(next - d);
if (remain > 0) {
memmove(U2_CopyBuff, next, remain);
U2_CopyIndex = remain;
U2_CopyBuff[U2_CopyIndex] = '\0';
U2_CopyFlag = 1; /* 还有消息待处理 */
} else {
U2_CopyIndex = 0;
U2_CopyBuff[0] = 0;
}
} else {
U2_CopyIndex = 0;
U2_CopyBuff[0] = 0;
}
}
/*==== MODBUS CRC16 ====*/
static uint16_t crc16_modbus_update(uint16_t crc, uint8_t data) {
crc ^= data;
for (int i = 0; i < 8; i++) {
if (crc & 1) crc = (crc >> 1) ^ 0xA001;
else crc >>= 1;
}
return crc;
}
/*==== OTA Download via HTTP Range ====*/
static void air780e_trigger_ota_body(const char *host, int port, const char *path, const char *ota_id, uint16_t new_ver, uint16_t crc16);
/* OTA 入口: 停止投喂 + 屏蔽业务网络发布, 然后执行下载。
* body ; */
void air780e_trigger_ota(const char *host, int port, const char *path, const char *ota_id, uint16_t new_ver, uint16_t crc16) {
log_info("> OTA: in");
feedScaleStop(); /* 称重模式下停止投喂状态机 */
relaySet(1, 0); /* 无秤模式下直接断开投喂继电器 */
g_net_tx_busy = 1; /* 屏蔽业务侧 NET_publish_*, 防止与 AT 应答交织 */
air780e_trigger_ota_body(host, port, path, ota_id, new_ver, crc16);
g_net_tx_busy = 0;
log_warn("> OTA: aborted, resume normal operation");
}
static void air780e_trigger_ota_body(const char *host, int port, const char *path, const char *ota_id, uint16_t new_ver, uint16_t crc16) {
ota_module_reset(); int r; log_info("> OTA: Net...");
for (r=5;r>0;r--){if(air780e_send_at("AT","OK",3000)==0)break;log_info("> AT %d",r);bg_delay(1000);}
if(r==0)return;
bg_delay(500); /* 模块刚回OK, 稍等再发下一条 */
log_info("> OTA: 2nd AT test");
if(air780e_send_at("AT","OK",5000)!=0){log_warn("> 2nd AT fail");return;}
log_info("> OTA: 2nd AT ok");
if(air780e_send_at("ATE0","OK",5000)!=0)return;
for(r=3;r>0;r--){if(air780e_send_at("AT+CPIN?","READY",5000)==0)break;log_info("> SIM %d",r);}
if(r==0)return;
if(air780e_send_at("AT+CSQ","OK",3000)!=0)return;
log_info("> SAPBR...");
if(air780e_send_at("AT+SAPBR=3,1,\"CONTYPE\",\"GPRS\"","OK",3000)!=0){log_info("> S1");return;}
if(air780e_send_at("AT+SAPBR=3,1,\"APN\",\"\"","OK",3000)!=0){log_info("> S2");return;}
if(air780e_send_at("AT+SAPBR=1,1","OK",10000)!=0){log_info("> S3");return;}
if(air780e_send_at("AT+SAPBR=2,1","OK",3000)!=0){log_info("> S4");return;}
/* MQTT connect for OTA progress reporting逐步检查连接失败要能看到 */
{ static char b[120]; sprintf(b,"AT+MCONFIG=\"%s\",\"%s\",\"%s\",0,0,0,0",mqtt_cfg.ClientID,mqtt_cfg.Username,mqtt_cfg.Passward);
if(air780e_send_at(b,"OK",2000)!=0)log_warn("> OTA: MCONFIG fail");
sprintf(b,"AT+MIPSTART=\"%s\",%d",mqtt_cfg.ServerIP,mqtt_cfg.ServerPort);
if(air780e_send_at(b,"OK",10000)!=0)log_warn("> OTA: MIPSTART fail");
if(air780e_send_at("AT+MCONNECT=1,60","OK",10000)!=0)log_warn("> OTA: MCONNECT fail");
log_info("> OTA: MQTT ready"); }
static char buf[250], fu[150]; sprintf(fu,"http://%s:%d%s",host,port,path);
log_info("> HTTP: %s",fu);
if(air780e_send_at("AT+HTTPINIT","OK",5000)!=0){log_info("> INIT");return;}
sprintf(buf,"AT+HTTPPARA=\"URL\",\"%s\"",fu);
if(air780e_send_at(buf,"OK",5000)!=0){log_info("> URL");air780e_send_at("AT+HTTPTERM","OK",2000);return;}
log_info("> HEAD...");
if(air780e_send_at("AT+HTTPACTION=2","+HTTPACTION:",10000)!=0){log_info("> HEAD");air780e_send_at("AT+HTTPTERM","OK",2000);return;}
int file_size=0;
{
memset(U2_CopyBuff,0,U2_COPY_SIZE);U2_CopyIndex=0;U2_CopyFlag=0;
HAL_UART_Transmit(&huart3,(uint8_t*)"AT+HTTPHEAD\r\n",14,500);
int t=2000;while(t>0){bg_delay(50);t-=50;if(strstr((char*)U2_CopyBuff,"OK"))break;}
char *cl=strstr((char*)U2_CopyBuff,"Content-Length:");if(!cl)cl=strstr((char*)U2_CopyBuff,"content-length:");
if(cl){cl=strchr(cl,':');if(cl){cl++;while(*cl==' '||*cl=='\t')cl++;while(*cl>='0'&&*cl<='9'){file_size=file_size*10+(*cl-'0');cl++;}}}
}
log_info("> Size=%dB",file_size);
log_info("**************************************");
log_info("* OTA Update Start *");
log_info("* Size: %d bytes *",file_size);
log_info("**************************************");
log_info("> Erasing W25Q64 new slot...");
W25Q64_EraseRegion(W25Q64_SLOT_NEW_ADDR, W25Q64_SLOT_SIZE);
log_info("> W25Q64 new slot erased");
/* Air780EG 手册HTTPREAD 单段最大 3072 字节 */
#define OTA_DL_CHUNK 3072
int total=0, offset=0, last_pct = -1;
uint16_t crc_acc = 0xFFFF;
air780e_mqtt_report("iot.ota.progress.post", ota_id, 0);
log_info("[Download] Start >>>");
while(1){
log_info("> DL offset=%d", offset);
sprintf(buf,"AT+HTTPPARA=\"BREAK\",%d",offset);
if(air780e_send_at(buf,"OK",3000)!=0){log_info("> BRK fail");break;}
sprintf(buf,"AT+HTTPPARA=\"BREAKEND\",%d",offset+OTA_DL_CHUNK-1);
if(air780e_send_at(buf,"OK",3000)!=0){log_info("> BRKEND fail");break;}
if(air780e_send_at("AT+HTTPACTION=0","+HTTPACTION:",5000)!=0){log_info("> ACT @%d",offset);break;}
int start = U2_CopyIndex;
HAL_UART_Transmit(&huart3, (uint8_t *)"AT+HTTPREAD\r\n", 14, 500);
/* 按 "+HTTPREAD: <len>" 头声明的长度接收:收够 len 字节立即处理,
500ms 0.5s */
int dlen=0, body=-1;
{ uint32_t t0=HAL_GetTick();
while(HAL_GetTick()-t0 < 10000){
if(body<0){
for(int k=start;k<(int)U2_CopyIndex-11;k++){
if(U2_CopyBuff[k]=='+'&&U2_CopyBuff[k+1]=='H'&&U2_CopyBuff[k+2]=='T'&&U2_CopyBuff[k+3]=='T'&&U2_CopyBuff[k+4]=='P'&&
U2_CopyBuff[k+5]=='R'&&U2_CopyBuff[k+6]=='E'&&U2_CopyBuff[k+7]=='A'&&U2_CopyBuff[k+8]=='D'&&U2_CopyBuff[k+9]==':'){
int j=k+10, d=0;
while(j<(int)U2_CopyIndex&&(U2_CopyBuff[j]==' '||U2_CopyBuff[j]=='\t'))j++;
while(j<(int)U2_CopyIndex&&U2_CopyBuff[j]>='0'&&U2_CopyBuff[j]<='9'){d=d*10+(U2_CopyBuff[j]-'0');j++;}
while(j<(int)U2_CopyIndex&&U2_CopyBuff[j]!='\n')j++;
if(j<(int)U2_CopyIndex){dlen=d;body=j+1;} /* 头部完整才生效,否则继续等 */
break;
}
}
}else if((int)U2_CopyIndex-body>=dlen){
break; /* 数据收齐,立即处理 */
}
bg_delay(20);
}
}
if(dlen<=0||body<0){log_info("> No data @%d",offset);break;}
/* 校验实际接收长度是否足够,防止 DMA 缓冲区溢出导致数据丢失 */
if ((int)U2_CopyIndex - body < dlen) {
log_warn("> DL chunk @%d incomplete: received %d/%d bytes, retry...", offset, U2_CopyIndex - body, dlen);
air780e_send_at("AT+HTTPTERM","OK",3000);
air780e_mqtt_report("iot.ota.result.post", ota_id, 0);
return;
}
W25Q64_WriteBuffer(W25Q64_SLOT_NEW_ADDR + offset, &U2_CopyBuff[body], dlen);
if (!W25Q64_Verify(W25Q64_SLOT_NEW_ADDR + offset, &U2_CopyBuff[body], dlen)) {
log_error("> W25Q64 verify fail at offset=%d, abort OTA", offset);
air780e_send_at("AT+HTTPTERM","OK",3000);
air780e_mqtt_report("iot.ota.result.post", ota_id, 0);
return;
}
for (int i = 0; i < dlen; i++) crc_acc = crc16_modbus_update(crc_acc, U2_CopyBuff[body + i]);
offset+=dlen;total+=dlen;
if(file_size>0){int pct=total*100/file_size;if(pct>100)pct=100;
log_info("> [%3d%%] %d/%dB",pct,total,file_size);
if(pct/10 > last_pct/10){last_pct = pct; air780e_mqtt_report("iot.ota.progress.post", ota_id, pct); LED2_TOGGLE;}
}
else log_info("> +%dB=%dB",dlen,total);
if(dlen<OTA_DL_CHUNK||(file_size>0&&total>=file_size)||offset>=Application_Size)break;
}
air780e_send_at("AT+HTTPTERM","OK",3000);
if(total==0){log_info("> Nothing");air780e_mqtt_report("iot.ota.result.post", ota_id, 0);return;}
if(file_size<=0)file_size=total;
/* 下载完成后用 MODBUS CRC16 校验整个外部固件 */
log_info("> W25Q64 CRC16=0x%04X (server=0x%04X)", crc_acc, crc16);
if (crc16 != 0 && crc_acc != crc16) {
log_error("> W25Q64 CRC16 mismatch! abort OTA");
air780e_mqtt_report("iot.ota.result.post", ota_id, 0);
return;
}
log_info(">>> Verify %dB...", file_size);
if(!Verify_Firmware_External(W25Q64_SLOT_NEW_ADDR, file_size)){
log_info("[FAIL]");air780e_mqtt_report("iot.ota.result.post", ota_id, 0);return;
}
log_info("[ OK ] Firmware OK!");
log_info("**************************************");
log_info("* OTA Download Complete! *");
log_info("* Rebooting to BootLoader... *");
log_info("**************************************");
air780e_mqtt_report("iot.ota.result.post", ota_id, 1);
bg_delay(3000); /* 等待 MQTT 结果消息发送完成后再复位 */
/* 只把固件信息写入 W25Q64版本号在 APP 确认成功后再写入 EEPROM */
W25Q64_OTA_SetNewInfo(new_ver, file_size, crc16);
bg_delay(100); NVIC_SystemReset();
}
/* 固件更新后由 APP 调用,确认运行稳定后写入 EEPROM 版本号 */
void air780e_ota_confirm(void)
{
W25Q64_OtaInfo info;
if (W25Q64_OTA_ReadInfo(&info) != 0) return;
if (info.update_state != W25Q64_STATE_INSTALLED) return;
if (info.app_confirmed) return;
/* 先向服务器上报“确认成功” */
char id_buf[24] = {0};
snprintf(id_buf, sizeof(id_buf), "%u", info.new_version); /* 用版本号作为 id 上报 */
air780e_mqtt_report("iot.ota.confirm.post", id_buf, 1);
if (info.new_version > 0) ota_ver_save(info.new_version);
/* 确认标志必须可靠落盘,否则 BL 会一直认为未确认;
3 */
int ok = 0;
for (int i = 0; i < 3 && !ok; i++) {
W25Q64_OTA_SetConfirmed();
W25Q64_OtaInfo chk;
if (W25Q64_OTA_ReadInfo(&chk) == 0 && chk.app_confirmed) ok = 1;
else bg_delay(50);
}
if (ok) log_info("> OTA confirm flag written");
else log_error("> OTA confirm write FAILED after 3 tries");
}
/*==== Init ====*/
void air780e_init(void) {
log_info("> 4G: Init...");
memset(&mqtt_cfg,0,sizeof(mqtt_cfg));
strncpy(mqtt_cfg.ClientID,MQTT_CLIENT_ID,sizeof(mqtt_cfg.ClientID)-1);
strncpy(mqtt_cfg.Username,MQTT_USERNAME,sizeof(mqtt_cfg.Username)-1);
strncpy(mqtt_cfg.Passward,MQTT_PASSWORD,sizeof(mqtt_cfg.Passward)-1);
strncpy(mqtt_cfg.ServerIP,MQTT_SERVER_IP,sizeof(mqtt_cfg.ServerIP)-1);
mqtt_cfg.ServerPort=MQTT_SERVER_PORT;
strncpy(mqtt_cfg.Topic,MQTT_TOPIC,sizeof(mqtt_cfg.Topic)-1);
int retry = 3;
while (retry-- > 0) {
cat_reset();
int net_ret = cat_connect_network();
if (net_ret == -2) {
/* 无 SIM 卡,退出让上层切换到 WiFi */
log_warn("> 4G: no SIM, abort 4G init");
return;
}
if (net_ret != 0) {
log_warn("> 4G: network failed, retry %d", retry);
HAL_Delay(5000);
continue;
}
if (cat_connect_mqtt() == 0) {
g_air780e_mqtt_ready = 1;
log_info("> 4G: Ready");
HAL_Delay(50);
char pub[256];
sprintf(pub, "AT+MPUB=\"/iot/data/up/%s\",0,0,\"{\\22header\\22:\\22iot.prop.post\\22,\\22body\\22:{\\22pow\\22:1}}\"", MqttInfoStr.ClientID);
CAT1_printf("%s", pub);
HAL_Delay(50);
return;
}
log_warn("> 4G: MQTT failed, retry %d", retry);
air780e_send_at("AT+MDISCONNECT", "OK", 2000);
air780e_send_at("AT+MIPSTOP", "OK", 2000);
HAL_Delay(3000);
}
log_error("> 4G: connect failed, no SIM=%d", g_no_sim_detected);
/* 若已判定无 SIM则不复位让上层切 WiFi否则保持原有复位行为 */
if (!g_no_sim_detected) {
HAL_Delay(100);
NVIC_SystemReset();
}
}
/*==== Process ====*/
void air780e_process(void){
/* 循环处理缓冲区中所有完整 MQTT 消息 */
while(U2_CopyFlag){
U2_CopyFlag = 0;
dispatch_one_mqtt_msg();
}
}