Linux應用開發(君正T23):三網智能切換及配網功能

????????前段時間接手了一個監控項目,其中甲方對于設備的要求有一條就是實現網口eth、WiFi、4G三種手段的聯網方式并且當某一個網絡不好的時候就去切換到下一個能用的網絡,讓監控設備持續不斷的有網絡,保證監控數據的上傳。這個部分的功能就交由我來實現了,下面就是一些簡單的記錄,包括優化。

? ? ? ? 由于交付初版時間緊急,我就來講解一下我在初版實現的框架和邏輯,后面會進行代碼重構優化。

目錄

一、整體架構設計

二、核心功能實現邏輯

1. 網絡狀態監測與更新

2. 三網自動切換邏輯

3. WiFi 配網流程(狀態機實現)

初版程序(主函數及兩個線程):

初版程序(三網切換代碼):

初版程序(配網監控代碼):

三、優化初版程序代碼(持續更新中)

優化點一:狀態機邏輯分散擴展性差,應改為狀態表

優化點二:用庫函數替代shell命令調用,提升處理速度(替代wpa_supplicant工具)


一、整體架構設計

????????初版本代碼主要以實現基礎邏輯功能為主要目標,由于我還不熟悉相關網絡的一些知識,所以在剛開始我用到了很多系統調用以及相關工具來幫我實現底層網絡的一些功能(比如udhcpc、wpa_supplicant等等)初版交付之后后期目標就是進行多方位的優化,包括舍棄系統工具改為API接口去實現網絡功能,代碼邏輯精簡,速度優化等等。

????????代碼圍繞 “三網切換” “WiFi 配網” 兩個核心功能,采用了 “模塊化 + 狀態機” 的設計思路,主要包含以下幾個部分:

  1. 網絡類型與優先級定義:明確網口(最高優先級)、WiFi(中優先級)、4G(最低優先級)的優先級順序,作為網絡切換的基礎。
  2. 狀態數據結構:用NetworkStatus存儲各網絡的實時狀態(連接狀態、信號強度、質量評分等),用WifiConfigFSM管理 WiFi 配網的狀態流轉。
  3. 核心邏輯模塊:包括網絡狀態更新、質量評分計算、網絡切換控制、配網狀態機等,各模塊職責清晰,通過函數調用協同工作。
  4. 輔助工具函數:提供 Ping 測試、AT 命令發送、系統命令執行等基礎功能,支撐核心邏輯實現。

二、核心功能實現邏輯

1. 網絡狀態監測與更新

通過update_network_status函數定期(隱含在主循環中)更新所有網絡的狀態,核心邏輯包括:

  • 接口啟用檢查:通過ip link show命令判斷網絡接口(如 eth0、wlan0)是否處于 “UP” 狀態,未啟用則直接標記為不可用。
  • 類型化狀態采集
    • 網口(ETH):讀取/sys/class/net下的統計文件,獲取接收 / 發送數據包數、錯誤數、鏈路速率等,并計算丟包率。
    • WiFi:通過/proc/net/wireless文件解析信號強度(將 - 120~-30dBm 轉為 0-100 的評分),并檢查是否與熱點關聯(is_wifi_associated)。
    • 4G:通過串口發送 AT 命令(send_at_command)獲取信號強度(CSQ 值),并檢查 SIM 卡狀態、網絡注冊狀態。
  • 質量評分計算:通過calculate_quality函數綜合多因素評分(不同網絡權重不同):
    • 網口:速率(30%)+ 接收丟包率(30%)+ Ping 網關丟包率(40%)。
    • WiFi:信號強度(50%)+ Ping 網關丟包率(50%)(適配初始連接時 Ping 不穩定的場景)。
    • 4G:信號強度(50%)+ Ping 公網(8.8.8.8)丟包率(50%)。
  • 連接狀態判斷:質量評分 > 30 分則標記為 “已連接”(connected=1),否則為 “未連接”。

2. 三網自動切換邏輯

核心函數為check_and_switch_network,遵循 “優先級 + 可用性” 原則,流程如下:

  1. 優先級排序:優先選擇網口,其次 WiFi,最后 4G(兜底)。
  2. 最優網絡選擇
    • 若當前有更高優先級的網絡可用(如網口突然插入),則觸發切換。
    • 若所有網絡均不可用,先重試 WiFi(最多 2 次),重試失敗后切換到 4G 兜底。
  3. 切換執行:通過switch_to_network函數實現具體切換:
    • 清理當前網絡:關閉進程(如 wpa_supplicant)、清除 IP 和路由。
    • 啟用目標網絡:啟動接口、獲取 IP(udhcpc)、添加默認路由(不同網絡 metric 不同,確保優先級)。
    • 就緒檢查:等待網絡獲取 IP(最多 10 秒),未獲取則判定切換失敗。

3. WiFi 配網流程(狀態機實現)

通過WifiConfigFSM狀態機管理配網全流程,狀態流轉如下:

  • 初始狀態(IDLE):等待觸發信號(trigger_config=1)。
  • 等待配網文件(WAIT_FILE):監測/system/wifi_config.txt文件(上層寫入的 SSID 和密碼),超時(60 秒)則進入失敗狀態。
  • 讀取與驗證(READ_FILE → VALIDATE):讀取文件中的 SSID 和密碼,檢查非空(非法則重試,最多 2 次)。
  • 寫入配置(WRITE_CONF):將 SSID 和密碼寫入wpa_supplicant.conf(WiFi 配置文件)。
  • 連接與檢查(CONNECT → CHECK):啟動 wpa_supplicant 并嘗試連接,超時(15 秒)后檢查是否關聯成功。
  • 結果狀態(SUCCESS/FAILED):成功則創建wifi_config_success標記文件(避免下次上電重復配網),失敗則進入結束狀態。
  • 結束狀態(FINISH):重置狀態機,等待下次觸發。

????????以下就是我的初版程序(盡量將注釋補全),要求的功能已經成功實現,可以實現網絡自動自主切換與配網(由于我們是項目協同開發,我這里的配網與上層MQTT開發人員對接,他們給我下發配網文件信息,我來進行解析與寫入,同時將配網狀態用FIFO告訴上層)。當前程序無需依賴任何庫,編譯后可以放在任何嵌入式Linux設備平臺上運行。

初版程序(主函數及兩個線程):

// 網絡監控線程函數
void *network_monitor_thread(void *arg)
{int loop_cnt = 0; // 循環計數器while (running){loop_cnt++;// 打印帶時間戳的循環日志,確保能看到線程在運行time_t now = time(NULL);struct tm *t = localtime(&now);LOG_NET("[%02d:%02d:%02d] [監控線程] 第 %d 次循環,當前網絡:%s\n",t->tm_hour, t->tm_min, t->tm_sec,loop_cnt,current_network != NETWORK_MAX ? networks[current_network].ifname : "無");check_and_switch_network(0);print_network_status();// 改用 usleep + 循環檢查 running,避免長時間 sleep 無法中斷int sleep_ms = (current_network == NETWORK_4G) ? 2000 : 3000;while (sleep_ms > 0 && running){usleep(100000); // 每次休眠 100ms,醒來檢查 runningsleep_ms -= 100;}}LOG_NET("[監控線程] 收到退出信號,線程停止\n");return NULL;
}// 配網監控線程函數
void *wifi_config_thread(void *arg)
{// 初始化配網文件時間戳(修復初始誤觸發)struct stat st;if (stat(WIFI_CONFIG_FILE, &st) == 0){last_config_file_mtime = st.st_mtime;LOG_WIFI("初始化時間戳:last_config_file_mtime=%ld\n", last_config_file_mtime);}else{last_config_file_mtime = 0;LOG_WIFI("初始化時間戳:文件不存在,設為0\n");}// 初始檢查配網成功文件if (access(WIFI_CONFIG_SUCCESS_FILE, F_OK) == 0){LOG_WIFI("初始檢查:檢測到配網成功文件,僅監聽文件刪除事件\n");}else{LOG_WIFI("初始檢查:未檢測到配網成功文件,監聽配網文件更新\n");}while (running){pthread_mutex_lock(&network_mutex);int config_success = (access(WIFI_CONFIG_SUCCESS_FILE, F_OK) == 0);if (!config_success){// 檢查1:原有觸發邏輯(正常文件更新)int trigger = should_trigger_config();if (trigger){if (wifi_fsm.current_state == WIFI_CONFIG_STATE_IDLE){trigger_config = 1;LOG_WIFI("[配網線程] 狀態機空閑,設置trigger_config=1(正常觸發)\n");}else{LOG_WIFI("[配網線程] 狀態機非IDLE(狀態=%d),立即重置并觸發\n",wifi_fsm.current_state);// 無論之前是什么狀態,強制重置(確保能響應新配置)reset_wifi_fsm();trigger_config = 1;}}// 檢查2:主動檢測時間戳異常(當前時間 < 記錄時間)else{// 僅當文件存在時才檢查時間戳異常if (access(WIFI_CONFIG_FILE, F_OK) == 0 && stat(WIFI_CONFIG_FILE, &st) == 0){if (st.st_mtime < last_config_file_mtime){LOG_WIFI("[配網線程] 檢測到時間戳異常(當前=%ld < 記錄=%ld),強制觸發\n",st.st_mtime, last_config_file_mtime);// 重置記錄時間,避免重復觸發last_config_file_mtime = st.st_mtime;// 無論狀態機是否空閑,強制觸發if (wifi_fsm.current_state != WIFI_CONFIG_STATE_IDLE){reset_wifi_fsm();}trigger_config = 1;}}}}else{// 配網已成功,僅當成功文件被刪除時重新監聽if (access(WIFI_CONFIG_SUCCESS_FILE, F_OK) != 0){LOG_WIFI("[配網線程] 配網成功文件已刪除,重置監聽狀態\n");// 強制將記錄時間設為0,確保下次文件更新100%能觸發if (stat(WIFI_CONFIG_FILE, &st) == 0){last_config_file_mtime = 0; // 關鍵:不使用文件當前時間,直接設0LOG_WIFI("[配網線程] 重置時間戳為0(成功文件被刪除)\n");}else{last_config_file_mtime = 0;}}}// 處理配網觸發(確保trigger_config被立即處理)if (trigger_config){if (wifi_fsm.current_state == WIFI_CONFIG_STATE_IDLE){LOG_WIFI("[配網線程] 啟動配網狀態機,進入WAIT_FILE\n");wifi_fsm.current_state = WIFI_CONFIG_STATE_WAIT_FILE;wifi_fsm.retry_count = 0;trigger_config = 0;}else{LOG_WIFI("[配網線程] 等待狀態機空閑后觸發(當前狀態=%d)\n",wifi_fsm.current_state);}}// 檢查文件存在性并打印詳細時間戳(輔助調試)int test_exists = access(WIFI_CONFIG_FILE, F_OK);if (test_exists == 0 && stat(WIFI_CONFIG_FILE, &st) == 0){LOG_WIFI("[文件檢測] 存在性:%d,狀態機狀態:%d,當前時間:%ld,記錄時間:%ld\n",test_exists, wifi_fsm.current_state, st.st_mtime, last_config_file_mtime);}else{LOG_WIFI("[文件檢測] 存在性:%d,狀態機狀態:%d,記錄時間:%ld\n",test_exists, wifi_fsm.current_state, last_config_file_mtime);}pthread_mutex_unlock(&network_mutex);process_wifi_fsm(); // 驅動狀態機執行sleep(1);}LOG_WIFI("配網線程退出\n");return NULL;
}int main()
{pthread_t monitor_thread, fsm_thread;signal(SIGINT, signal_handler);signal(SIGTERM, signal_handler);init_networks();init_fifo();// 創建網絡監控線程if (pthread_create(&monitor_thread, NULL, network_monitor_thread, NULL) != 0){fprintf(stderr, "創建監控線程失敗\n");return 1;}// 創建配網監控線程if (pthread_create(&fsm_thread, NULL, wifi_config_thread, NULL) != 0){fprintf(stderr, "創建配網線程失敗\n");return 1;}pthread_join(monitor_thread, NULL);pthread_join(fsm_thread, NULL);printf("程序退出\n");return 0;
}

初版程序(三網切換代碼):

