FILEEM

POWER OF DREAM

使用ESP8266搭建一个自带配网AP的智能硬件

搭建配网网关

固定 ESP8266 的 AP IP 为 192.168.4.1。
配网后,长按板载FLASH键3秒以上可以重新配网。

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <EEPROM.h>

// 定义Flash按钮引脚(D1 Mini上的Flash按钮连接到GPIO0)
#define FLASH_BUTTON_PIN 0

// 创建Web服务器对象,监听80端口
ESP8266WebServer server(80);

// 存储WiFi配置的结构体
struct WiFiConfig {
  char ssid[32];
  char password[64];
};

// 全局变量
WiFiConfig config;
bool inAPMode = false;
unsigned long buttonPressTime = 0;
bool buttonPressed = false;

// 保存配置到EEPROM
void saveConfig() {
  EEPROM.begin(512);
  EEPROM.put(0, config);
  EEPROM.commit();
  EEPROM.end();
}

// 从EEPROM加载配置
void loadConfig() {
  EEPROM.begin(512);
  EEPROM.get(0, config);
  EEPROM.end();

  // 检查配置是否有效
  if (strlen(config.ssid) == 0) {
    strcpy(config.ssid, "");
    strcpy(config.password, "");
  }
}

// 清除配置
void clearConfig() {
  strcpy(config.ssid, "");
  strcpy(config.password, "");
  saveConfig();
  Serial.println("配置已清除");
}

// 检查按钮状态
void checkButton() {
  if (digitalRead(FLASH_BUTTON_PIN) == LOW) {
    if (!buttonPressed) {
      buttonPressed = true;
      buttonPressTime = millis();
    } else if (millis() - buttonPressTime > 3000) {
      // 长按3秒,清除配置
      clearConfig();
      ESP.restart();
    }
  } else {
    buttonPressed = false;
  }
}

// 生成配置页面HTML
String getConfigPage() {
  String page = "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
  // 添加视口标签,确保在手机上正确显示
  page += "<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'>";
  page += "<title>WiFi配置</title>";
  page += "<style>";
  // 基础样式
  page += "body{font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', sans-serif;margin:0;padding:20px;background:#f0f2f5;}";
  // 容器样式 - 响应式设计
  page += ".container{max-width:500px;margin:0 auto;background:white;padding:30px;border-radius:12px;box-shadow:0 2px 15px rgba(0,0,0,0.05);}";
  // 标题样式
  page += "h1{color:#1a73e8;font-size:24px;margin-top:0;text-align:center;}";
  // 表单元素样式 - 增大尺寸便于触控
  page += "select,input{width:100%;padding:15px;margin:12px 0;border:1px solid #dadce0;border-radius:8px;font-size:16px;box-sizing:border-box;}";
  page += "select:focus,input:focus{outline:none;border-color:#1a73e8;box-shadow:0 0 0 2px rgba(26, 115, 232, 0.2);}";
  // 按钮样式 - 更大尺寸和点击反馈
  page += "button{width:100%;background:#1a73e8;color:white;padding:15px;border:none;border-radius:8px;font-size:16px;font-weight:500;cursor:pointer;transition:background 0.3s;}";
  page += "button:hover{background:#1557b0;}";
  // 标签样式
  page += "label{display:block;margin-top:20px;margin-bottom:8px;color:#5f6368;font-size:14px;font-weight:500;}";
  // 信号强度指示
  page += ".signal{color:#80868b;font-size:12px;}";
  page += "</style></head><body><div class='container'>";
  page += "<h1>WiFi网络配置</h1>";
  page += "<form method='get' action='save'>";
  page += "<label for='ssid'>选择WiFi网络:</label>";
  page += "<select name='ssid' id='ssid'>";

  // 扫描WiFi网络
  int n = WiFi.scanNetworks();
  for (int i = 0; i < n; i++) {
    // 添加信号强度指示
    String signalStrength;
    int rssi = WiFi.RSSI(i);
    if (rssi > -50) signalStrength = "▂▃▅▇ (强)";
    else if (rssi > -70) signalStrength = "▂▃▅ (中)";
    else signalStrength = "▂▃ (弱)";

    page += "<option value='" + WiFi.SSID(i) + "'>" + WiFi.SSID(i) + " <span class='signal'>" + signalStrength + "</span></option>";
  }

  page += "</select>";
  page += "<label for='password'>WiFi密码:</label>";
  page += "<input type='password' name='password' placeholder='输入WiFi密码'>";
  page += "<button type='submit'>保存配置并连接</button>";
  page += "</form></div></body></html>";

  return page;
}

