feat:增加蓝牙配网功能
This commit is contained in:
@@ -1,36 +1,28 @@
|
||||
/**
|
||||
* @file wifi_manager.cpp
|
||||
* @brief WiFi 配网管理器实现
|
||||
* @brief WiFi 配网管理器 — AP + WebServer
|
||||
*/
|
||||
|
||||
#include "wifi_manager.h"
|
||||
|
||||
// 单例指针
|
||||
WiFiMgmt* WiFiMgmt::_instance = nullptr;
|
||||
|
||||
// ============================================================
|
||||
// 构造函数 & 析构函数
|
||||
// ============================================================
|
||||
|
||||
WiFiMgmt::WiFiMgmt()
|
||||
: _connected(false)
|
||||
, _lastCheckTime(0)
|
||||
, _buttonPressTime(0)
|
||||
, _buttonPressed(false)
|
||||
, _ledTaskHandle(nullptr)
|
||||
, _ledMode(0)
|
||||
, _param_mqtt_server(nullptr)
|
||||
, _param_mqtt_port(nullptr)
|
||||
, _param_mqtt_user(nullptr)
|
||||
, _param_mqtt_pass(nullptr)
|
||||
, _param_device_id(nullptr)
|
||||
, _param_env_note(nullptr)
|
||||
: _server(nullptr), _dns(nullptr)
|
||||
, _connected(false)
|
||||
, _portalActive(false), _portalStopFlag(false)
|
||||
, _portalStartTime(0)
|
||||
, _lastCheckTime(0), _buttonPressTime(0), _buttonPressed(false)
|
||||
, _lastScanTime(0)
|
||||
, _provisionSuccess(false)
|
||||
, _ledTaskHandle(nullptr), _ledMode(0)
|
||||
{
|
||||
memset(&_config, 0, sizeof(_config));
|
||||
_instance = this; // 设置单例
|
||||
_instance = this;
|
||||
}
|
||||
|
||||
WiFiMgmt::~WiFiMgmt() {
|
||||
_stopPortal();
|
||||
_instance = nullptr;
|
||||
}
|
||||
|
||||
@@ -39,176 +31,52 @@ WiFiMgmt::~WiFiMgmt() {
|
||||
// ============================================================
|
||||
|
||||
bool WiFiMgmt::begin() {
|
||||
Serial.println(F("\n========================================"));
|
||||
Serial.println(F(" WiFi 配网管理器 初始化"));
|
||||
Serial.println(F("========================================"));
|
||||
Serial.println(("\n=== WiFi 配网管理器 (SmartConfig) ==="));
|
||||
|
||||
// 初始化引脚
|
||||
pinMode(CONFIG_BUTTON_PIN, INPUT_PULLUP);
|
||||
pinMode(LED_WIFI_PIN, OUTPUT);
|
||||
digitalWrite(LED_WIFI_PIN, LOW);
|
||||
|
||||
// 启动 LED 独立任务 (避免 startConfigPortal 阻塞影响闪烁)
|
||||
xTaskCreate(_ledTaskFunc, "WiFiLed", 1024, nullptr, 1, &_ledTaskHandle);
|
||||
|
||||
// 初始化 LittleFS
|
||||
// 冷启动延迟, 等 Flash 稳定
|
||||
delay(500);
|
||||
if (!LittleFS.begin(true)) {
|
||||
Serial.println(F("[WiFi] ❌ LittleFS 挂载失败!"));
|
||||
Serial.println(("[WiFi] LittleFS 挂载失败!"));
|
||||
return false;
|
||||
}
|
||||
Serial.println(F("[WiFi] ✅ LittleFS 已挂载"));
|
||||
Serial.println(("[WiFi] LittleFS 就绪"));
|
||||
|
||||
// 加载配置
|
||||
if (!_loadConfig()) {
|
||||
Serial.println(F("[WiFi] 使用默认配置"));
|
||||
// 设置默认值
|
||||
strncpy(_config.mqtt_server, MQTT_DEFAULT_SERVER, sizeof(_config.mqtt_server) - 1);
|
||||
strncpy(_config.mqtt_port, MQTT_DEFAULT_PORT_STR, sizeof(_config.mqtt_port) - 1);
|
||||
strncpy(_config.mqtt_user, MQTT_DEFAULT_USER, sizeof(_config.mqtt_user) - 1);
|
||||
strncpy(_config.mqtt_pass, MQTT_DEFAULT_PASS, sizeof(_config.mqtt_pass) - 1);
|
||||
strncpy(_config.device_id, MQTT_DEFAULT_DEVICE, sizeof(_config.device_id) - 1);
|
||||
strncpy(_config.env_note, MQTT_DEFAULT_ENV, sizeof(_config.env_note) - 1);
|
||||
Serial.println(("[WiFi] 使用默认配置"));
|
||||
strncpy(_config.mqtt_server, MQTT_DEFAULT_SERVER, sizeof(_config.mqtt_server)-1);
|
||||
strncpy(_config.mqtt_port, MQTT_DEFAULT_PORT_STR, sizeof(_config.mqtt_port)-1);
|
||||
strncpy(_config.mqtt_user, MQTT_DEFAULT_USER, sizeof(_config.mqtt_user)-1);
|
||||
strncpy(_config.mqtt_pass, MQTT_DEFAULT_PASS, sizeof(_config.mqtt_pass)-1);
|
||||
strncpy(_config.device_id, MQTT_DEFAULT_DEVICE, sizeof(_config.device_id)-1);
|
||||
strncpy(_config.env_note, MQTT_DEFAULT_ENV, sizeof(_config.env_note)-1);
|
||||
memset(_config.wifi_ssid, 0, sizeof(_config.wifi_ssid));
|
||||
memset(_config.wifi_pass, 0, sizeof(_config.wifi_pass));
|
||||
}
|
||||
|
||||
// 配置 WiFiManager
|
||||
_wm.setDebugOutput(false); // 关闭调试输出 (避免干扰)
|
||||
_wm.setConfigPortalTimeout(WIFI_CONFIG_TIMEOUT);
|
||||
_wm.setConnectTimeout(WIFI_CONNECT_TIMEOUT);
|
||||
_wm.setTitle("健康监测系统 - 设备配网");
|
||||
_wm.setCustomHeadElement(
|
||||
"<style>"
|
||||
/* ===== 字体与全局 ===== */
|
||||
"*{margin:0;padding:0;box-sizing:border-box}"
|
||||
"body{"
|
||||
" font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif;"
|
||||
" background:linear-gradient(160deg, #f0f5ff 0%, #e8f0fe 40%, #f3f0ff 100%);"
|
||||
" min-height:100vh;display:flex;flex-direction:column;align-items:center;"
|
||||
" padding:20px 16px 40px;color:#1e293b"
|
||||
"}"
|
||||
"body::before{"
|
||||
" content:'';position:fixed;top:-200px;right:-200px;"
|
||||
" width:400px;height:400px;border-radius:50%;"
|
||||
" background:radial-gradient(circle, rgba(99,102,241,0.08) 0%, transparent 70%);"
|
||||
" pointer-events:none;z-index:0"
|
||||
"}"
|
||||
"body::after{"
|
||||
" content:'';position:fixed;bottom:-100px;left:-100px;"
|
||||
" width:300px;height:300px;border-radius:50%;"
|
||||
" background:radial-gradient(circle, rgba(34,197,94,0.06) 0%, transparent 70%);"
|
||||
" pointer-events:none;z-index:0"
|
||||
"}"
|
||||
/* ===== 头部卡片 ===== */
|
||||
".msg{"
|
||||
" width:100%;max-width:440px;position:relative;z-index:1;"
|
||||
" background:#fff;border-radius:20px;padding:32px 24px 24px;"
|
||||
" margin-bottom:16px;text-align:center;"
|
||||
" box-shadow:0 1px 3px rgba(0,0,0,0.04),0 8px 24px rgba(0,0,0,0.06)"
|
||||
"}"
|
||||
".msg::before{"
|
||||
" content:'';display:block;width:56px;height:56px;"
|
||||
" margin:0 auto 16px;"
|
||||
" background:linear-gradient(135deg,#6366f1,#8b5cf6);border-radius:16px;"
|
||||
" box-shadow:0 8px 24px rgba(99,102,241,0.25)"
|
||||
"}"
|
||||
".msg h1,.msg h2{font-size:20px;font-weight:700;color:#1e293b;margin-bottom:6px}"
|
||||
".msg p,.msg .info{font-size:13px;color:#94a3b8;line-height:1.5}"
|
||||
/* 隐藏 WiFiManager 默认的 h1 重复标题 */
|
||||
".msg h1{font-size:0;margin:0;padding:0}"
|
||||
".msg h1::after{content:'健康监测系统';font-size:22px;font-weight:700;color:#1e293b}"
|
||||
".msg .info{font-size:13px!important}"
|
||||
/* ===== 表单卡片 ===== */
|
||||
"form{"
|
||||
" width:100%;max-width:440px;position:relative;z-index:1;"
|
||||
" background:#fff;border-radius:20px;padding:24px;"
|
||||
" box-shadow:0 1px 3px rgba(0,0,0,0.04),0 8px 24px rgba(0,0,0,0.06)"
|
||||
"}"
|
||||
"label{"
|
||||
" display:block;font-size:11px;font-weight:600;"
|
||||
" text-transform:uppercase;letter-spacing:0.8px;"
|
||||
" color:#6366f1;margin:16px 0 6px"
|
||||
"}"
|
||||
"label:first-of-type{margin-top:0}"
|
||||
"input[type=text],input[type=password],input[type=number]{"
|
||||
" width:100%;height:46px;padding:0 14px;"
|
||||
" border:2px solid #e2e8f0;border-radius:12px;"
|
||||
" font-size:15px;color:#1e293b;background:#f8fafc;"
|
||||
" outline:none;transition:all 0.2s;-webkit-appearance:none"
|
||||
"}"
|
||||
"input:focus{"
|
||||
" border-color:#6366f1;background:#fff;"
|
||||
" box-shadow:0 0 0 4px rgba(99,102,241,0.1)"
|
||||
"}"
|
||||
"input::placeholder{color:#cbd5e1}"
|
||||
/* ===== 提交按钮 ===== */
|
||||
"button[type=submit]{"
|
||||
" width:100%;height:50px;margin-top:24px;"
|
||||
" background:linear-gradient(135deg,#6366f1,#8b5cf6);"
|
||||
" color:#fff;border:none;border-radius:14px;"
|
||||
" font-size:16px;font-weight:600;letter-spacing:1px;"
|
||||
" cursor:pointer;transition:all 0.2s;"
|
||||
" box-shadow:0 4px 16px rgba(99,102,241,0.3)"
|
||||
"}"
|
||||
"button:active{transform:scale(0.97);box-shadow:0 2px 8px rgba(99,102,241,0.3)}"
|
||||
/* ===== 页脚 ===== */
|
||||
".msg:last-of-type{"
|
||||
" background:transparent;box-shadow:none;padding:16px;margin-top:4px"
|
||||
"}"
|
||||
".msg:last-of-type::before{display:none}"
|
||||
".msg:last-of-type p,.msg:last-of-type .info{font-size:12px;color:#94a3b8}"
|
||||
/* ===== 成功/错误状态页 ===== */
|
||||
".msg .success{color:#16a34a}"
|
||||
".msg .error{color:#dc2626}"
|
||||
"a{color:#6366f1;text-decoration:none;font-weight:500}"
|
||||
"</style>"
|
||||
);
|
||||
WiFi.setAutoReconnect(true); // ESP32 自动重连
|
||||
WiFi.setAutoReconnect(true);
|
||||
|
||||
// 注册保存回调 (必须使用静态函数, 不能使用 lambda)
|
||||
_wm.setSaveConfigCallback(_saveParamsCallback);
|
||||
|
||||
// 创建自定义参数
|
||||
_param_mqtt_server = new WiFiManagerParameter(
|
||||
"mqtt_server", "MQTT 服务器地址",
|
||||
_config.mqtt_server, 40);
|
||||
_param_mqtt_port = new WiFiManagerParameter(
|
||||
"mqtt_port", "MQTT 端口",
|
||||
_config.mqtt_port, 6);
|
||||
_param_mqtt_user = new WiFiManagerParameter(
|
||||
"mqtt_user", "MQTT 用户名",
|
||||
_config.mqtt_user, 20);
|
||||
_param_mqtt_pass = new WiFiManagerParameter(
|
||||
"mqtt_pass", "MQTT 密码",
|
||||
_config.mqtt_pass, 20);
|
||||
_param_device_id = new WiFiManagerParameter(
|
||||
"device_id", "设备名称",
|
||||
_config.device_id, 20);
|
||||
_param_env_note = new WiFiManagerParameter(
|
||||
"env_note", "🏠 环境备注 (如: 主卧/客厅/老人房)",
|
||||
_config.env_note, 20);
|
||||
|
||||
// 注册自定义参数
|
||||
_wm.addParameter(_param_mqtt_server);
|
||||
_wm.addParameter(_param_mqtt_port);
|
||||
_wm.addParameter(_param_mqtt_user);
|
||||
_wm.addParameter(_param_mqtt_pass);
|
||||
_wm.addParameter(_param_device_id);
|
||||
_wm.addParameter(_param_env_note);
|
||||
|
||||
// 设置 AP 模式回调 (静态函数)
|
||||
_wm.setAPCallback(_apCallback);
|
||||
|
||||
// 尝试连接 WiFi
|
||||
_connected = _connectWiFi();
|
||||
// 有配置 → 直接连
|
||||
bool hasConfig = LittleFS.exists(CONFIG_FILE_DEVICE);
|
||||
Serial.printf("[WiFi] 配置文件: %s\n", hasConfig ? "存在" : "不存在");
|
||||
if (hasConfig) {
|
||||
_connected = _connectWiFi();
|
||||
Serial.printf("[WiFi] 自动连接: %s\n", _connected ? "成功" : "失败");
|
||||
} else {
|
||||
Serial.println(("[WiFi] 无配置, 跳过连接"));
|
||||
_connected = false;
|
||||
}
|
||||
|
||||
if (!_connected) {
|
||||
// 如果自动连接失败, 启动配网页面
|
||||
Serial.println(F("[WiFi] 自动连接失败, 启动配网模式..."));
|
||||
_setLEDMode(2); // 快闪 = 配网模式
|
||||
_connected = _connectWiFi(); // 内部会启动配网页面
|
||||
}
|
||||
|
||||
if (_connected) {
|
||||
_setLEDMode(3); // 常亮 = 已连接
|
||||
_startPortal(); // 只开 AP
|
||||
} else {
|
||||
_setLEDMode(3); // 常亮
|
||||
}
|
||||
|
||||
_lastCheckTime = millis();
|
||||
@@ -220,29 +88,59 @@ bool WiFiMgmt::begin() {
|
||||
// ============================================================
|
||||
|
||||
bool WiFiMgmt::loop() {
|
||||
// 检测按钮长按
|
||||
// --- Portal 模式 ---
|
||||
if (_portalActive) {
|
||||
if (_portalStopFlag) {
|
||||
_stopPortal();
|
||||
_portalStopFlag = false;
|
||||
if (_pendingSSID.length() > 0) {
|
||||
Serial.printf("[WiFi] 尝试连接: %s\n", _pendingSSID.c_str());
|
||||
_connected = _tryConnect(_pendingSSID.c_str(), _pendingPass.c_str());
|
||||
_pendingSSID = ""; _pendingPass = "";
|
||||
if (_connected) {
|
||||
_setLEDMode(3);
|
||||
_provisionSuccess = true; // 标记配网成功
|
||||
} else {
|
||||
_setLEDMode(2);
|
||||
_startPortal();
|
||||
}
|
||||
}
|
||||
return _connected;
|
||||
}
|
||||
|
||||
_dns->processNextRequest();
|
||||
_server->handleClient();
|
||||
delay(10);
|
||||
return _connected;
|
||||
}
|
||||
|
||||
// --- 正常运行 ---
|
||||
if (_checkButtonLongPress()) {
|
||||
Serial.println(F("[WiFi] ⚠️ 检测到长按, 清除配置并重启..."));
|
||||
resetSettings();
|
||||
Serial.println(("[WiFi] 长按, 进配网模式"));
|
||||
_setLEDMode(2);
|
||||
WiFi.disconnect();
|
||||
_startPortal();
|
||||
_connected = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查 WiFi 状态
|
||||
if (millis() - _lastCheckTime > WIFI_RETRY_INTERVAL) {
|
||||
_lastCheckTime = millis();
|
||||
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
_connected = false;
|
||||
_setLEDMode(1); // 慢闪 = 未连接
|
||||
Serial.println(F("[WiFi] ⚠️ WiFi 断开, 尝试重连..."));
|
||||
_connectWiFi();
|
||||
} else {
|
||||
wl_status_t s = WiFi.status();
|
||||
if (s != WL_CONNECTED) {
|
||||
if (_connected) {
|
||||
_connected = false;
|
||||
_setLEDMode(1);
|
||||
Serial.printf("[WiFi] 断开! status=%d RSSI=%d\n", s, WiFi.RSSI());
|
||||
}
|
||||
// 不手动 reconnect(), 让 WiFi.setAutoReconnect(true) 自动处理
|
||||
} else if (!_connected) {
|
||||
_connected = true;
|
||||
_setLEDMode(3); // 常亮 = 已连接
|
||||
_setLEDMode(3);
|
||||
Serial.printf("[WiFi] 已连接 RSSI=%d\n", WiFi.RSSI());
|
||||
}
|
||||
}
|
||||
|
||||
// LED 由独立 FreeRTOS 任务处理 (不受 startConfigPortal 阻塞影响)
|
||||
return _connected;
|
||||
}
|
||||
|
||||
@@ -251,128 +149,272 @@ bool WiFiMgmt::loop() {
|
||||
// ============================================================
|
||||
|
||||
bool WiFiMgmt::_connectWiFi() {
|
||||
// 检查是否有已保存的配置
|
||||
// autoConnect 会先尝试连接, 失败后启动 AP 配网页面
|
||||
_connected = _wm.autoConnect(WIFI_AP_SSID, WIFI_AP_PASSWORD);
|
||||
|
||||
if (_connected) {
|
||||
Serial.println(F("[WiFi] ✅ 已连接!"));
|
||||
Serial.printf(" WiFi名称: %s\n", WiFi.SSID().c_str());
|
||||
Serial.printf(" IP地址: %s\n", WiFi.localIP().toString().c_str());
|
||||
Serial.printf(" MAC地址: %s\n", WiFi.macAddress().c_str());
|
||||
|
||||
// 重新加载配置 (确保获取最新值)
|
||||
// autoConnect 过程中如果有保存, 回调已写入 LittleFS
|
||||
_loadConfig();
|
||||
|
||||
// 同步自定义参数 (如果配置是从 LittleFS 加载的)
|
||||
_param_mqtt_server->setValue(_config.mqtt_server, sizeof(_config.mqtt_server));
|
||||
_param_mqtt_port->setValue(_config.mqtt_port, sizeof(_config.mqtt_port));
|
||||
_param_mqtt_user->setValue(_config.mqtt_user, sizeof(_config.mqtt_user));
|
||||
_param_mqtt_pass->setValue(_config.mqtt_pass, sizeof(_config.mqtt_pass));
|
||||
_param_device_id->setValue(_config.device_id, sizeof(_config.device_id));
|
||||
_param_env_note->setValue(_config.env_note, sizeof(_config.env_note));
|
||||
|
||||
Serial.printf(" 设备名: %s\n", _config.device_id);
|
||||
Serial.printf(" 环境: %s\n", _config.env_note);
|
||||
Serial.printf(" MQTT服务器: %s:%s\n", _config.mqtt_server, _config.mqtt_port);
|
||||
if (_config.wifi_ssid[0] == '\0') {
|
||||
Serial.println(("[WiFi] 无已存WiFi密码"));
|
||||
return false;
|
||||
}
|
||||
Serial.printf("[WiFi] 连接: %s\n", _config.wifi_ssid);
|
||||
|
||||
// 崩溃重启后 WiFi 模块可能残留旧状态, 彻底重置
|
||||
WiFi.disconnect(true);
|
||||
delay(200);
|
||||
WiFi.mode(WIFI_STA);
|
||||
delay(100);
|
||||
WiFi.begin(_config.wifi_ssid, _config.wifi_pass);
|
||||
int retry = 0;
|
||||
while (WiFi.status() != WL_CONNECTED && retry < 40) { // 20s
|
||||
delay(500); Serial.print("."); yield(); retry++;
|
||||
}
|
||||
Serial.println();
|
||||
_connected = (WiFi.status() == WL_CONNECTED);
|
||||
Serial.printf("[WiFi] 连接结果: %s status=%d\n", _connected ? "成功" : "失败", WiFi.status());
|
||||
if (_connected) Serial.printf("[WiFi] SSID=%s IP=%s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
|
||||
return _connected;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 配网页面
|
||||
// ============================================================
|
||||
|
||||
void WiFiMgmt::startConfigPortal() {
|
||||
Serial.println(F("[WiFi] 🔧 手动启动配网页面..."));
|
||||
_setLEDMode(2); // 快闪 = 配网模式
|
||||
_wm.startConfigPortal(WIFI_AP_SSID, WIFI_AP_PASSWORD);
|
||||
_connected = (WiFi.status() == WL_CONNECTED);
|
||||
if (_connected) {
|
||||
_setLEDMode(3); // 常亮 = 已连接
|
||||
bool WiFiMgmt::_tryConnect(const char* ssid, const char* pass) {
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.persistent(true); // 确保持久化 WiFi 密码到 NVS (断电不丢)
|
||||
WiFi.begin(ssid, pass);
|
||||
int retry = 0;
|
||||
while (WiFi.status() != WL_CONNECTED && retry < 40) { // 20s
|
||||
delay(500); Serial.print("."); retry++;
|
||||
}
|
||||
bool ok = (WiFi.status() == WL_CONNECTED);
|
||||
Serial.printf("\n[WiFi] 连接结果: %s status=%d\n", ok ? "成功!" : "失败", WiFi.status());
|
||||
return ok;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 重置设置
|
||||
// 配网 Portal (AP + WebServer)
|
||||
// ============================================================
|
||||
|
||||
void WiFiMgmt::startConfigPortal() {
|
||||
_setLEDMode(2);
|
||||
_startPortal();
|
||||
}
|
||||
|
||||
void WiFiMgmt::_startPortal() {
|
||||
if (_portalActive) return;
|
||||
|
||||
WiFi.disconnect(true);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
delay(200);
|
||||
WiFi.mode(WIFI_AP);
|
||||
WiFi.softAPConfig(IPAddress(192,168,4,1), IPAddress(192,168,4,1), IPAddress(255,255,255,0));
|
||||
WiFi.softAP(WIFI_AP_SSID, WIFI_AP_PASSWORD);
|
||||
Serial.printf("[WiFi] AP: %s IP: 192.168.4.1\n", WIFI_AP_SSID);
|
||||
|
||||
// DNS 劫持: 所有域名 → 192.168.4.1 → 触发手机强制门户弹窗
|
||||
_dns = new DNSServer();
|
||||
_dns->start(53, "*", WiFi.softAPIP());
|
||||
|
||||
_server = new WebServer(80);
|
||||
_server->on("/", HTTP_GET, [this]() { _handleRoot(); });
|
||||
_server->on("/save", HTTP_POST, [this]() { _handleSave(); });
|
||||
_server->on("/reset", HTTP_GET, [this]() { _handleReset(); });
|
||||
_server->onNotFound( [this]() { _handleRoot(); });
|
||||
_server->begin();
|
||||
|
||||
_portalActive = true;
|
||||
_portalStartTime = millis();
|
||||
|
||||
Serial.println(("[WiFi] 手机连 AP 后浏览器打开 http://192.168.4.1"));
|
||||
}
|
||||
|
||||
void WiFiMgmt::_stopPortal() {
|
||||
if (!_portalActive) return;
|
||||
if (_server) { _server->stop(); delete _server; _server = nullptr; }
|
||||
if (_dns) { _dns->stop(); delete _dns; _dns = nullptr; }
|
||||
WiFi.softAPdisconnect(true);
|
||||
_portalActive = false;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Web 处理器
|
||||
// ============================================================
|
||||
|
||||
void WiFiMgmt::_handleRoot() {
|
||||
_server->send(200, "text/html; charset=utf-8", _htmlPage());
|
||||
}
|
||||
|
||||
void WiFiMgmt::_handleSave() {
|
||||
if (_server->hasArg("s")) {
|
||||
_pendingSSID = _server->arg("s");
|
||||
_pendingPass = _server->arg("p");
|
||||
// 持久化 WiFi 密码到 LittleFS
|
||||
strncpy(_config.wifi_ssid, _pendingSSID.c_str(), sizeof(_config.wifi_ssid)-1);
|
||||
strncpy(_config.wifi_pass, _pendingPass.c_str(), sizeof(_config.wifi_pass)-1);
|
||||
if (_server->hasArg("device_id")) strncpy(_config.device_id, _server->arg("device_id").c_str(), sizeof(_config.device_id)-1);
|
||||
if (_server->hasArg("env_note")) strncpy(_config.env_note, _server->arg("env_note").c_str(), sizeof(_config.env_note)-1);
|
||||
if (_server->hasArg("mqtt_server")) strncpy(_config.mqtt_server, _server->arg("mqtt_server").c_str(), sizeof(_config.mqtt_server)-1);
|
||||
if (_server->hasArg("mqtt_port")) strncpy(_config.mqtt_port, _server->arg("mqtt_port").c_str(), sizeof(_config.mqtt_port)-1);
|
||||
if (_server->hasArg("mqtt_user")) strncpy(_config.mqtt_user, _server->arg("mqtt_user").c_str(), sizeof(_config.mqtt_user)-1);
|
||||
if (_server->hasArg("mqtt_pass")) strncpy(_config.mqtt_pass, _server->arg("mqtt_pass").c_str(), sizeof(_config.mqtt_pass)-1);
|
||||
_saveConfig();
|
||||
|
||||
_server->send(200, "text/html; charset=utf-8",
|
||||
"<!DOCTYPE html><html lang='zh-CN'><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width,initial-scale=1'>"
|
||||
"<title>配置完成</title><style>body{font-family:sans-serif;background:#f3faf8;display:flex;align-items:center;justify-content:center;height:100vh;margin:0}"
|
||||
".box{text-align:center;background:#fff;padding:32px;border-radius:16px;box-shadow:0 2px 12px rgba(0,0,0,0.08);max-width:340px}"
|
||||
"h1{color:#12a889;font-size:20px}.ok{font-size:48px;margin:16px}"
|
||||
"p{color:#8aa09a;font-size:14px;line-height:1.8}"
|
||||
"</style></head><body><div class='box'><div class='ok' id='icon'>⏳</div>"
|
||||
"<h1 id='title'>配置已保存</h1><p id='msg'>设备正在连接 WiFi...<br>等待指示灯<b>常亮</b></p>"
|
||||
"</div><script>"
|
||||
"var n=0;setInterval(function(){n++;"
|
||||
"if(n>8){document.getElementById('icon').textContent='✅';"
|
||||
"document.getElementById('msg').innerHTML='请切换回您的 WiFi 网络<br><b>指示灯常亮</b>即连接成功';clearInterval()}"
|
||||
"},2000);</script></body></html>");
|
||||
|
||||
_portalStopFlag = true;
|
||||
return;
|
||||
}
|
||||
_handleRoot();
|
||||
}
|
||||
|
||||
void WiFiMgmt::_handleReset() {
|
||||
_server->send(200, "text/html; charset=utf-8",
|
||||
"<!DOCTYPE html><html lang='zh-CN'><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>已重置</title>"
|
||||
"<style>body{font-family:sans-serif;background:#f3faf8;display:flex;align-items:center;justify-content:center;height:100vh;margin:0}"
|
||||
".box{text-align:center;background:#fff;padding:32px;border-radius:16px;box-shadow:0 2px 12px rgba(0,0,0,0.08)}"
|
||||
"h1{color:#f53f3f;font-size:18px}p{color:#8aa09a;font-size:14px}</style></head><body><div class='box'><h1>配置已清除</h1><p>设备正在重启...</p></div></body></html>");
|
||||
delay(500);
|
||||
_stopPortal();
|
||||
resetSettings();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 配网 HTML 页面 (精简版)
|
||||
// ============================================================
|
||||
|
||||
void WiFiMgmt::_updateWifiScan() {
|
||||
_wifiScanCache = "";
|
||||
int n = WiFi.scanNetworks(false, true);
|
||||
for (int i = 0; i < n; i++) {
|
||||
_wifiScanCache += "<option value='" + WiFi.SSID(i) + "'>" + WiFi.SSID(i) + " (" + String(WiFi.RSSI(i)) + "dBm)</option>";
|
||||
}
|
||||
if (n == 0) _wifiScanCache = "<option value=''>未发现 WiFi</option>";
|
||||
WiFi.scanDelete();
|
||||
_lastScanTime = millis();
|
||||
}
|
||||
|
||||
String WiFiMgmt::_htmlPage() {
|
||||
if (_wifiScanCache.length() == 0) _updateWifiScan();
|
||||
|
||||
String html;
|
||||
html.reserve(4096);
|
||||
html += "<!DOCTYPE html><html lang='zh-CN'><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'>";
|
||||
html += "<meta name='theme-color' content='#12a889'><title>设备配网</title><style>";
|
||||
html += ":root{--p:#12a889;--bg:#f3faf8;--cb:#fff;--ch:#eef8f5;--cg:#e9fff8;--t:#15201d;--t2:#4e6660;--t3:#8aa09a}";
|
||||
html += "*{box-sizing:border-box;margin:0;padding:0}body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:var(--bg);min-height:100vh;padding:12px 16px 24px;color:var(--t);font-size:14px;line-height:1.5}";
|
||||
html += ".wrap{max-width:420px;margin:0 auto}.hdr{display:flex;align-items:center;justify-content:center;position:relative;padding:20px 0 16px}";
|
||||
html += ".hdricon{width:56px;height:56px;border-radius:50%;background:var(--cg);display:flex;align-items:center;justify-content:center;font-size:28px;margin-right:14px;box-shadow:0 4px 12px rgba(18,168,137,.12)}";
|
||||
html += "h1{font-size:20px;font-weight:700;color:var(--t)}h3{font-size:12px;font-weight:400;color:var(--t2);margin-top:4px}";
|
||||
html += ".card{background:var(--cb);border-radius:16px;padding:20px;margin-bottom:16px;box-shadow:0 2px 12px rgba(0,0,0,.06)}";
|
||||
html += ".chdr{display:flex;align-items:center;gap:12px;margin-bottom:18px}.chicon{width:36px;height:36px;border-radius:10px;background:var(--cg);display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0}";
|
||||
html += ".ctitle{font-size:16px;font-weight:600;color:var(--t)}.csub{font-size:12px;color:var(--t3);margin-top:2px}";
|
||||
html += ".field{margin-top:14px}.field:first-child{margin-top:0}label{display:block;font-size:14px;font-weight:500;color:var(--t2);margin-bottom:8px}";
|
||||
html += ".iwrap{display:flex;align-items:center;background:var(--ch);border-radius:10px;border:1px solid transparent;transition:border-color .2s}";
|
||||
html += ".iicon{padding:0 10px;font-size:16px;color:var(--t3)}input,select{flex:1;width:100%;padding:12px 14px;font-size:14px;border:none;background:transparent;color:var(--t);outline:none}";
|
||||
html += ".iwrap:focus-within{border-color:var(--p);background:#fff}select{-webkit-appearance:none;padding-right:30px}";
|
||||
html += ".gate{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;border-radius:12px;background:var(--cg);margin-bottom:14px;font-size:13px}";
|
||||
html += ".glabel{color:var(--t2)}.gid{color:var(--p);font-weight:600}";
|
||||
html += ".btns{display:flex;flex-direction:column;gap:12px;margin-top:8px}";
|
||||
html += "button{width:100%;height:48px;border:none;border-radius:24px;font-size:15px;font-weight:600;color:#fff;background:var(--p);box-shadow:0 2px 6px rgba(18,168,137,.2);cursor:pointer}";
|
||||
html += "button.danger{background:#fff;color:#f53f3f;border:1px solid #ffd6d6;box-shadow:none}";
|
||||
html += ".ft{text-align:center;font-size:11px;color:var(--t3);padding:20px 0 6px}";
|
||||
html += "@media(prefers-color-scheme:dark){body{background:#0f1f1c;color:#e6f0ee}.card{background:#182b27}.iwrap{background:#1f3530}.iwrap:focus-within{background:#243d38}}";
|
||||
html += "</style></head><body><div class='wrap'>";
|
||||
|
||||
// Header + 提示
|
||||
html += "<div class='hdr'><div class='hdricon'>📡</div><div><h1>观照-五劳无感 设备配网</h1><h3>WiFi 联网与参数配置</h3></div></div>";
|
||||
html += "<div class='gate' style='justify-content:center'><span style='color:var(--p)'>💡 也可用 <b>ESP-Touch</b> App 一键配网</span></div>";
|
||||
|
||||
html += "<form method='POST' action='/save'>";
|
||||
html += "<div class='card'><div class='chdr'><div class='chicon'>📶</div><div><div class='ctitle'>无线网络</div><div class='csub'>选择 WiFi 或使用 SmartConfig</div></div></div>";
|
||||
html += "<div class='field'><label>WiFi 名称</label><div class='iwrap'><span class='iicon'>📡</span><select name='s'>";
|
||||
html += _wifiScanCache;
|
||||
html += "</select></div></div>";
|
||||
html += "<div class='field'><label>WiFi 密码</label><div class='iwrap'><span class='iicon'>🔒</span><input type='password' name='p' placeholder='请输入 WiFi 密码'></div></div></div>";
|
||||
|
||||
html += "<div class='card'><div class='chdr'><div class='chicon'>⚙️</div><div><div class='ctitle'>设备基础信息</div><div class='csub'>唯一标识与安装位置</div></div></div>";
|
||||
html += "<div class='field'><label>设备编号</label><div class='iwrap'><span class='iicon'>#</span><input type='text' name='device_id' value='";
|
||||
html += _config.device_id;
|
||||
html += "'></div></div>";
|
||||
html += "<div class='field'><label>安装环境</label><div class='iwrap'><span class='iicon'>🏠</span><input type='text' name='env_note' placeholder='如: 主卧 / 客厅 / 老人房' value='";
|
||||
html += _config.env_note;
|
||||
html += "'></div></div></div>";
|
||||
|
||||
html += "<input type='hidden' name='mqtt_server' value='";
|
||||
html += _config.mqtt_server;
|
||||
html += "'><input type='hidden' name='mqtt_port' value='";
|
||||
html += _config.mqtt_port;
|
||||
html += "'><input type='hidden' name='mqtt_user' value='";
|
||||
html += _config.mqtt_user;
|
||||
html += "'><input type='hidden' name='mqtt_pass' value='";
|
||||
html += _config.mqtt_pass;
|
||||
html += "'>";
|
||||
|
||||
html += "<div class='btns'><button type='submit'>保存配置并重启连接</button></div></form>";
|
||||
html += "<button class='danger' onclick=\"if(confirm('确定恢复出厂?'))location.href='/reset'\">恢复出厂配置</button>";
|
||||
html += "<div class='ft'>Health Monitor · Device Provisioning</div></div></body></html>";
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 重置
|
||||
// ============================================================
|
||||
|
||||
void WiFiMgmt::resetSettings() {
|
||||
Serial.println(F("[WiFi] 清除所有配置..."));
|
||||
|
||||
// 删除 LittleFS 中的配置文件
|
||||
if (LittleFS.exists(CONFIG_FILE_WIFI)) LittleFS.remove(CONFIG_FILE_WIFI);
|
||||
if (LittleFS.exists(CONFIG_FILE_MQTT)) LittleFS.remove(CONFIG_FILE_MQTT);
|
||||
Serial.println(("[WiFi] 清除所有配置..."));
|
||||
if (LittleFS.exists(CONFIG_FILE_WIFI)) LittleFS.remove(CONFIG_FILE_WIFI);
|
||||
if (LittleFS.exists(CONFIG_FILE_MQTT)) LittleFS.remove(CONFIG_FILE_MQTT);
|
||||
if (LittleFS.exists(CONFIG_FILE_DEVICE)) LittleFS.remove(CONFIG_FILE_DEVICE);
|
||||
|
||||
// 清除 WiFiManager 的配置
|
||||
_wm.resetSettings();
|
||||
|
||||
// LED 快闪提示
|
||||
for (int i = 0; i < 10; i++) {
|
||||
digitalWrite(LED_WIFI_PIN, !digitalRead(LED_WIFI_PIN));
|
||||
delay(100);
|
||||
}
|
||||
|
||||
Serial.println(F("[WiFi] 🔄 正在重启..."));
|
||||
WiFi.disconnect(true, true);
|
||||
for (int i = 0; i < 10; i++) { digitalWrite(LED_WIFI_PIN, !digitalRead(LED_WIFI_PIN)); delay(100); }
|
||||
Serial.println(("[WiFi] 正在重启..."));
|
||||
delay(500);
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 按钮长按检测
|
||||
// 按钮
|
||||
// ============================================================
|
||||
|
||||
bool WiFiMgmt::_checkButtonLongPress() {
|
||||
bool pressed = (digitalRead(CONFIG_BUTTON_PIN) == LOW);
|
||||
|
||||
if (pressed && !_buttonPressed) {
|
||||
// 按下
|
||||
_buttonPressed = true;
|
||||
_buttonPressTime = millis();
|
||||
_buttonPressed = true; _buttonPressTime = millis();
|
||||
} else if (!pressed && _buttonPressed) {
|
||||
// 松开
|
||||
_buttonPressed = false;
|
||||
if (millis() - _buttonPressTime >= BUTTON_LONG_PRESS_MS) {
|
||||
return true; // 长按确认
|
||||
}
|
||||
if (millis() - _buttonPressTime >= BUTTON_LONG_PRESS_MS) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// LittleFS 配置持久化
|
||||
// LittleFS
|
||||
// ============================================================
|
||||
|
||||
bool WiFiMgmt::_loadConfig() {
|
||||
if (!LittleFS.exists(CONFIG_FILE_DEVICE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!LittleFS.exists(CONFIG_FILE_DEVICE)) return false;
|
||||
File file = LittleFS.open(CONFIG_FILE_DEVICE, "r");
|
||||
if (!file) return false;
|
||||
|
||||
JsonDocument doc;
|
||||
DeserializationError err = deserializeJson(doc, file);
|
||||
file.close();
|
||||
|
||||
if (err) {
|
||||
Serial.printf("[WiFi] JSON 解析失败: %s\n", err.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
strncpy(_config.mqtt_server, doc["mqtt_server"] | MQTT_DEFAULT_SERVER, sizeof(_config.mqtt_server) - 1);
|
||||
strncpy(_config.mqtt_port, doc["mqtt_port"] | MQTT_DEFAULT_PORT_STR, sizeof(_config.mqtt_port) - 1);
|
||||
strncpy(_config.mqtt_user, doc["mqtt_user"] | MQTT_DEFAULT_USER, sizeof(_config.mqtt_user) - 1);
|
||||
strncpy(_config.mqtt_pass, doc["mqtt_pass"] | MQTT_DEFAULT_PASS, sizeof(_config.mqtt_pass) - 1);
|
||||
strncpy(_config.device_id, doc["device_id"] | MQTT_DEFAULT_DEVICE, sizeof(_config.device_id) - 1);
|
||||
strncpy(_config.env_note, doc["env_note"] | MQTT_DEFAULT_ENV, sizeof(_config.env_note) - 1);
|
||||
|
||||
Serial.println(F("[WiFi] ✅ 设备配置已加载"));
|
||||
Serial.printf(" 设备名: %s, 环境: %s\n", _config.device_id, _config.env_note);
|
||||
if (err) return false;
|
||||
strncpy(_config.mqtt_server, doc["mqtt_server"] | MQTT_DEFAULT_SERVER, sizeof(_config.mqtt_server)-1);
|
||||
strncpy(_config.mqtt_port, doc["mqtt_port"] | MQTT_DEFAULT_PORT_STR, sizeof(_config.mqtt_port)-1);
|
||||
strncpy(_config.mqtt_user, doc["mqtt_user"] | MQTT_DEFAULT_USER, sizeof(_config.mqtt_user)-1);
|
||||
strncpy(_config.mqtt_pass, doc["mqtt_pass"] | MQTT_DEFAULT_PASS, sizeof(_config.mqtt_pass)-1);
|
||||
strncpy(_config.device_id, doc["device_id"] | MQTT_DEFAULT_DEVICE, sizeof(_config.device_id)-1);
|
||||
strncpy(_config.env_note, doc["env_note"] | MQTT_DEFAULT_ENV, sizeof(_config.env_note)-1);
|
||||
strncpy(_config.wifi_ssid, doc["wifi_ssid"] | "", sizeof(_config.wifi_ssid)-1);
|
||||
strncpy(_config.wifi_pass, doc["wifi_pass"] | "", sizeof(_config.wifi_pass)-1);
|
||||
Serial.printf("[WiFi] 配置加载, 已存WiFi: %s\n", _config.wifi_ssid[0] ? _config.wifi_ssid : "(空)");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -384,134 +426,44 @@ bool WiFiMgmt::_saveConfig() {
|
||||
doc["mqtt_pass"] = _config.mqtt_pass;
|
||||
doc["device_id"] = _config.device_id;
|
||||
doc["env_note"] = _config.env_note;
|
||||
|
||||
doc["wifi_ssid"] = _config.wifi_ssid;
|
||||
doc["wifi_pass"] = _config.wifi_pass;
|
||||
File file = LittleFS.open(CONFIG_FILE_DEVICE, "w");
|
||||
if (!file) {
|
||||
Serial.println(F("[WiFi] ❌ 无法创建配置文件"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (serializeJson(doc, file) == 0) {
|
||||
Serial.println(F("[WiFi] ❌ 配置写入失败"));
|
||||
file.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file) return false;
|
||||
if (serializeJson(doc, file) == 0) { file.close(); return false; }
|
||||
file.close();
|
||||
Serial.println(F("[WiFi] ✅ 设备配置已保存"));
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// WiFiManager 保存参数回调
|
||||
// Getter
|
||||
// ============================================================
|
||||
|
||||
// ============================================================
|
||||
// 静态回调函数 (WiFiManager 要求 C 函数指针)
|
||||
// ============================================================
|
||||
|
||||
void WiFiMgmt::_apCallback(WiFiManager* wm) {
|
||||
Serial.println(F("[WiFi] 🔧 进入配网模式"));
|
||||
Serial.printf(" AP WiFi名称: %s\n", WIFI_AP_SSID);
|
||||
Serial.printf(" AP 密码: %s\n", WIFI_AP_PASSWORD);
|
||||
Serial.println(F(" 手机连接此 WiFi 即可打开配网页面 (自动弹出)"));
|
||||
}
|
||||
|
||||
void WiFiMgmt::_saveParamsCallback() {
|
||||
if (!_instance) return;
|
||||
|
||||
Serial.println(F("[WiFi] 💾 参数已保存"));
|
||||
|
||||
// 从 WiFiManager 参数中读取值
|
||||
strncpy(_instance->_config.mqtt_server, _instance->_param_mqtt_server->getValue(),
|
||||
sizeof(_instance->_config.mqtt_server) - 1);
|
||||
strncpy(_instance->_config.mqtt_port, _instance->_param_mqtt_port->getValue(),
|
||||
sizeof(_instance->_config.mqtt_port) - 1);
|
||||
strncpy(_instance->_config.mqtt_user, _instance->_param_mqtt_user->getValue(),
|
||||
sizeof(_instance->_config.mqtt_user) - 1);
|
||||
strncpy(_instance->_config.mqtt_pass, _instance->_param_mqtt_pass->getValue(),
|
||||
sizeof(_instance->_config.mqtt_pass) - 1);
|
||||
strncpy(_instance->_config.device_id, _instance->_param_device_id->getValue(),
|
||||
sizeof(_instance->_config.device_id) - 1);
|
||||
strncpy(_instance->_config.env_note, _instance->_param_env_note->getValue(),
|
||||
sizeof(_instance->_config.env_note) - 1);
|
||||
|
||||
_instance->_saveConfig();
|
||||
|
||||
Serial.println(F("[WiFi] 📋 新配置:"));
|
||||
Serial.printf(" MQTT: %s:%s\n",
|
||||
_instance->_config.mqtt_server,
|
||||
_instance->_config.mqtt_port);
|
||||
Serial.printf(" 设备: %s\n", _instance->_config.device_id);
|
||||
Serial.printf(" 环境: %s\n", _instance->_config.env_note);
|
||||
}
|
||||
bool WiFiMgmt::isConnected() const { return _connected && (WiFi.status() == WL_CONNECTED); }
|
||||
bool WiFiMgmt::justProvisioned() { bool v = _provisionSuccess; _provisionSuccess = false; return v; }
|
||||
DeviceConfig WiFiMgmt::getConfig() const { return _config; }
|
||||
const char* WiFiMgmt::getDeviceId() const { return _config.device_id; }
|
||||
const char* WiFiMgmt::getEnvNote() const { return _config.env_note; }
|
||||
|
||||
// ============================================================
|
||||
// Getter 方法
|
||||
// LED
|
||||
// ============================================================
|
||||
|
||||
bool WiFiMgmt::isConnected() const {
|
||||
return _connected && (WiFi.status() == WL_CONNECTED);
|
||||
}
|
||||
|
||||
DeviceConfig WiFiMgmt::getConfig() const {
|
||||
return _config;
|
||||
}
|
||||
|
||||
const char* WiFiMgmt::getDeviceId() const {
|
||||
return _config.device_id;
|
||||
}
|
||||
|
||||
const char* WiFiMgmt::getEnvNote() const {
|
||||
return _config.env_note;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// LED 模式控制 (线程安全, 仅写模式变量,由独立任务执行)
|
||||
// ============================================================
|
||||
|
||||
void WiFiMgmt::_setLEDMode(uint8_t mode) {
|
||||
_ledMode = mode; // uint8_t 赋值在 ESP32 上是原子的
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// LED 闪烁独立任务 (FreeRTOS)
|
||||
// 独立于主循环,startConfigPortal 阻塞期间也能正常闪烁
|
||||
// ============================================================
|
||||
void WiFiMgmt::_setLEDMode(uint8_t mode) { _ledMode = mode; }
|
||||
|
||||
void WiFiMgmt::_ledTaskFunc(void* param) {
|
||||
const TickType_t fastMs = pdMS_TO_TICKS(300); // 快闪 = 配网模式
|
||||
const TickType_t slowMs = pdMS_TO_TICKS(1000); // 慢闪 = 未连接
|
||||
|
||||
const TickType_t fastMs = pdMS_TO_TICKS(300);
|
||||
const TickType_t slowMs = pdMS_TO_TICKS(1000);
|
||||
while (true) {
|
||||
if (!_instance) {
|
||||
vTaskDelay(slowMs);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_instance) { vTaskDelay(slowMs); continue; }
|
||||
uint8_t mode = _instance->_ledMode;
|
||||
|
||||
switch (mode) {
|
||||
case 0: // 灭
|
||||
digitalWrite(LED_WIFI_PIN, LOW);
|
||||
vTaskDelay(slowMs);
|
||||
break;
|
||||
case 1: // 慢闪 — WiFi 断连
|
||||
digitalWrite(LED_WIFI_PIN, HIGH);
|
||||
vTaskDelay(slowMs);
|
||||
digitalWrite(LED_WIFI_PIN, LOW);
|
||||
vTaskDelay(slowMs);
|
||||
break;
|
||||
case 2: // 快闪 — 配网模式
|
||||
digitalWrite(LED_WIFI_PIN, HIGH);
|
||||
vTaskDelay(fastMs);
|
||||
digitalWrite(LED_WIFI_PIN, LOW);
|
||||
vTaskDelay(fastMs);
|
||||
break;
|
||||
case 3: // 常亮 — 已连接
|
||||
digitalWrite(LED_WIFI_PIN, HIGH);
|
||||
vTaskDelay(slowMs);
|
||||
break;
|
||||
case 0: digitalWrite(LED_WIFI_PIN, LOW); vTaskDelay(slowMs); break;
|
||||
case 1: digitalWrite(LED_WIFI_PIN, HIGH); vTaskDelay(slowMs);
|
||||
digitalWrite(LED_WIFI_PIN, LOW); vTaskDelay(slowMs); break;
|
||||
case 2: digitalWrite(LED_WIFI_PIN, HIGH); vTaskDelay(fastMs);
|
||||
digitalWrite(LED_WIFI_PIN, LOW); vTaskDelay(fastMs); break;
|
||||
case 3: digitalWrite(LED_WIFI_PIN, HIGH); vTaskDelay(slowMs); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user