前段时间接手了一个监控项目,其中甲方对于设备的要求有一条就是实现网口eth、WiFi、4G三种手段的联网方式并且当某一个网络不好的时候就去切换到下一个能用的网络,让监控设备持续不断的有网络,保证监控数据的上传。这个部分的功能就交由我来实现了,下面就是一些简单的记录,包括优化。
由于交付初版时间紧急,我就来讲解一下我在初版实现的框架和逻辑,后面会进行代码重构优化。
目录
优化点二:用库函数替代shell命令调用,提升处理速度(替代wpa_supplicant工具)
一、整体架构设计
初版本代码主要以实现基础逻辑功能为主要目标,由于我还不熟悉相关网络的一些知识,所以在刚开始我用到了很多系统调用以及相关工具来帮我实现底层网络的一些功能(比如udhcpc、wpa_supplicant等等)初版交付之后后期目标就是进行多方位的优化,包括舍弃系统工具改为API接口去实现网络功能,代码逻辑精简,速度优化等等。
代码围绕 “三网切换” 和 “WiFi 配网” 两个核心功能,采用了 “模块化 + 状态机” 的设计思路,主要包含以下几个部分:
- 网络类型与优先级定义:明确网口(最高优先级)、WiFi(中优先级)、4G(最低优先级)的优先级顺序,作为网络切换的基础。
- 状态数据结构:用
NetworkStatus
存储各网络的实时状态(连接状态、信号强度、质量评分等),用WifiConfigFSM
管理 WiFi 配网的状态流转。 - 核心逻辑模块:包括网络状态更新、质量评分计算、网络切换控制、配网状态机等,各模块职责清晰,通过函数调用协同工作。
- 辅助工具函数:提供 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 卡状态、网络注册状态。
- 网口(ETH):读取
- 质量评分计算:通过
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
,遵循 “优先级 + 可用性” 原则,流程如下:
- 优先级排序:优先选择网口,其次 WiFi,最后 4G(兜底)。
- 最优网络选择:
- 若当前有更高优先级的网络可用(如网口突然插入),则触发切换。
- 若所有网络均不可用,先重试 WiFi(最多 2 次),重试失败后切换到 4G 兜底。
- 切换执行:通过
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,醒来检查 running
sleep_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; // 关键:不使用文件当前时间,直接设0
LOG_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-100
time_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]; // 临时存储SSID
char 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"); // 非阻塞获取IP
exec_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; // 临时存储带小数的level
if (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/VMIN
tcsetattr(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,主动重试DHCP
if (!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);
// 新增:检查关联状态,未关联则强制信号强度为0
int 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和路由,再强制重新获取IP
snprintf(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. 启用网卡并获取 IP
snprintf(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)
{
// 再次确认一下是否已经分到IP
snprintf(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); // 直接切换到4G
pthread_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 存在才重试 WiFi
if (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 根本不存在 → 直接启用 4G
LOG_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,醒来检查 running
sleep_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; // 关键:不使用文件当前时间,直接设0
LOG_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));
// 读取第一行作为SSID
if (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; // 信号强度强制为0
networks[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. 函数指针:让 “条件” 和 “动作” 可灵活替换
TriggerFunc
和ActionFunc
是两个函数指针类型,作用是 “定义触发条件和动作的函数格式”,让不同状态的逻辑可以独立实现:// 触发条件函数指针:输入状态机上下文,返回“是否满足条件”(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);
}