// 处理根路径请求
void handleRoot() {
  server.send(200, "text/html", getConfigPage());
}

// 处理配置保存请求
void handleSave() {
  if (server.hasArg("ssid") && server.hasArg("password")) {
    String ssid = server.arg("ssid");
    String password = server.arg("password");

    ssid.toCharArray(config.ssid, sizeof(config.ssid));
    password.toCharArray(config.password, sizeof(config.password));

    saveConfig();

    String page = "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
    // 添加视口标签
    page += "<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'>";
    page += "<title>配置成功</title>";
    page += "<style>";
    page += "body{font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', sans-serif;margin:0;padding:20px;background:#f0f2f5;}";
    page += ".container{max-width:500px;margin:0 auto;background:white;padding:30px;border-radius:12px;box-shadow:0 2px 15px rgba(0,0,0,0.05);text-align:center;}";
    page += "h1{color:#0f9d58;font-size:24px;}";
    page += ".checkmark{font-size:50px;color:#0f9d58;margin-bottom:20px;}";
    page += "p{color:#5f6368;line-height:1.6;}";
    page += "</style></head><body><div class='container'>";
    page += "<div class='checkmark'>✓</div>";
    page += "<h1>配置成功!</h1>";
    page += "<p>设备将尝试连接WiFi网络:</p>";
    page += "<p><strong>" + ssid + "</strong></p>";
    page += "<p>AP模式已关闭,如需重新配置,请长按Flash按钮3秒</p>";
    page += "</div></body></html>";

    server.send(200, "text/html", page);

    // 延迟一下让客户端收到响应
    delay(1000);

    // 关闭AP模式并尝试连接WiFi
    WiFi.softAPdisconnect(true);
    inAPMode = false;
    WiFi.begin(config.ssid, config.password);
  } else {
    server.send(400, "text/plain", "参数错误");
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(FLASH_BUTTON_PIN, INPUT_PULLUP);

  // 加载配置
  loadConfig();

  // 尝试连接已保存的WiFi
  if (strlen(config.ssid) > 0) {
    Serial.print("尝试连接WiFi: ");
    Serial.println(config.ssid);
    WiFi.begin(config.ssid, config.password);

    // 等待10秒连接
    for (int i = 0; i < 20; i++) {
      if (WiFi.status() == WL_CONNECTED) {
        Serial.println("连接成功!");
        Serial.print("IP地址: ");
        Serial.println(WiFi.localIP());
        return;
      }
      delay(500);
      Serial.print(".");
    }
    Serial.println("连接失败,进入AP模式");
  }

  // 连接失败或没有配置,进入AP模式
  inAPMode = true;
  WiFi.mode(WIFI_AP);
  WiFi.softAP("AIOTS_WIFI", "");
  Serial.println("AP模式已启动");
  Serial.print("AP IP地址: ");
  Serial.println(WiFi.softAPIP());

  // 设置Web服务器路由
  server.on("/", handleRoot);
  server.on("/save", handleSave);

  // 启动Web服务器
  server.begin();
  Serial.println("Web服务器已启动");
}

void loop() {
  if (inAPMode) {
    server.handleClient();
  }

  checkButton();
  delay(10);
}

实现连接WIFI后强制打开浏览器

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
#include <EEPROM.h>

// 定义Flash按钮引脚(D1 Mini上的Flash按钮连接到GPIO0)
#define FLASH_BUTTON_PIN 0

// DNS和Web服务器配置
const byte DNS_PORT = 53;
IPAddress apIP(192, 168, 4, 1);
DNSServer dnsServer;
ESP8266WebServer server(80);

// 存储WiFi配置的结构体
struct WiFiConfig {
  char ssid[32];
  char password[64];
};

// 全局变量
WiFiConfig config;
bool inAPMode = false;
unsigned long buttonPressTime = 0;
bool buttonPressed = false;
bool captivePortalActive = false;

// 保存配置到EEPROM
void saveConfig() {
  EEPROM.begin(512);
  EEPROM.put(0, config);
  EEPROM.commit();
  EEPROM.end();
}

// 从EEPROM加载配置
void loadConfig() {
  EEPROM.begin(512);
  EEPROM.get(0, config);
  EEPROM.end();

  // 检查配置是否有效
  if (strlen(config.ssid) == 0) {
    strcpy(config.ssid, "");
    strcpy(config.password, "");
  }
}

// 清除配置
void clearConfig() {
  strcpy(config.ssid, "");
  strcpy(config.password, "");
  saveConfig();
  Serial.println("配置已清除");
}

// 检查按钮状态
void checkButton() {
  if (digitalRead(FLASH_BUTTON_PIN) == LOW) {
    if (!buttonPressed) {
      buttonPressed = true;
      buttonPressTime = millis();
    } else if (millis() - buttonPressTime > 3000) {
      // 长按3秒,清除配置
      clearConfig();
      ESP.restart();
    }
  } else {
    buttonPressed = false;
  }
}

// 生成配置页面HTML
String getConfigPage() {
  String page = "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
  // 添加视口标签,确保在手机上正确显示
  page += "<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'>";
  page += "<title>WiFi配置</title>";
  page += "<style>";
  // 基础样式
  page += "body{font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', sans-serif;margin:0;padding:20px;background:#f0f2f5;}";
  // 容器样式 - 响应式设计
  page += ".container{max-width:500px;margin:0 auto;background:white;padding:30px;border-radius:12px;box-shadow:0 2px 15px rgba(0,0,0,0.05);}";
  // 标题样式
  page += "h1{color:#1a73e8;font-size:24px;margin-top:0;text-align:center;}";
  // 表单元素样式 - 增大尺寸便于触控
  page += "select,input{width:100%;padding:15px;margin:12px 0;border:1px solid #dadce0;border-radius:8px;font-size:16px;box-sizing:border-box;}";
  page += "select:focus,input:focus{outline:none;border-color:#1a73e8;box-shadow:0 0 0 2px rgba(26, 115, 232, 0.2);}";
  // 按钮样式 - 更大尺寸和点击反馈
  page += "button{width:100%;background:#1a73e8;color:white;padding:15px;border:none;border-radius:8px;font-size:16px;font-weight:500;cursor:pointer;transition:background 0.3s;}";
  page += "button:hover{background:#1557b0;}";
  // 标签样式
  page += "label{display:block;margin-top:20px;margin-bottom:8px;color:#5f6368;font-size:14px;font-weight:500;}";
  // 信号强度指示
  page += ".signal{color:#80868b;font-size:12px;}";
  page += "</style></head><body><div class='container'>";
  page += "<h1>WiFi网络配置</h1>";
  page += "<form method='get' action='save'>";
  page += "<label for='ssid'>选择WiFi网络:</label>";
  page += "<select name='ssid' id='ssid'>";

  // 扫描WiFi网络
  int n = WiFi.scanNetworks();
  for (int i = 0; i < n; i++) {
    // 添加信号强度指示
    String signalStrength;
    int rssi = WiFi.RSSI(i);
    if (rssi > -50) signalStrength = "▂▃▅▇ (强)";
    else if (rssi > -70) signalStrength = "▂▃▅ (中)";
    else signalStrength = "▂▃ (弱)";

    page += "<option value='" + WiFi.SSID(i) + "'>" + WiFi.SSID(i) + " <span class='signal'>" + signalStrength + "</span></option>";
  }

  page += "</select>";
  page += "<label for='password'>WiFi密码:</label>";
  page += "<input type='password' name='password' placeholder='输入WiFi密码'>";
  page += "<button type='submit'>保存配置并连接</button>";
  page += "</form></div></body></html>";

  return page;
}

// 处理根路径请求
void handleRoot() {
  if (captivePortalActive) {
    server.send(200, "text/html", getConfigPage());
  } else {
    server.send(200, "text/plain", "已连接到WiFi,无需配置");
  }
}

// 处理配置保存请求
void handleSave() {
  if (server.hasArg("ssid") && server.hasArg("password")) {
    String ssid = server.arg("ssid");
    String password = server.arg("password");

    ssid.toCharArray(config.ssid, sizeof(config.ssid));
    password.toCharArray(config.password, sizeof(config.password));

    saveConfig();

    String page = "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
    // 添加视口标签
    page += "<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'>";
    page += "<title>配置成功</title>";
    page += "<style>";
    page += "body{font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', sans-serif;margin:0;padding:20px;background:#f0f2f5;}";
    page += ".container{max-width:500px;margin:0 auto;background:white;padding:30px;border-radius:12px;box-shadow:0 2px 15px rgba(0,0,0,0.05);text-align:center;}";
    page += "h1{color:#0f9d58;font-size:24px;}";
    page += ".checkmark{font-size:50px;color:#0f9d58;margin-bottom:20px;}";
    page += "p{color:#5f6368;line-height:1.6;}";
    page += "</style></head><body><div class='container'>";
    page += "<div class='checkmark'>✓</div>";
    page += "<h1>配置成功!</h1>";
    page += "<p>设备将尝试连接WiFi网络:</p>";
    page += "<p><strong>" + ssid + "</strong></p>";
    page += "<p>AP模式已关闭,如需重新配置,请长按Flash按钮3秒</p>";
    page += "</div></body></html>";

    server.send(200, "text/html", page);

    // 延迟一下让客户端收到响应
    delay(1000);

    // 关闭AP模式并尝试连接WiFi
    WiFi.softAPdisconnect(true);
    inAPMode = false;
    captivePortalActive = false;
    WiFi.begin(config.ssid, config.password);
  } else {
    server.send(400, "text/plain", "参数错误");
  }
}

// 处理强制门户请求
void handleCaptivePortal() {
  server.sendHeader("Location", "http://" + String(apIP[0]) + "." + String(apIP[1]) + "." + String(apIP[2]) + "." + String(apIP[3]), true);
  server.send(302, "text/plain", "");
}

void setup() {
  Serial.begin(115200);
  pinMode(FLASH_BUTTON_PIN, INPUT_PULLUP);

  // 加载配置
  loadConfig();

  // 尝试连接已保存的WiFi
  if (strlen(config.ssid) > 0) {
    Serial.print("尝试连接WiFi: ");
    Serial.println(config.ssid);
    WiFi.begin(config.ssid, config.password);

    // 等待10秒连接
    for (int i = 0; i < 20; i++) {
      if (WiFi.status() == WL_CONNECTED) {
        Serial.println("连接成功!");
        Serial.print("IP地址: ");
        Serial.println(WiFi.localIP());

        // 设置Web服务器路由
        server.on("/", handleRoot);
        server.on("/save", handleSave);
        server.begin();
        return;
      }
      delay(500);
      Serial.print(".");
    }
    Serial.println("连接失败,进入AP模式");
  }

  // 连接失败或没有配置,进入AP模式
  inAPMode = true;
  captivePortalActive = true;
  WiFi.mode(WIFI_AP);
  WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
  WiFi.softAP("AIOTS_WIFI", "");

  // 启动DNS服务器,将所有请求重定向到门户页面
  dnsServer.start(DNS_PORT, "*", apIP);

  Serial.println("AP模式已启动");
  Serial.print("AP IP地址: ");
  Serial.println(WiFi.softAPIP());

  // 设置Web服务器路由
  server.on("/", handleRoot);
  server.on("/save", handleSave);

  // 捕获所有其他请求并重定向到门户
  server.onNotFound(handleCaptivePortal);

  // 启动Web服务器
  server.begin();
  Serial.println("Web服务器已启动");
  Serial.println("强制门户已激活");
}

void loop() {
  if (inAPMode) {
    dnsServer.processNextRequest();
  }

  server.handleClient();
  checkButton();
  delay(10);
}

控制舵机

这里也加了WIFI密码是否正确的检测

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
#include <EEPROM.h>
#include <PubSubClient.h>
#include <time.h>
#include <ArduinoJson.h>
#include <Servo.h>

// 定义Flash按钮引脚
#define FLASH_BUTTON_PIN 0

// DNS和Web服务器配置
const byte DNS_PORT = 53;
IPAddress apIP(192, 168, 4, 1);
DNSServer dnsServer;
ESP8266WebServer server(80);

// 存储WiFi配置的结构体
struct WiFiConfig {
  char ssid[32];
  char password[64];
};

// MQTT配置
const int mqtt_port = 8883;
const char *mqtt_broker = "c039f6b7.ala.cn-hangzhou.emqxsl.cn";
const char *mqtt_topic = "emqx/esp8266";
const char *mqtt_username = "test";
const char *mqtt_password = "test";

// NTP服务器配置
const char *ntp_server = "pool.ntp.org";
const long gmt_offset_sec = 0;
const int daylight_offset_sec = 0;

// 舵机配置
const int servoPin = 16;
Servo myServo;
int targetAngle = 0;
bool moveServo = false;

// LED配置
const int ledPin = LED_BUILTIN;
unsigned long ledStartTime = 0;
bool ledBlinking = false;

// 全局变量
WiFiConfig config;
bool inAPMode = false;
unsigned long buttonPressTime = 0;
bool buttonPressed = false;
bool captivePortalActive = false;
bool wifiConnected = false;
bool mqttInitialized = false;

// SSL证书
static const char ca_cert[]
PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXX
-----END CERTIFICATE-----
)EOF";

