initial button manager

This commit is contained in:
JQ 2025-05-27 19:10:56 -07:00
parent d8c2b3ab47
commit e7761dc9dc
4 changed files with 288 additions and 49 deletions

View file

@ -0,0 +1,127 @@
#include "Button.h"
Button::Button(uint8_t pin, bool activeState)
: _pin(pin), _activeState(activeState), _isAnalog(false), _analogThreshold(20) {
_currentState = !_activeState;
_lastState = _currentState;
}
Button::Button(uint8_t pin, bool activeState, bool isAnalog, uint16_t analogThreshold)
: _pin(pin), _activeState(activeState), _isAnalog(isAnalog), _analogThreshold(analogThreshold) {
_currentState = !_activeState;
_lastState = _currentState;
}
void Button::begin() {
if (!_isAnalog) {
pinMode(_pin, INPUT_PULLUP);
}
_currentState = readButton();
_lastState = _currentState;
}
void Button::update() {
uint32_t now = millis();
// Read button at specified interval
if (now - _lastReadTime < BUTTON_READ_INTERVAL_MS) {
return;
}
_lastReadTime = now;
bool newState = readButton();
// Check if state has changed
if (newState != _lastState) {
_stateChangeTime = now;
}
// Debounce check
if ((now - _stateChangeTime) > BUTTON_DEBOUNCE_TIME_MS) {
if (newState != _currentState) {
_currentState = newState;
handleStateChange();
}
}
_lastState = newState;
// Handle multi-click timeout
if (_state == WAITING_FOR_MULTI_CLICK && (now - _releaseTime) > BUTTON_CLICK_TIMEOUT_MS) {
// Timeout reached, process the clicks
if (_clickCount == 1) {
triggerEvent(SHORT_PRESS);
} else if (_clickCount == 2) {
triggerEvent(DOUBLE_PRESS);
} else if (_clickCount >= 3) {
triggerEvent(TRIPLE_PRESS);
}
_clickCount = 0;
_state = IDLE;
}
// Handle long press
if (_state == PRESSED && (now - _pressTime) > BUTTON_LONG_PRESS_TIME_MS) {
triggerEvent(LONG_PRESS);
_state = IDLE; // Prevent multiple long press events
_clickCount = 0;
}
}
bool Button::readButton() {
if (_isAnalog) {
return (analogRead(_pin) < _analogThreshold) ? _activeState : !_activeState;
} else {
return digitalRead(_pin);
}
}
void Button::handleStateChange() {
uint32_t now = millis();
if (_currentState == _activeState) {
// Button pressed
_pressTime = now;
_state = PRESSED;
triggerEvent(ANY_PRESS);
} else {
// Button released
if (_state == PRESSED) {
uint32_t pressDuration = now - _pressTime;
if (pressDuration < BUTTON_LONG_PRESS_TIME_MS) {
// Short press detected
_clickCount++;
_releaseTime = now;
_state = WAITING_FOR_MULTI_CLICK;
} else {
// Long press already handled in update()
_state = IDLE;
}
}
}
}
void Button::triggerEvent(EventType event) {
_lastEvent = event;
switch (event) {
case ANY_PRESS:
if (_onAnyPress) _onAnyPress();
break;
case SHORT_PRESS:
if (_onShortPress) _onShortPress();
break;
case DOUBLE_PRESS:
if (_onDoublePress) _onDoublePress();
break;
case TRIPLE_PRESS:
if (_onTriplePress) _onTriplePress();
break;
case LONG_PRESS:
if (_onLongPress) _onLongPress();
break;
default:
break;
}
}

View file

@ -0,0 +1,77 @@
#pragma once
#include <Arduino.h>
#include <functional>
// Button timing configuration
#define BUTTON_DEBOUNCE_TIME_MS 50 // Debounce time in ms
#define BUTTON_CLICK_TIMEOUT_MS 400 // Max time between clicks for multi-click
#define BUTTON_LONG_PRESS_TIME_MS 5000 // Time to trigger long press (5 seconds)
#define BUTTON_READ_INTERVAL_MS 10 // How often to read the button
class Button {
public:
enum EventType {
NONE,
SHORT_PRESS,
DOUBLE_PRESS,
TRIPLE_PRESS,
LONG_PRESS,
ANY_PRESS
};
using EventCallback = std::function<void()>;
Button(uint8_t pin, bool activeState = LOW);
Button(uint8_t pin, bool activeState, bool isAnalog, uint16_t analogThreshold = 20);
void begin();
void update();
// Set callbacks for different events
void onShortPress(EventCallback callback) { _onShortPress = callback; }
void onDoublePress(EventCallback callback) { _onDoublePress = callback; }
void onTriplePress(EventCallback callback) { _onTriplePress = callback; }
void onLongPress(EventCallback callback) { _onLongPress = callback; }
void onAnyPress(EventCallback callback) { _onAnyPress = callback; } // New method
// State getters
bool isPressed() const { return _currentState == _activeState; }
EventType getLastEvent() const { return _lastEvent; }
private:
enum State {
IDLE,
PRESSED,
RELEASED,
WAITING_FOR_MULTI_CLICK
};
uint8_t _pin;
bool _activeState;
bool _isAnalog;
uint16_t _analogThreshold;
State _state = IDLE;
bool _currentState;
bool _lastState;
uint32_t _stateChangeTime = 0;
uint32_t _pressTime = 0;
uint32_t _releaseTime = 0;
uint32_t _lastReadTime = 0;
uint8_t _clickCount = 0;
EventType _lastEvent = NONE;
// Callbacks
EventCallback _onShortPress = nullptr;
EventCallback _onDoublePress = nullptr;
EventCallback _onTriplePress = nullptr;
EventCallback _onLongPress = nullptr;
EventCallback _onAnyPress = nullptr;
bool readButton();
void handleStateChange();
void triggerEvent(EventType event);
};

