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

391 lines
15 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.

/* 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: last mode saved -> %s",
mode == NET_MODE_4G ? "4G" :
mode == NET_MODE_WIFI_FIXED ? "WiFi(fixed AP)" : "WiFi(smartconfig AP)");
}
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 no SIM, please insert SIM card");
/* 不立即切 WiFi由 NET_process 重试几次仍无卡后再切换 */
}
g_net_mqtt_ready = g_air780e_mqtt_ready;
} else {
/* 上次 WiFi 成功WiFi 优先NET_process 会立即发起 net_wifi_init */
log_info("> NET: last mode is WiFi, WiFi first");
s_backend = NET_BACKEND_WIFI;
}
}
/* 切到 WiFi 通道:停 4G 接收共享缓冲互斥WiFi 建链由 NET_process 立即发起 */
static void net_switch_to_wifi(void)
{
log_warn("> NET: switch 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: switch 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 unavailable (no_sim=%d, no_signal=%d), retry init %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/signal OK or network recovered");
fail_count = 0;
} else if (fail_count >= NET_NO_SIM_SWITCH_COUNT) {
log_warn("> NET: 4G unavailable after %d retries, fallback to 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 connect attempt %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 connect failed, fallback to 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: status dropped (tx busy)"); return; }
if (net_at_trylock() != 0) { log_info("> NET: status dropped (AT busy)"); return; }
/* ver 字段:固件版本号,平台据此可识别设备是否发生了 OTA 回退 */
/* sw1 与 feeding 都映射继电器 1 状态(无秤模式两者等效),
* 两个字段都上报,平台的 sw1 开关和 feeding 状态才能同步刷新 */
if (s_backend == NET_BACKEND_4G) {
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 {
char json[256];
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);
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: response dropped (tx busy)"); return; }
if (net_at_trylock() != 0) { log_info("> NET: response dropped (AT busy)"); 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: power dropped (tx busy)"); return; }
if (net_at_trylock() != 0) { log_info("> NET: power dropped (AT busy)"); 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: report dropped (tx busy)"); return; }
if (net_at_trylock() != 0) { log_info("> NET: report dropped (AT busy)"); 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: dup skip, 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 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*)msg, "ver", &v)) new_ver = (uint16_t)v; }
proto_get_hex16((char*)msg, "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*)msg, ota_id, sizeof(ota_id));
log_info("> OTA: same or lower ver, skip (cur=%u, new=%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 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);
/* 两条通道共用 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);
}
}
}
}