// WiFi和MQTT客户端初始化
BearSSL::WiFiClientSecure espClient;
PubSubClient mqtt_client(espClient);

// 保存配置到EEPROM
void saveConfig() {
  EEPROM.begin(512);
  EEPROM.put(0, config);
  EEPROM.commit();
  EEPROM.end();
}

// 从EEPROM加载配置
void loadConfig() {
  EEPROM.begin(512);
  EEPROM.get(0, config);
  EEPROM.end();

  // 检查配置是否有效
  if (strlen(config.ssid) == 0) {
    strcpy(config.ssid, "");
    strcpy(config.password, "");
  }
}

// 清除配置
void clearConfig() {
  strcpy(config.ssid, "");
  strcpy(config.password, "");
  saveConfig();
  Serial.println("配置已清除");
}

// 检查按钮状态
void checkButton() {
  if (digitalRead(FLASH_BUTTON_PIN) == LOW) {
    if (!buttonPressed) {
      buttonPressed = true;
      buttonPressTime = millis();
    } else if (millis() - buttonPressTime > 3000) {
      // 长按3秒,清除配置
      clearConfig();
      ESP.restart();
    }
  } else {
    buttonPressed = false;
  }
}

// 同步时间
void syncTime() {
  configTime(gmt_offset_sec, daylight_offset_sec, ntp_server);
  Serial.print("Waiting for NTP time sync: ");
  while (time(nullptr) < 8 * 3600 * 2) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("Time synchronized");
  struct tm timeinfo;
  if (getLocalTime(&timeinfo)) {
    Serial.print("Current time: ");
    Serial.println(asctime(&timeinfo));
  } else {
    Serial.println("Failed to obtain local time");
  }
}

