feat: 按钮去抖+配网界面美化+水浸离线+LED独立任务+报警全拓展(人身优先)+有人才检测

This commit is contained in:
yangzhijia
2026-07-31 15:58:09 +08:00
parent 49e3abbb34
commit 69e927c5f0
16 changed files with 1352 additions and 549 deletions

View File

@@ -17,8 +17,8 @@ WiFiMgmt::WiFiMgmt()
, _lastCheckTime(0)
, _buttonPressTime(0)
, _buttonPressed(false)
, _ledLastToggle(0)
, _ledState(false)
, _ledTaskHandle(nullptr)
, _ledMode(0)
, _param_mqtt_server(nullptr)
, _param_mqtt_port(nullptr)
, _param_mqtt_user(nullptr)
@@ -48,6 +48,9 @@ bool WiFiMgmt::begin() {
pinMode(LED_WIFI_PIN, OUTPUT);
digitalWrite(LED_WIFI_PIN, LOW);
// 启动 LED 独立任务 (避免 startConfigPortal 阻塞影响闪烁)
xTaskCreate(_ledTaskFunc, "WiFiLed", 1024, nullptr, 1, &_ledTaskHandle);
// 初始化 LittleFS
if (!LittleFS.begin(true)) {
Serial.println(F("[WiFi] ❌ LittleFS 挂载失败!"));
@@ -71,11 +74,91 @@ bool WiFiMgmt::begin() {
_wm.setDebugOutput(false); // 关闭调试输出 (避免干扰)
_wm.setConfigPortalTimeout(WIFI_CONFIG_TIMEOUT);
_wm.setConnectTimeout(WIFI_CONNECT_TIMEOUT);
_wm.setTitle("🏥 五劳无感康养监测 - 设备配网"); // 配网页面的中文标题
_wm.setTitle("健康监测系统 - 设备配网");
_wm.setCustomHeadElement(
"<style>"
"body { font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif; }"
".msg .info { font-size: 14px; }"
/* ===== 字体与全局 ===== */
"*{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 自动重连
@@ -120,12 +203,12 @@ bool WiFiMgmt::begin() {
if (!_connected) {
// 如果自动连接失败, 启动配网页面
Serial.println(F("[WiFi] 自动连接失败, 启动配网模式..."));
_setLEDMode(1); // 快闪 = 配网模式
_setLEDMode(2); // 快闪 = 配网模式
_connected = _connectWiFi(); // 内部会启动配网页面
}
if (_connected) {
_setLEDMode(2); // 常亮 = 已连接
_setLEDMode(3); // 常亮 = 已连接
}
_lastCheckTime = millis();
@@ -150,25 +233,16 @@ bool WiFiMgmt::loop() {
if (WiFi.status() != WL_CONNECTED) {
_connected = false;
_setLEDMode(0); // 慢闪 = 未连接
_setLEDMode(1); // 慢闪 = 未连接
Serial.println(F("[WiFi] ⚠️ WiFi 断开, 尝试重连..."));
_connectWiFi();
} else {
_connected = true;
_setLEDMode(2); // 常亮
}
}
// LED 闪烁处理
if (!_connected) {
uint32_t interval = (_wm.getConfigPortalActive()) ? 300 : 1000;
if (millis() - _ledLastToggle > interval) {
_ledLastToggle = millis();
_ledState = !_ledState;
digitalWrite(LED_WIFI_PIN, _ledState ? HIGH : LOW);
_setLEDMode(3); // 常亮 = 已连接
}
}
// LED 由独立 FreeRTOS 任务处理 (不受 startConfigPortal 阻塞影响)
return _connected;
}
@@ -213,11 +287,11 @@ bool WiFiMgmt::_connectWiFi() {
void WiFiMgmt::startConfigPortal() {
Serial.println(F("[WiFi] 🔧 手动启动配网页面..."));
_setLEDMode(1);
_setLEDMode(2); // 快闪 = 配网模式
_wm.startConfigPortal(WIFI_AP_SSID, WIFI_AP_PASSWORD);
_connected = (WiFi.status() == WL_CONNECTED);
if (_connected) {
_setLEDMode(2);
_setLEDMode(3); // 常亮 = 已连接
}
}
@@ -393,20 +467,51 @@ const char* WiFiMgmt::getEnvNote() const {
}
// ============================================================
// LED 模式控制
// LED 模式控制 (线程安全, 仅写模式变量,由独立任务执行)
// ============================================================
void WiFiMgmt::_setLEDMode(uint8_t mode) {
// mode 由 loop() 中的闪烁逻辑处理
// 0=慢闪 1=快闪 2=常亮
switch (mode) {
case 0:
case 1:
// 闪烁由 loop 处理
break;
case 2:
digitalWrite(LED_WIFI_PIN, HIGH);
_ledState = true;
break;
_ledMode = mode; // uint8_t 赋值在 ESP32 上是原子的
}
// ============================================================
// LED 闪烁独立任务 (FreeRTOS)
// 独立于主循环startConfigPortal 阻塞期间也能正常闪烁
// ============================================================
void WiFiMgmt::_ledTaskFunc(void* param) {
const TickType_t fastMs = pdMS_TO_TICKS(300); // 快闪 = 配网模式
const TickType_t slowMs = pdMS_TO_TICKS(1000); // 慢闪 = 未连接
while (true) {
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;
}
}
}