????????在這里我將代碼全部揉在一起展示了和下面的配網監控代碼以及上面的main是連在一起的,實際項目中是按功能模塊進行拆分以方便維護更改。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/stat.h>
#include <signal.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <sys/wait.h>// ================= 日志開關 =================
// 0=關閉全部打印, 1=只打印配網相關, 2=打印全部
#define LOG_LEVEL 2
#define LOG_NET(fmt, ...)               \do                                  \{                                   \if (LOG_LEVEL >= 2)             \printf(fmt, ##__VA_ARGS__); \} while (0)
#define LOG_WIFI(fmt, ...)              \do                                  \{                                   \if (LOG_LEVEL >= 1)             \printf(fmt, ##__VA_ARGS__); \} while (0)// 配網相關宏定義
#define WIFI_CONFIG_FILE "/system/wifi_config.txt"             // 上層寫入的配網文件
#define WIFI_CONFIG_RETRY_MAX 2                                // 關鍵步驟最大重試次數
#define WIFI_CONNECT_TIMEOUT 15                                // 連接超時時間(秒)
#define WIFI_FILE_WAIT_TIMEOUT 60                              // 等待配網文件超時時間(秒)
#define WIFI_CONFIG_FIFO "/system/wifi_config_fifo"            // FIFO文件路徑
#define WIFI_CONFIG_SUCCESS_FILE "/system/wifi_config_success" // 配網成功標記文件(避免下次上電再執行配網)// 網絡接口類型定義
typedef enum
{NETWORK_ETH = 0, // 網口,最高優先級NETWORK_WIFI,    // WiFi,中優先級NETWORK_4G,      // 4G,低優先級NETWORK_MAX
} NetworkType;// 網口統計信息結構體
typedef struct
{unsigned long rx_packets; // 接收總包數unsigned long tx_packets; // 發送總包數unsigned long rx_errors;  // 接收錯誤數unsigned long tx_errors;  // 發送錯誤數unsigned long rx_dropped; // 接收丟棄數unsigned long tx_dropped; // 發送丟棄數int speed;                // 鏈路速率(Mbps)float rx_loss_rate;       // 接收丟包率(%)float tx_loss_rate;       // 發送丟包率(%)
} EthStats;// 網絡狀態結構體
typedef struct
{NetworkType type;       // 網絡類型char ifname[16];        // 接口名稱,如"eth0"、"wlan0"char tty_dev[32];       // 4G模組串口設備(如"/dev/ttyUSB1")int connected;          // 是否連接 0-未連接 1-已連接int signal_strength;    // 信號強度,0-100,僅用于無線int quality;            // 網絡質量評分,0-100time_t last_check_time; // 最后檢查時間EthStats eth_stats;     // 網口統計信息,僅用于網口float ping_loss_rate;   // ping測試丟包率(%)
} NetworkStatus;// 配網狀態枚舉(狀態機核心)
typedef enum
{WIFI_CONFIG_STATE_IDLE,       // 初始狀態:未開始,等待觸發WIFI_CONFIG_STATE_WAIT_FILE,  // 等待上層寫入配網文件(wifi_config.txt)WIFI_CONFIG_STATE_READ_FILE,  // 讀取配網文件(SSID和密碼)WIFI_CONFIG_STATE_VALIDATE,   // 驗證配置合法性(SSID/密碼非空)WIFI_CONFIG_STATE_WRITE_CONF, // 寫入wpa_supplicant.conf配置WIFI_CONFIG_STATE_CONNECT,    // 啟動WiFi并嘗試連接WIFI_CONFIG_STATE_CHECK,      // 檢查連接結果WIFI_CONFIG_STATE_SUCCESS,    // 配網成功WIFI_CONFIG_STATE_FAILED,     // 配網失敗WIFI_CONFIG_STATE_FINISH      // 配網流程結束(準備重置)
} WifiConfigState;// 狀態機上下文(保存當前狀態和臨時數據)
typedef struct
{WifiConfigState current_state; // 當前狀態char ssid[128];                // 臨時存儲SSIDchar psk[128];                 // 臨時存儲密碼int retry_count;               // 重試計數器(可選,用于失敗重試)
} WifiConfigFSM;// 配網狀態機全局實例
WifiConfigFSM wifi_fsm = {.current_state = WIFI_CONFIG_STATE_IDLE,.ssid = {0},.psk = {0},.retry_count = 0};// 配網觸發標志(外部可設置,用于從IDLE狀態啟動配網)
int trigger_config = 0;// 新增:記錄配網文件的上次修改時間(用于檢測更新)
time_t last_config_file_mtime = 0;NetworkStatus networks[NETWORK_MAX];
NetworkType current_network = NETWORK_MAX;
pthread_mutex_t network_mutex = PTHREAD_MUTEX_INITIALIZER;
volatile int running = 1;// const char *server_ip = "192.168.1.100";  // 遠端服務器IP(測試的時候為室內路由器IP)
const char *wpa_conf = "/system/init/wpa_supplicant.conf"; // WiFi配置文件路徑// 提前聲明4G模組AT命令發送函數
static int send_at_command(const char *tty_dev, const char *cmd, char *resp, int resp_len);void check_and_switch_network(int force_4g);// 信號處理函數
void signal_handler(int sig)
{running = 0;
}// 執行系統命令并返回退出碼
int exec_cmd(const char *cmd)
{int ret = system(cmd);if (WIFEXITED(ret)){return WEXITSTATUS(ret); // 返回命令退出碼(0表示成功)}return -1; // 命令執行異常
}// 初始化網絡接口信息
void init_networks()
{// 初始化網口(eth0)networks[NETWORK_ETH].type = NETWORK_ETH;snprintf(networks[NETWORK_ETH].ifname, sizeof(networks[NETWORK_ETH].ifname), "eth0");LOG_NET("初始化網口,獲取IP中...\n");char cmd[128];snprintf(cmd, sizeof(cmd), "udhcpc -i eth0 -q -n -t 3"); // 非阻塞獲取IPexec_cmd(cmd);sleep(2); // 等待獲取結果networks[NETWORK_ETH].tty_dev[0] = '\0';networks[NETWORK_ETH].connected = 0;networks[NETWORK_ETH].signal_strength = 0;networks[NETWORK_ETH].quality = 0;memset(&networks[NETWORK_ETH].eth_stats, 0, sizeof(EthStats));networks[NETWORK_ETH].ping_loss_rate = 100.0;// 初始化WiFi(wlan0)LOG_NET("WiFi配置文件路徑: %s\n", wpa_conf);networks[NETWORK_WIFI].type = NETWORK_WIFI;snprintf(networks[NETWORK_WIFI].ifname, sizeof(networks[NETWORK_WIFI].ifname), "wlan0");networks[NETWORK_WIFI].tty_dev[0] = '\0'; // WiFi無需串口networks[NETWORK_WIFI].connected = 0;networks[NETWORK_WIFI].signal_strength = 0;networks[NETWORK_WIFI].quality = 0;memset(&networks[NETWORK_WIFI].eth_stats, 0, sizeof(EthStats));networks[NETWORK_WIFI].ping_loss_rate = 100.0;// 初始化4G(eth1,配置串口設備)networks[NETWORK_4G].type = NETWORK_4G;snprintf(networks[NETWORK_4G].ifname, sizeof(networks[NETWORK_4G].ifname), "eth1");snprintf(networks[NETWORK_4G].tty_dev, sizeof(networks[NETWORK_4G].tty_dev), "/dev/ttyUSB1");if (access(networks[NETWORK_4G].tty_dev, R_OK | W_OK) != 0){LOG_NET("4G串口設備不存在: %s\n", networks[NETWORK_4G].tty_dev);}networks[NETWORK_4G].connected = 0;networks[NETWORK_4G].signal_strength = 0;networks[NETWORK_4G].quality = 0;memset(&networks[NETWORK_4G].eth_stats, 0, sizeof(EthStats));networks[NETWORK_4G].ping_loss_rate = 100.0;
}// 檢查網絡接口物理連接狀態
int check_link_status(const char *ifname)
{char path[128], buf[16];snprintf(path, sizeof(path), "/sys/class/net/%s/carrier", ifname);FILE *fp = fopen(path, "r");if (!fp)return 0;int ret = (fgets(buf, sizeof(buf), fp) && buf[0] == '1') ? 1 : 0;fclose(fp);return ret;
}// 從/sys文件讀取數值
unsigned long read_stat(const char *ifname, const char *stat_name)
{char path[128];FILE *fp;char buf[64];snprintf(path, sizeof(path), "/sys/class/net/%s/statistics/%s", ifname, stat_name);fp = fopen(path, "r");if (!fp)return 0;fgets(buf, sizeof(buf), fp);fclose(fp);return strtoul(buf, NULL, 10);
}// 讀取網口速率
int read_speed(const char *ifname)
{char path[128];FILE *fp;char buf[64];snprintf(path, sizeof(path), "/sys/class/net/%s/speed", ifname);fp = fopen(path, "r");if (!fp)return -1;fgets(buf, sizeof(buf), fp);fclose(fp);return atoi(buf);
}// Ping 測試,返回丟包率(百分比)
float ping_test(const char *ip, const char *ifname)
{char cmd[256];if (ifname && strlen(ifname) > 0){// -I 綁定接口(busybox ping 支持);把 stderr 丟掉,取包含 "packet loss" 的行snprintf(cmd, sizeof(cmd),"ping -I %s -c 3 -W 1 %s 2>/dev/null | grep -m1 'packet loss' || true",ifname, ip);}else{snprintf(cmd, sizeof(cmd),"ping -c 3 -W 1 %s 2>/dev/null | grep -m1 'packet loss' || true",ip);}FILE *fp = popen(cmd, "r");if (!fp)return 100.0f;char line[4096] = {0}, buf[256];while (fgets(buf, sizeof(buf), fp)){// 將所有行拼進一個緩沖再解析,保險起見strncat(line, buf, sizeof(line) - strlen(line) - 1);}pclose(fp);if (strlen(line) == 0)return 100.0f;float loss = 100.0f;// 常見格式解析嘗試if (sscanf(line, "%*d packets transmitted, %*d received, %f%% packet loss", &loss) == 1){return loss;}if (sscanf(line, "%*d packets transmitted, %*d packets received, %f%% packet loss", &loss) == 1){return loss;}// 回退解析:找到 '%' 前的數字char *pct = strchr(line, '%');if (pct){char *s = pct - 1;while (s >= line && (isdigit((unsigned char)*s) || *s == '.'))s--;s++;if (s < pct){char num[32] = {0};int n = pct - s;if (n > 0 && n < (int)sizeof(num)){strncpy(num, s, n);num[n] = '\0';loss = atof(num);return loss;}}}return 100.0f;
}// 獲取指定網卡的默認網關(修復版:移除head依賴)
int get_gateway_by_ifname(const char *ifname, char *gateway, size_t size)
{if (!ifname || !gateway)return -1;char cmd[128];// 關鍵修改:用awk 'NR==1'替代head -n1,兼容所有系統snprintf(cmd, sizeof(cmd),"ip route show dev %s | grep '^default' | awk 'NR==1 {print $3}'",ifname);FILE *fp = popen(cmd, "r");if (!fp){perror("popen failed");return -1;}// 讀取結果并清除換行符if (fgets(gateway, size, fp) != NULL){gateway[strcspn(gateway, "\n")] = '\0';}pclose(fp);// 輸出調試信息(方便排查)if (strlen(gateway) == 0){LOG_NET("[get_gateway] 未找到%s的默認網關,命令輸出為空\n", ifname);}else{LOG_NET("[get_gateway] %s的網關為:%s\n", ifname, gateway);}return (strlen(gateway) > 0) ? 0 : -1;
}// 檢測wifi是否關聯(ssid和pkg是否對,是否有)
int is_wifi_associated(const char *ifname)
{FILE *fp = fopen("/proc/net/wireless", "r");if (!fp){LOG_WIFI("[關聯檢查] 打開/proc/net/wireless失敗,視為未關聯\n");return 0;}char buf[256];char iface_buf[16];int link = 0;int found = 0;// 跳過前2行表頭(你的系統格式:Inter-| sta-| ... 和 face | tus | ...)if (!fgets(buf, sizeof(buf), fp))goto end; // 跳過第一行if (!fgets(buf, sizeof(buf), fp))goto end; // 跳過第二行// 解析數據行:wlan0: 0000    0     0     0 ...(第3個數值為link)while (fgets(buf, sizeof(buf), fp)){// 匹配格式:"wlan0: 0000    0     0     0 ..."// %15s匹配接口名(含冒號),%*d跳過狀態碼,%d讀取link值if (sscanf(buf, "%15s %*d %d", iface_buf, &link) == 2){// 去掉接口名后的冒號(如"wlan0:" → "wlan0")char *colon = strchr(iface_buf, ':');if (colon)*colon = '\0';// 匹配目標接口名if (strcmp(iface_buf, ifname) == 0){found = 1;break;}}}
end:fclose(fp);// 你的系統中link=0表示未關聯,>0表示已關聯if (found){LOG_WIFI("[關聯檢查] %s link值=%d → %s\n",ifname, link, (link > 0) ? "已關聯" : "未關聯");return (link > 0) ? 1 : 0;}else{LOG_WIFI("[關聯檢查] 未找到%s的記錄,視為未關聯\n", ifname);return 0;}
}// 獲取WiFi信號強度
int get_wifi_strength(const char *ifname)
{FILE *fp;char buf[256];int level = -120; // 默認極差信號char iface_buf[16];int found = 0;// 關鍵:先檢查wlan0是否存在(避免無效掃描)if (access("/sys/class/net/wlan0", F_OK) != 0){LOG_NET("[WiFi解析] 接口%s不存在\n", ifname);return 0;}// 關鍵:強制觸發WiFi掃描(確保能發現熱點,獲取真實信號)system("ifconfig wlan0 up > /dev/null 2>&1"); // 確保接口激活fp = fopen("/proc/net/wireless", "r");if (!fp){LOG_NET("[WiFi解析] 打開/proc/net/wireless失敗: %m\n");return 0;}// 跳過前2行表頭(匹配日志中的格式)fgets(buf, sizeof(buf), fp);fgets(buf, sizeof(buf), fp);// 解析格式:wlan0: 0000  30  -70  -95 → 第4個字段是level(-70)while (fgets(buf, sizeof(buf), fp)){// 用%*f處理帶小數點的數值(link和level)float level_float; // 臨時存儲帶小數的levelif (sscanf(buf, "%15s %*d %*f %f", iface_buf, &level_float) >= 2){char *colon = strchr(iface_buf, ':');if (colon)*colon = '\0';if (strcmp(iface_buf, ifname) == 0){level = (int)level_float; // 轉為整數found = 1;break;}}}fclose(fp);// 處理異常值(排除0或無效值)if (!found || level == 0 || level > -30 || level < -120){LOG_NET("[WiFi解析] 信號異常(level=%d),重置為-80dBm\n", level);level = -80;}// 計算信號評分(-120→0,-30→100,跨度90dBm)int strength = (level + 120) * 100 / 90;strength = (strength < 0) ? 0 : (strength > 100) ? 100: strength;// 打印調試信息,確認解析結果LOG_NET("[WiFi解析] %s: level=%d dBm → 信號評分=%d/100\n", ifname, level, strength);return strength;
}// 獲取4G信號強度
int get_4g_strength(const char *tty_dev)
{char resp[512] = {0};if (send_at_command(tty_dev, "AT+CSQ", resp, sizeof(resp)) <= 0){return 0;}// 定位到 "+CSQ:" 子串char *p = strstr(resp, "+CSQ:");if (!p)p = resp; // 沒找到,就從頭解析// 去掉前導空白和回車while (*p && (*p == '\r' || *p == '\n' || *p == ' '))p++;int rssi = -1, ber = -1;if (sscanf(p, "+CSQ: %d,%d", &rssi, &ber) >= 1 ||sscanf(p, "+CSQ: %d", &rssi) == 1){if (rssi >= 0 && rssi <= 31)return (rssi * 100) / 31;}return 0;
}// 4G模組發送AT命令并返回響應
static int send_at_command(const char *tty_dev, const char *cmd, char *resp, int resp_len)
{if (!tty_dev || !cmd || !resp || resp_len <= 0)return -1;int fd = open(tty_dev, O_RDWR | O_NOCTTY | O_NDELAY);if (fd < 0){LOG_NET("4G串口打開失敗:%s (%m)\n", tty_dev);return -1;}// 配置串口struct termios ts;if (tcgetattr(fd, &ts) != 0){close(fd);return -1;}cfsetispeed(&ts, B115200);cfsetospeed(&ts, B115200);ts.c_cflag &= ~PARENB;ts.c_cflag &= ~CSTOPB;ts.c_cflag &= ~CSIZE;ts.c_cflag |= CS8;ts.c_cflag |= CLOCAL | CREAD;ts.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);ts.c_oflag &= ~OPOST;// 設置非阻塞讀超時(可選):使用 select 超時替代 VTIME/VMINtcsetattr(fd, TCSANOW, &ts);tcflush(fd, TCIFLUSH);// 發送 AT 命令char at_cmd[256];snprintf(at_cmd, sizeof(at_cmd), "%s\r\n", cmd);write(fd, at_cmd, strlen(at_cmd));// 讀響應:用 select 循環,最多等待 total_timeout 秒int total_timeout_ms = 1200; // 總等待時間 1200ms(可調)int elapsed_ms = 0;int chunk_timeout_ms = 200;int pos = 0;memset(resp, 0, resp_len);while (elapsed_ms < total_timeout_ms && pos < resp_len - 1){fd_set rfds;struct timeval tv;FD_ZERO(&rfds);FD_SET(fd, &rfds);tv.tv_sec = chunk_timeout_ms / 1000;tv.tv_usec = (chunk_timeout_ms % 1000) * 1000;int rv = select(fd + 1, &rfds, NULL, NULL, &tv);if (rv > 0 && FD_ISSET(fd, &rfds)){int r = read(fd, resp + pos, resp_len - 1 - pos);if (r > 0){pos += r;// 如果返回包含 "OK" 或 "ERROR",可以立即結束resp[pos] = '\0';if (strstr(resp, "\r\nOK\r\n") || strstr(resp, "OK\r\n") || strstr(resp, "\nOK\n") || strstr(resp, "ERROR") || strstr(resp, "+CME ERROR")){break;}}else{// 無數據,短暫停后重試usleep(100 * 1000);}}else{// select 超時elapsed_ms += chunk_timeout_ms;}}// 最終確保以 NUL 結尾resp[resp_len - 1] = '\0';close(fd);return pos;
}// 獲取當前網口統計信息
void get_eth_stats(const char *ifname, EthStats *stats, EthStats *prev_stats)
{EthStats temp = *stats;stats->rx_packets = read_stat(ifname, "rx_packets");stats->tx_packets = read_stat(ifname, "tx_packets");stats->rx_errors = read_stat(ifname, "rx_errors");stats->tx_errors = read_stat(ifname, "tx_errors");stats->rx_dropped = read_stat(ifname, "rx_dropped");stats->tx_dropped = read_stat(ifname, "tx_dropped");stats->speed = read_speed(ifname);if (prev_stats->rx_packets > 0){unsigned long rx_total = stats->rx_packets - prev_stats->rx_packets;unsigned long rx_drop = stats->rx_dropped - prev_stats->rx_dropped;stats->rx_loss_rate = (rx_total > 0) ? (float)rx_drop / rx_total * 100 : 0.0;}else{stats->rx_loss_rate = 0.0;}if (prev_stats->tx_packets > 0){unsigned long tx_total = stats->tx_packets - prev_stats->tx_packets;unsigned long tx_drop = stats->tx_dropped - prev_stats->tx_dropped;stats->tx_loss_rate = (tx_total > 0) ? (float)tx_drop / tx_total * 100 : 0.0;}else{stats->tx_loss_rate = 0.0;}*prev_stats = temp;
}// 等待網絡接口獲取IP(新增:動態檢測就緒狀態)
int wait_network_ready(const char *ifname, int timeout)
{int retries = timeout;char cmd[128];char buf[64];while (retries-- > 0){// 檢查接口是否獲取到有效IP(排除169.254.x.x臨時IP)snprintf(cmd, sizeof(cmd),"ip addr show %s | grep 'inet ' | grep -v '169.254' | wc -l", ifname);FILE *fp = popen(cmd, "r");if (fp){if (fgets(buf, sizeof(buf), fp) && atoi(buf) > 0){pclose(fp);return 0; // 已獲取IP,就緒}pclose(fp);}sleep(1); // 每秒檢測一次}return -1; // 超時未就緒
}// 三網切換評分邏輯
int calculate_quality(NetworkType type, NetworkStatus *net)
{char gateway[16] = {0};float ping_loss = 100.0f;// 步驟1:檢查物理鏈路和關聯狀態(WiFi新增關聯檢查)if (type == NETWORK_WIFI){// 未關聯直接返回0分if (!is_wifi_associated(net->ifname)){LOG_NET("[WiFi評分] 未關聯,質量直接記為0\n");return 0;}}else if (type != NETWORK_4G){// 網口的物理鏈路檢查(原有邏輯不變)int link = check_link_status(net->ifname);if (!link)return 0;}if (type == NETWORK_4G){// 4G用公網IP(如8.8.8.8),無需網關(4G模組自動處理路由)ping_loss = ping_test("8.8.8.8", net->ifname);}else{// 網口/WiFi:獲取自身網關IP,Ping網關(必可達)if (get_gateway_by_ifname(net->ifname, gateway, sizeof(gateway)) == 0 && strlen(gateway) > 0){ping_loss = ping_test(gateway, net->ifname);LOG_NET("[%s Ping] 目標網關: %s, 丟包率: %.1f%%\n", net->ifname, gateway, ping_loss);}else{// 未獲取到網關(如IP未分配),視為Ping失敗ping_loss = 100.0f;LOG_NET("[%s Ping] 未獲取到網關,丟包率: 100%%\n", net->ifname);}}net->ping_loss_rate = ping_loss;// 步驟2:根據網絡類型做丟包率容錯(保留你原有的WiFi優化邏輯)// 非WiFi網絡(網口/4G):丟包率>50%視為不可用,直接返回0分if (type != NETWORK_WIFI && net->ping_loss_rate > 50.0){return 0;}// WiFi網絡:即使丟包率>50%,也不直接判0,保留信號強度的基礎評分(適配初始激活場景)if (type == NETWORK_WIFI && net->ping_loss_rate > 50.0){net->ping_loss_rate = 100.0; // 明確Ping結果,但不影響基礎評分}// 步驟3:根據網絡類型計算綜合評分(保留你原有的權重邏輯,適配測試需求)int quality = 0;if (type == NETWORK_ETH){// 網口:速率(30%)+ 接收丟包率(30%)+ Ping穩定性(40%)int speed_score = 0;if (net->eth_stats.speed >= 1000)speed_score = 100;else if (net->eth_stats.speed >= 100)speed_score = 80;else if (net->eth_stats.speed >= 10)speed_score = 50;int loss_score = 100 - (int)(net->eth_stats.rx_loss_rate * 2);loss_score = (loss_score < 0) ? 0 : loss_score;int ping_score = 100 - (int)net->ping_loss_rate;ping_score = (ping_score < 0) ? 0 : ping_score;quality = (int)(speed_score * 0.3 + loss_score * 0.3 + ping_score * 0.4);}else if (type == NETWORK_WIFI){// WiFi:信號強度(80%)+ Ping(20%),適配初始激活時Ping不穩定的場景// quality = (int)(net->signal_strength * 0.8 + (100 - (int)net->ping_loss_rate) * 0.2);// 關鍵修復:當Ping完全失敗(100%丟包),強制限制最高評分(20分,低于30分的“可用”閾值)int ping_score = (100 - (int)net->ping_loss_rate);if (net->ping_loss_rate >= 100.0){ping_score = 0; // 完全丟包時Ping評分為0}// 調整權重:降低信號強度權重,提高Ping有效性權重quality = (int)(net->signal_strength * 0.5 + ping_score * 0.5);// 額外保險:若完全無法Ping通,直接限制為20分(確保<30分,標記為未連接)if (net->ping_loss_rate >= 100.0){quality = 20;}}else if (type == NETWORK_4G){// 4G:信號(50%)+ Ping(50%),均衡判斷無線穩定性quality = (int)(net->signal_strength * 0.5 + (100 - (int)net->ping_loss_rate) * 0.5);}// 限制評分范圍在0-100(避免計算溢出或異常值)quality = (quality < 0) ? 0 : quality;quality = (quality > 100) ? 100 : quality;return quality;
}// 更新所有網絡狀態
void update_network_status()
{pthread_mutex_lock(&network_mutex);EthStats prev_eth_stats = networks[NETWORK_ETH].eth_stats;char buffer[128];// 遍歷所有網絡(網口、WiFi、4G)for (int i = 0; i < NETWORK_MAX; i++){NetworkStatus *st = &networks[i];st->connected = 0; // 初始化為未連接st->quality = 0;   // 初始化為0分// 1. 先檢查接口是否啟用(UP狀態)char cmd[128];snprintf(cmd, sizeof(cmd), "ip link show %s | grep 'UP'", st->ifname);FILE *fp = popen(cmd, "r");int is_up = (fp && fgets(buffer, sizeof(buffer), fp)) ? 1 : 0;if (fp)pclose(fp);// 接口未啟用,直接跳過后續邏輯if (!is_up){st->connected = 0;st->quality = 0;continue;}// 2. 按網絡類型執行具體檢測if (i == NETWORK_ETH){// 網口:更新統計信息 + 檢測IP + 綁定網口Ping測試get_eth_stats(st->ifname, &st->eth_stats, &prev_eth_stats);// 檢查是否有有效IP(排除169.254臨時IP)snprintf(cmd, sizeof(cmd), "ip -4 addr show %s | grep 'inet ' | grep -v '169.254' | wc -l", st->ifname);fp = popen(cmd, "r");char buf[16];int has_ip = 0;if (fp){if (fgets(buf, sizeof(buf), fp)){has_ip = (atoi(buf) > 0) ? 1 : 0;}pclose(fp);}// 若網口物理連接但無IP,主動重試DHCPif (!has_ip && check_link_status(st->ifname)){LOG_NET("[eth0] 物理連接已恢復,重試獲取IP...\n");snprintf(cmd, sizeof(cmd), "udhcpc -i %s -q -n -t 3", st->ifname);exec_cmd(cmd);sleep(1); // 等待獲取結果}}else if (i == NETWORK_WIFI){// WiFi:獲取信號強度st->signal_strength = get_wifi_strength(st->ifname);// 新增:檢查關聯狀態,未關聯則強制信號強度為0int associated = is_wifi_associated(st->ifname);if (!associated){LOG_NET("[WiFi狀態] %s 未關聯,強制信號強度為0\n", st->ifname);st->signal_strength = 0; // 信號為0則質量評分必然<30}}else if (i == NETWORK_4G){// 4G:檢測IP + 獲取信號 + Ping測試snprintf(cmd, sizeof(cmd), "ip -4 addr show %s | grep 'inet '", st->ifname);fp = popen(cmd, "r");int has_ip = (fp && fgets(buffer, sizeof(buffer), fp)) ? 1 : 0;if (fp)pclose(fp);if (has_ip){st->signal_strength = get_4g_strength(st->tty_dev);}}// 3. 關鍵:為當前網絡計算質量評分(必須在for循環內!)st->quality = calculate_quality(st->type, st);// 質量>30分為“已連接”,否則“未連接”st->connected = (st->quality > 30) ? 1 : 0;st->last_check_time = time(NULL);}pthread_mutex_unlock(&network_mutex);
}// 切換到指定網絡具體實現
int switch_to_network(NetworkType type)
{if (type >= NETWORK_MAX)return -1;if (current_network == type)return 0;LOG_NET("切換到網絡: %d (%s)\n", type, networks[type].ifname);char cmd[512];// 1. 清理當前網絡if (current_network != NETWORK_MAX){LOG_NET("清理當前網絡: %s\n", networks[current_network].ifname);if (current_network == NETWORK_WIFI){// WiFi 必須徹底關掉,避免搶路由/耗電exec_cmd("killall wpa_supplicant 2>/dev/null");snprintf(cmd, sizeof(cmd),"ifconfig %s down; ip addr flush dev %s; ip route del default 2>/dev/null",networks[current_network].ifname,networks[current_network].ifname);exec_cmd(cmd);}else if (current_network == NETWORK_4G){// 4G 也需要徹底關掉snprintf(cmd, sizeof(cmd),"ifconfig %s down; ip addr flush dev %s; ip route del default 2>/dev/null",networks[current_network].ifname,networks[current_network].ifname);exec_cmd(cmd);}else if (current_network == NETWORK_ETH){// 網口切換出去時,不要 down,保持 up 用于熱插拔snprintf(cmd, sizeof(cmd),"ip addr flush dev %s; ip route del default 2>/dev/null",networks[current_network].ifname);exec_cmd(cmd);}}int ret = -1;// 2. 啟用目標網絡printf("啟用網絡: %s\n", networks[type].ifname);if (type == NETWORK_ETH){// 原邏輯:僅執行一次 udhcpc,且未清理舊配置// 新增:先清理舊IP和路由,再強制重新獲取IPsnprintf(cmd, sizeof(cmd),"ifconfig %s up; ""ip addr flush dev %s; "                                       // 清除舊IP"ip route del default dev %s 2>/dev/null; "                    // 清除舊路由"udhcpc -i %s -q -n -t 5 -s /usr/share/udhcpc/default.script", // 重試5次networks[type].ifname,networks[type].ifname,networks[type].ifname,networks[type].ifname);ret = exec_cmd(cmd);}else if (type == NETWORK_WIFI){snprintf(cmd, sizeof(cmd),"killall wpa_supplicant 2>/dev/null; ""ifconfig %s up; ""chmod +x /system/init/wpa_supplicant; ""/system/init/wpa_supplicant -B -i %s -c /system/init/wpa_supplicant.conf; ""sleep 4; ""udhcpc -i %s -q -n -t 3; ""ip route add default dev %s metric 200",networks[type].ifname,networks[type].ifname,networks[type].ifname,networks[type].ifname);LOG_NET("[WiFi激活命令] %s\n", cmd);ret = exec_cmd(cmd);}else if (type == NETWORK_4G){char resp[256];LOG_NET("[4G激活] 開始 AT 流程...\n");// 1. 檢查 SIM 卡狀態send_at_command(networks[type].tty_dev, "AT+CPIN?", resp, sizeof(resp));if (strstr(resp, "READY")){LOG_NET("[4G] SIM 卡已就緒\n");}else{LOG_NET("[4G錯誤] SIM 卡未就緒或不存在: %s\n", resp);return -1;}// 2. 檢查網絡注冊狀態send_at_command(networks[type].tty_dev, "AT+CREG?", resp, sizeof(resp));if (strstr(resp, ",1") || strstr(resp, ",5")){LOG_NET("[4G] 已注冊到網絡\n");}else{LOG_NET("[4G錯誤] 未注冊到網絡: %s\n", resp);return -1;}// 3. 檢查并附著數據網絡send_at_command(networks[type].tty_dev, "AT+CGATT?", resp, sizeof(resp));if (strstr(resp, "1")){LOG_NET("[4G] 已附著網絡\n");}else{LOG_NET("[4G] 未附著,嘗試附著...\n");send_at_command(networks[type].tty_dev, "AT+CGATT=1", resp, sizeof(resp));sleep(2);}// 4. 設置 PDP 上下文 (APN,可根據運營商修改)send_at_command(networks[type].tty_dev, "AT+CGDCONT=1,\"IP\",\"CMNET\"", resp, sizeof(resp));// 5. 啟動數據連接 (不同模組命令不同,這里給出常見兩種)send_at_command(networks[type].tty_dev, "AT+CGACT=1,1", resp, sizeof(resp));send_at_command(networks[type].tty_dev, "AT+NETOPEN", resp, sizeof(resp));sleep(3);// 6. 啟用網卡并獲取 IPsnprintf(cmd, sizeof(cmd), "ifconfig %s up", networks[type].ifname);exec_cmd(cmd);snprintf(cmd, sizeof(cmd), "udhcpc -i %s -q -n -t 3", networks[type].ifname);exec_cmd(cmd);// 保險起見,清掉舊的 default 再加snprintf(cmd, sizeof(cmd), "ip route del default 2>/dev/null; ip route add default dev %s metric 50", networks[type].ifname);exec_cmd(cmd);}// 3. 等待網絡就緒(最多10秒)if (wait_network_ready(networks[type].ifname, 10) != 0){// 再次確認一下是否已經分到IPsnprintf(cmd, sizeof(cmd), "ip -4 addr show %s | grep 'inet ' | wc -l", networks[type].ifname);FILE *fp = popen(cmd, "r");int has_ip = 0;if (fp){char buf[16];if (fgets(buf, sizeof(buf), fp)){has_ip = (atoi(buf) > 0);}pclose(fp);}if (!has_ip){LOG_NET("警告:%s 啟用后未獲取IP,切換可能失敗\n", networks[type].ifname);return -1;}}// 4. 更新當前網絡current_network = type;return ret;
}// 三網切換邏輯:按優先級(網口→WiFi→4G)選擇最優網絡
void check_and_switch_network(int force_4g)
{update_network_status(); // 先更新所有網絡的最新狀態(連接、信號、丟包率等)pthread_mutex_lock(&network_mutex);// 修復7:如果強制4G,直接激活4G并返回if (force_4g){LOG_NET("[強制切換] 配網失敗,強制激活4G\n");switch_to_network(NETWORK_4G); // 直接切換到4Gpthread_mutex_unlock(&network_mutex);return;}static int wifi_retry_count = 0; // WiFi重試計數器(避免無限重試)// 打印當前各網絡的可用狀態(調試用,可選保留)LOG_NET("[切換檢查] 網口可用:%d, WiFi可用:%d, 4G可用:%d\n",networks[NETWORK_ETH].connected,networks[NETWORK_WIFI].connected,networks[NETWORK_4G].connected);NetworkType best_network = NETWORK_MAX; // 初始化“最優網絡”為“無”// === 1. 按優先級選擇最優網絡(核心邏輯)===// 優先級1:網口(物理連接+質量達標才算可用)if (networks[NETWORK_ETH].connected){best_network = NETWORK_ETH;}// 優先級2:WiFi(信號+質量達標才算可用)else if (networks[NETWORK_WIFI].connected){best_network = NETWORK_WIFI;}// 優先級3:4G(最后兜底,僅當網口/WiFi都不可用時使用)else if (networks[NETWORK_4G].connected){best_network = NETWORK_4G;}// === 2. 處理不同場景的切換邏輯 ===if (best_network == NETWORK_MAX){// 先判斷 wlan0 接口是否存在if (access("/sys/class/net/wlan0", F_OK) == 0){// wlan0 存在才重試 WiFiif (wifi_retry_count < 2){LOG_NET("當前網口不可用,嘗試啟動WiFi(第%d次重試)...\n", wifi_retry_count + 1);switch_to_network(NETWORK_WIFI);wifi_retry_count++;}else{LOG_NET("WiFi已重試%d次仍失敗,嘗試啟動4G兜底...\n", wifi_retry_count);switch_to_network(NETWORK_4G);wifi_retry_count = 0;}}else{// wlan0 根本不存在 → 直接啟用 4GLOG_NET("WiFi接口不存在,直接啟用4G兜底...\n");switch_to_network(NETWORK_4G);wifi_retry_count = 0;}}// 新增:檢查WiFi是否真正可用(不僅看評分,還要看Ping結果)else if (best_network == NETWORK_WIFI && networks[NETWORK_WIFI].ping_loss_rate >= 100.0){LOG_NET("[選網邏輯] WiFi完全丟包,視為不可用\n");best_network = NETWORK_MAX; // 強制重新選擇網絡}// 場景2:檢測到更高優先級的網絡 → 強制切換(例如:網口突然插入,從WiFi切到網口)else if (best_network != current_network){LOG_NET("[優先級切換] 從當前網絡(%s)切換到更高優先級網絡(%s)\n",current_network != NETWORK_MAX ? networks[current_network].ifname : "無",networks[best_network].ifname);switch_to_network(best_network); // 切換到最優網絡wifi_retry_count = 0;            // 切換成功,重置WiFi重試計數器}// 場景3:當前網絡可用,但質量過低(<20分)→ 嘗試切換到其他可用網絡else if (current_network != NETWORK_MAX && networks[current_network].quality < 20){LOG_NET("[質量過低] 當前網絡(%s)質量=%d/100,嘗試尋找替代網絡...\n",networks[current_network].ifname, networks[current_network].quality);// 遍歷所有網絡,找第一個可用的替代網絡(仍遵循優先級)for (int i = 0; i < NETWORK_MAX; i++){if (i != current_network && networks[i].connected){LOG_NET("[替代切換] 發現可用網絡(%s),切換過去\n", networks[i].ifname);switch_to_network(i);wifi_retry_count = 0;break;}}}// 場景4:當前網絡是最優且質量達標 → 保持不變else{wifi_retry_count = 0; // 重置計數器LOG_NET("[保持網絡] 當前網絡(%s)為最優,質量=%d/100\n",networks[current_network].ifname, networks[current_network].quality);}pthread_mutex_unlock(&network_mutex);
}// 打印詳細網絡狀態
void print_network_status()
{pthread_mutex_lock(&network_mutex);LOG_NET("\n===== 網絡狀態報告 =====");for (int i = 0; i < NETWORK_MAX; i++){LOG_NET("\n--- %s (%s) ---\n",i == NETWORK_ETH ? "網口" : (i == NETWORK_WIFI ? "WiFi" : "4G"),networks[i].ifname);LOG_NET("  連接狀態: %s\n", networks[i].connected ? "已連接" : "未連接");LOG_NET("  質量評分: %d/100\n", networks[i].quality);LOG_NET("  Ping丟包率: %.1f%%\n", networks[i].ping_loss_rate);if (i == NETWORK_ETH){LOG_NET("  鏈路速率: %d Mbps\n", networks[i].eth_stats.speed);LOG_NET("  接收丟包率: %.2f%%\n", networks[i].eth_stats.rx_loss_rate);LOG_NET("  發送丟包率: %.2f%%\n", networks[i].eth_stats.tx_loss_rate);}else{LOG_NET("  信號強度: %d/100\n", networks[i].signal_strength);}}LOG_NET("當前使用網絡: %s\n",current_network != NETWORK_MAX ? networks[current_network].ifname : "無");LOG_NET("========================\n\n");pthread_mutex_unlock(&network_mutex);
}// =====================================================================================================================================================================
// =====================================================================================================================================================================
// =====================================================================================================================================================================// 網絡監控線程函數
void *network_monitor_thread(void *arg)
{int loop_cnt = 0; // 循環計數器while (running){loop_cnt++;// 打印帶時間戳的循環日志,確保能看到線程在運行time_t now = time(NULL);struct tm *t = localtime(&now);LOG_NET("[%02d:%02d:%02d] [監控線程] 第 %d 次循環,當前網絡:%s\n",t->tm_hour, t->tm_min, t->tm_sec,loop_cnt,current_network != NETWORK_MAX ? networks[current_network].ifname : "無");check_and_switch_network(0);print_network_status();// 改用 usleep + 循環檢查 running,避免長時間 sleep 無法中斷int sleep_ms = (current_network == NETWORK_4G) ? 2000 : 3000;while (sleep_ms > 0 && running){usleep(100000); // 每次休眠 100ms,醒來檢查 runningsleep_ms -= 100;}}LOG_NET("[監控線程] 收到退出信號,線程停止\n");return NULL;
}// 配網監控線程函數
void *wifi_config_thread(void *arg)
{// 初始化配網文件時間戳(修復初始誤觸發)struct stat st;if (stat(WIFI_CONFIG_FILE, &st) == 0){last_config_file_mtime = st.st_mtime;LOG_WIFI("初始化時間戳:last_config_file_mtime=%ld\n", last_config_file_mtime);}else{last_config_file_mtime = 0;LOG_WIFI("初始化時間戳:文件不存在,設為0\n");}// 初始檢查配網成功文件if (access(WIFI_CONFIG_SUCCESS_FILE, F_OK) == 0){LOG_WIFI("初始檢查:檢測到配網成功文件,僅監聽文件刪除事件\n");}else{LOG_WIFI("初始檢查:未檢測到配網成功文件,監聽配網文件更新\n");}while (running){pthread_mutex_lock(&network_mutex);int config_success = (access(WIFI_CONFIG_SUCCESS_FILE, F_OK) == 0);if (!config_success){// 檢查1:原有觸發邏輯(正常文件更新)int trigger = should_trigger_config();if (trigger){if (wifi_fsm.current_state == WIFI_CONFIG_STATE_IDLE){trigger_config = 1;LOG_WIFI("[配網線程] 狀態機空閑,設置trigger_config=1(正常觸發)\n");}else{LOG_WIFI("[配網線程] 狀態機非IDLE(狀態=%d),立即重置并觸發\n",wifi_fsm.current_state);// 無論之前是什么狀態,強制重置(確保能響應新配置)reset_wifi_fsm();trigger_config = 1;}}// 檢查2:主動檢測時間戳異常(當前時間 < 記錄時間)else{// 僅當文件存在時才檢查時間戳異常if (access(WIFI_CONFIG_FILE, F_OK) == 0 && stat(WIFI_CONFIG_FILE, &st) == 0){if (st.st_mtime < last_config_file_mtime){LOG_WIFI("[配網線程] 檢測到時間戳異常(當前=%ld < 記錄=%ld),強制觸發\n",st.st_mtime, last_config_file_mtime);// 重置記錄時間,避免重復觸發last_config_file_mtime = st.st_mtime;// 無論狀態機是否空閑,強制觸發if (wifi_fsm.current_state != WIFI_CONFIG_STATE_IDLE){reset_wifi_fsm();}trigger_config = 1;}}}}else{// 配網已成功,僅當成功文件被刪除時重新監聽if (access(WIFI_CONFIG_SUCCESS_FILE, F_OK) != 0){LOG_WIFI("[配網線程] 配網成功文件已刪除,重置監聽狀態\n");// 強制將記錄時間設為0,確保下次文件更新100%能觸發if (stat(WIFI_CONFIG_FILE, &st) == 0){last_config_file_mtime = 0; // 關鍵:不使用文件當前時間,直接設0LOG_WIFI("[配網線程] 重置時間戳為0(成功文件被刪除)\n");}else{last_config_file_mtime = 0;}}}// 處理配網觸發(確保trigger_config被立即處理)if (trigger_config){if (wifi_fsm.current_state == WIFI_CONFIG_STATE_IDLE){LOG_WIFI("[配網線程] 啟動配網狀態機,進入WAIT_FILE\n");wifi_fsm.current_state = WIFI_CONFIG_STATE_WAIT_FILE;wifi_fsm.retry_count = 0;trigger_config = 0;}else{LOG_WIFI("[配網線程] 等待狀態機空閑后觸發(當前狀態=%d)\n",wifi_fsm.current_state);}}// 檢查文件存在性并打印詳細時間戳(輔助調試)int test_exists = access(WIFI_CONFIG_FILE, F_OK);if (test_exists == 0 && stat(WIFI_CONFIG_FILE, &st) == 0){LOG_WIFI("[文件檢測] 存在性:%d,狀態機狀態:%d,當前時間:%ld,記錄時間:%ld\n",test_exists, wifi_fsm.current_state, st.st_mtime, last_config_file_mtime);}else{LOG_WIFI("[文件檢測] 存在性:%d,狀態機狀態:%d,記錄時間:%ld\n",test_exists, wifi_fsm.current_state, last_config_file_mtime);}pthread_mutex_unlock(&network_mutex);process_wifi_fsm(); // 驅動狀態機執行sleep(1);}LOG_WIFI("配網線程退出\n");return NULL;
}int main()
{pthread_t monitor_thread, fsm_thread;signal(SIGINT, signal_handler);signal(SIGTERM, signal_handler);init_networks();init_fifo();// 創建網絡監控線程if (pthread_create(&monitor_thread, NULL, network_monitor_thread, NULL) != 0){fprintf(stderr, "創建監控線程失敗\n");return 1;}// 創建配網監控線程if (pthread_create(&fsm_thread, NULL, wifi_config_thread, NULL) != 0){fprintf(stderr, "創建配網線程失敗\n");return 1;}pthread_join(monitor_thread, NULL);pthread_join(fsm_thread, NULL);printf("程序退出\n");return 0;
}

初版程序(配網監控代碼):

// 初始化FIFO(程序啟動時調用)
void init_fifo()
{// 檢查FIFO是否已存在,不存在則創建if (access(WIFI_CONFIG_FIFO, F_OK) != 0){if (mkfifo(WIFI_CONFIG_FIFO, 0666) == -1){LOG_WIFI("[FIFO初始化] 創建FIFO失敗: %m\n");}else{LOG_WIFI("[FIFO初始化] 成功創建FIFO: %s\n", WIFI_CONFIG_FIFO);}}else{LOG_WIFI("[FIFO初始化] FIFO已存在: %s\n", WIFI_CONFIG_FIFO);}
}// 重置狀態機(配網結束后調用)
void reset_wifi_fsm()
{pthread_mutex_lock(&network_mutex);wifi_fsm.current_state = WIFI_CONFIG_STATE_IDLE;memset(wifi_fsm.ssid, 0, sizeof(wifi_fsm.ssid));memset(wifi_fsm.psk, 0, sizeof(wifi_fsm.psk));wifi_fsm.retry_count = 0;trigger_config = 0;// 關鍵新增:重置文件修改時間記錄,確保下次上層寫文件能檢測到更新last_config_file_mtime = 0;pthread_mutex_unlock(&network_mutex);
}// // 檢查是否需要觸發配網
static int should_trigger_config()
{// 步驟1:配網成功文件存在 → 不觸發if (access(WIFI_CONFIG_SUCCESS_FILE, F_OK) == 0){LOG_WIFI("[觸發檢查] 配網成功文件存在,不觸發配網\n");return 0;}// 步驟2:配網文件不存在 → 不觸發(增加重試)struct stat file_stat;int stat_ret = -1;for (int i = 0; i < 3; i++){stat_ret = stat(WIFI_CONFIG_FILE, &file_stat);if (stat_ret == 0)break;LOG_WIFI("[觸發檢查] 第%d次stat配網文件失敗,err=%d\n", i + 1, errno);usleep(100000); // 100ms重試,確保上層寫文件完成}if (stat_ret != 0){LOG_WIFI("[觸發檢查] 配網文件不存在或無法訪問,不觸發配網\n");return 0;}// 步驟3:時間戳異常容錯(核心修復)// 若當前文件時間 < 記錄時間(可能是系統時間回退或記錄未重置),強制認為文件已更新if (file_stat.st_mtime < last_config_file_mtime){LOG_WIFI("[觸發檢查] 時間戳異常(當前=%ld,記錄=%ld),強制認為文件已更新\n",file_stat.st_mtime, last_config_file_mtime);last_config_file_mtime = file_stat.st_mtime; // 重置記錄時間return 1;                                    // 觸發配網}// 步驟4:正常檢查文件是否更新if (file_stat.st_mtime <= last_config_file_mtime){LOG_WIFI("[觸發檢查] 配網文件未更新(當前=%ld,記錄=%ld),不觸發配網\n",file_stat.st_mtime, last_config_file_mtime);return 0;}// 所有條件滿足 → 觸發配網last_config_file_mtime = file_stat.st_mtime;LOG_WIFI("[觸發檢查] 配網文件已更新,觸發配網流程\n");return 1;
}// 更新配網狀態到FIFO
void update_wifi_config_status(const char *status)
{if (!status)return;// 以非阻塞模式打開FIFO(O_NONBLOCK避免無讀端時阻塞)int fd = open(WIFI_CONFIG_FIFO, O_WRONLY | O_NONBLOCK);if (fd == -1){// 無讀端時不報錯(上層可能未啟動),僅打印調試信息LOG_WIFI("[FIFO反饋] 寫入失敗(無讀端): %s\n", status);return;}// 寫入狀態(末尾加換行符,方便上層按行讀取)char buf[128];snprintf(buf, sizeof(buf), "%s\n", status);ssize_t bytes_written = write(fd, buf, strlen(buf));if (bytes_written == -1){LOG_WIFI("[FIFO反饋] 寫入失敗: %m\n");}else{LOG_WIFI("[FIFO反饋] 已發送狀態: %s", status); // 注意%s后已有換行}close(fd); // 每次寫入后關閉,避免句柄泄漏
}// 狀態機處理函數(核心邏輯)
void process_wifi_fsm()
{LOG_WIFI("[狀態機] 進入process_wifi_fsm(當前狀態:%d)\n", wifi_fsm.current_state); // 新增日志WifiConfigState next_state = wifi_fsm.current_state;int need_reset = 0;switch (wifi_fsm.current_state){case WIFI_CONFIG_STATE_IDLE:if (trigger_config){LOG_WIFI("[配網狀態機] 收到配網觸發信號,進入等待配網文件狀態\n");next_state = WIFI_CONFIG_STATE_WAIT_FILE;wifi_fsm.retry_count = 0;update_wifi_config_status("WAITING");}break;case WIFI_CONFIG_STATE_WAIT_FILE:LOG_WIFI("[狀態流轉] 當前狀態:WAIT_FILE\n");// 保持原有邏輯,增加access返回值日志int file_exists = access(WIFI_CONFIG_FILE, F_OK);LOG_WIFI("[WAIT_FILE] access()返回值:%d(0=存在,非0=不存在)\n", file_exists);if (file_exists == 0){LOG_WIFI("配網文件存在,開始讀取SSID和密碼\n");next_state = WIFI_CONFIG_STATE_READ_FILE;}else{LOG_WIFI("配網文件已被刪除,終止配網流程\n");next_state = WIFI_CONFIG_STATE_IDLE;}break;case WIFI_CONFIG_STATE_READ_FILE:{LOG_WIFI("[狀態流轉] 當前狀態:READ_FILE\n");FILE *fp = fopen(WIFI_CONFIG_FILE, "r");if (!fp){LOG_WIFI("讀取配網文件失敗,配網失敗\n");next_state = WIFI_CONFIG_STATE_FAILED;break;}// 解析SSID和密碼(適配“第一行SSID,第二行密碼”格式)char line[256];memset(wifi_fsm.ssid, 0, sizeof(wifi_fsm.ssid));memset(wifi_fsm.psk, 0, sizeof(wifi_fsm.psk));// 讀取第一行作為SSIDif (fgets(line, sizeof(line), fp) != NULL){line[strcspn(line, "\n")] = '\0'; // 去掉換行符strncpy(wifi_fsm.ssid, line, sizeof(wifi_fsm.ssid) - 1);}// 讀取第二行作為密碼if (fgets(line, sizeof(line), fp) != NULL){line[strcspn(line, "\n")] = '\0'; // 去掉換行符strncpy(wifi_fsm.psk, line, sizeof(wifi_fsm.psk) - 1);}fclose(fp);// 驗證文件內容是否有效if (strlen(wifi_fsm.ssid) == 0 || strlen(wifi_fsm.psk) == 0){LOG_WIFI("配網文件內容無效(SSID或密碼為空)\n");next_state = WIFI_CONFIG_STATE_FAILED;break;}LOG_WIFI("成功讀取配網信息:SSID=%s, PSK=***\n", wifi_fsm.ssid);next_state = WIFI_CONFIG_STATE_VALIDATE;break;}case WIFI_CONFIG_STATE_VALIDATE:{if (strlen(wifi_fsm.ssid) == 0 || strlen(wifi_fsm.psk) == 0){LOG_WIFI("[配網狀態機] SSID或密碼為空,驗證失敗\n");next_state = WIFI_CONFIG_STATE_FAILED;update_wifi_config_status("FAILED");}else if (strlen(wifi_fsm.ssid) > 32 || strlen(wifi_fsm.psk) > 64){LOG_WIFI("[配網狀態機] SSID或密碼長度超限,驗證失敗\n");next_state = WIFI_CONFIG_STATE_FAILED;update_wifi_config_status("FAILED");}else{LOG_WIFI("[配網狀態機] 配置驗證通過\n");next_state = WIFI_CONFIG_STATE_WRITE_CONF;}break;}case WIFI_CONFIG_STATE_WRITE_CONF:{FILE *fp = fopen(wpa_conf, "w");if (!fp){LOG_WIFI("[配網狀態機] 打開WiFi配置文件失敗:%m\n");if (wifi_fsm.retry_count < WIFI_CONFIG_RETRY_MAX){wifi_fsm.retry_count++;next_state = WIFI_CONFIG_STATE_WRITE_CONF;}else{next_state = WIFI_CONFIG_STATE_FAILED;update_wifi_config_status("FAILED");}break;}// fprintf(fp, "ctrl_interface=/var/run/wpa_supplicant\n");fprintf(fp, "update_config=1\n");fprintf(fp, "network={\n");fprintf(fp, "    ssid=\"%s\"\n", wifi_fsm.ssid);fprintf(fp, "    psk=\"%s\"\n", wifi_fsm.psk);fprintf(fp, "    key_mgmt=WPA-PSK\n");fprintf(fp, "}\n");fclose(fp);LOG_WIFI("[配網狀態機] WiFi配置文件寫入完成:%s\n", wpa_conf);next_state = WIFI_CONFIG_STATE_CONNECT;wifi_fsm.retry_count = 0;break;}case WIFI_CONFIG_STATE_CONNECT:{update_wifi_config_status("RUNNING");exec_cmd("killall wpa_supplicant 2>/dev/null;");sleep(1);char cmd[256] = {0};snprintf(cmd, sizeof(cmd),"/system/init/wpa_supplicant -B -i %s -c %s;",networks[NETWORK_WIFI].ifname, wpa_conf);int ret = exec_cmd(cmd);if (ret != 0){LOG_WIFI("[配網狀態機] wpa_supplicant啟動失敗(退出碼:%d)\n", ret);wifi_fsm.retry_count++; // 無論何種失敗,均累計重試if (wifi_fsm.retry_count < WIFI_CONFIG_RETRY_MAX){next_state = WIFI_CONFIG_STATE_CONNECT;}else{next_state = WIFI_CONFIG_STATE_FAILED;update_wifi_config_status("FAILED");}break;}snprintf(cmd, sizeof(cmd), "udhcpc -i %s -q -n -t 3", networks[NETWORK_WIFI].ifname);ret = exec_cmd(cmd);if (ret != 0){LOG_WIFI("[配網狀態機] DHCP獲取IP失敗退出碼:%d)\n", ret);wifi_fsm.retry_count++; // 無論何種失敗,均累計重試if (wifi_fsm.retry_count < WIFI_CONFIG_RETRY_MAX){next_state = WIFI_CONFIG_STATE_CONNECT;}else{next_state = WIFI_CONFIG_STATE_FAILED;update_wifi_config_status("FAILED");}break;}// 啟動和DHCP成功,進入檢查狀態(此時不重置retry_count,留待CHECK狀態判斷)LOG_WIFI("[配網狀態機] WiFi連接流程啟動完成,進入檢查狀態(當前重試次數:%d)\n", wifi_fsm.retry_count);next_state = WIFI_CONFIG_STATE_CHECK;break;}case WIFI_CONFIG_STATE_CHECK:{int associated = is_wifi_associated(networks[NETWORK_WIFI].ifname);int has_ip = (wait_network_ready(networks[NETWORK_WIFI].ifname, 3) == 0);if (associated && has_ip){LOG_WIFI("[配網狀態機] WiFi連接成功!\n");networks[NETWORK_WIFI].connected = 1;networks[NETWORK_WIFI].signal_strength = get_wifi_strength(networks[NETWORK_WIFI].ifname);wifi_fsm.retry_count = 0;next_state = WIFI_CONFIG_STATE_SUCCESS;update_wifi_config_status("SUCCESS");}else{// 關聯失敗或無IP(大概率密碼錯誤),累計重試LOG_WIFI("[配網狀態機] 連接失敗(關聯:%d,IP:%d),重試次數:%d\n",associated, has_ip, wifi_fsm.retry_count + 1);wifi_fsm.retry_count++;if (wifi_fsm.retry_count >= WIFI_CONFIG_RETRY_MAX){// 達到最大重試次數,終止配網LOG_WIFI("[配網狀態機] 已達最大重試次數(%d次),配網失敗\n", WIFI_CONFIG_RETRY_MAX);next_state = WIFI_CONFIG_STATE_FAILED;update_wifi_config_status("FAILED");}else{// 未達上限,返回CONNECT狀態重試next_state = WIFI_CONFIG_STATE_CONNECT;}}break;}case WIFI_CONFIG_STATE_SUCCESS:LOG_WIFI("[配網狀態機] 配網成功,創建成功標記文件,等待結束\n");// 創建空的成功文件(通過系統命令或文件操作)int fd = open(WIFI_CONFIG_SUCCESS_FILE, O_CREAT | O_WRONLY, 0644);if (fd >= 0){close(fd); // 僅創建空文件即可}else{LOG_WIFI("警告:創建配網成功文件失敗!\n");}next_state = WIFI_CONFIG_STATE_FINISH;break;case WIFI_CONFIG_STATE_FAILED:{// 1. 徹底清理WiFi資源,防止殘留進程繼續嘗試連接exec_cmd("killall wpa_supplicant 2>/dev/null;"); // 強制殺死WiFi進程exec_cmd("ifconfig wlan0 down");                 // 關閉WiFi接口LOG_WIFI("[配網狀態機] 配網失敗,已徹底清理WiFi進程和接口\n");// 2. 強制標記WiFi為不可用(覆蓋所有可能的判斷條件)networks[NETWORK_WIFI].connected = 0;          // 標記為未連接networks[NETWORK_WIFI].quality = 0;            // 質量強制為0(低于可用閾值)networks[NETWORK_WIFI].signal_strength = 0;    // 信號強度強制為0networks[NETWORK_WIFI].ping_loss_rate = 100.0; // 強制標記為完全丟包LOG_WIFI("[配網狀態機] WiFi已被強制標記為不可用(質量=0,丟包=100%)\n");// 3. 解鎖互斥鎖,準備切換到4G(增加重試機制確保成功)pthread_mutex_unlock(&network_mutex);int switch_ret = -1;const int MAX_RETRIES = 3; // 最多重試3次,提高成功率for (int i = 0; i < MAX_RETRIES; i++){// 直接調用4G切換函數,而非依賴通用切換邏輯switch_ret = switch_to_network(NETWORK_4G);if (switch_ret == 0){LOG_WIFI("[配網狀態機] 第%d次嘗試切換到4G成功\n", i + 1);break; // 成功則退出重試}else{LOG_WIFI("[配網狀態機] 第%d次嘗試切換到4G失敗,返回碼:%d\n", i + 1, switch_ret);sleep(2); // 失敗后間隔2秒重試,給模塊恢復時間}}// 4. 無論切換結果如何,記錄最終狀態(便于排查4G模塊問題)if (switch_ret != 0){LOG_WIFI("[配網狀態機] 所有嘗試切換到4G均失敗,需檢查4G模塊狀態\n");}// 重新加鎖,繼續狀態機流程pthread_mutex_lock(&network_mutex);// 3. 關鍵新增:無論4G切換是否成功,都重置文件時間戳last_config_file_mtime = 0;LOG_WIFI("[配網狀態機] 配網失敗,強制重置文件時間戳為0\n");next_state = WIFI_CONFIG_STATE_FINISH;break;}case WIFI_CONFIG_STATE_FINISH:LOG_WIFI("[配網狀態機] 配網流程結束,重置狀態機\n");need_reset = 1;update_wifi_config_status("WAITING");break;default:next_state = WIFI_CONFIG_STATE_IDLE;break;}wifi_fsm.current_state = next_state;if (need_reset){reset_wifi_fsm();need_reset = 0; // 清除重置標記}LOG_WIFI("[狀態機] 退出process_wifi_fsm(下一狀態:%d)\n", next_state); // 新增日志
}

三、優化初版程序代碼(持續更新中)

????????交付完初版能用的產品之后,不難發現其實我的代碼有很多的問題,之后的任務就是對代碼進行優化,讓其更加高效合理,以下是更改記錄。

優化點一:狀態機邏輯分散擴展性差,應改為狀態表

???????傳統switch-case的問題是:這四個要素分散在不同的 case 分支里(比如在case WAIT_FILE里寫 “檢測文件” 的觸發邏輯,再寫 “讀文件” 的動作,最后手動設next_state),狀態多了之后,分支冗長、流轉關系藏在代碼里,查錯要翻遍所有 case。

????????狀態表的核心是:用一個 “表格數組”,把每個狀態的「觸發條件→動作→目標狀態」打包成一行 “配置”,讓所有流轉規則一目了然。

????????這個 “表格” 就是state_table數組,每個元素是WifiStateTransition結構體 —— 這個結構體剛好對應上面的四要素:

// 狀態表的“一行配置”:對應一次完整的狀態轉換規則
typedef struct {WifiConfigState current_state;  // 1. 當前狀態(我在哪)TriggerFunc trigger;            // 2. 觸發條件(滿足什么才能走)WifiConfigState next_state;     // 3. 目標狀態(我要去哪)ActionFunc action;              // 4. 動作(走之前要做什么)
} WifiStateTransition;

再看你代碼中的state_table數組,每一行就是一條 “配網步驟規則”,比如:

static const WifiStateTransition state_table[] = {// 規則1:當前是IDLE狀態,觸發條件是“收到配網信號”,目標是WAIT_FILE,無動作{WIFI_CONFIG_STATE_IDLE, trigger_start_config, WIFI_CONFIG_STATE_WAIT_FILE, NULL},// 規則2:當前是WAIT_FILE狀態,觸發條件是“文件存在”,目標是READ_FILE,動作是“讀文件”{WIFI_CONFIG_STATE_WAIT_FILE, trigger_file_exist, WIFI_CONFIG_STATE_READ_FILE, action_read_file},// 規則3:當前是READ_FILE狀態,無條件觸發(trigger_any),目標是VALIDATE,動作是“驗證配置”{WIFI_CONFIG_STATE_READ_FILE, trigger_any, WIFI_CONFIG_STATE_VALIDATE, action_validate},// ... 其他規則 ...
};

????????現在你要查 “WAIT_FILE 狀態能轉到哪”,不用翻switch-case,直接看state_table的第二行就行 —— 這就是狀態表的核心優勢:流轉關系可視化

拆解狀態表的關鍵組成部分

1. 函數指針:讓 “條件” 和 “動作” 可靈活替換

TriggerFuncActionFunc是兩個函數指針類型,作用是 “定義觸發條件和動作的函數格式”,讓不同狀態的邏輯可以獨立實現:

// 觸發條件函數指針:輸入狀態機上下文,返回“是否滿足條件”(true/false)
typedef bool (*TriggerFunc)(WifiConfigFSM *fsm);// 動作函數指針:輸入狀態機上下文,執行具體操作(無返回值)
typedef void (*ActionFunc)(WifiConfigFSM *fsm);

為什么用函數指針?因為不同狀態的 “觸發條件” 和 “動作” 完全不同:

  • 比如 “檢測文件存在”(trigger_file_exist)和 “檢測連接成功”(trigger_connect_success)的邏輯不一樣,需要兩個獨立函數;
  • 用函數指針把這些函數 “統一包裝”,狀態表就可以用同一個結構體字段(trigger/action)指向不同函數,實現 “一表多用”。

2. 觸發函數(trigger_*):判斷 “能不能轉狀態”

觸發函數是TriggerFunc類型,核心職責是 “判斷當前狀態下,是否滿足切換到下一個狀態的條件”,返回true(滿足)或false(不滿足)。

你的代碼中每個觸發函數都對應一個具體條件,比如:

  • trigger_start_config:判斷是否收到配網觸發信號(trigger_config != 0),對應 IDLE 狀態的觸發;
  • trigger_file_exist:判斷配網文件是否存在且更新(stat(WIFI_CONFIG_FILE)成功 + 時間戳更新),對應 WAIT_FILE 狀態的觸發;
  • trigger_file_valid:判斷 SSID 和密碼是否非空(strlen(fsm->ssid) > 0),對應 VALIDATE 狀態的觸發;
  • trigger_any:無條件返回true,對應 “不需要條件就能轉” 的場景(比如 SUCCESS→FINISH,不管什么情況都要結束流程)。

這些函數把原來散在case里的 “條件判斷” 抽成了獨立函數,既方便復用,也方便單獨調試(比如想改 “文件檢測邏輯”,只改trigger_file_exist就行)。


3. 動作函數(action_*):執行 “轉狀態時要做的事”

動作函數是ActionFunc類型,核心職責是 “在狀態切換時執行具體操作”,比如讀文件、寫配置、啟動 WiFi 連接。

你的代碼中每個動作函數都對應配網的一個具體步驟,比如:

  • action_read_file:打開wifi_config.txt,讀取 SSID 和密碼,存到wifi_fsm上下文;
  • action_write_conf:根據讀取的 SSID / 密碼,生成wpa_supplicant.conf配置文件;
  • action_connect:執行系統命令(啟動 wpa_supplicant、獲取 DHCP),嘗試 WiFi 連接;
  • action_success:創建配網成功標記文件(wifi_config_success),避免下次重復配網。

這些函數把原來散在case里的 “業務邏輯” 抽成了獨立函數,職責清晰 —— 比如想改 “配網成功后的操作”,只改action_success就行,不用動狀態流轉的核心邏輯。


?????????修改為狀態表后的代碼:在配網線程函數中只需要將process_wifi_fsm狀態機函數替換為新的wifi_config_fsm_process狀態表函數即可,然后將原本的狀態機代碼替換為狀態表代碼并新增新的狀態轉換表結構及新的函數聲明即可。

//新增結構體與函數聲明typedef struct
{WifiConfigState current_state;TriggerFunc trigger;        // 觸發條件判斷函數,NULL表示無條件轉換WifiConfigState next_state; // 滿足條件后跳轉的下一個狀態ActionFunc action;          // 狀態轉換時執行的動作
} WifiStateTransition;typedef bool (*TriggerFunc)(WifiConfigFSM *fsm);
typedef void (*ActionFunc)(WifiConfigFSM *fsm);static bool trigger_start_config(WifiConfigFSM *fsm);
static bool trigger_file_exist(WifiConfigFSM *fsm);
static bool trigger_file_valid(WifiConfigFSM *fsm);
static bool trigger_write_success(WifiConfigFSM *fsm);
static bool trigger_connect_success(WifiConfigFSM *fsm);
static bool trigger_connect_failed(WifiConfigFSM *fsm);
static bool trigger_any(WifiConfigFSM *fsm);static void action_wait_file(WifiConfigFSM *fsm);
static void action_read_file(WifiConfigFSM *fsm);
static void action_validate(WifiConfigFSM *fsm);
static void action_write_conf(WifiConfigFSM *fsm);
static void action_connect(WifiConfigFSM *fsm);
static void action_check_result(WifiConfigFSM *fsm);
static void action_success(WifiConfigFSM *fsm);
static void action_failed(WifiConfigFSM *fsm);
static void action_finish(WifiConfigFSM *fsm);
//以下為新的邏輯代碼替換掉原先狀態機// 狀態轉換表 - 集中定義所有狀態流轉規則
static const WifiStateTransition state_table[] = {// 從 idle 狀態開始,當觸發配網時進入等待文件狀態{WIFI_CONFIG_STATE_IDLE, trigger_start_config, WIFI_CONFIG_STATE_WAIT_FILE, NULL},// 等待文件狀態,文件出現后進入讀取文件狀態{WIFI_CONFIG_STATE_WAIT_FILE, trigger_file_exist, WIFI_CONFIG_STATE_READ_FILE, action_read_file},// 讀取文件狀態,驗證文件有效后進入驗證狀態{WIFI_CONFIG_STATE_READ_FILE, trigger_any, WIFI_CONFIG_STATE_VALIDATE, action_validate},// 驗證狀態,有效則進入寫入配置狀態,無效則進入失敗狀態{WIFI_CONFIG_STATE_VALIDATE, trigger_file_valid, WIFI_CONFIG_STATE_WRITE_CONF, action_write_conf},{WIFI_CONFIG_STATE_VALIDATE, NULL, WIFI_CONFIG_STATE_FAILED, NULL},// 寫入配置狀態,成功則進入連接狀態,失敗則進入失敗狀態{WIFI_CONFIG_STATE_WRITE_CONF, trigger_write_success, WIFI_CONFIG_STATE_CONNECT, action_connect},{WIFI_CONFIG_STATE_WRITE_CONF, NULL, WIFI_CONFIG_STATE_FAILED, NULL},// 連接狀態,無論成功失敗都進入檢查狀態{WIFI_CONFIG_STATE_CONNECT, trigger_any, WIFI_CONFIG_STATE_CHECK, action_check_result},// 檢查狀態,根據連接結果進入成功或失敗狀態{WIFI_CONFIG_STATE_CHECK, trigger_connect_success, WIFI_CONFIG_STATE_SUCCESS, action_success},{WIFI_CONFIG_STATE_CHECK, trigger_connect_failed, WIFI_CONFIG_STATE_FAILED, action_failed},// 成功狀態無條件進入結束狀態{WIFI_CONFIG_STATE_SUCCESS, trigger_any, WIFI_CONFIG_STATE_FINISH, action_finish},// 失敗狀態無條件進入結束狀態{WIFI_CONFIG_STATE_FAILED, trigger_any, WIFI_CONFIG_STATE_FINISH, action_finish},// 結束狀態無條件回到空閑狀態{WIFI_CONFIG_STATE_FINISH, trigger_any, WIFI_CONFIG_STATE_IDLE, NULL},// 結束標志{WIFI_CONFIG_STATE_MAX, NULL, WIFI_CONFIG_STATE_MAX, NULL}};// 觸發條件實現
static bool trigger_start_config(WifiConfigFSM *fsm)
{return (trigger_config != 0);
}static bool trigger_file_exist(WifiConfigFSM *fsm)
{// 檢查配網文件是否存在struct stat file_stat;if (stat(WIFI_CONFIG_FILE, &file_stat) == 0){// 檢查文件是否有更新if (file_stat.st_mtime > last_config_file_mtime){last_config_file_mtime = file_stat.st_mtime;return true;}}return false;
}static bool trigger_file_valid(WifiConfigFSM *fsm)
{// 驗證SSID和密碼是否有效return (strlen(fsm->ssid) > 0 && strlen(fsm->psk) > 0);
}static bool trigger_write_success(WifiConfigFSM *fsm)
{// 檢查配置文件是否寫入成功struct stat file_stat;return (stat(wpa_conf, &file_stat) == 0);
}static bool trigger_connect_success(WifiConfigFSM *fsm)
{// 檢查WiFi是否連接成功return is_wifi_associated(networks[NETWORK_WIFI].ifname) &&(networks[NETWORK_WIFI].quality > 30);
}static bool trigger_connect_failed(WifiConfigFSM *fsm)
{// 檢查WiFi是否連接失敗return !trigger_connect_success(fsm);
}static bool trigger_any(WifiConfigFSM *fsm)
{// 無條件觸發return true;
}// 狀態動作實現
static void action_wait_file(WifiConfigFSM *fsm)
{LOG_WIFI("等待配網文件 %s...\n", WIFI_CONFIG_FILE);fsm->retry_count = 0;
}static void action_read_file(WifiConfigFSM *fsm)
{LOG_WIFI("讀取配網文件 %s...\n", WIFI_CONFIG_FILE);FILE *fp = fopen(WIFI_CONFIG_FILE, "r");if (fp){// 讀取SSID和密碼memset(fsm->ssid, 0, sizeof(fsm->ssid));memset(fsm->psk, 0, sizeof(fsm->psk));fgets(fsm->ssid, sizeof(fsm->ssid) - 1, fp);fgets(fsm->psk, sizeof(fsm->psk) - 1, fp);// 去除換行符fsm->ssid[strcspn(fsm->ssid, "\n\r")] = '\0';fsm->psk[strcspn(fsm->psk, "\n\r")] = '\0';fclose(fp);}
}static void action_validate(WifiConfigFSM *fsm)
{LOG_WIFI("驗證配網信息...\n");LOG_WIFI("SSID: %s, 密碼長度: %zu\n", fsm->ssid, strlen(fsm->psk));
}static void action_write_conf(WifiConfigFSM *fsm)
{LOG_WIFI("寫入WiFi配置文件 %s...\n", wpa_conf);FILE *fp = fopen(wpa_conf, "w");if (fp){fprintf(fp, "update_config=1\n");fprintf(fp, "network={\n");fprintf(fp, "    ssid=\"%s\"\n", fsm->ssid);fprintf(fp, "    psk=\"%s\"\n", fsm->psk);fprintf(fp, "    key_mgmt=WPA-PSK\n");fprintf(fp, "}\n");fclose(fp);}
}static void action_connect(WifiConfigFSM *fsm)
{LOG_WIFI("嘗試連接WiFi網絡...\n");// 啟動WiFi連接char cmd[512];snprintf(cmd, sizeof(cmd),"killall wpa_supplicant 2>/dev/null; ""ifconfig %s up; ""/system/init/wpa_supplicant -B -i %s -c %s; ""sleep 2; ""udhcpc -i %s -q -n -t 3",networks[NETWORK_WIFI].ifname,networks[NETWORK_WIFI].ifname,wpa_conf,networks[NETWORK_WIFI].ifname);exec_cmd(cmd);
}static void action_check_result(WifiConfigFSM *fsm)
{LOG_WIFI("檢查WiFi連接結果...\n");// 等待連接結果sleep(WIFI_CONNECT_TIMEOUT);update_network_status();
}static void action_success(WifiConfigFSM *fsm)
{LOG_WIFI("WiFi配網成功!\n");// 創建成功標記文件FILE *fp = fopen(WIFI_CONFIG_SUCCESS_FILE, "w");if (fp){fclose(fp);}// 重置觸發標志trigger_config = 0;
}static void action_failed(WifiConfigFSM *fsm)
{LOG_WIFI("WiFi配網失敗!\n");fsm->retry_count++;// 達到最大重試次數則重置觸發標志if (fsm->retry_count >= WIFI_CONFIG_RETRY_MAX){trigger_config = 0;}
}static void action_finish(WifiConfigFSM *fsm)
{LOG_WIFI("配網流程結束\n");// 可以在這里添加清理工作
}// 狀態機處理函數 - 替代原來的switch-case
void wifi_config_fsm_process()
{pthread_mutex_lock(&network_mutex);// 遍歷狀態表查找匹配的轉換規則for (int i = 0; state_table[i].current_state != WIFI_CONFIG_STATE_MAX; i++){if (state_table[i].current_state == wifi_fsm.current_state){// 檢查觸發條件if (!state_table[i].trigger || state_table[i].trigger(&wifi_fsm)){// 執行狀態轉換動作if (state_table[i].action){state_table[i].action(&wifi_fsm);}// 轉換到下一個狀態wifi_fsm.current_state = state_table[i].next_state;LOG_WIFI("狀態轉換: %d -> %d\n",state_table[i].current_state,state_table[i].next_state);break;}}}pthread_mutex_unlock(&network_mutex);
}

優化點二:用庫函數替代shell命令調用,提升處理速度(替代wpa_supplicant工具)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/97866.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/97866.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/97866.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

IvorySQL 4.6:DocumentDB+FerretDB 實現 MongoDB 兼容部署指南

背景 MongoDB 誕生之初&#xff0c;便以出色的易用性與詳盡的驅動程序文檔脫穎而出&#xff0c;堪稱對傳統關系型數據庫的一次重要革新&#xff0c;也正因如此&#xff0c;它迅速成為開發者社區的熱門之選。 然而&#xff0c;隨著其許可模式從開源轉向 SSPL 許可證&#xff0…

論文閱讀:arixv 2025 One Token to Fool LLM-as-a-Judge

總目錄 大模型相關研究&#xff1a;https://blog.csdn.net/WhiffeYF/article/details/142132328 https://arxiv.org/pdf/2507.08794 https://www.doubao.com/chat/20698287584991234 速覽 這篇文檔主要講了一個關于“大語言模型當裁判”的重要發現——很多我們以為靠譜的AI裁…

webrtc弱網-AlrDetector類源碼分析與算法原理

AlrDetector&#xff08;應用受限區域檢測器&#xff09;是WebRTC中用于檢測發送端是否處于應用層限速狀態的核心組件。它通過維護一個基于時間間隔的預算系統&#xff0c;監控實際發送數據量與網絡容量之間的關系。當發送速率持續低于網絡容量的設定比例&#xff08;如65%&…

ABP + Verify(快照) 驅動的 PDF/Excel 導出回歸

ABP + Verify(快照) 驅動的 PDF/Excel 導出回歸 ?? ?? 目錄 ABP + Verify(快照) 驅動的 PDF/Excel 導出回歸 ?? 0) TL;DR ? 1) 背景與目標 ?? 2) 架構與職責(解耦渲染器) ?? 3) “確定性”前置條件(去偽差異) ?? 4) PDF 回歸策略(以 QuestPDF 為例) ?? 4.…