// 连接MQTT
void connectToMQTT() {
  BearSSL::X509List serverTrustedCA(ca_cert);
  espClient.setTrustAnchors(&serverTrustedCA);

  String client_id = "esp8266-client-" + String(WiFi.macAddress());
  Serial.printf("Connecting to MQTT Broker as %s.....\n", client_id.c_str());

  if (mqtt_client.connect(client_id.c_str(), mqtt_username, mqtt_password)) {
    Serial.println("Connected to MQTT broker");
    mqtt_client.subscribe(mqtt_topic);
    mqtt_client.publish(mqtt_topic, "Hi EMQX~ I'm ESP8266 ^^");
  } else {
    char err_buf[128];
    espClient.getLastSSLError(err_buf, sizeof(err_buf));
    Serial.print("Failed to connect to MQTT broker, rc=");
    Serial.println(mqtt_client.state());
    Serial.print("SSL error: ");
    Serial.println(err_buf);
  }
}

// MQTT回调函数
void mqttCallback(char *topic, byte *payload, unsigned int length) {
  Serial.print("Message received on topic: ");
  Serial.print(topic);
  Serial.print("]: ");

  // 将payload转换为字符串
  String message;
  for (int i = 0; i < length; i++) {
    message += (char)payload[i];
  }
  Serial.println(message);

  // 尝试解析JSON
  StaticJsonDocument<200> doc;
  DeserializationError error = deserializeJson(doc, message);

  if (!error) {
    // 检查是否有angle字段
    if (doc.containsKey("angle")) {
      targetAngle = doc["angle"];
      Serial.print("Setting servo angle to: ");
      Serial.println(targetAngle);

      // 限制角度范围在0-180度之间
      if (targetAngle < 0) targetAngle = 0;
      if (targetAngle > 180) targetAngle = 180;

      moveServo = true; // 标记需要转动舵机
    }
  } else {
    Serial.print("JSON parsing failed: ");
    Serial.println(error.c_str());
  }

  // 无论是否包含angle参数,都点亮LED
  digitalWrite(ledPin, LOW); // 点亮LED(低电平点亮)
  ledStartTime = millis();
  ledBlinking = true;
}

// 处理LED
void handleLED() {
  if (ledBlinking) {
    // 检查是否已经闪烁了1秒
    if (millis() - ledStartTime >= 1000) {
      digitalWrite(ledPin, HIGH); // 关闭LED
      ledBlinking = false;
    }
  }
}

// 处理舵机
void handleServo() {
  if (moveServo) {
    myServo.write(targetAngle); // 转动舵机到目标角度
    Serial.print("Servo moved to: ");
    Serial.println(targetAngle);
    moveServo = false; // 重置标志
  }
}

// 生成配置页面HTML
String getConfigPage() {
  String page = "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
  // 添加视口标签,确保在手机上正确显示
  page += "<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'>";
  page += "<title>WiFi配置</title>";
  page += "<style>";
  // 基础样式
  page += "body{font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', sans-serif;margin:0;padding:20px;background:#f0f2f5;}";
  // 容器样式 - 响应式设计
  page += ".container{max-width:500px;margin:0 auto;background:white;padding:30px;border-radius:12px;box-shadow:0 2px 15px rgba(0,0,0,0.05);}";
  // 标题样式
  page += "h1{color:#1a73e8;font-size:24px;margin-top:0;text-align:center;}";
  // 表单元素样式 - 增大尺寸便于触控
  page += "select,input{width:100%;padding:15px;margin:12px 0;border:1px solid #dadce0;border-radius:8px;font-size:16px;box-sizing:border-box;}";
  page += "select:focus,input:focus{outline:none;border-color:#1a73e8;box-shadow:0 0 0 2px rgba(26, 115, 232, 0.2);}";
  // 按钮样式 - 更大尺寸和点击反馈
  page += "button{width:100%;background:#1a73e8;color:white;padding:15px;border:none;border-radius:8px;font-size:16px;font-weight:500;cursor:pointer;transition:background 0.3s;}";
  page += "button:hover{background:#1557b0;}";
  // 标签样式
  page += "label{display:block;margin-top:20px;margin-bottom:8px;color:#5f6368;font-size:14px;font-weight:500;}";
  // 信号强度指示
  page += ".signal{color:#80868b;font-size:12px;}";
  // 错误消息样式
  page += ".error{color:#ea4335;background:#fce8e6;padding:10px;border-radius:4px;margin-top:15px;display:none;}";
  page += "</style>";
  // JavaScript用于密码验证
  page += "<script>";
  page += "function validateForm() {";
  page += "  var password = document.getElementById('password').value;";
  page += "  if (password.length < 8) {";
  page += "    document.getElementById('error-msg').style.display = 'block';";
  page += "    document.getElementById('error-msg').innerHTML = '密码长度至少8位字符';";
  page += "    return false;";
  page += "  }";
  page += "  return true;";
  page += "}";
  page += "</script>";
  page += "</head><body><div class='container'>";
  page += "<h1>WiFi网络配置</h1>";
  page += "<div id='error-msg' class='error'></div>";
  page += "<form method='get' action='save' onsubmit='return validateForm()'>";
  page += "<label for='ssid'>选择WiFi网络:</label>";
  page += "<select name='ssid' id='ssid'>";

  // 扫描WiFi网络
  int n = WiFi.scanNetworks();
  for (int i = 0; i < n; i++) {
    // 添加信号强度指示
    String signalStrength;
    int rssi = WiFi.RSSI(i);
    if (rssi > -50) signalStrength = "▂▃▅▇ (强)";
    else if (rssi > -70) signalStrength = "▂▃▅ (中)";
    else signalStrength = "▂▃ (弱)";

    page += "<option value='" + WiFi.SSID(i) + "'>" + WiFi.SSID(i) + " <span class='signal'>" + signalStrength + "</span></option>";
  }

  page += "</select>";
  page += "<label for='password'>WiFi密码:</label>";
  page += "<input type='password' name='password' id='password' placeholder='输入WiFi密码'>";
  page += "<button type='submit'>保存配置并连接</button>";
  page += "</form></div></body></html>";

  return page;
}

