ESP8266实现配网

本文最后更新于 4 个月前,文中所描述的信息可能已发生改变。

ESP8266实现配网

我目前处于大学阶段,在参加一个社团,里面主要是做项目,比赛的一个小团队。
在这个小团队中,我主要负责的是一个项目的硬件部分,
比如说硬件的组装,电路的调试连接,代码的开发都是由我来完成。其中不可避免的就会和嵌入式相关联,通常会使用到arduino IDE来进行开发。使用的开发板有arduino uno,auduino 2560,esp8266,esp32等。
在其中不乏会遇到需要配置网络的步骤,用到最多的联网模块就是esp8266,超小的板子,有着基础的计算能力,且可以联网所以项目中会经常使用到这个小板子。

下面我就分享一些我用到的esp8266开发板的配网方法。

ts
//页面简单,实现esp8266启动时检测是否连接wifi,如果连接了就继续,如果没有连接上就开启ap模式,ap模式如果120s没有连接成功就重启重复执行。手机,pc设备连接esp8266wifi进行配网。
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
#include <EEPROM.h>

const char* apSSID = "esp82config";
const char* apPassword = "";
const int EEPROM_SIZE = 512;
const int SSID_ADDRESS = 0;
const int PASSWORD_ADDRESS = 128;
unsigned long apStartTime = 0;        // AP模式启动时间
const unsigned long AP_TIMEOUT = 60000; // 超时时间(60秒)

// 创建服务器和DNS服务器
ESP8266WebServer server(80);
DNSServer dnsServer;

// 从EEPROM读取WiFi配置
void readWiFiConfig() {
  EEPROM.begin(EEPROM_SIZE);
  delay(10);
  
  char ssid[128];
  char password[128];
  
  for (int i = 0; i < 128; i++) {
    ssid[i] = EEPROM.read(SSID_ADDRESS + i);
  }
  
  for (int i = 0; i < 128; i++) {
    password[i] = EEPROM.read(PASSWORD_ADDRESS + i);
  }
  
  // 检查是否有保存的配置
  if (strlen(ssid) > 0) {
    WiFi.begin(ssid, password);
    Serial.print("找到已保存的配置。正在连接到: ");
    Serial.println(ssid);
  }
  
  EEPROM.end();
}

// 保存WiFi配置到EEPROM
void saveWiFiConfig(String ssid, String password) {
  EEPROM.begin(EEPROM_SIZE);
  delay(10);
  
  // 保存SSID
  for (int i = 0; i < ssid.length(); i++) {
    EEPROM.write(SSID_ADDRESS + i, ssid[i]);
  }
  EEPROM.write(SSID_ADDRESS + ssid.length(), '\0'); // 字符串结束符
  
  // 保存密码
  for (int i = 0; i < password.length(); i++) {
    EEPROM.write(PASSWORD_ADDRESS + i, password[i]);
  }
  EEPROM.write(PASSWORD_ADDRESS + password.length(), '\0'); // 字符串结束符
  
  EEPROM.commit();
  EEPROM.end();
  
  Serial.println("WiFi配置已保存到EEPROM");
}

// 检查是否有保存的WiFi配置
bool hasSavedWiFiConfig() {
  EEPROM.begin(EEPROM_SIZE);
  delay(10);
  
  char ssid[128];
  for (int i = 0; i < 128; i++) {
    ssid[i] = EEPROM.read(SSID_ADDRESS + i);
  }
  
  EEPROM.end();
  
  return (strlen(ssid) > 0);
}

// 连接到保存的WiFi
bool connectToSavedWiFi() {
  WiFi.mode(WIFI_STA);
  readWiFiConfig(); // 从EEPROM读取配置
  
  Serial.print("正在连接到已保存的网络...");
  
  int retryCount = 0;
  while (WiFi.status() != WL_CONNECTED && retryCount < 20) {
    delay(500);
    Serial.print(".");
    retryCount++;
  }
  
  Serial.println();
  
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("已连接到WiFi");
    Serial.print("IP地址: ");
    Serial.println(WiFi.localIP());
    return true;
  } else {
    Serial.println("连接失败");
    return false;
  }
}

// 启动AP模式
void startAPMode() {
  WiFi.mode(WIFI_AP);
  WiFi.softAP(apSSID, apPassword);
  
  Serial.println("AP模式已启动");
  Serial.print("AP SSID: ");
  Serial.println(apSSID);
  Serial.print("AP IP地址: ");
  Serial.println(WiFi.softAPIP());
  
  apStartTime = millis(); // 记录AP启动时间
  
  // 设置Web服务器路由
  server.on("/", []() {
    String page = "<html><body>";
    page += "<h1>WiFi配置</h1>";
    page += "<form method='POST' action='/connect'>";
    page += "SSID: <input type='text' name='ssid'><br>";
    page += "密码: <input type='password' name='password'><br>";
    page += "<input type='submit' value='连接'>";
    page += "</form>";
    page += "</body></html>";
    server.send(200, "text/html", page);
  });
  
  server.on("/connect", []() {
    if (server.hasArg("ssid") && server.hasArg("password")) {
      String ssid = server.arg("ssid");
      String password = server.arg("password");
      
      saveWiFiConfig(ssid, password); // 保存配置到EEPROM
      
      server.send(200, "text/plain", "WiFi配置已保存。正在重启...");
      delay(1000);
      ESP.restart();
    } else {
      server.send(400, "text/plain", "错误请求");
    }
  });
  
  // 处理所有未定义的请求,重定向到配置页面
  server.onNotFound([]() {
    server.sendHeader("Location", String("http://") + WiFi.softAPIP().toString(), true);
    server.send(302, "text/plain", "");
  });
  
  server.begin();
  Serial.println("Web服务器已启动");
}

void setup() {
  Serial.begin(9600);
  Serial.println();
  
  // 尝试连接到保存的WiFi
  if (hasSavedWiFiConfig() && connectToSavedWiFi()) {
    // WiFi连接成功,执行正常操作
    // 这里可以添加你的应用代码
  } else {
    // WiFi连接失败或没有保存的配置,启动AP模式
    startAPMode();
  }
}

void loop() {
  // 处理DNS请求
  dnsServer.processNextRequest();
  
  // 处理HTTP请求
  server.handleClient();
  
  // 检查AP模式是否超时
  if (WiFi.getMode() == WIFI_AP && (millis() - apStartTime >= AP_TIMEOUT)) {
    Serial.println("AP模式超时。正在重启...");
    ESP.restart();
  }
  
  // 如果WiFi已连接,可以添加你的应用逻辑
  if (WiFi.status() == WL_CONNECTED) {
    // 应用代码
  }
  
  delay(10);
}
ts
//ESP8266连接WiFi页面已美化
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
#include <EEPROM.h>

const char* apSSID = "esp8266config";
const char* apPassword = "";
const int EEPROM_SIZE = 512;
const int SSID_ADDRESS = 0;
const int PASSWORD_ADDRESS = 128;
unsigned long apStartTime = 0;        // AP模式启动时间
const unsigned long AP_TIMEOUT = 120000; // 超时时间(120秒)