SIFT特征匹配實戰:KNN算法實現指紋認證

這個利用了前面學到的SIFT特征檢測來實現的&#xff0c;然后這里主要就是引入了一個新的匹配器。這里匹配是用KNN算法進行匹配的。下面來看下細節。介紹函數由于要頻繁展示&#xff0c;所以這里定義了一個函數。def cv_show(name, img):cv2.imshow(name, img)cv2.waitKey(0)導入…

網絡安全滲透測試第一步信息收集

信息收集是滲透測試中最基礎且關鍵的一步&#xff0c;它直接影響后續漏洞發現和利用的成功率。本文將系統介紹信息收集的常用方法、工具和技巧&#xff0c;幫助你在實戰中高效定位目標弱點。 一、搜索引擎利用 1. Google Hacking 通過Google搜索語法快速定位敏感信息、后臺地…

C++——類和對象1

1.類的定義1.1 類定義格式class為定義類的關鍵字&#xff0c;Stack為類的名字&#xff0c;{ }中的內容是類的主題為了&#xff0c;注意類定義結束時后面的分號不能省略。類體中的內容稱為類的成員&#xff1a;類中的變量稱為類的屬性或成員變量&#xff1b;類中的函數稱為類的方…

動手學Agent:Agent設計模式——構建有效Agent的7種模型

