feat: button handler with press detection and combo support
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
110
src/button_handler.cpp
Normal file
110
src/button_handler.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
#include "button_handler.h"
|
||||
#include "power_manager.h"
|
||||
#include "settings.h"
|
||||
|
||||
void ButtonHandler::begin(PowerManager& power, SettingsManager& settings) {
|
||||
_power = &power;
|
||||
_settings = &settings;
|
||||
|
||||
pinMode(PIN_BTN_TOP, INPUT_PULLUP);
|
||||
pinMode(PIN_BTN_UP, INPUT_PULLUP);
|
||||
pinMode(PIN_BTN_DOWN, INPUT_PULLUP);
|
||||
|
||||
Serial.println("[buttons] Initialized (G1=random, G9=next, G10=pause)");
|
||||
}
|
||||
|
||||
ButtonEvent ButtonHandler::poll() {
|
||||
unsigned long now = millis();
|
||||
|
||||
bool topNow = (digitalRead(PIN_BTN_TOP) == LOW);
|
||||
bool upNow = (digitalRead(PIN_BTN_UP) == LOW);
|
||||
bool downNow = (digitalRead(PIN_BTN_DOWN) == LOW);
|
||||
|
||||
// Track press start times
|
||||
if (topNow && !_btnTopPressed) {
|
||||
_btnTopPressTime = now;
|
||||
_btnTopPressed = true;
|
||||
} else if (!topNow) {
|
||||
_btnTopPressed = false;
|
||||
}
|
||||
|
||||
if (upNow && !_btnUpPressed) {
|
||||
_btnUpPressTime = now;
|
||||
_btnUpPressed = true;
|
||||
} else if (!upNow) {
|
||||
_btnUpPressed = false;
|
||||
}
|
||||
|
||||
if (downNow && !_btnDownPressed) {
|
||||
_btnDownPressTime = now;
|
||||
_btnDownPressed = true;
|
||||
} else if (!downNow) {
|
||||
_btnDownPressed = false;
|
||||
}
|
||||
|
||||
// Check combos first (higher priority)
|
||||
// BTN_TOP + BTN_DOWN held 3s = deep sleep
|
||||
if (_btnTopPressed && _btnDownPressed) {
|
||||
unsigned long holdTime = now - max(_btnTopPressTime, _btnDownPressTime);
|
||||
if (holdTime >= COMBO_HOLD_MS) {
|
||||
Serial.println("[buttons] Combo: deep sleep");
|
||||
_power->flashLED(255, 0, 0, 500);
|
||||
_power->enterDeepSleep();
|
||||
return ButtonEvent::DeepSleep;
|
||||
}
|
||||
}
|
||||
|
||||
// BTN_UP held 5s = factory reset
|
||||
if (_btnUpPressed && !_btnTopPressed && !_btnDownPressed) {
|
||||
if (now - _btnUpPressTime >= FACTORY_RESET_HOLD_MS) {
|
||||
Serial.println("[buttons] Combo: factory reset");
|
||||
_power->flashLED(255, 255, 0, 1000);
|
||||
_settings->factoryReset();
|
||||
return ButtonEvent::FactoryReset;
|
||||
}
|
||||
}
|
||||
|
||||
// Single press detection (on release, with debounce)
|
||||
if (!debounced(now)) {
|
||||
return ButtonEvent::None;
|
||||
}
|
||||
|
||||
// BTN_TOP released (was short press)
|
||||
if (!topNow && _btnTopPressTime > 0 &&
|
||||
(now - _btnTopPressTime < COMBO_HOLD_MS) &&
|
||||
(now - _btnTopPressTime > BUTTON_DEBOUNCE_MS)) {
|
||||
_btnTopPressTime = 0;
|
||||
_power->flashLED(255, 255, 255);
|
||||
Serial.println("[buttons] BTN_TOP: random photo");
|
||||
_lastDebounce = now;
|
||||
return ButtonEvent::RandomPhoto;
|
||||
}
|
||||
|
||||
// BTN_UP released (was short press)
|
||||
if (!upNow && _btnUpPressTime > 0 &&
|
||||
(now - _btnUpPressTime < FACTORY_RESET_HOLD_MS) &&
|
||||
(now - _btnUpPressTime > BUTTON_DEBOUNCE_MS)) {
|
||||
_btnUpPressTime = 0;
|
||||
_power->flashLED(255, 255, 255);
|
||||
Serial.println("[buttons] BTN_UP: next photo");
|
||||
_lastDebounce = now;
|
||||
return ButtonEvent::NextPhoto;
|
||||
}
|
||||
|
||||
// BTN_DOWN released (was short press)
|
||||
if (!downNow && _btnDownPressTime > 0 &&
|
||||
(now - _btnDownPressTime < COMBO_HOLD_MS) &&
|
||||
(now - _btnDownPressTime > BUTTON_DEBOUNCE_MS)) {
|
||||
_btnDownPressTime = 0;
|
||||
_power->flashLED(255, 255, 255);
|
||||
Serial.println("[buttons] BTN_DOWN: play/pause");
|
||||
_lastDebounce = now;
|
||||
return ButtonEvent::PlayPause;
|
||||
}
|
||||
|
||||
return ButtonEvent::None;
|
||||
}
|
||||
|
||||
bool ButtonHandler::debounced(unsigned long now) {
|
||||
return (now - _lastDebounce) > BUTTON_DEBOUNCE_MS;
|
||||
}
|
||||
38
src/button_handler.h
Normal file
38
src/button_handler.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "config.h"
|
||||
|
||||
class PowerManager;
|
||||
class SettingsManager;
|
||||
|
||||
enum class ButtonEvent : uint8_t {
|
||||
None,
|
||||
NextPhoto,
|
||||
RandomPhoto,
|
||||
PlayPause,
|
||||
DeepSleep,
|
||||
FactoryReset
|
||||
};
|
||||
|
||||
class ButtonHandler {
|
||||
public:
|
||||
void begin(PowerManager& power, SettingsManager& settings);
|
||||
ButtonEvent poll();
|
||||
|
||||
private:
|
||||
PowerManager* _power = nullptr;
|
||||
SettingsManager* _settings = nullptr;
|
||||
|
||||
unsigned long _btnTopPressTime = 0;
|
||||
unsigned long _btnUpPressTime = 0;
|
||||
unsigned long _btnDownPressTime = 0;
|
||||
|
||||
bool _btnTopPressed = false;
|
||||
bool _btnUpPressed = false;
|
||||
bool _btnDownPressed = false;
|
||||
|
||||
unsigned long _lastDebounce = 0;
|
||||
|
||||
bool debounced(unsigned long now);
|
||||
};
|
||||
32
src/main.cpp
32
src/main.cpp
@@ -4,10 +4,12 @@
|
||||
#include "settings.h"
|
||||
#include "wifi_manager.h"
|
||||
#include "power_manager.h"
|
||||
#include "button_handler.h"
|
||||
|
||||
SettingsManager settingsManager;
|
||||
WiFiManager wifiManager;
|
||||
PowerManager powerManager;
|
||||
ButtonHandler buttonHandler;
|
||||
|
||||
void setup() {
|
||||
auto cfg = M5.config();
|
||||
@@ -19,15 +21,37 @@ void setup() {
|
||||
powerManager.begin();
|
||||
settingsManager.begin();
|
||||
wifiManager.begin(settingsManager);
|
||||
buttonHandler.begin(powerManager, settingsManager);
|
||||
|
||||
Serial.printf("[main] Battery: %d%%, Charging: %s\n",
|
||||
powerManager.getBatteryPercent(),
|
||||
powerManager.isCharging() ? "yes" : "no");
|
||||
Serial.println("[main] Boot complete");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
M5.update();
|
||||
powerManager.updateBatteryLED();
|
||||
delay(1000);
|
||||
|
||||
ButtonEvent event = buttonHandler.poll();
|
||||
switch (event) {
|
||||
case ButtonEvent::NextPhoto:
|
||||
Serial.println("[main] → Next photo requested");
|
||||
break;
|
||||
case ButtonEvent::RandomPhoto:
|
||||
Serial.println("[main] → Random photo requested");
|
||||
break;
|
||||
case ButtonEvent::PlayPause:
|
||||
Serial.println("[main] → Play/Pause toggled");
|
||||
break;
|
||||
case ButtonEvent::DeepSleep:
|
||||
case ButtonEvent::FactoryReset:
|
||||
break; // Handled internally
|
||||
case ButtonEvent::None:
|
||||
break;
|
||||
default: {
|
||||
ButtonEvent unreachable = event;
|
||||
(void)unreachable;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
delay(10); // 100Hz poll rate for responsive buttons
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user