// 处理根路径请求
void handleRoot() {
  if (captivePortalActive) {
    server.send(200, "text/html", getConfigPage());
  } else {
    server.send(200, "text/plain", "已连接到WiFi,无需配置");
  }
}

// 处理配置保存请求
void handleSave() {
  if (server.hasArg("ssid") && server.hasArg("password")) {
    String ssid = server.arg("ssid");
    String password = server.arg("password");

    // 验证密码长度
    if (password.length() < 8) {
      String errorPage = "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
      errorPage += "<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
      errorPage += "<title>错误</title>";
      errorPage += "<style>body{font-family:Arial,sans-serif;text-align:center;padding:50px;}";
      errorPage += ".error{color:#d93025;font-size:18px;margin-bottom:20px;}";
      errorPage += "button{padding:10px 20px;background:#1a73e8;color:white;border:none;border-radius:4px;cursor:pointer;}";
      errorPage += "</style></head><body>";
      errorPage += "<div class='error'>密码长度至少8位字符,请重新输入</div>";
      errorPage += "<button onclick='history.back()'>返回</button>";
      errorPage += "</body></html>";

      server.send(200, "text/html", errorPage);
      return;
    }

    // 尝试连接WiFi验证密码
    WiFi.begin(ssid.c_str(), password.c_str());

    // 等待5秒连接
    bool connected = false;
    for (int i = 0; i < 10; i++) {
      if (WiFi.status() == WL_CONNECTED) {
        connected = true;
        break;
      }
      delay(500);
    }

    if (!connected) {
      // 密码错误或无法连接
      String errorPage = "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
      errorPage += "<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
      errorPage += "<title>错误</title>";
      errorPage += "<style>body{font-family:Arial,sans-serif;text-align:center;padding:50px;}";
      errorPage += ".error{color:#d93025;font-size:18px;margin-bottom:20px;}";
      errorPage += "button{padding:10px 20px;background:#1a73e8;color:white;border:none;border-radius:4px;cursor:pointer;}";
      errorPage += "</style></head><body>";
      errorPage += "<div class='error'>无法连接到WiFi,请检查密码是否正确</div>";
      errorPage += "<button onclick='history.back()'>返回</button>";
      errorPage += "</body></html>";

      server.send(200, "text/html", errorPage);
      return;
    }

    // 连接成功,保存配置
    ssid.toCharArray(config.ssid, sizeof(config.ssid));
    password.toCharArray(config.password, sizeof(config.password));
    saveConfig();

    String page = "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
    // 添加视口标签
    page += "<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'>";
    page += "<title>配置成功</title>";
    page += "<style>";
    page += "body{font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', sans-serif;margin:0;padding:20px;background:#f0f2f5;}";
    page += ".container{max-width:500px;margin:0 auto;background:white;padding:30px;border-radius:12px;box-shadow:0 2px 15px rgba(0,0,0,0.05);text-align:center;}";
    page += "h1{color:#0f9d58;font-size:24px;}";
    page += ".checkmark{font-size:50px;color:#0f9d58;margin-bottom:20px;}";
    page += "p{color:#5f6368;line-height:1.6;}";
    page += "</style></head><body><div class='container'>";
    page += "<div class='checkmark'>✓</div>";
    page += "<h1>配置成功!</h1>";
    page += "<p>设备已成功连接到WiFi网络:</p>";
    page += "<p><strong>" + ssid + "</strong></p>";
    page += "<p>AP模式已关闭,如需重新配置,请长按Flash按钮3秒</p>";
    page += "</div></body></html>";

    server.send(200, "text/html", page);

    // 延迟一下让客户端收到响应
    delay(1000);

    // 关闭AP模式
    WiFi.softAPdisconnect(true);
    inAPMode = false;
    captivePortalActive = false;
    wifiConnected = true;

    // 初始化MQTT
    syncTime();
    mqtt_client.setServer(mqtt_broker, mqtt_port);
    mqtt_client.setCallback(mqttCallback);
    connectToMQTT();
    mqttInitialized = true;
  } else {
    server.send(400, "text/plain", "参数错误");
  }
}