Agent本身的定義也不是絕對的&#xff0c;從LLM到最高等級的Agent&#xff0c;中間是有大量灰度地帶的&#xff0c;在Anthropic看來&#xff0c;Agent可以以多種方式定義&#xff0c;有些人將完全自主系統定義為Agent&#xff0c;而另一些團隊則將預定義的工作流程定義為Agent。…

Windows 下 .venv 激活腳本深度定制:同時注入 PyTorch 調試日志與國內網絡加速通道——從“能跑”到“好調”的完整工程化方案

Windows 下 .venv 激活腳本深度定制&#xff1a;同時注入 PyTorch 調試日志與國內網絡加速通道 ——從“能跑”到“好調”的完整工程化方案 一、為什么非得改激活腳本&#xff1f; 重復勞動最耗時 每次打開終端都要敲四五行 set/export&#xff0c;人腦就是不可靠的剪貼板。 環…

[BX]和loop指令,debug和masm匯編編譯器對指令的不同處理,循環,大小寄存器的包含關系,操作數據長度與寄存器的關系,段前綴

[bx]是什么[bx]這個表達方式和[0]很像&#xff0c;他們倆的功能也很像。之前就提到了&#xff0c;[0]表示一個內存單元&#xff0c;他的偏移地址是0。從這邊我們可以引出內存單元的定義&#xff1a;要有內存單元的地址&#xff0c;要有內存單元的長度&#xff08;類型&#xff…