View file

@ -57,6 +57,24 @@ void UITask::begin(DisplayDriver* display, NodePrefs* node_prefs, const char* bu
#ifdef PIN_BUZZER
buzzer.begin();
#endif
// Initialize button with appropriate configuration
#if defined(PIN_USER_BTN) || defined(PIN_USER_BTN_ANA)
#ifdef PIN_USER_BTN
_userButton = new Button(PIN_USER_BTN, USER_BTN_PRESSED);
#else
_userButton = new Button(PIN_USER_BTN_ANA, USER_BTN_PRESSED, true, 20);
#endif
_userButton->begin();
// Set up button callbacks
_userButton->onShortPress([this]() { handleButtonShortPress(); });
_userButton->onDoublePress([this]() { handleButtonDoublePress(); });
_userButton->onTriplePress([this]() { handleButtonTriplePress(); });
_userButton->onLongPress([this]() { handleButtonLongPress(); });
_userButton->onAnyPress([this]() { handleButtonAnyPress(); });
#endif
}
void UITask::soundBuzzer(UIEventType bet) {
@ -233,53 +251,8 @@ void UITask::userLedHandler() {
#endif
}
void UITask::buttonHandler() {
#if defined(PIN_USER_BTN) || defined(PIN_USER_BTN_ANA)
static int prev_btn_state = !USER_BTN_PRESSED;
static int prev_btn_state_ana = !USER_BTN_PRESSED;
static unsigned long btn_state_change_time = 0;
static unsigned long next_read = 0;
int cur_time = millis();
if (cur_time >= next_read) {
int btn_state = 0;
int btn_state_ana = 0;
#ifdef PIN_USER_BTN
btn_state = digitalRead(PIN_USER_BTN);
#endif
#ifdef PIN_USER_BTN_ANA
btn_state_ana = (analogRead(PIN_USER_BTN_ANA) < 20); // analogRead returns a value hopefully below 20 when button is pressed.
#endif
if (btn_state != prev_btn_state || btn_state_ana != prev_btn_state_ana) { // check for either digital or analogue button change of state
if (btn_state == USER_BTN_PRESSED || btn_state_ana == USER_BTN_PRESSED) { // pressed?
if (_display != NULL) {
if (_display->isOn()) {
clearMsgPreview();
} else {
_display->turnOn();
_need_refresh = true;
}
_auto_off = cur_time + AUTO_OFF_MILLIS; // extend auto-off timer
}
} else { // unpressed ? check pressed time ...
if ((cur_time - btn_state_change_time) > 5000) {
#ifdef PIN_STATUS_LED
digitalWrite(PIN_STATUS_LED, LOW);
delay(10);
#endif
shutdown(); // without restart
}
}
btn_state_change_time = millis();
prev_btn_state = btn_state;
prev_btn_state_ana = btn_state_ana;
}
next_read = millis() + 100; // 10 reads per second
}
#endif
}
/* hardware-agnostic pre-shutdown activity should be done here
/*
hardware-agnostic pre-shutdown activity should be done here
*/
void UITask::shutdown(bool restart){
@ -303,7 +276,11 @@ void UITask::shutdown(bool restart){
}
void UITask::loop() {
buttonHandler();
#if defined(PIN_USER_BTN) || defined(PIN_USER_BTN_ANA)
if (_userButton) {
_userButton->update();
}
#endif
userLedHandler();
#ifdef PIN_BUZZER
@ -328,3 +305,48 @@ void UITask::loop() {
}
}
}
void UITask::handleButtonAnyPress() {
MESH_DEBUG_PRINTLN("UITask: any press triggered");
if (_display != NULL) {
if (!_display->isOn()) {
_display->turnOn();
_need_refresh = true;
} else {
// Turn on display
_display->turnOn();
_need_refresh = true;
}
_auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer
}
}
void UITask::handleButtonShortPress() {
MESH_DEBUG_PRINTLN("UITask: short press triggered");
if (_display != NULL) {
if (_display->isOn()) {
// If display is on and showing message preview, clear it
if (_origin[0] && _msg[0]) {
clearMsgPreview();
}
}
}
}
void UITask::handleButtonDoublePress() {
MESH_DEBUG_PRINTLN("UITask: double press triggered");
// Not implemented. TODO: possibly send an advert here?
}
void UITask::handleButtonTriplePress() {
MESH_DEBUG_PRINTLN("UITask: triple press triggered");
// Toggle buzzer quiet mode
#ifdef PIN_BUZZER
buzzer.quiet(!buzzer.isQuiet());
#endif
}
void UITask::handleButtonLongPress() {
MESH_DEBUG_PRINTLN("UITask: long press triggered");
shutdown();
}

View file

@ -9,6 +9,7 @@
#endif
#include "NodePrefs.h"
#include "Button.h"
enum class UIEventType
{
@ -35,10 +36,22 @@ class UITask {
int _msgcount;
bool _need_refresh = true;
// Button handlers
#if defined(PIN_USER_BTN) || defined(PIN_USER_BTN_ANA)
Button* _userButton = nullptr;
#endif
void renderCurrScreen();
void buttonHandler();
void userLedHandler();
void renderBatteryIndicator(uint16_t batteryMilliVolts);
// Button action handlers
void handleButtonAnyPress();
void handleButtonShortPress();
void handleButtonDoublePress();
void handleButtonTriplePress();
void handleButtonLongPress();
public: