shuichan_old/V1.4-Chinese/STM32F103_App1/HardWare/NETWORK/network.c

403 lines
16 KiB
C
Raw Normal View History

/* network.c - 网络层4G/WiFi 双通道抽象与故障切换
*
* (app_loop/relay/feed_scale) NET_*
* EEPROM "上次成功联网方式"
* 4G 4G/ 10s 3 WiFi 4G
* WiFi WiFi 4G WiFi
* WiFi AP CIPSTATUS, 10s
* SSID60s SmartConfig60s
* NET_SaveMode() EEPROM
*/
#include "network.h"
#include "net_wifi.h"
#include "net_wifi_ota.h"
#include "air780e.h"
#include "led.h"
#include "log.h"
#include "main.h"
#include "uart.h"
#include "relay.h"
#include "proto.h"
#include "24c02.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "stdio.h"
#include "string.h"
volatile uint8_t g_net_mqtt_ready = 0;
volatile uint8_t g_net_first_publish_ok = 0;
/* 网络发送互斥标志OTA 下载等 AT 交互期间置位 */
volatile uint8_t g_net_tx_busy = 0;
/* 4G 无 SIM 卡标志:由 air780e.c 在初始化失败时设置 */
volatile uint8_t g_no_sim_detected = 0;
/* 4G 无信号标志:由 air780e.c 在 AT+CSQ 检测时设置 */
volatile uint8_t g_no_signal_detected = 0;
/* 当前激活的网络后端 */
static net_backend_t s_backend = NET_BACKEND_4G;
/* 上次成功联网方式EEPROM 偏移 130秤参数占用到 126避开 */
#define EEPROM_NETMODE_ADDR 130
static uint8_t s_last_mode = NET_MODE_4G;
/* 无 SIM 重试多少次后切 WiFi */
#define NET_NO_SIM_SWITCH_COUNT 3
/* WiFi 一轮完整建链(已存AP→固定SSID→SmartConfig)失败后切回 4G */
#define NET_WIFI_FAIL_SWITCH_COUNT 1
/*==== 网络模组 AT 会话互斥RTOS 版)====
* net AT //OTA
* NET_publish_*
* AT
* net net_wifi_init */
static SemaphoreHandle_t s_net_at_mutex = NULL;
void NET_LockInit(void)
{
s_net_at_mutex = xSemaphoreCreateRecursiveMutex();
}
static void net_at_lock(void)
{
if (s_net_at_mutex && xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
xSemaphoreTakeRecursive(s_net_at_mutex, portMAX_DELAY);
}
}
static void net_at_unlock(void)
{
if (s_net_at_mutex && xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
xSemaphoreGiveRecursive(s_net_at_mutex);
}
}
/* 尝试拿锁0=拿到(含调度器未启动的直通),-1=被 AT 会话占用 */
static int net_at_trylock(void)
{
if (s_net_at_mutex && xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
return xSemaphoreTakeRecursive(s_net_at_mutex, 0) == pdTRUE ? 0 : -1;
}
return 0;
}
net_backend_t NET_GetActiveBackend(void)
{
return s_backend;
}
uint8_t NET_GetLastMode(void)
{
return s_last_mode;
}
/* 联网方式记忆:仅方式变化时写一次 EEPROM"只存一次" */
void NET_SaveMode(uint8_t mode)
{
if (mode == s_last_mode || mode > NET_MODE_WIFI_SMART) return;
s_last_mode = mode;
eepromWriteData(EEPROM_NETMODE_ADDR, &mode, 1);
log_info("> NET: 联网方式已保存 -> %s",
mode == NET_MODE_4G ? "4G" :
mode == NET_MODE_WIFI_FIXED ? "WiFi(固定热点)" : "WiFi(配网热点)");
}
void NET_init(void)
{
g_net_mqtt_ready = 0;
g_net_first_publish_ok = 0;
g_no_sim_detected = 0;
g_no_signal_detected = 0;
/* 读 EEPROM 记忆的上次成功联网方式:非法值(含 EEPROM 未初始化的 0xFF)按 4G */
uint8_t m = NET_MODE_4G;
eepromReadData(EEPROM_NETMODE_ADDR, &m, 1);
if (m > NET_MODE_WIFI_SMART) m = NET_MODE_4G;
s_last_mode = m;
if (m == NET_MODE_4G) {
s_backend = NET_BACKEND_4G;
net_at_lock();
air780e_init();
net_at_unlock();
if (g_no_sim_detected) {
log_warn("> 4G 无 SIM 卡,请插入 SIM 卡");
/* 不立即切 WiFi由 NET_process 重试几次仍无卡后再切换 */
}
g_net_mqtt_ready = g_air780e_mqtt_ready;
} else {
/* 上次 WiFi 成功WiFi 优先NET_process 会立即发起 net_wifi_init */
log_info("> NET: 上次联网方式为 WiFiWiFi 优先");
s_backend = NET_BACKEND_WIFI;
}
}
/* 切到 WiFi 通道:停 4G 接收共享缓冲互斥WiFi 建链由 NET_process 立即发起 */
static void net_switch_to_wifi(void)
{
log_warn("> NET: 切换 4G -> WiFi");
USART3_StopRx();
g_net_mqtt_ready = 0;
g_net_first_publish_ok = 0;
s_backend = NET_BACKEND_WIFI;
}
/* 切回 4G 通道:停 WiFi 接收,恢复 4G 接收并重新初始化 */
static void net_switch_to_4g(void)
{
log_warn("> NET: 切换 WiFi -> 4G");
UART4_StopRx();
g_net_mqtt_ready = 0;
g_net_first_publish_ok = 0;
g_no_sim_detected = 0;
g_no_signal_detected = 0;
g_air780e_mqtt_ready = 0;
g_air780e_first_publish_ok = 0;
s_backend = NET_BACKEND_4G;
USART3_StartRx();
net_at_lock();
air780e_init();
net_at_unlock();
}
void NET_process(void)
{
static uint32_t retry_tick = 0;
static uint16_t fail_count = 0;
if (s_backend == NET_BACKEND_4G) {
/* 网络未就绪时自动重试 */
if (!g_air780e_mqtt_ready) {
if (g_no_sim_detected || g_no_signal_detected) {
/* 无 SIM 卡或无信号:每 10s 重试一次,连续多次仍不可用则切换到 WiFi 通道 */
if (retry_tick == 0 ||
(HAL_GetTick() - retry_tick) >= 10000) {
retry_tick = HAL_GetTick();
fail_count++;
log_warn("> NET: 4G 不可用 (no_sim=%d, no_signal=%d),重试初始化 %d/%d",
g_no_sim_detected, g_no_signal_detected,
fail_count, NET_NO_SIM_SWITCH_COUNT);
g_no_sim_detected = 0;
g_no_signal_detected = 0;
net_at_lock();
air780e_init();
net_at_unlock();
if (!g_no_sim_detected && !g_no_signal_detected) {
log_info("> NET: 4G SIM卡/信号正常或网络已恢复");
fail_count = 0;
} else if (fail_count >= NET_NO_SIM_SWITCH_COUNT) {
log_warn("> NET: 4G 重试 %d 次仍不可用,回退到 WiFi",
NET_NO_SIM_SWITCH_COUNT);
net_switch_to_wifi();
fail_count = 0;
retry_tick = 0;
}
}
} else {
retry_tick = 0;
fail_count = 0;
}
} else {
retry_tick = 0;
fail_count = 0;
NET_SaveMode(NET_MODE_4G); /* 4G 联网成功:更新方式记忆(未变化则空操作) */
}
air780e_process();
g_net_mqtt_ready = g_air780e_mqtt_ready;
g_net_first_publish_ok = g_air780e_first_publish_ok;
} else {
/* WiFi 通道:未就绪时重新建链(首轮立即),一轮失败即切回 4G */
if (!g_wifi_mqtt_ready) {
if (retry_tick == 0 ||
(HAL_GetTick() - retry_tick) >= 10000) {
retry_tick = HAL_GetTick();
fail_count++;
log_warn("> NET: WiFi 连接尝试 %d/%d",
fail_count, NET_WIFI_FAIL_SWITCH_COUNT);
net_at_lock();
int wifi_rc = net_wifi_init();
net_at_unlock();
if (wifi_rc == 0) {
fail_count = 0;
} else if (fail_count >= NET_WIFI_FAIL_SWITCH_COUNT) {
log_warn("> NET: WiFi 连接失败,回退到 4G");
net_switch_to_4g();
fail_count = 0;
retry_tick = 0;
}
}
} else {
retry_tick = 0;
fail_count = 0;
}
net_wifi_process();
g_net_mqtt_ready = g_wifi_mqtt_ready;
g_net_first_publish_ok = g_wifi_first_publish_ok;
}
}
void NET_publish_status(int sw1, int feeding, int sw2, int sw3, int sw4,
float weight_kg, float once_wt_kg, float zero_kg)
{
if (g_net_tx_busy) { log_info("> NET: 状态上报被丢弃 (发送忙)"); return; }
if (net_at_trylock() != 0) { log_info("> NET: 状态上报被丢弃 (AT 忙)"); return; }
/* ver 字段:固件版本号,平台据此可识别设备是否发生了 OTA 回退 */
/* sw1 与 feeding 都映射继电器 1 状态(无秤模式两者等效),
* sw1 feeding */
/* 无秤模式PRODUCT_WITH_FEED_SCALE=0不上报 zero/onceWt 字段 */
if (s_backend == NET_BACKEND_4G) {
#if PRODUCT_WITH_FEED_SCALE
CAT1_printf("AT+MPUB=\"/iot/data/up/%s\",0,0,\"{\\22header\\22:\\22iot.prop.post\\22,\\22body\\22:{\\22ver\\22:%u,\\22weight\\22:%.1f,\\22onceWt\\22:%.1f,\\22zero\\22:%.1f,\\22sw1\\22:%d,\\22feeding\\22:%d,\\22sw2\\22:%d,\\22sw3\\22:%d,\\22sw4\\22:%d}}\"",
MqttInfoStr.ClientID, (unsigned)MqttInfoStr.Ver, zero_kg, once_wt_kg, weight_kg, sw1, feeding, sw2, sw3, sw4);
#else
CAT1_printf("AT+MPUB=\"/iot/data/up/%s\",0,0,\"{\\22header\\22:\\22iot.prop.post\\22,\\22body\\22:{\\22ver\\22:%u,\\22weight\\22:%.1f,\\22sw1\\22:%d,\\22feeding\\22:%d,\\22sw2\\22:%d,\\22sw3\\22:%d,\\22sw4\\22:%d}}\"",
MqttInfoStr.ClientID, (unsigned)MqttInfoStr.Ver, zero_kg, sw1, feeding, sw2, sw3, sw4);
#endif
} else {
char json[256];
#if PRODUCT_WITH_FEED_SCALE
snprintf(json, sizeof(json),
"{\"header\":\"iot.prop.post\",\"body\":{\"ver\":%u,\"weight\":%.1f,\"onceWt\":%.1f,\"zero\":%.1f,\"sw1\":%d,\"feeding\":%d,\"sw2\":%d,\"sw3\":%d,\"sw4\":%d}}",
(unsigned)MqttInfoStr.Ver, zero_kg, once_wt_kg, weight_kg, sw1, feeding, sw2, sw3, sw4);
#else
snprintf(json, sizeof(json),
"{\"header\":\"iot.prop.post\",\"body\":{\"ver\":%u,\"weight\":%.1f,\"sw1\":%d,\"feeding\":%d,\"sw2\":%d,\"sw3\":%d,\"sw4\":%d}}",
(unsigned)MqttInfoStr.Ver, zero_kg, sw1, feeding, sw2, sw3, sw4);
#endif
net_wifi_publish_up(json);
}
net_at_unlock();
}
void NET_publish_response(const char *cmd_id, int ok, const char *msg)
{
if (g_net_tx_busy) { log_info("> NET: 应答上报被丢弃 (发送忙)"); return; }
if (net_at_trylock() != 0) { log_info("> NET: 应答上报被丢弃 (AT 忙)"); return; }
const char *text = msg ? msg : (ok ? "OK" : "command failed");
if (s_backend == NET_BACKEND_4G) {
CAT1_printf("AT+MPUB=\"/iot/data/up/%s\",0,0,\"{\\22id\\22:\\22%s\\22,\\22code\\22:0,\\22message\\22:\\22%s\\22}\"",
MqttInfoStr.ClientID, cmd_id, text);
} else {
char json[200];
snprintf(json, sizeof(json),
"{\"id\":\"%s\",\"code\":0,\"message\":\"%s\"}", cmd_id, text);
net_wifi_publish_up(json);
}
net_at_unlock();
}
/* 上报电量状态: on=1 正常, on=0 低电量 */
void NET_publish_power(int on)
{
if (g_net_tx_busy) { log_info("> NET: 电量上报被丢弃 (发送忙)"); return; }
if (net_at_trylock() != 0) { log_info("> NET: 电量上报被丢弃 (AT 忙)"); return; }
if (s_backend == NET_BACKEND_4G) {
CAT1_printf("AT+MPUB=\"/iot/data/up/%s\",0,0,\"{\\22header\\22:\\22iot.prop.post\\22,\\22body\\22:{\\22pow\\22:%d}}\"",
MqttInfoStr.ClientID, on ? 1 : 0);
} else {
char json[96];
snprintf(json, sizeof(json),
"{\"header\":\"iot.prop.post\",\"body\":{\"pow\":%d}}", on ? 1 : 0);
net_wifi_publish_up(json);
}
net_at_unlock();
}
void NET_mqtt_report(const char *header, const char *ota_id, int val)
{
if (g_net_tx_busy) { log_info("> NET: 进度/结果上报被丢弃 (发送忙)"); return; }
if (net_at_trylock() != 0) { log_info("> NET: 进度/结果上报被丢弃 (AT 忙)"); return; }
if (s_backend == NET_BACKEND_4G) {
air780e_mqtt_report(header, ota_id, val);
net_at_unlock();
return;
}
if (!header || !ota_id || !ota_id[0]) { net_at_unlock(); return; }
char json[200];
if (strstr(header, "progress")) {
snprintf(json, sizeof(json),
"{\"header\":\"%s\",\"body\":{\"id\":\"%.16s\",\"progress\":%d}}",
header, ota_id, val);
} else {
snprintf(json, sizeof(json),
"{\"header\":\"%s\",\"body\":{\"id\":\"%.16s\",\"success\":%s}}",
header, ota_id, val ? "true" : "false");
}
net_wifi_publish_up(json);
net_at_unlock();
}
void NET_ota_confirm(void)
{
/* 两通道复用同一确认流程:无挂起 OTA 时内部直接返回;
* NET_mqtt_report */
air780e_ota_confirm();
}
/*==== 下行消息统一分发(从 air780e.c 上提,与模组无关)====
* msg +MSUB URC4G JSONWiFi */
void net_dispatch_message(const char *msg)
{
/* dedup by message id */
static char last_id[20] = {0};
char cur_id[20] = {0};
proto_get_id((char*)msg, cur_id, sizeof(cur_id));
if (cur_id[0] && strcmp(cur_id, last_id) == 0) {
log_info("> MQTT: 重复消息跳过id=%.16s", cur_id);
return;
}
strncpy(last_id, cur_id, sizeof(last_id)-1);
/* relay control */
if (strstr(msg, "iot.prop.set")) { relay_action((uint8_t *)msg); }
/* OTA upgrade */
if (strstr(msg, "iot.ota.upgrade.post")) {
log_info("> OTA 升级命令!");
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*)msg, "ver", &v)) new_ver = (uint16_t)v; }
proto_get_hex16((char*)msg, "crc16", &crc16);
log_info("> OTA 版本: 当前=%u, 新版本=%u, crc16=0x%04X", MqttInfoStr.Ver, new_ver, crc16);
/* 版本号 ≤ 当前版本 → 跳过下载 (防止重复或降级) */
if (new_ver > 0 && new_ver <= MqttInfoStr.Ver) {
proto_get_id((char*)msg, ota_id, sizeof(ota_id));
log_info("> OTA: 版本相同或更低,跳过 (当前=%u, 新版本=%u)", MqttInfoStr.Ver, new_ver);
NET_mqtt_report("iot.ota.progress.post", ota_id, 10);
} else {
strncpy(host, MqttInfoStr.ServerIP, sizeof(host)-1); strcpy(path, "/firmware.bin");
proto_get_id((char*)msg, ota_id, sizeof(ota_id));
{ char fu[100]={0};
if (proto_get_str((char*)msg, "url", fu, sizeof(fu))) {
log_info("> OTA 下载地址: %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);
/* 两条通道共用 W25Q64 双槽 + CRC + BootLoader 回退体系,仅下载传输不同 */
if (s_backend == NET_BACKEND_WIFI) {
net_wifi_ota(host, port, path, ota_id, new_ver, crc16);
} else {
air780e_trigger_ota(host, port, path, ota_id, new_ver, crc16);
}
}
}
}