域格YM310 X09移芯CAT1模組HTTPS連接服務器

HTTPS連接服務器 本文檔介紹了HTTPS連接服務器的大致流程&#xff0c;測試服務器為httpbin.org。 HTTPS連接服務器流程 創建證書文件 創建一個文件 ATFSCREATE<filename>參數&#xff1a;<filename> 文件名 寫入CA證書 ATFSWRITE<filename>,<mode&…

【ManiSkill】常見envs學習筆記

1. StackCube-v1 用于模擬機器人在桌面場景中將紅色立方體&#xff08;cubeA&#xff09;堆疊到綠色立方體&#xff08;cubeB&#xff09;上的操作。該任務強調精確抓取、放置和穩定性控制。成功條件包括紅色立方體穩定堆疊在綠色立方體上且不被機器人抓取。 參數 (Arguments…

Java 網絡編程全解析

前言&#xff1a;網絡編程的意義與價值 前言&#xff1a;網絡編程的意義與價值 在當今互聯網時代&#xff0c;網絡編程是軟件開發的核心技能之一。無論是桌面應用、移動應用還是企業級系統&#xff0c;幾乎都需要與網絡交互。Java 作為一門跨平臺的編程語言&#xff0c;提供了完…

HarmonyOS應用拉起系列(三):如何直接拉起騰訊/百度/高德地圖進行導航

