From 95853083c6c1edbd9183b1b5d8263ae3e7b2b773 Mon Sep 17 00:00:00 2001 From: helei Date: Sun, 9 Aug 2026 10:50:11 +0800 Subject: [PATCH] =?UTF-8?q?V1.4-Chinese=EF=BC=9ASmartConfig=E6=97=B6NET=5F?= =?UTF-8?q?LED=E5=BF=AB=E9=97=AA=E6=8F=90=E7=A4=BA=EF=BC=9Bserial=5Fcaptur?= =?UTF-8?q?e.py=E4=B8=8A=E7=A7=BB=E5=88=B0=E7=89=88=E6=9C=AC=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=A4=B9=E5=A4=96=E5=85=B1=E7=94=A8=EF=BC=8C=E6=B8=85?= =?UTF-8?q?=E7=90=86=E6=97=A5=E5=BF=97=E4=B8=B4=E6=97=B6=E6=96=87=E4=BB=B6?= =?UTF-8?q?=EF=BC=9BREADME=E5=8A=A0=E6=8C=87=E7=A4=BA=E7=81=AF=E5=90=AB?= =?UTF-8?q?=E4=B9=89=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- V1.2/serial_capture.py | 30 - V1.3/serial_capture.py | 30 - .../STM32F103_App1/HardWare/LED/led.c | 12 +- .../STM32F103_App1/HardWare/LED/led.h | 2 + .../STM32F103_App1/HardWare/WIFI/esp8266.c | 10 +- .../STM32F103_App1/HardWare/WIFI/esp8266.h | 2 +- V1.4-Chinese/STM32F103_App1/README.md | 11 +- V1.4-Chinese/serial_capture.py | 30 - V1.4/serial_capture.py | 30 - _air780e_baseline.c | 893 ------------------ V1.1/serial_capture.py => serial_capture.py | 0 11 files changed, 31 insertions(+), 1019 deletions(-) delete mode 100644 V1.2/serial_capture.py delete mode 100644 V1.3/serial_capture.py delete mode 100644 V1.4-Chinese/serial_capture.py delete mode 100644 V1.4/serial_capture.py delete mode 100644 _air780e_baseline.c rename V1.1/serial_capture.py => serial_capture.py (100%) diff --git a/V1.2/serial_capture.py b/V1.2/serial_capture.py deleted file mode 100644 index 724de41..0000000 --- a/V1.2/serial_capture.py +++ /dev/null @@ -1,30 +0,0 @@ -import serial, sys, time - -port = sys.argv[1] if len(sys.argv) > 1 else "COM8" -baud = int(sys.argv[2]) if len(sys.argv) > 2 else 115200 -outfile = sys.argv[3] if len(sys.argv) > 3 else "serial_capture.log" - -while True: - try: - ser = serial.Serial(port, baud, timeout=1) - print(f"opened {port} @ {baud}", flush=True) - break - except Exception as e: - print(f"open failed: {e}, retry in 2s", flush=True) - time.sleep(2) - -with open(outfile, "ab", buffering=0) as f: - while True: - try: - data = ser.read(4096) - if data: - f.write(data) - except serial.SerialException as e: - print(f"read error: {e}, reopening...", flush=True) - time.sleep(2) - while True: - try: - ser = serial.Serial(port, baud, timeout=1) - break - except Exception: - time.sleep(2) diff --git a/V1.3/serial_capture.py b/V1.3/serial_capture.py deleted file mode 100644 index 724de41..0000000 --- a/V1.3/serial_capture.py +++ /dev/null @@ -1,30 +0,0 @@ -import serial, sys, time - -port = sys.argv[1] if len(sys.argv) > 1 else "COM8" -baud = int(sys.argv[2]) if len(sys.argv) > 2 else 115200 -outfile = sys.argv[3] if len(sys.argv) > 3 else "serial_capture.log" - -while True: - try: - ser = serial.Serial(port, baud, timeout=1) - print(f"opened {port} @ {baud}", flush=True) - break - except Exception as e: - print(f"open failed: {e}, retry in 2s", flush=True) - time.sleep(2) - -with open(outfile, "ab", buffering=0) as f: - while True: - try: - data = ser.read(4096) - if data: - f.write(data) - except serial.SerialException as e: - print(f"read error: {e}, reopening...", flush=True) - time.sleep(2) - while True: - try: - ser = serial.Serial(port, baud, timeout=1) - break - except Exception: - time.sleep(2) diff --git a/V1.4-Chinese/STM32F103_App1/HardWare/LED/led.c b/V1.4-Chinese/STM32F103_App1/HardWare/LED/led.c index d7253cb..a331ffd 100644 --- a/V1.4-Chinese/STM32F103_App1/HardWare/LED/led.c +++ b/V1.4-Chinese/STM32F103_App1/HardWare/LED/led.c @@ -5,6 +5,8 @@ static uint32_t net_led_tick = 0; /* 联网状态标志:0=未联网,1=联网成功 */ volatile uint8_t g_net_led_connected = 0; +/* SmartConfig 配网中标志:1=NET_LED 100ms 快闪 */ +volatile uint8_t g_net_led_smartconfig = 0; void ledInit(void) { @@ -41,8 +43,14 @@ void activeEvents(void) LED2_TOGGLE; } - /* 联网指示灯控制:未联网时每 500ms 闪烁,联网成功后常亮 */ - if (!g_net_led_connected) { + /* 联网指示灯控制:SmartConfig 配网中 100ms 快闪; + * 未联网时每 500ms 闪烁,联网成功后常亮 */ + if (g_net_led_smartconfig) { + if (HAL_GetTick() - net_led_tick >= 100) { + net_led_tick = HAL_GetTick(); + NET_LED_TOGGLE; + } + } else if (!g_net_led_connected) { if (HAL_GetTick() - net_led_tick >= 500) { net_led_tick = HAL_GetTick(); NET_LED_TOGGLE; diff --git a/V1.4-Chinese/STM32F103_App1/HardWare/LED/led.h b/V1.4-Chinese/STM32F103_App1/HardWare/LED/led.h index c73c3dd..06d1146 100644 --- a/V1.4-Chinese/STM32F103_App1/HardWare/LED/led.h +++ b/V1.4-Chinese/STM32F103_App1/HardWare/LED/led.h @@ -26,6 +26,8 @@ /* 联网状态标志:0=未联网,1=联网成功 */ extern volatile uint8_t g_net_led_connected; +/* SmartConfig 配网中标志:1=NET_LED 100ms 快闪提示配网模式 */ +extern volatile uint8_t g_net_led_smartconfig; void ledInit(void); void activeEvents(void); diff --git a/V1.4-Chinese/STM32F103_App1/HardWare/WIFI/esp8266.c b/V1.4-Chinese/STM32F103_App1/HardWare/WIFI/esp8266.c index 8f4ddfd..5c5a519 100644 --- a/V1.4-Chinese/STM32F103_App1/HardWare/WIFI/esp8266.c +++ b/V1.4-Chinese/STM32F103_App1/HardWare/WIFI/esp8266.c @@ -12,6 +12,7 @@ #include "air780e.h" /* U2_CopyBuff / bg_delay */ #include "uart.h" #include "log.h" +#include "led.h" #include "system.h" #include "stdio.h" #include "string.h" @@ -250,7 +251,9 @@ int esp_join_fixed_ap(void) ESP_printf("%s", cmd); esp_rx_clear(); if (esp_wait_countdown("WIFI GOT IP", WIFI_JOIN_TIMEOUT_S) != 0) { - log_warn("> WiFi: 固定热点连接超时"); + /* 提前结束 = 模块回了 FAIL/ERROR(密码错误等);数满倒计时 = 真超时(热点不在) */ + log_warn("> WiFi: 固定热点连接失败或超时"); + esp_dump_rx("CWJAP"); return -1; } log_info("> WiFi: 固定热点已连接"); @@ -263,17 +266,20 @@ int esp_join_fixed_ap(void) int esp_smartconfig(void) { log_info("> WiFi: SmartConfig 开始 (%ds), 请用 ESP-Touch APP", WIFI_SMART_TIMEOUT_S); + g_net_led_smartconfig = 1; /* NET_LED 100ms 快闪提示配网模式 */ - if (esp_send_cmd("AT+CWSTARTSMART", "OK", 5000) != 0) return -1; + if (esp_send_cmd("AT+CWSTARTSMART", "OK", 5000) != 0) { g_net_led_smartconfig = 0; return -1; } esp_rx_clear(); if (esp_wait_countdown("connected", WIFI_SMART_TIMEOUT_S) != 0) { log_warn("> WiFi: SmartConfig 超时"); esp_send_cmd("AT+CWSTOPSMART", "OK", 2000); + g_net_led_smartconfig = 0; return -1; } log_info("> WiFi: SmartConfig 已获取热点"); esp_send_cmd("AT+CWSTOPSMART", "OK", 2000); + g_net_led_smartconfig = 0; /* 配网成功后模块自动连接热点。主动查 CIPSTATUS(STATUS:2=已拿到IP), * 不能等 "WIFI GOT IP" 打印——它可能在 CWSTOPSMART 交互期间被清缓冲擦掉 */ diff --git a/V1.4-Chinese/STM32F103_App1/HardWare/WIFI/esp8266.h b/V1.4-Chinese/STM32F103_App1/HardWare/WIFI/esp8266.h index 26abd95..51979ea 100644 --- a/V1.4-Chinese/STM32F103_App1/HardWare/WIFI/esp8266.h +++ b/V1.4-Chinese/STM32F103_App1/HardWare/WIFI/esp8266.h @@ -15,7 +15,7 @@ /* 超时配置 */ #define WIFI_JOIN_TIMEOUT_S 40 /* 固定 WiFi 连接超时(秒) */ -#define WIFI_SMART_TIMEOUT_S 60 /* Smart Config 配网超时(秒) */ +#define WIFI_SMART_TIMEOUT_S 100 /* Smart Config 配网超时(秒) */ #define WIFI_TCP_TIMEOUT_MS 10000 /* TCP 连接服务器超时 */ /* 波特率配置:模块出厂/RESTORE 后为 115200, diff --git a/V1.4-Chinese/STM32F103_App1/README.md b/V1.4-Chinese/STM32F103_App1/README.md index 665c201..879262d 100644 --- a/V1.4-Chinese/STM32F103_App1/README.md +++ b/V1.4-Chinese/STM32F103_App1/README.md @@ -59,9 +59,18 @@ | SPI(软件) | W25Q64 外部 Flash | PB3/PB4/PA15/PB5 | OTA 固件暂存 | | ADC1 | 电池电压检测 | PA0 | 低电量上报 `pow:0` | | PA8 | 手动模式检测 | PA8 | 高电平=手动模式 | -| PA1 | NET_LED 联网指示 | PA1 | 低电平点亮:连接中闪烁,连上常亮 | +| PA1 | NET_LED 联网指示 | PA1 | 低电平点亮,见下表 | | PC13 | LED2 心跳 / TPL5010 喂狗 | PC13 | 每 1s 翻转(见下文看门狗) | +### 指示灯含义 + +| 灯 | 状态 | 含义 | +| --- | --- | --- | +| NET_LED(PA1) | 100ms 快闪 | SmartConfig 智能配网中,请用 ESP-Touch APP 广播密码 | +| NET_LED(PA1) | 500ms 慢闪 | 联网中(4G/WiFi 建链、重连) | +| NET_LED(PA1) | 常亮 | 已联网(MQTT 就绪) | +| LED2(PC13) | 1s 心跳闪烁 | 系统正常运行(兼任 TPL5010 外置看门狗喂狗) | + ## 看门狗(双狗架构) | 看门狗 | 超时 | 喂狗点 | 作用 | diff --git a/V1.4-Chinese/serial_capture.py b/V1.4-Chinese/serial_capture.py deleted file mode 100644 index 724de41..0000000 --- a/V1.4-Chinese/serial_capture.py +++ /dev/null @@ -1,30 +0,0 @@ -import serial, sys, time - -port = sys.argv[1] if len(sys.argv) > 1 else "COM8" -baud = int(sys.argv[2]) if len(sys.argv) > 2 else 115200 -outfile = sys.argv[3] if len(sys.argv) > 3 else "serial_capture.log" - -while True: - try: - ser = serial.Serial(port, baud, timeout=1) - print(f"opened {port} @ {baud}", flush=True) - break - except Exception as e: - print(f"open failed: {e}, retry in 2s", flush=True) - time.sleep(2) - -with open(outfile, "ab", buffering=0) as f: - while True: - try: - data = ser.read(4096) - if data: - f.write(data) - except serial.SerialException as e: - print(f"read error: {e}, reopening...", flush=True) - time.sleep(2) - while True: - try: - ser = serial.Serial(port, baud, timeout=1) - break - except Exception: - time.sleep(2) diff --git a/V1.4/serial_capture.py b/V1.4/serial_capture.py deleted file mode 100644 index 724de41..0000000 --- a/V1.4/serial_capture.py +++ /dev/null @@ -1,30 +0,0 @@ -import serial, sys, time - -port = sys.argv[1] if len(sys.argv) > 1 else "COM8" -baud = int(sys.argv[2]) if len(sys.argv) > 2 else 115200 -outfile = sys.argv[3] if len(sys.argv) > 3 else "serial_capture.log" - -while True: - try: - ser = serial.Serial(port, baud, timeout=1) - print(f"opened {port} @ {baud}", flush=True) - break - except Exception as e: - print(f"open failed: {e}, retry in 2s", flush=True) - time.sleep(2) - -with open(outfile, "ab", buffering=0) as f: - while True: - try: - data = ser.read(4096) - if data: - f.write(data) - except serial.SerialException as e: - print(f"read error: {e}, reopening...", flush=True) - time.sleep(2) - while True: - try: - ser = serial.Serial(port, baud, timeout=1) - break - except Exception: - time.sleep(2) diff --git a/_air780e_baseline.c b/_air780e_baseline.c deleted file mode 100644 index 618d5e1..0000000 --- a/_air780e_baseline.c +++ /dev/null @@ -1,893 +0,0 @@ -#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 "FreeRTOS.h" -#include "task.h" -#include "stdio.h" -#include "string.h" -#include -#include -#include - -/* 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; - -/* GNSS 能力标志:cat_connect_network 里 AT+CGNSPWR 成功才置 1(Air780EG 才有) */ -static uint8_t s_gnss_present = 0; - -/*==== 外部 Flash 校验:读取固件头 8 字节判断合法性 ==== - * 非 static:WiFi OTA(net_wifi_ota.c) 复用 */ -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: 带重试, 参考代码风格 ====*/ -/* 切片延时(RTOS 版): - * 调度器运行中 → vTaskDelay 让出 CPU,其他任务(秤/LED/喂狗)照常运行; - * 调度器未启动(开机初始化阶段)→ 退化为 HAL_Delay + 喂狗。 - * 非 static:WiFi 后端(esp8266.c)的 AT 等待同样复用该机制 */ -void bg_delay(uint32_t ms) -{ - if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) { - while (ms >= 20) { - vTaskDelay(20); - ms -= 20; - } - if (ms) vTaskDelay(ms); - } else { - while (ms >= 20) { - HAL_Delay(20); - IWDG_Feed(); - 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 模块目标波特率:模块出厂默认 115200,AT+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; - - /* 信号质量检测:AT+CSQ → +CSQ: rssi,ber(rssi 0~31,99=未知/无信号)。 - * 转成百分比输出一次;无信号时置标志返回 -3,让上层重试/回退 WiFi, - * 信号偏弱(<20%)只告警不阻断 */ - g_no_signal_detected = 0; - if (catSendCmd("AT+CSQ", "+CSQ:", 1, 15) == 0) { - char *p = strstr((char *)U2_CopyBuff, "+CSQ: "); - int rssi = p ? atoi(p + 6) : 99; - int pct = (rssi >= 0 && rssi <= 31) ? (rssi * 100 / 31) : 0; - log_info("> 4G: signal %d%% (rssi=%d)", pct, rssi); - if (rssi == 99 || rssi < 2) { - log_error("> 4G: no signal (%d%%)", pct); - g_no_signal_detected = 1; - return -3; - } - if (pct < 20) { - log_warn("> 4G: signal weak (%d%%)", pct); - } - } else { - log_warn("> 4G: CSQ query failed, continue anyway"); - } - - 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; - - /* GNSS 上电 + 辅助定位(Air780EG 内置北斗/GPS,需 PDP 激活后才能开辅助定位)。 - * 非 GNSS 版本模组(纯 Air780E)会回 ERROR:仅告警不阻断联网, - * 同时置 s_gnss_present=0 关闭后续定位轮询 */ - s_gnss_present = 0; - if (catSendCmd("AT+CGNSPWR=1", "OK", 1, 30) != 0) { - log_warn("> 4G: GNSS power on failed (module without GNSS?)"); - } else if (catSendCmd("AT+CGNSAID=31,1,1,1", "OK", 1, 30) != 0) { - log_warn("> 4G: GNSS aiding failed"); - } else { - s_gnss_present = 1; - log_info("> 4G: GNSS on"); - } - - 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(); - } - - /* 业务分发(id去重/继电器/OTA)已上提到网络层,与模组无关 */ - net_dispatch_message(d); - - /* 恢复截断 */ - 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:WiFi OTA(net_wifi_ota.c) 复用 */ -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 字节立即处理, - 不再等 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(dlen0&&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; - - /* 先向服务器上报“确认成功”(走网络层路由,4G/WiFi 通道均可) */ - char id_buf[24] = {0}; - snprintf(id_buf, sizeof(id_buf), "%u", info.new_version); /* 用版本号作为 id 上报 */ - NET_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 == -3) { - /* 无信号,退出让上层重试/切换 WiFi */ - log_warn("> 4G: no signal, 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, no signal=%d", g_no_sim_detected, g_no_signal_detected); - /* 若已判定无 SIM 或无信号,则不复位,让上层重试/切 WiFi;否则保持原有复位行为 */ - if (!g_no_sim_detected && !g_no_signal_detected) { - HAL_Delay(100); - NVIC_SystemReset(); - } -} - -/*==== GNSS (北斗/GPS) 定位周期上报:仅 4G 通道(Air780EG 内置GNSS) ==== - * 参考工程《air780EG-V1.1加入北斗GPS数据》的实现: - * AT+CGNSINF → +CGNSINF: run,fix,UTC,lat,lon,...(经纬度为十进制度字符串) - * 修正了参考工程的几个缺陷:定位无效不上报空串、OTA 期间不插队 AT、 - * 有平台下行命令插队时主动让行、手工解析不依赖 sscanf 的 scanset。 */ -#define GPS_FIRST_DELAY_MS 60000u /* 联网后首次查询延迟,给搜星留时间 */ -#define GPS_REPORT_INTERVAL_MS 300000u /* 定位上报周期 5min */ - -/*==== WGS-84 → GCJ-02(高德/火星坐标系)转换 ==== - * GPS 模块输出 WGS-84 原始坐标,直接输入国内高德地图会有 100~600m 加密偏移, - * 上报前先转换成 GCJ-02。公开的标准算法,中国境外直接返回原值。 */ -#define GCJ_PI 3.1415926535897932384626 -#define GCJ_A 6378245.0 /* 克拉索夫斯基椭球长半轴 */ -#define GCJ_EE 0.00669342162296594323 /* 偏心率平方 */ - -static double gcj_transform_lat(double x, double y) -{ - double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(fabs(x)); - ret += (20.0 * sin(6.0 * x * GCJ_PI) + 20.0 * sin(2.0 * x * GCJ_PI)) * 2.0 / 3.0; - ret += (20.0 * sin(y * GCJ_PI) + 40.0 * sin(y / 3.0 * GCJ_PI)) * 2.0 / 3.0; - ret += (160.0 * sin(y / 12.0 * GCJ_PI) + 320.0 * sin(y * GCJ_PI / 30.0)) * 2.0 / 3.0; - return ret; -} - -static double gcj_transform_lon(double x, double y) -{ - double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(fabs(x)); - ret += (20.0 * sin(6.0 * x * GCJ_PI) + 20.0 * sin(2.0 * x * GCJ_PI)) * 2.0 / 3.0; - ret += (20.0 * sin(x * GCJ_PI) + 40.0 * sin(x / 3.0 * GCJ_PI)) * 2.0 / 3.0; - ret += (150.0 * sin(x / 12.0 * GCJ_PI) + 300.0 * sin(x / 30.0 * GCJ_PI)) * 2.0 / 3.0; - return ret; -} - -static void wgs84_to_gcj02(double wgs_lon, double wgs_lat, double *gcj_lon, double *gcj_lat) -{ - /* 中国境外不做偏移 */ - if (wgs_lon < 72.004 || wgs_lon > 137.8347 || wgs_lat < 0.8293 || wgs_lat > 55.8271) { - *gcj_lon = wgs_lon; - *gcj_lat = wgs_lat; - return; - } - double dlat = gcj_transform_lat(wgs_lon - 105.0, wgs_lat - 35.0); - double dlon = gcj_transform_lon(wgs_lon - 105.0, wgs_lat - 35.0); - double radlat = wgs_lat / 180.0 * GCJ_PI; - double magic = sin(radlat); - magic = 1 - GCJ_EE * magic * magic; - double sqrtmagic = sqrt(magic); - dlat = (dlat * 180.0) / ((GCJ_A * (1 - GCJ_EE)) / (magic * sqrtmagic) * GCJ_PI); - dlon = (dlon * 180.0) / (GCJ_A / sqrtmagic * cos(radlat) * GCJ_PI); - *gcj_lat = wgs_lat + dlat; - *gcj_lon = wgs_lon + dlon; -} - -static uint32_t s_gps_tick = 0; -static uint8_t s_gps_first = 1; - -static void air780e_gps_poll(void) -{ - if (!g_air780e_mqtt_ready) { s_gps_tick = 0; s_gps_first = 1; return; } - if (!s_gnss_present) return; - if (g_net_tx_busy) return; /* OTA 下载期间禁止插队 AT */ - - uint32_t now = HAL_GetTick(); - if (s_gps_tick == 0) { s_gps_tick = now; return; } - if (now - s_gps_tick < (s_gps_first ? GPS_FIRST_DELAY_MS : GPS_REPORT_INTERVAL_MS)) return; - s_gps_tick = now; - s_gps_first = 0; - - /* 清缓冲,发 AT+CGNSINF 查询定位 */ - U2_CopyIndex = 0; U2_CopyBuff[0] = 0; U2_CopyFlag = 0; - CAT1_printf("AT+CGNSINF"); - - int t = 20; /* 最多等 2s */ - while (--t) { - bg_delay(100); - if (U2_CopyFlag) { - /* 有平台下行命令插队:本轮放弃查询,先让 dispatch 处理命令 */ - if (strstr((char *)U2_CopyBuff, "+MSUB:")) return; - if (strstr((char *)U2_CopyBuff, "+CGNSINF:")) break; - } - } - if (t == 0) { log_warn("> GPS: CGNSINF timeout"); return; } - - /* 手工解析逗号字段:+CGNSINF: run,fix,UTC,lat,lon,... */ - char *q = strstr((char *)U2_CopyBuff, "+CGNSINF:") + 10; - int run = atoi(q); - char *c1 = strchr(q, ','); /* run 后的逗号 */ - char *c2 = c1 ? strchr(c1 + 1, ',') : NULL; /* fix 后的逗号 */ - char *c3 = c2 ? strchr(c2 + 1, ',') : NULL; /* UTC 后的逗号 → lat 起点 */ - char *c4 = c3 ? strchr(c3 + 1, ',') : NULL; /* lat 后的逗号 → lon 起点 */ - char *c5 = c4 ? strchr(c4 + 1, ',') : NULL; /* lon 后的逗号 */ - int fix = c1 ? atoi(c1 + 1) : 0; - - char lat[12] = {0}, lon[13] = {0}; - if (c3 && c4 && c5) { - int ll = (int)(c4 - (c3 + 1)); - int ol = (int)(c5 - (c4 + 1)); - if (ll > 0 && ll <= 11) { memcpy(lat, c3 + 1, ll); } - if (ol > 0 && ol <= 12) { memcpy(lon, c4 + 1, ol); } - } - U2_CopyIndex = 0; U2_CopyBuff[0] = 0; U2_CopyFlag = 0; - - /* 定位无效(fix!=1)或字段缺失:跳过本次上报(室内搜不到星属正常) */ - if (fix != 1 || lat[0] == 0 || lon[0] == 0) { - log_info("> GPS: no fix (run=%d fix=%d), skip", run, fix); - return; - } - - /* WGS-84 → GCJ-02(高德坐标系),上报的坐标可直接用于高德地图 */ - { - double glon, glat; - wgs84_to_gcj02(atof(lon), atof(lat), &glon, &glat); - snprintf(lat, sizeof(lat), "%.6f", glat); - snprintf(lon, sizeof(lon), "%.6f", glon); - } - - log_info("> GPS: %s,%s", lon, lat); /* 高德/GeoJSON 习惯:经度在前 */ - CAT1_printf("AT+MPUB=\"/iot/data/up/%s\",0,0,\"{\\22header\\22:\\22iot.prop.post\\22,\\22body\\22:{\\22GPS\\22:\\22%s,%s\\22}}\"", - MqttInfoStr.ClientID, lon, lat); -} - -/*==== Process ====*/ -void air780e_process(void){ - /* 循环处理缓冲区中所有完整 MQTT 消息 */ - while(U2_CopyFlag){ - U2_CopyFlag = 0; - dispatch_one_mqtt_msg(); - } - air780e_gps_poll(); -} - diff --git a/V1.1/serial_capture.py b/serial_capture.py similarity index 100% rename from V1.1/serial_capture.py rename to serial_capture.py