// 处理强制门户请求
void handleCaptivePortal() {
  server.sendHeader("Location", "http://" + String(apIP[0]) + "." + String(apIP[1]) + "." + String(apIP[2]) + "." + String(apIP[3]), true);
  server.send(302, "text/plain", "");
}

void setup() {
  Serial.begin(115200);
  pinMode(FLASH_BUTTON_PIN, INPUT_PULLUP);

  // 初始化LED引脚
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, HIGH); // 初始状态关闭

  // 初始化舵机
  myServo.attach(servoPin);
  myServo.write(0); // 初始位置设为0度

  // 加载配置
  loadConfig();

  // 尝试连接已保存的WiFi
  if (strlen(config.ssid) > 0) {
    Serial.print("尝试连接WiFi: ");
    Serial.println(config.ssid);
    WiFi.begin(config.ssid, config.password);

    // 等待10秒连接
    for (int i = 0; i < 20; i++) {
      if (WiFi.status() == WL_CONNECTED) {
        Serial.println("连接成功!");
        Serial.print("IP地址: ");
        Serial.println(WiFi.localIP());
        wifiConnected = true;

        // 初始化MQTT
        syncTime();
        mqtt_client.setServer(mqtt_broker, mqtt_port);
        mqtt_client.setCallback(mqttCallback);
        connectToMQTT();
        mqttInitialized = true;

        // 设置Web服务器路由
        server.on("/", handleRoot);
        server.on("/save", handleSave);
        server.begin();
        return;
      }
      delay(500);
      Serial.print(".");
    }
    Serial.println("连接失败,进入AP模式");
  }

  // 连接失败或没有配置,进入AP模式
  inAPMode = true;
  captivePortalActive = true;
  WiFi.mode(WIFI_AP);
  WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
  WiFi.softAP("AIOTS_WIFI", "");

  // 启动DNS服务器,将所有请求重定向到门户页面
  dnsServer.start(DNS_PORT, "*", apIP);

  Serial.println("AP模式已启动");
  Serial.print("AP IP地址: ");
  Serial.println(WiFi.softAPIP());

  // 设置Web服务器路由
  server.on("/", handleRoot);
  server.on("/save", handleSave);

  // 捕获所有其他请求并重定向到门户
  server.onNotFound(handleCaptivePortal);

  // 启动Web服务器
  server.begin();
  Serial.println("Web服务器已启动");
  Serial.println("强制门户已激活");
}

void loop() {
  if (inAPMode) {
    dnsServer.processNextRequest();
  }

  server.handleClient();
  checkButton();

  // 如果WiFi已连接且MQTT已初始化,处理MQTT和舵机
  if (wifiConnected && mqttInitialized) {
    if (!mqtt_client.connected()) {
      connectToMQTT();
    }
    mqtt_client.loop();

    // 处理LED闪烁
    handleLED();

    // 处理舵机转动
    handleServo();
  }

  delay(10);
}

点赞