在鴻蒙應用開發中&#xff0c;經常需要跳轉第三方地圖應用&#xff08;如 騰訊地圖、百度地圖、高德地圖&#xff09;進行導航。無論是出行類 App、物流類 App&#xff0c;還是線下活動類應用&#xff0c;都存在“跳轉地圖導航”的實際需求。寫完HarmonyOS應用拉起系列一和二后…

PCGrad解決多任務沖突

論文解讀&#xff1a;"Gradient Surgery for Multi-Task Learning" 1. 論文標題直譯 Gradient Surgery: 梯度手術for Multi-Task Learning: 應用于多任務學習 合在一起就是&#xff1a;為多任務學習量身定制的梯度手術。這個名字非常形象地概括了它的核心思想。 …

Nvidia顯卡架構解析與cuda應用生態淺析

文章目錄 0. Nvidia顯卡簡介 一、主要顯卡系列 二、主要GPU架構與代表產品 1.main 1.1 CUDA 13.0 的重大變化 1.2 V100 的硬件短板已顯現 1.3 這意味著什么? 1.4 寫在后面 彩蛋:V100 0. Nvidia顯卡簡介 一、主要顯卡系列 GeForce 系列(消費級) 用途:游戲、創作、日常圖形…

開發指南:使用 MQTTNet 庫構建 .Net 物聯網 MQTT 應用程序

