搭建配网网关
固定 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);
}