ESP8266WebServer server(80);
DNSServer dnsServer;

// 响应式Web配置页面 (HTML/CSS)
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ESP8266 WiFi配置</title>
    <style>
        * { box-sizing: border-box; margin: 0; padding: 0; font-family: Arial, sans-serif; }
        body { background-color: #f0f2f5; padding: 20px; }
        .container { max-width: 500px; margin: 0 auto; }
        .card { background: white; border-radius: 10px; padding: 25px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
        h1 { color: #1a73e8; text-align: center; margin-bottom: 20px; font-size: 24px; }
        .form-group { margin-bottom: 18px; }
        label { display: block; margin-bottom: 6px; color: #5f6368; font-weight: 500; }
        input { width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; }
        input:focus { outline: none; border-color: #1a73e8; box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.2); }
        button { width: 100%; padding: 12px; background: #1a73e8; color: white; border: none; 
                 border-radius: 6px; font-size: 16px; font-weight: 500; cursor: pointer; margin-bottom: 10px; }
        button:hover { background: #0d47a1; transition: background 0.3s; }
        .status { margin-top: 15px; padding: 12px; border-radius: 6px; text-align: center; 
                 color: white; font-weight: 500; }
        .success { background: #34a853; }
        .error { background: #ea4335; }
        .loading { background: #4285f4; }
        .wifi-list { margin-bottom: 20px; max-height: 200px; overflow-y: auto; border: 1px solid #eee; border-radius: 6px; }
        .wifi-item { padding: 12px; border-bottom: 1px solid #eee; display: flex; align-items: center; cursor: pointer; }
        .wifi-item:last-child { border-bottom: none; }
        .wifi-item:hover { background: #f8f9fa; }
        .wifi-item.selected { background: #e8f0fe; border-left: 4px solid #1a73e8; }
        .signal { margin-right: 10px; font-weight: bold; }
        .signal-strong { color: #34a853; }
        .signal-medium { color: #fbbc05; }
        .signal-weak { color: #ea4335; }
        .instructions { color: #5f6368; margin-bottom: 20px; text-align: center; }
    </style>
</head>
<body>
    <div class="container">
        <div class="card">
            <h1>WiFi网络配置</h1>
            <p class="instructions">选择可用网络或手动输入WiFi信息</p>
            
            <div class="wifi-list" id="wifiList">
                <div class="wifi-item">正在扫描网络...</div>
            </div>

            <div class="form-group">
                <label for="ssid">WiFi名称 (SSID)</label>
                <input type="text" id="ssid" name="ssid" placeholder="输入WiFi名称">
            </div>

            <div class="form-group">
                <label for="password">WiFi密码</label>
                <input type="password" id="password" name="password" placeholder="输入WiFi密码">
            </div>

            <form method="POST" action="/connect">
                <input type="hidden" id="formSsid" name="ssid">
                <input type="hidden" id="formPassword" name="password">
                <button type="submit">连接WiFi</button>
            </form>
            <button onclick="window.location.reload()">重新扫描</button>
        </div>
    </div>

    <script>
        // 页面加载完成后获取WiFi列表
        window.onload = function() {
            fetch('/wifiscan')
                .then(response => response.json())
                .then(networks => {
                    const list = document.getElementById('wifiList');
                    list.innerHTML = '';
                    
                    if (networks.length === 0) {
                        list.innerHTML = '<div class="wifi-item">未发现可用网络</div>';
                        return;
                    }
                    
                    networks.forEach(net => {
                        const item = document.createElement('div');
                        item.className = 'wifi-item';
                        
                        let signalClass = 'signal-weak';
                        let signalIcon = '▂___';
                        
                        if (net.rssi > -60) {
                            signalClass = 'signal-strong';
                            signalIcon = '▂▄▆█';
                        } else if (net.rssi > -75) {
                            signalClass = 'signal-medium';
                            signalIcon = '▂▄▆_';
                        }
                        
                        item.innerHTML = `
                            <div class="signal ${signalClass}">${signalIcon}</div>
                            <div class="ssid">${net.ssid}</div>
                        `;
                        
                        item.onclick = () => {
                            document.querySelectorAll('.wifi-item').forEach(el => el.classList.remove('selected'));
                            item.classList.add('selected');
                            document.getElementById('ssid').value = net.ssid;
                            document.getElementById('formSsid').value = net.ssid;
                        };
                        
                        list.appendChild(item);
                    });
                });
            
            // 同步输入框值到表单隐藏域
            document.getElementById('ssid').addEventListener('input', function() {
                document.getElementById('formSsid').value = this.value;
            });
            
            document.getElementById('password').addEventListener('input', function() {
                document.getElementById('formPassword').value = this.value;
            });
        };
    </script>
</body>
</html>
)rawliteral";

// 从EEPROM读取WiFi配置
void readWiFiConfig() {
  EEPROM.begin(EEPROM_SIZE);
  delay(10);
  
  char ssid[128];
  char password[128];
  
  for (int i = 0; i < 128; i++) {
    ssid[i] = EEPROM.read(SSID_ADDRESS + i);
  }
  
  for (int i = 0; i < 128; i++) {
    password[i] = EEPROM.read(PASSWORD_ADDRESS + i);
  }
  
  if (strlen(ssid) > 0) {
    WiFi.begin(ssid, password);
    Serial.print("找到已保存的配置。正在连接到: ");
    Serial.println(ssid);
  }
  
  EEPROM.end();
}

// 保存WiFi配置到EEPROM
void saveWiFiConfig(String ssid, String password) {
  EEPROM.begin(EEPROM_SIZE);
  delay(10);
  
  for (int i = 0; i < ssid.length(); i++) {
    EEPROM.write(SSID_ADDRESS + i, ssid[i]);
  }
  EEPROM.write(SSID_ADDRESS + ssid.length(), '\0');
  
  for (int i = 0; i < password.length(); i++) {
    EEPROM.write(PASSWORD_ADDRESS + i, password[i]);
  }
  EEPROM.write(PASSWORD_ADDRESS + password.length(), '\0');
  
  EEPROM.commit();
  EEPROM.end();
  
  Serial.println("WiFi配置已保存到EEPROM");
}

// 检查是否有保存的WiFi配置
bool hasSavedWiFiConfig() {
  EEPROM.begin(EEPROM_SIZE);
  delay(10);
  
  char ssid[128];
  for (int i = 0; i < 128; i++) {
    ssid[i] = EEPROM.read(SSID_ADDRESS + i);
  }
  
  EEPROM.end();
  return (strlen(ssid) > 0);
}

// 连接到保存的WiFi
bool connectToSavedWiFi() {
  WiFi.mode(WIFI_STA);
  readWiFiConfig();
  
  Serial.print("正在连接到已保存的网络...");
  
  int retryCount = 0;
  while (WiFi.status() != WL_CONNECTED && retryCount < 20) {
    delay(500);
    Serial.print(".");
    retryCount++;
  }
  
  Serial.println();
  
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("已连接到WiFi");
    Serial.print("IP地址: ");
    Serial.println(WiFi.localIP());
    return true;
  } else {
    Serial.println("连接失败");
    return false;
  }
}

// 获取WiFi扫描结果并转换为JSON
String getWiFiScanResults() {
  int n = WiFi.scanNetworks();
  String json = "[";
  
  for (int i = 0; i < n; ++i) {
    if (i > 0) json += ",";
    json += "{\"ssid\":\"" + WiFi.SSID(i) + "\",\"rssi\":" + WiFi.RSSI(i) + "}";
  }
  
  json += "]";
  WiFi.scanDelete(); // 清除扫描结果
  return json;
}

// 启动AP模式
void startAPMode() {
  WiFi.mode(WIFI_AP_STA); // 同时支持AP和STA模式以便扫描
  WiFi.softAP(apSSID, apPassword);
  
  Serial.println("AP模式已启动");
  Serial.print("AP SSID: ");
  Serial.println(apSSID);
  Serial.print("AP IP地址: ");
  Serial.println(WiFi.softAPIP());
  
  apStartTime = millis();
  
  // 服务器路由配置
  server.on("/", []() {
    server.send_P(200, "text/html", index_html);
  });
  
  server.on("/wifiscan", []() {
    String json = getWiFiScanResults();
    server.send(200, "application/json", json);
    Serial.println("已发送WiFi扫描结果");
  });
  
  server.on("/connect", HTTP_POST, []() {
    String ssid = server.arg("ssid");
    String password = server.arg("password");
    
    saveWiFiConfig(ssid, password);
    
    // 美化的配置成功页面,显示已保存的信息和倒计时
    String response = R"rawliteral(
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>配置成功</title>
    <style>
        * { box-sizing: border-box; margin: 0; padding: 0; font-family: Arial, sans-serif; }
        body { background-color: #f0f2f5; padding: 20px; display: flex; justify-content: center; align-items: center; min-height: 100vh; }
        .card { background: white; border-radius: 10px; padding: 30px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); max-width: 400px; width: 100%; }
        .success-icon { text-align: center; font-size: 60px; color: #34a853; margin-bottom: 20px; }
        h2 { color: #333; text-align: center; margin-bottom: 15px; }
        .config-info { margin-bottom: 25px; }
        .config-item { margin-bottom: 10px; }
        .config-label { color: #666; font-size: 14px; }
        .config-value { font-size: 18px; font-weight: 500; word-break: break-all; }
        .countdown { text-align: center; color: #666; margin-bottom: 20px; }
        .countdown-number { font-size: 24px; font-weight: bold; color: #1a73e8; }
        .instruction { text-align: center; color: #666; font-size: 14px; }
    </style>
</head>
<body>
    <div class="card">
        <div class="success-icon"></div>
        <h2>WiFi配置已保存</h2>
        
        <div class="config-info">
            <div class="config-item">
                <div class="config-label">WiFi名称:</div>
                <div class="config-value">)rawliteral";
    response += ssid;
    response += R"rawliteral(</div>
            </div>
            <div class="config-item">
                <div class="config-label">WiFi密码:</div>
                <div class="config-value">)rawliteral";
    response += password;
    response += R"rawliteral(</div>
            </div>
        </div>
        
        <div class="countdown">
            设备正在重启 <span class="countdown-number" id="countdown">10</span>
        </div>
        
        <div class="instruction">
            请观察手机WiFi列表,<br>
<b>)rawliteral";
    response += apSSID;
    response += R"rawliteral(</b> 仍然存在,<br>
            请重新给设备配置WiFi
        </div>
    </div>

    <script>
        let seconds = 10;
        const countdownEl = document.getElementById('countdown');
        
        function updateCountdown() {
            seconds--;
            countdownEl.textContent = seconds;
            
            if (seconds <= 0) {
                // 倒计时结束后可以添加重定向逻辑
                // window.location.href = '/';
            } else {
                setTimeout(updateCountdown, 1000);
            }
        }
        
        // 开始倒计时
        setTimeout(updateCountdown, 1000);
    </script>
</body>
</html>
)rawliteral";
    
    server.send(200, "text/html; charset=utf-8", response);
    
    // 延长延迟时间,确保页面完全加载
    delay(10000); // 等待10秒后重启
    ESP.restart();
  });
  
  // 处理常见的Captive Portal探测URL
  server.on("/generate_204", []() {
    server.sendHeader("Location", String("http://") + WiFi.softAPIP().toString(), true);
    server.send(302, "text/plain", "");
  });
  
  server.on("/connecttest.txt", []() {
    server.sendHeader("Location", String("http://") + WiFi.softAPIP().toString(), true);
    server.send(302, "text/plain", "");
  });
  
  server.on("/captive.apple.com", []() {
    server.sendHeader("Location", String("http://") + WiFi.softAPIP().toString(), true);
    server.send(302, "text/plain", "");
  });
  
  server.on("/ncsi.txt", []() {
    server.sendHeader("Location", String("http://") + WiFi.softAPIP().toString(), true);
    server.send(302, "text/plain", "");
  });
  
  // 重定向所有其他请求到配置页面
  server.onNotFound([]() {
    server.sendHeader("Location", String("http://") + WiFi.softAPIP().toString(), true);
    server.send(302, "text/plain", "");
  });
  
  // 启动DNS服务器和Web服务器
  dnsServer.start(53, "*", WiFi.softAPIP());
  server.begin();
  Serial.println("Web服务器已启动");
}

void setup() {
  Serial.begin(9600);
  Serial.println();
  
  // 尝试连接到保存的WiFi
  if (hasSavedWiFiConfig() && connectToSavedWiFi()) {
    // WiFi连接成功,执行正常操作
    // 这里可以添加你的应用代码
  } else {
    // WiFi连接失败或没有保存的配置,启动AP模式
    startAPMode();
  }
}

void loop() {
  // 处理DNS请求
  dnsServer.processNextRequest();
  
  // 处理HTTP请求
  server.handleClient();
  
  // 检查AP模式是否超时
  if (WiFi.getMode() & WIFI_AP && (millis() - apStartTime >= AP_TIMEOUT)) {
    Serial.println("AP模式超时。正在重启...");
    ESP.restart();
  }
  
  // 如果WiFi已连接,可以添加你的应用逻辑
  if (WiFi.status() == WL_CONNECTED) {
    // 应用代码
  }
  
  delay(10);
}
ts
//ESP8266连接WiFi页面已压缩
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
#include <EEPROM.h>

// 核心配置参数
const char* apSSID = "esp8266config";
const char* apPassword = "";
const int EEPROM_SIZE = 512, SSID_ADDR = 0, PWD_ADDR = 128;
unsigned long apStartTime = 0;
const unsigned long AP_TIMEOUT = 120000; // 120秒超时

ESP8266WebServer server(80);
DNSServer dnsServer;

// 精简响应式HTML(合并CSS、缩短类名)
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>WiFi配置</title>
    <style>
        *{box-sizing:border-box;margin:0;padding:0;font-family:Arial,sans-serif}
        body{background:#f0f2f5;padding:20px}
        .c{max-width:500px;margin:0 auto}
        .card{background:#fff;border-radius:10px;padding:25px;box-shadow:0 2px 10px rgba(0,0,0,0.1)}
        h1{color:#1a73e8;text-align:center;margin:0 0 20px;font-size:24px}
        .instr{color:#5f6368;margin:0 0 20px;text-align:center}
        .wifi-list{max-height:200px;overflow-y:auto;border:1px solid #eee;border-radius:6px;margin:0 0 20px}
        .wifi-item{padding:12px;border-bottom:1px solid #eee;cursor:pointer}
        .wifi-item:last-child{border-bottom:none}
        .wifi-item:hover{background:#f8f9fa}
        .wifi-item.sel{background:#e8f0fe;border-left:4px solid #1a73e8}
        .form-group{margin:0 0 18px}
        label{display:block;margin:0 0 6px;color:#5f6368;font-weight:500}
        input{width:100%;padding:12px;border:1px solid #ddd;border-radius:6px;font-size:16px}
        input:focus{outline:none;border-color:#1a73e8;box-shadow:0 0 0 2px rgba(26,115,232,0.2)}
        button{width:100%;padding:12px;background:#1a73e8;color:#fff;border:none;border-radius:6px;font-size:16px;font-weight:500;cursor:pointer;margin:0 0 10px}
        button:hover{background:#0d47a1;transition:background 0.3s}
        .signal{margin:0 10px 0 0;font-weight:bold}
        .sig-strong{color:#34a853}
        .sig-med{color:#fbbc05}
        .sig-weak{color:#ea4335}
    </style>
</head>
<body>
    <div class="c">
        <div class="card">
            <h1>WiFi网络配置</h1>
            <p class="instr">选择可用网络或手动输入WiFi信息</p>
            
            <div class="wifi-list" id="wifiList">
                <div class="wifi-item">正在扫描网络...</div>
            </div>

            <div class="form-group">
                <label for="ssid">WiFi名称 (SSID)</label>
                <input type="text" id="ssid" placeholder="输入WiFi名称">
            </div>

            <div class="form-group">
                <label for="password">WiFi密码</label>
                <input type="password" id="password" placeholder="输入WiFi密码">
            </div>

            <form method="POST" action="/connect">
                <input type="hidden" id="formSsid" name="ssid">
                <input type="hidden" id="formPassword" name="password">
                <button type="submit">连接WiFi</button>
            </form>
            <button onclick="window.location.reload()">重新扫描</button>
        </div>
    </div>

    <script>
        window.onload=()=>{
            fetch('/wifiscan').then(r=>r.json()).then(networks=>{
                const list=document.getElementById('wifiList');
                list.innerHTML='';
                if(!networks.length){list.innerHTML='<div class="wifi-item">未发现可用网络</div>';return}
                networks.forEach(net=>{
                    const item=document.createElement('div');
                    item.className='wifi-item';
                    let sigClass='sig-weak',sigIcon='▂___';
                    if(net.rssi>-60){sigClass='sig-strong';sigIcon='▂▄▆█'}
                    else if(net.rssi>-75){sigClass='sig-med';sigIcon='▂▄▆_'}
                    item.innerHTML=`<div class="signal ${sigClass}">${sigIcon}</div><div>${net.ssid}</div>`;
                    item.onclick=()=>{
                        document.querySelectorAll('.wifi-item').forEach(el=>el.classList.remove('sel'));
                        item.classList.add('sel');
                        document.getElementById('ssid').value=net.ssid;
                        document.getElementById('formSsid').value=net.ssid;
                    };
                    list.appendChild(item);
                });
            });
            document.getElementById('ssid').addEventListener('input',e=>{
                document.getElementById('formSsid').value=e.target.value;
            });
            document.getElementById('password').addEventListener('input',e=>{
                document.getElementById('formPassword').value=e.target.value;
            });
        };
    </script>
</body>
</html>
)rawliteral";

// EEPROM读取WiFi配置
void readWiFiConfig() {
  EEPROM.begin(EEPROM_SIZE);
  char ssid[128]={0}, pwd[128]={0};
  for(int i=0;i<128;i++){ssid[i]=EEPROM.read(SSID_ADDR+i);pwd[i]=EEPROM.read(PWD_ADDR+i);}
  if(strlen(ssid)){WiFi.begin(ssid,pwd);Serial.print("连接保存的WiFi: ");Serial.println(ssid);}
  EEPROM.end();
}

// 保存WiFi配置到EEPROM
void saveWiFiConfig(String ssid, String pwd) {
  EEPROM.begin(EEPROM_SIZE);
  for(int i=0;i<128;i++){EEPROM.write(SSID_ADDR+i,0);EEPROM.write(PWD_ADDR+i,0);}
  for(int i=0;i<ssid.length()&&i<127;i++)EEPROM.write(SSID_ADDR+i,ssid[i]);
  for(int i=0;i<pwd.length()&&i<127;i++)EEPROM.write(PWD_ADDR+i,pwd[i]);
  EEPROM.commit();EEPROM.end();
  Serial.println("WiFi配置已保存");
}

// 检查是否有保存的配置
bool hasSavedWiFiConfig() {
  EEPROM.begin(EEPROM_SIZE);
  char ssid[128]={0};
  for(int i=0;i<128;i++)ssid[i]=EEPROM.read(SSID_ADDR+i);
  EEPROM.end();
  return strlen(ssid)>0;
}

// 连接保存的WiFi
bool connectToSavedWiFi() {
  WiFi.mode(WIFI_STA);
  readWiFiConfig();
  Serial.print("连接中...");
  int retry=0;
  while(WiFi.status()!=WL_CONNECTED&&retry<20){delay(500);Serial.print(".");retry++;}
  Serial.println();
  if(WiFi.status()==WL_CONNECTED){
    Serial.println("WiFi已连接");
    Serial.print("IP: ");Serial.println(WiFi.localIP());
    return true;
  }
  Serial.println("连接失败");
  return false;
}

// 生成WiFi扫描结果JSON
String getWiFiScanResults() {
  int n=WiFi.scanNetworks();
  String json="[";
  for(int i=0;i<n;i++){
    if(i)json+=",";
    json+="{\"ssid\":\""+WiFi.SSID(i)+"\",\"rssi\":"+WiFi.RSSI(i)+"}";
  }
  json+="]";
  WiFi.scanDelete();
  return json;
}

// 启动AP模式
void startAPMode() {
  WiFi.mode(WIFI_AP_STA);
  WiFi.softAP(apSSID, apPassword);
  Serial.println("AP模式启动");
  Serial.print("AP: ");Serial.println(apSSID);
  Serial.print("AP IP: ");Serial.println(WiFi.softAPIP());
  apStartTime=millis();

  server.on("/",[](){server.send_P(200,"text/html",index_html);});
  server.on("/wifiscan",[](){server.send(200,"application/json",getWiFiScanResults());});
  server.on("/connect",HTTP_POST,[](){
    String ssid=server.arg("ssid"),pwd=server.arg("password");
    saveWiFiConfig(ssid,pwd);
    String res=R"rawliteral(
<!DOCTYPE html>
<html><head><meta name="viewport" content="width=device-width,initial-scale=1">
<style>
    *{box-sizing:border-box;margin:0;padding:0;font-family:Arial,sans-serif}
    body{background:#f0f2f5;min-height:100vh;display:flex;justify-content:center;align-items:center;padding:20px}
    .card{background:#fff;border-radius:10px;padding:30px;box-shadow:0 4px 20px rgba(0,0,0,0.1);max-width:400px;width:100%}
    .icon{text-align:center;font-size:60px;color:#34a853;margin:0 0 20px}
    h2{color:#333;text-align:center;margin:0 0 15px}
    .info{margin:0 0 25px}
    .item{margin:0 0 10px}
    .label{color:#666;font-size:14px}
    .val{font-size:18px;font-weight:500;word-break:break-all}
    .countdown{text-align:center;color:#666;margin:0 0 20px}
    .num{font-size:24px;font-weight:bold;color:#1a73e8}
    .inst{text-align:center;color:#666;font-size:14px}
</style></head><body><div class="card">
    <div class="icon"></div>
    <h2>配置已保存</h2>
    <div class="info">
        <div class="item"><div class="label">WiFi名称:</div><div class="val">)rawliteral"+ssid+R"rawliteral(</div></div>
        <div class="item"><div class="label">WiFi密码:</div><div class="val">)rawliteral"+pwd+R"rawliteral(</div></div>
    </div>
    <div class="countdown">重启倒计时 <span class="num" id="t">10</span> 秒</div>
    <div class="inst">
        观察WiFi列表,若 <b>)rawliteral"+String(apSSID)+R"rawliteral(</b> 仍存在,<br>请重新配置
    </div>
</div><script>
    let s=10;const el=document.getElementById('t');
    function update(){s--;el.textContent=s;s>0?setTimeout(update,1000):null}
    update();
</script></body></html>
)rawliteral";
    server.send(200,"text/html;charset=utf-8",res);
    delay(10000);
    ESP.restart();
  });

  //  captive portal重定向
  server.on("/generate_204",[](){server.sendHeader("Location","http://"+WiFi.softAPIP().toString(),true);server.send(302,"text/plain","");});
  server.on("/connecttest.txt",[](){server.sendHeader("Location","/",true);server.send(302,"text/plain","");});
  server.on("/captive.apple.com",[](){server.sendHeader("Location","/",true);server.send(302,"text/plain","");});
  server.on("/ncsi.txt",[](){server.sendHeader("Location","/",true);server.send(302,"text/plain","");});
  server.onNotFound([](){server.sendHeader("Location","/",true);server.send(302,"text/plain","");});

  dnsServer.start(53,"*",WiFi.softAPIP());
  server.begin();
}

void setup() {
  Serial.begin(9600);
  Serial.println();
  if(hasSavedWiFiConfig()&&connectToSavedWiFi()){
    // WiFi连接成功后的应用代码
  }else{
    startAPMode();
  }
}

void loop() {
  dnsServer.processNextRequest();
  server.handleClient();
  if((WiFi.getMode()&WIFI_AP)&&millis()-apStartTime>=AP_TIMEOUT){
    Serial.println("AP超时,重启中...");
    ESP.restart();
  }
  if(WiFi.status()==WL_CONNECTED){
    // 运行中的应用逻辑
  }
  delay(10);
}
ts
//ESP8266连接WIFI代码压缩后添加指示灯
//LED 状态指示说明:
//未连接状态(包括连接失败、正在连接):
//慢速闪烁(1 秒间隔)
//对应枚举值:LED_DISCONNECTED
//已连接状态:
//常亮
//对应枚举值:LED_CONNECTED
//AP 模式(等待配网):
//快速闪烁(200ms 间隔)
//对应枚举值:LED_AP_MODE
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
#include <EEPROM.h>

// 核心配置参数
const char* apSSID = "esp8266config";
const char* apPassword = "";
const int EEPROM_SIZE = 512, SSID_ADDR = 0, PWD_ADDR = 128;
unsigned long apStartTime = 0;
const unsigned long AP_TIMEOUT = 120000; // 120秒超时

ESP8266WebServer server(80);
DNSServer dnsServer;

// LED状态定义
enum LedState {
  LED_DISCONNECTED,  // 未连接(慢速闪烁)
  LED_CONNECTED,     // 已连接(常亮)
  LED_AP_MODE,       // AP模式(快速闪烁)
  LED_SAVED_SUCCESS  // 配置保存成功(双闪)
};

LedState currentLedState = LED_DISCONNECTED;
unsigned long lastLedToggleTime = 0;
int ledToggleInterval = 1000;  // 默认未连接间隔(1秒)
bool ledState = HIGH;          // 默认LED熄灭
int doubleFlashCount = 0;      // 双闪计数器

// 精简响应式HTML
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>WiFi配置</title>
    <style>
        *{box-sizing:border-box;margin:0;padding:0;font-family:Arial,sans-serif}
        body{background:#f0f2f5;padding:20px}
        .c{max-width:500px;margin:0 auto}
        .card{background:#fff;border-radius:10px;padding:25px;box-shadow:0 2px 10px rgba(0,0,0,0.1)}
        h1{color:#1a73e8;text-align:center;margin:0 0 20px;font-size:24px}
        .instr{color:#5f6368;margin:0 0 20px;text-align:center}
        .wifi-list{max-height:200px;overflow-y:auto;border:1px solid #eee;border-radius:6px;margin:0 0 20px}
        .wifi-item{padding:12px;border-bottom:1px solid #eee;cursor:pointer}
        .wifi-item:last-child{border-bottom:none}
        .wifi-item:hover{background:#f8f9fa}
        .wifi-item.sel{background:#e8f0fe;border-left:4px solid #1a73e8}
        .form-group{margin:0 0 18px}
        label{display:block;margin:0 0 6px;color:#5f6368;font-weight:500}
        input{width:100%;padding:12px;border:1px solid #ddd;border-radius:6px;font-size:16px}
        input:focus{outline:none;border-color:#1a73e8;box-shadow:0 0 0 2px rgba(26,115,232,0.2)}
        button{width:100%;padding:12px;background:#1a73e8;color:#fff;border:none;border-radius:6px;font-size:16px;font-weight:500;cursor:pointer;margin:0 0 10px}
        button:hover{background:#0d47a1;transition:background 0.3s}
        .signal{margin:0 10px 0 0;font-weight:bold}
        .sig-strong{color:#34a853}
        .sig-med{color:#fbbc05}
        .sig-weak{color:#ea4335}
    </style>
</head>
<body>
    <div class="c">
        <div class="card">
            <h1>WiFi网络配置</h1>
            <p class="instr">选择可用网络或手动输入WiFi信息</p>
            
            <div class="wifi-list" id="wifiList">
                <div class="wifi-item">正在扫描网络...</div>
            </div>

            <div class="form-group">
                <label for="ssid">WiFi名称 (SSID)</label>
                <input type="text" id="ssid" placeholder="输入WiFi名称">
            </div>

            <div class="form-group">
                <label for="password">WiFi密码</label>
                <input type="password" id="password" placeholder="输入WiFi密码">
            </div>

            <form method="POST" action="/connect">
                <input type="hidden" id="formSsid" name="ssid">
                <input type="hidden" id="formPassword" name="password">
                <button type="submit">连接WiFi</button>
            </form>
            <button onclick="window.location.reload()">重新扫描</button>
        </div>
    </div>

    <script>
        window.onload=()=>{
            fetch('/wifiscan').then(r=>r.json()).then(networks=>{
                const list=document.getElementById('wifiList');
                list.innerHTML='';
                if(!networks.length){list.innerHTML='<div class="wifi-item">未发现可用网络</div>';return}
                networks.forEach(net=>{
                    const item=document.createElement('div');
                    item.className='wifi-item';
                    let sigClass='sig-weak',sigIcon='▂___';
                    if(net.rssi>-60){sigClass='sig-strong';sigIcon='▂▄▆█'}
                    else if(net.rssi>-75){sigClass='sig-med';sigIcon='▂▄▆_'}
                    item.innerHTML=`<div class="signal ${sigClass}">${sigIcon}</div><div>${net.ssid}</div>`;
                    item.onclick=()=>{
                        document.querySelectorAll('.wifi-item').forEach(el=>el.classList.remove('sel'));
                        item.classList.add('sel');
                        document.getElementById('ssid').value=net.ssid;
                        document.getElementById('formSsid').value=net.ssid;
                    };
                    list.appendChild(item);
                });
            });
            document.getElementById('ssid').addEventListener('input',e=>{
                document.getElementById('formSsid').value=e.target.value;
            });
            document.getElementById('password').addEventListener('input',e=>{
                document.getElementById('formPassword').value=e.target.value;
            });
        };
    </script>
</body>
</html>
)rawliteral";

// 设置LED状态
void setLedState(LedState state) {
  currentLedState = state;
  lastLedToggleTime = millis();
  doubleFlashCount = 0;  // 重置双闪计数器
  
  // 根据不同状态设置闪烁间隔
  switch(state) {
    case LED_DISCONNECTED:  // 未连接状态
      ledToggleInterval = 1000;  // 1秒间隔(慢速闪烁)
      break;
      
    case LED_CONNECTED:     // 已连接状态
      digitalWrite(LED_BUILTIN, LOW);  // 常亮
      return;
      
    case LED_AP_MODE:       // AP模式状态
      ledToggleInterval = 200;  // 200ms间隔(快速闪烁)
      break;
      
    case LED_SAVED_SUCCESS: // 配置保存成功状态
      ledToggleInterval = 250;  // 250ms间隔(双闪)
      break;
  }
}

// 更新LED状态(在loop中调用)
void updateLed() {
  // 常亮状态无需更新
  if(currentLedState == LED_CONNECTED) return;
  
  // 双闪逻辑
  if(currentLedState == LED_SAVED_SUCCESS) {
    if(millis() - lastLedToggleTime >= ledToggleInterval) {
      lastLedToggleTime = millis();
      ledState = !ledState;
      digitalWrite(LED_BUILTIN, ledState);
      
      doubleFlashCount++;
      // 完成3次双闪(6次切换)后保持熄灭
      if(doubleFlashCount >= 6) {
        digitalWrite(LED_BUILTIN, HIGH);
      }
    }
    return;
  }
  
  // 其他闪烁状态
  if(millis() - lastLedToggleTime >= ledToggleInterval) {
    lastLedToggleTime = millis();
    ledState = !ledState;
    digitalWrite(LED_BUILTIN, ledState);
  }
}

// EEPROM读取WiFi配置
void readWiFiConfig() {
  EEPROM.begin(EEPROM_SIZE);
  char ssid[128]={0}, pwd[128]={0};
  for(int i=0;i<128;i++){ssid[i]=EEPROM.read(SSID_ADDR+i);pwd[i]=EEPROM.read(PWD_ADDR+i);}
  if(strlen(ssid)){WiFi.begin(ssid,pwd);Serial.print("连接保存的WiFi: ");Serial.println(ssid);}
  EEPROM.end();
}

// 保存WiFi配置到EEPROM
void saveWiFiConfig(String ssid, String pwd) {
  EEPROM.begin(EEPROM_SIZE);
  for(int i=0;i<128;i++){EEPROM.write(SSID_ADDR+i,0);EEPROM.write(PWD_ADDR+i,0);}
  for(int i=0;i<ssid.length()&&i<127;i++)EEPROM.write(SSID_ADDR+i,ssid[i]);
  for(int i=0;i<pwd.length()&&i<127;i++)EEPROM.write(PWD_ADDR+i,pwd[i]);
  EEPROM.commit();EEPROM.end();
  Serial.println("WiFi配置已保存");
}

// 检查是否有保存的配置
bool hasSavedWiFiConfig() {
  EEPROM.begin(EEPROM_SIZE);
  char ssid[128]={0};
  for(int i=0;i<128;i++)ssid[i]=EEPROM.read(SSID_ADDR+i);
  EEPROM.end();
  return strlen(ssid)>0;
}

// 连接保存的WiFi
bool connectToSavedWiFi() {
  setLedState(LED_DISCONNECTED);  // 设置为未连接状态(慢速闪烁)
  WiFi.mode(WIFI_STA);
  readWiFiConfig();
  Serial.print("连接中...");
  
  int retry=0;
  while(WiFi.status()!=WL_CONNECTED&&retry<20){
    delay(500);
    Serial.print(".");
    retry++;
  }
  Serial.println();
  
  if(WiFi.status()==WL_CONNECTED){
    Serial.println("WiFi已连接");
    Serial.print("IP: ");Serial.println(WiFi.localIP());
    setLedState(LED_CONNECTED);  // 设置为已连接状态(常亮)
    return true;
  }
  
  Serial.println("连接失败");
  return false;
}

// 生成WiFi扫描结果JSON
String getWiFiScanResults() {
  int n=WiFi.scanNetworks();
  String json="[";
  for(int i=0;i<n;i++){
    if(i)json+=",";
    json+="{\"ssid\":\""+WiFi.SSID(i)+"\",\"rssi\":"+WiFi.RSSI(i)+"}";
  }
  json+="]";
  WiFi.scanDelete();
  return json;
}

// 启动AP模式
void startAPMode() {
  setLedState(LED_AP_MODE);  // 设置为AP模式状态(快速闪烁)
  WiFi.mode(WIFI_AP_STA);
  WiFi.softAP(apSSID, apPassword);
  Serial.println("AP模式启动");
  Serial.print("AP: ");Serial.println(apSSID);
  Serial.print("AP IP: ");Serial.println(WiFi.softAPIP());
  apStartTime=millis();

  server.on("/",[](){server.send_P(200,"text/html",index_html);});
  server.on("/wifiscan",[](){server.send(200,"application/json",getWiFiScanResults());});
  server.on("/connect",HTTP_POST,[](){
    String ssid=server.arg("ssid"),pwd=server.arg("password");
    saveWiFiConfig(ssid,pwd);
    
    // 设置LED为配置保存成功状态
    setLedState(LED_SAVED_SUCCESS);
    
    // 配置成功页面添加LED状态提示
    String res=R"rawliteral(
<!DOCTYPE html>
<html><head><meta name="viewport" content="width=device-width,initial-scale=1">
<style>
    *{box-sizing:border-box;margin:0;padding:0;font-family:Arial,sans-serif}
    body{background:#f0f2f5;min-height:100vh;display:flex;justify-content:center;align-items:center;padding:20px}
    .card{background:#fff;border-radius:10px;padding:30px;box-shadow:0 4px 20px rgba(0,0,0,0.1);max-width:400px;width:100%}
    .icon{text-align:center;font-size:60px;color:#34a853;margin:0 0 20px}
    h2{color:#333;text-align:center;margin:0 0 15px}
    .info{margin:0 0 25px}
    .item{margin:0 0 10px}
    .label{color:#666;font-size:14px}
    .val{font-size:18px;font-weight:500;word-break:break-all}
    .countdown{text-align:center;color:#666;margin:0 0 20px}
    .num{font-size:24px;font-weight:bold;color:#1a73e8}
    .inst{text-align:center;color:#666;font-size:14px;margin:0 0 15px}
    .led-icon{font-size:24px;margin:0 0 5px}
    .led-text{font-weight:500}
</style></head><body><div class="card">
    <div class="icon"></div>
    <h2>配置已保存</h2>
    <div class="info">
        <div class="item"><div class="label">WiFi名称:</div><div class="val">)rawliteral"+ssid+R"rawliteral(</div></div>
        <div class="item"><div class="label">WiFi密码:</div><div class="val">)rawliteral"+pwd+R"rawliteral(</div></div>
    </div>

    <div class="countdown">重启倒计时 <span class="num" id="t">10</span> 秒</div>
    <div class="inst">
        观察设备LED状态变化:<br>
        - 重启后常亮: WiFi连接成功<br>
        - 重启后慢速闪烁: WiFi连接失败<br>
        - 若手机WiFi列表中 <b>)rawliteral"+String(apSSID)+R"rawliteral(</b> <br>仍存在,请重新配置。
    </div>
</div><script>
    let s=10;const el=document.getElementById('t');
    function update(){s--;el.textContent=s;s>0?setTimeout(update,1000):null}
    update();
</script></body></html>
)rawliteral";
    
    server.send(200,"text/html;charset=utf-8",res);
    
    // 显示保存成功状态3秒后重启
    delay(3000);
    ESP.restart();
  });

  //  captive portal重定向
  server.on("/generate_204",[](){server.sendHeader("Location","http://"+WiFi.softAPIP().toString(),true);server.send(302,"text/plain","");});
  server.on("/connecttest.txt",[](){server.sendHeader("Location","/",true);server.send(302,"text/plain","");});
  server.on("/captive.apple.com",[](){server.sendHeader("Location","/",true);server.send(302,"text/plain","");});
  server.on("/ncsi.txt",[](){server.sendHeader("Location","/",true);server.send(302,"text/plain","");});
  server.onNotFound([](){server.sendHeader("Location","/",true);server.send(302,"text/plain","");});

  dnsServer.start(53,"*",WiFi.softAPIP());
  server.begin();
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);  // 初始熄灭
  
  Serial.begin(9600);
  Serial.println();
  
  setLedState(LED_DISCONNECTED);  // 默认未连接状态
  delay(1000);  // 显示初始状态1秒
  
  if(hasSavedWiFiConfig()&&connectToSavedWiFi()){
    // WiFi连接成功后的应用代码
  }else{
    startAPMode();
  }
}

void loop() {
  dnsServer.processNextRequest();
  server.handleClient();
  
  // 检查AP模式超时
  if((WiFi.getMode()&WIFI_AP)&&millis()-apStartTime>=AP_TIMEOUT){
    Serial.println("AP超时,重启中...");
    ESP.restart();
  }
  
  // 如果WiFi已连接,可以添加应用逻辑
  if(WiFi.status()==WL_CONNECTED){
    // 应用代码
  }
  
  // 更新LED状态
  updateLed();
  
  delay(10);
}
ts
//ESP8266实现连接MQTT服务器
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
#include <EEPROM.h>
#include <PubSubClient.h>  // MQTT库

// -------------------------- 配置参数区(请在此处统一配置) --------------------------
// WiFi配置(无需修改,通过网页配置)
const char* apSSID = "esp82config";       // AP模式热点名称
const char* apPassword = "";      // AP模式热点密码

// MQTT配置
const char* mqtt_server = "broker.emqx.io";  // MQTT服务器地址
const uint16_t mqtt_port = 1883;             // MQTT服务器端口
const char* mqtt_client_id = "esp8266_client2";// MQTT客户端ID(建议唯一,可自定义)
const char* mqtt_username = "";               // MQTT用户名(无则留空)
const char* mqtt_password = "";               // MQTT密码(无则留空)
const char* mqtt_sub_topic = "esp8266/sub";   // 订阅的主题
const char* mqtt_pub_topic = "esp8266/pub";   // 发布的主题
// -----------------------------------------------------------------------------------

// EEPROM配置
const int EEPROM_SIZE = 512;
const int SSID_ADDRESS = 0;
const int PASSWORD_ADDRESS = 128;

// 网络对象
ESP8266WebServer server(80);
DNSServer dnsServer;
WiFiClient espClient;               // WiFi客户端(用于MQTT)
PubSubClient client(espClient);     // MQTT客户端


// -------------------------- MQTT相关函数 --------------------------
// MQTT消息回调函数(收到订阅主题消息时触发)
void mqttCallback(char* topic, byte* payload, unsigned int length) {
  Serial.print("收到消息 [");
  Serial.print(topic);
  Serial.print("]: ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
  // 可在此处添加消息处理逻辑(例如控制外设)
}

// MQTT重连函数
bool mqttReconnect() {
  // 生成唯一客户端ID(避免重复连接)
  String clientId = mqtt_client_id;
  clientId += "-";
  clientId += String(random(0xffff), HEX);

  // 尝试连接MQTT服务器
  if (client.connect(clientId.c_str(), mqtt_username, mqtt_password)) {
    Serial.println("MQTT连接成功");
    // 连接成功后订阅主题
    if (client.subscribe(mqtt_sub_topic)) {
      Serial.print("已订阅主题: ");
      Serial.println(mqtt_sub_topic);
    } else {
      Serial.print("订阅主题失败: ");
      Serial.println(mqtt_sub_topic);
    }
    // 可在此处添加初始发布消息(例如设备上线通知)
    client.publish(mqtt_pub_topic, "ESP8266已上线");
    return true;
  } else {
    Serial.print("MQTT连接失败,错误代码: ");
    Serial.println(client.state());  // 打印错误状态码(参考PubSubClient文档)
    return false;
  }
}

// 初始化MQTT
void initMQTT() {
  client.setServer(mqtt_server, mqtt_port);  // 设置MQTT服务器
  client.setCallback(mqttCallback);          // 设置消息回调函数
}


// -------------------------- 原有WiFi配置代码(无需修改) --------------------------
// 从EEPROM读取WiFi配置
void readWiFiConfig() {
  EEPROM.begin(EEPROM_SIZE);
  delay(10);
  
  char ssid[128];
  char password[128];
  
  for (int i = 0; i < 128; i++) {
    ssid[i] = EEPROM.read(SSID_ADDRESS + i);
  }
  
  for (int i = 0; i < 128; i++) {
    password[i] = EEPROM.read(PASSWORD_ADDRESS + i);
  }
  
  // 检查是否有保存的配置
  if (strlen(ssid) > 0) {
    WiFi.begin(ssid, password);
    Serial.print("Found saved configuration. Connecting to: ");
    Serial.println(ssid);
  }
  
  EEPROM.end();
}

// 保存WiFi配置到EEPROM
void saveWiFiConfig(String ssid, String password) {
  EEPROM.begin(EEPROM_SIZE);
  delay(10);
  
  // 保存SSID
  for (int i = 0; i < ssid.length(); i++) {
    EEPROM.write(SSID_ADDRESS + i, ssid[i]);
  }
  EEPROM.write(SSID_ADDRESS + ssid.length(), '\0'); // 字符串结束符
  
  // 保存密码
  for (int i = 0; i < password.length(); i++) {
    EEPROM.write(PASSWORD_ADDRESS + i, password[i]);
  }
  EEPROM.write(PASSWORD_ADDRESS + password.length(), '\0'); // 字符串结束符
  
  EEPROM.commit();
  EEPROM.end();
  
  Serial.println("WiFi configuration saved to EEPROM");
}

// 检查是否有保存的WiFi配置
bool hasSavedWiFiConfig() {
  EEPROM.begin(EEPROM_SIZE);
  delay(10);
  
  char ssid[128];
  for (int i = 0; i < 128; i++) {
    ssid[i] = EEPROM.read(SSID_ADDRESS + i);
  }
  
  EEPROM.end();
  
  return (strlen(ssid) > 0);
}

// 连接到保存的WiFi
bool connectToSavedWiFi() {
  WiFi.mode(WIFI_STA);
  readWiFiConfig(); // 从EEPROM读取配置
  
  Serial.print("Connecting to saved network...");
  
  int retryCount = 0;
  while (WiFi.status() != WL_CONNECTED && retryCount < 20) {
    delay(500);
    Serial.print(".");
    retryCount++;
  }
  
  Serial.println();
  
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("Connected to WiFi");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    return true;
  } else {
    Serial.println("Connection failed");
    return false;
  }
}

// 启动AP模式
void startAPMode() {
  WiFi.mode(WIFI_AP);
  WiFi.softAP(apSSID, apPassword);
  
  Serial.println("AP mode started");
  Serial.print("AP SSID: ");
  Serial.println(apSSID);
  Serial.print("AP IP address: ");
  Serial.println(WiFi.softAPIP());
  
  // 设置DNS服务器
  dnsServer.start(53, "*", WiFi.softAPIP());
  
  // 设置Web服务器路由
  server.on("/", []() {
    String page = "<html><body>";
    page += "<h1>WiFi Configuration</h1>";
    page += "<form method='POST' action='/connect'>";
    page += "SSID: <input type='text' name='ssid'><br>";
    page += "Password: <input type='password' name='password'><br>";
    page += "<input type='submit' value='Connect'>";
    page += "</form>";
    page += "</body></html>";
    server.send(200, "text/html", page);
  });
  
  server.on("/connect", []() {
    if (server.hasArg("ssid") && server.hasArg("password")) {
      String ssid = server.arg("ssid");
      String password = server.arg("password");
      
      saveWiFiConfig(ssid, password); // 保存配置到EEPROM
      
      server.send(200, "text/plain", "WiFi configuration saved. Rebooting...");
      delay(1000);
      ESP.restart();
    } else {
      server.send(400, "text/plain", "Bad Request");
    }
  });
  
  // 处理所有未定义的请求,重定向到配置页面
  server.onNotFound([]() {
    server.sendHeader("Location", String("http://") + WiFi.softAPIP().toString(), true);
    server.send(302, "text/plain", "");
  });
  
  server.begin();
  Serial.println("Web server started");
}


// -------------------------- 初始化与主循环 --------------------------
void setup() {
  Serial.begin(9600);
  Serial.println();
  randomSeed(micros());  // 初始化随机数生成器(用于MQTT客户端ID)
  
  // 尝试连接到保存的WiFi
  if (hasSavedWiFiConfig() && connectToSavedWiFi()) {
    // WiFi连接成功,初始化MQTT
    initMQTT();
  } else {
    // WiFi连接失败或无保存配置,启动AP模式
    startAPMode();
  }
}

void loop() {
  // 处理DNS和HTTP请求(AP模式下需要)
  dnsServer.processNextRequest();
  server.handleClient();
  
  // 如果WiFi已连接,处理MQTT逻辑
  if (WiFi.status() == WL_CONNECTED) {
    // 检查MQTT连接状态,断开则重连
    if (!client.connected()) {
      static unsigned long lastReconnectAttempt = 0;
      unsigned long now = millis();
      // 每5秒尝试重连一次
      if (now - lastReconnectAttempt > 5000) {
        lastReconnectAttempt = now;
        if (mqttReconnect()) {
          lastReconnectAttempt = 0; // 重连成功重置计时器
        }
      }
    } else {
      // MQTT正常连接,处理消息循环
      client.loop();
      
      // 可在此处添加周期性发布逻辑(例如定时发送传感器数据)
      // 示例:每10秒发布一次消息
      static unsigned long lastPublishTime = 0;
      if (millis() - lastPublishTime > 10000) {
        lastPublishTime = millis();
        String temp = "当前时间: " + String(millis()/1000) + "s"; // 示例数据
        client.publish(mqtt_pub_topic, temp.c_str());
        Serial.print("已发布消息: ");
        Serial.println(temp);
      }
    }
  }
  
  delay(10);
}

最后

代码开源,你可以随意复制使用修改,但是请注明出处,谢谢。

点击最下方的邮箱给我发送邮件。

我的“舞台”
小破站成立了!