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

This commit is contained in:
2026-07-31 15:58:09 +08:00
parent 49e3abbb34
commit 69e927c5f0
16 changed files with 1352 additions and 549 deletions
+104 -27
View File
@@ -1,6 +1,12 @@
/**
* @file button_manager.cpp
* @brief 三按钮管理器实现 - 去抖 + 长按/短按
* @brief 三按钮管理器实现 - 双向去抖 + 长按/短按 + 超时自愈
*
* 核心改进 (v2):
* 1. 按下沿和释放沿都经过 BTN_DEBOUNCE_MS 稳定确认
* 2. 长按期间持续检测 (不再等到松手才触发)
* 3. 按下超过 BTN_STUCK_TIMEOUT_MS 自动复位, 防止状态机卡死
* 4. 用 stableState 替代瞬时 raw 驱动状态, 消除噪声误触发
*/
#include "button_manager.h"
@@ -14,9 +20,19 @@ ButtonManager::ButtonManager()
, _cbDismiss(nullptr)
, _cbPage(nullptr)
{
_btnProvision = { BTN_PROVISION_PIN, false, 0, false };
_btnDismiss = { BTN_ALARM_DISMISS_PIN, false, 0, false };
_btnPage = { BTN_PAGE_SWITCH_PIN, false, 0, false };
auto initBtn = [](ButtonState& s, uint8_t pin) {
s.pin = pin;
s.stableState = false;
s.rawState = false;
s.changeTime = 0;
s.pressTime = 0;
s.longTriggered = false;
s.shortPending = false;
};
initBtn(_btnProvision, BTN_PROVISION_PIN);
initBtn(_btnDismiss, BTN_ALARM_DISMISS_PIN);
initBtn(_btnPage, BTN_PAGE_SWITCH_PIN);
}
// ============================================================
@@ -28,9 +44,19 @@ void ButtonManager::begin() {
pinMode(BTN_ALARM_DISMISS_PIN, INPUT_PULLUP);
pinMode(BTN_PAGE_SWITCH_PIN, INPUT_PULLUP);
Serial.println(F("[按钮] ✅ 三按钮初始化完成"));
// 读取初始状态, 避免启动时的虚假边沿
_btnProvision.rawState = _readPin(BTN_PROVISION_PIN);
_btnProvision.stableState = _btnProvision.rawState;
_btnDismiss.rawState = _readPin(BTN_ALARM_DISMISS_PIN);
_btnDismiss.stableState = _btnDismiss.rawState;
_btnPage.rawState = _readPin(BTN_PAGE_SWITCH_PIN);
_btnPage.stableState = _btnPage.rawState;
Serial.println(F("[按钮] ✅ 三按钮初始化完成 (双向去抖+超时自愈)"));
Serial.printf(" 配网: GPIO%d | 解除报警: GPIO%d | 翻页: GPIO%d\n",
BTN_PROVISION_PIN, BTN_ALARM_DISMISS_PIN, BTN_PAGE_SWITCH_PIN);
Serial.printf(" 去抖:%dms | 长按:%dms | 卡死超时:%dms\n",
BTN_DEBOUNCE_MS, BTN_LONG_PRESS_MS, BTN_STUCK_TIMEOUT_MS);
}
// ============================================================
@@ -49,40 +75,91 @@ void ButtonManager::loop() {
}
// ============================================================
// 单按钮状态机
// 单按钮状态机 (基于稳定状态, 非瞬时边沿)
// ============================================================
void ButtonManager::_processButton(ButtonState& btn, bool hasShort, bool hasLong) {
bool raw = _readPin(btn.pin); // LOW = 按下
uint32_t now = millis();
bool raw = _readPin(btn.pin); // true = 按下 (LOW)
if (raw && !btn.pressed) {
// 按下沿
btn.pressed = true;
btn.pressTime = millis();
btn.longTriggered = false;
// ── 第1步: raw 状态变化时启动去抖计时 ──
if (raw != btn.rawState) {
btn.rawState = raw;
btn.changeTime = now;
// 不立即改变 stableState — 等稳定确认
}
else if (!raw && btn.pressed) {
// 松开沿
btn.pressed = false;
uint32_t holdTime = millis() - btn.pressTime;
if (holdTime < BTN_DEBOUNCE_MS) {
return; // 抖动脉冲,忽略
// ── 第2步: 去抖确认 — raw 保持稳定超过 BTN_DEBOUNCE_MS 才生效 ──
bool debounced = (now - btn.changeTime >= BTN_DEBOUNCE_MS);
bool stateChanged = (btn.rawState != btn.stableState);
if (debounced && stateChanged) {
// 状态正式切换
btn.stableState = btn.rawState;
if (btn.stableState) {
// ── 确认按下 ──
btn.pressTime = now;
btn.longTriggered = false;
btn.shortPending = hasShort; // 只有支持短按的按钮才标记待判定
} else {
// ── 确认释放 ──
uint32_t holdTime = now - btn.pressTime;
// 短按判定: 按下时长在去抖和长按阈值之间
if (btn.shortPending && holdTime >= BTN_DEBOUNCE_MS
&& holdTime < BTN_LONG_PRESS_MS && !btn.longTriggered) {
// 根据引脚分发短按回调
if (btn.pin == BTN_ALARM_DISMISS_PIN && _cbDismiss) {
_cbDismiss();
}
if (btn.pin == BTN_PAGE_SWITCH_PIN && _cbPage) {
_cbPage();
}
}
btn.shortPending = false;
btn.longTriggered = false;
}
}
if (hasLong && holdTime >= BTN_LONG_PRESS_MS && !btn.longTriggered) {
// 长按触发
// ── 第3步: 长按检测 (在按下稳定期间持续检查) ──
if (hasLong && btn.stableState && !btn.longTriggered) {
uint32_t holdTime = now - btn.pressTime;
if (holdTime >= BTN_LONG_PRESS_MS) {
btn.longTriggered = true;
if (_cbProvision) _cbProvision();
}
else if (hasShort && holdTime >= BTN_DEBOUNCE_MS && holdTime < BTN_LONG_PRESS_MS) {
// 短按触发
if (btn.pin == BTN_ALARM_DISMISS_PIN && _cbDismiss) _cbDismiss();
if (btn.pin == BTN_PAGE_SWITCH_PIN && _cbPage) _cbPage();
btn.shortPending = false; // 长按已触发, 不再判定短按
if (_cbProvision) {
_cbProvision();
}
}
}
// ── 第4步: 卡死自愈 — 按下超时强制复位 ──
if (btn.stableState && (now - btn.pressTime >= BTN_STUCK_TIMEOUT_MS)) {
Serial.printf("[按钮] ⚠️ GPIO%d 按下超时 %lums, 强制复位\n",
btn.pin, now - btn.pressTime);
_resetButton(btn);
}
}
// ============================================================
// 强制复位
// ============================================================
void ButtonManager::_resetButton(ButtonState& btn) {
btn.stableState = false;
btn.rawState = _readPin(btn.pin); // 重新同步真实硬件状态
btn.changeTime = 0;
btn.pressTime = 0;
btn.longTriggered = false;
btn.shortPending = false;
}
// ============================================================
// 读引脚 (INPUT_PULLUP → LOW = 按下)
// ============================================================
bool ButtonManager::_readPin(uint8_t pin) {
return digitalRead(pin) == LOW; // INPUT_PULLUP: LOW = 按下
return digitalRead(pin) == LOW;
}