一、背景介紹 隨著物聯網的興起&#xff0c;.Net 框架在構建物聯網應用程序方面變得越來越流行。微軟的 .Net Core 和 .Net 框架為開發人員提供了一組工具和庫&#xff0c;以構建可以在 Raspberry Pi、HummingBoard、BeagleBoard、Pine A64 等平臺上運行的物聯網應用程序。 MQT…

突破性能瓶頸:基于騰訊云EdgeOne的AI圖片生成器全球加速實踐

1. 項目背景與挑戰 1.1 開發背景 隨著AIGC技術爆發&#xff0c;我們團隊決定開發一款多模型支持的AI圖片生成器&#xff0c;主要解決以下痛點&#xff1a; 不同AI模型的參數規范不統一生成結果難以系統化管理缺乏企業級的安全水印方案全球用戶訪問延遲高&#xff0c;中國用戶…

一、Java 基礎入門:從 0 到 1 認識 Java(詳細筆記)

1.1 Java 語言簡介與發展歷程 Java 是一門面向對象的高級編程語言&#xff0c;以“跨平臺、安全、穩定”為核心特性&#xff0c;自誕生以來長期占據編程語言排行榜前列&#xff0c;廣泛應用于后端開發、移動端開發、大數據等領域。 1.1.1 起源與核心人物 起源背景&#xff1…

uniapp:根據目的地經緯度,名稱,喚起高德/百度地圖來導航,兼容App,H5,小程序

1、需要自行申請高德地圖的key,配置manifest.json 2、MapSelector選擇組件封裝 <template><view><u-action-sheet :list="mapList" v-model="show" @click="changeMap"></u-action-sheet></view> </template&…