mirror of
https://github.com/meshtastic/firmware.git
synced 2026-01-26 03:37:38 +00:00
add a .clang-format file (#9154)
This commit is contained in:
@@ -8,302 +8,284 @@
|
||||
|
||||
using namespace NicheGraphics::Inputs;
|
||||
|
||||
TwoButton::TwoButton() : concurrency::OSThread("TwoButton")
|
||||
{
|
||||
// Don't start polling buttons for release immediately
|
||||
// Assume they are in a "released" state at boot
|
||||
OSThread::disable();
|
||||
TwoButton::TwoButton() : concurrency::OSThread("TwoButton") {
|
||||
// Don't start polling buttons for release immediately
|
||||
// Assume they are in a "released" state at boot
|
||||
OSThread::disable();
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
// Register callbacks for before and after lightsleep
|
||||
lsObserver.observe(¬ifyLightSleep);
|
||||
lsEndObserver.observe(¬ifyLightSleepEnd);
|
||||
// Register callbacks for before and after lightsleep
|
||||
lsObserver.observe(¬ifyLightSleep);
|
||||
lsEndObserver.observe(¬ifyLightSleepEnd);
|
||||
#endif
|
||||
|
||||
// Explicitly initialize these, just to keep cppcheck quiet..
|
||||
buttons[0] = Button();
|
||||
buttons[1] = Button();
|
||||
// Explicitly initialize these, just to keep cppcheck quiet..
|
||||
buttons[0] = Button();
|
||||
buttons[1] = Button();
|
||||
}
|
||||
|
||||
// Get access to (or create) the singleton instance of this class
|
||||
// Accessible inside the ISRs, even though we maybe shouldn't
|
||||
TwoButton *TwoButton::getInstance()
|
||||
{
|
||||
// Instantiate the class the first time this method is called
|
||||
static TwoButton *const singletonInstance = new TwoButton;
|
||||
TwoButton *TwoButton::getInstance() {
|
||||
// Instantiate the class the first time this method is called
|
||||
static TwoButton *const singletonInstance = new TwoButton;
|
||||
|
||||
return singletonInstance;
|
||||
return singletonInstance;
|
||||
}
|
||||
|
||||
// Begin receiving button input
|
||||
// We probably need to do this after sleep, as well as at boot
|
||||
void TwoButton::start()
|
||||
{
|
||||
if (buttons[0].pin != 0xFF)
|
||||
attachInterrupt(buttons[0].pin, TwoButton::isrPrimary, buttons[0].activeLogic == LOW ? FALLING : RISING);
|
||||
void TwoButton::start() {
|
||||
if (buttons[0].pin != 0xFF)
|
||||
attachInterrupt(buttons[0].pin, TwoButton::isrPrimary, buttons[0].activeLogic == LOW ? FALLING : RISING);
|
||||
|
||||
if (buttons[1].pin != 0xFF)
|
||||
attachInterrupt(buttons[1].pin, TwoButton::isrSecondary, buttons[1].activeLogic == LOW ? FALLING : RISING);
|
||||
if (buttons[1].pin != 0xFF)
|
||||
attachInterrupt(buttons[1].pin, TwoButton::isrSecondary, buttons[1].activeLogic == LOW ? FALLING : RISING);
|
||||
}
|
||||
|
||||
// Stop receiving button input, and run custom sleep code
|
||||
// Called before device sleeps. This might be power-off, or just ESP32 light sleep
|
||||
// Some devices will want to attach interrupts here, for the user button to wake from sleep
|
||||
void TwoButton::stop()
|
||||
{
|
||||
if (buttons[0].pin != 0xFF)
|
||||
detachInterrupt(buttons[0].pin);
|
||||
void TwoButton::stop() {
|
||||
if (buttons[0].pin != 0xFF)
|
||||
detachInterrupt(buttons[0].pin);
|
||||
|
||||
if (buttons[1].pin != 0xFF)
|
||||
detachInterrupt(buttons[1].pin);
|
||||
if (buttons[1].pin != 0xFF)
|
||||
detachInterrupt(buttons[1].pin);
|
||||
}
|
||||
|
||||
// Attempt to resolve a GPIO pin for the user button, honoring userPrefs.jsonc and device settings
|
||||
// This helper method isn't used by the TweButton class itself, it could be moved elsewhere.
|
||||
// Intention is to pass this value to TwoButton::setWiring in the setupNicheGraphics method.
|
||||
uint8_t TwoButton::getUserButtonPin()
|
||||
{
|
||||
uint8_t pin = 0xFF; // Unset
|
||||
uint8_t TwoButton::getUserButtonPin() {
|
||||
uint8_t pin = 0xFF; // Unset
|
||||
|
||||
// Use default pin for variant, if no better source
|
||||
// Use default pin for variant, if no better source
|
||||
#ifdef BUTTON_PIN
|
||||
pin = BUTTON_PIN;
|
||||
pin = BUTTON_PIN;
|
||||
#endif
|
||||
|
||||
// From userPrefs.jsonc, if set
|
||||
// From userPrefs.jsonc, if set
|
||||
#ifdef USERPREFS_BUTTON_PIN
|
||||
pin = USERPREFS_BUTTON_PIN;
|
||||
pin = USERPREFS_BUTTON_PIN;
|
||||
#endif
|
||||
|
||||
// From user's override in device settings, if set
|
||||
if (config.device.button_gpio)
|
||||
pin = config.device.button_gpio;
|
||||
// From user's override in device settings, if set
|
||||
if (config.device.button_gpio)
|
||||
pin = config.device.button_gpio;
|
||||
|
||||
return pin;
|
||||
return pin;
|
||||
}
|
||||
|
||||
// Configures the wiring and logic of either button
|
||||
// Called when outlining your NicheGraphics implementation, in variant/nicheGraphics.cpp
|
||||
void TwoButton::setWiring(uint8_t whichButton, uint8_t pin, bool internalPullup)
|
||||
{
|
||||
// Prevent the same GPIO being assigned to multiple buttons
|
||||
// Allows an edge case when the user remaps hardware buttons using device settings, due to a broken user button
|
||||
for (uint8_t i = 0; i < whichButton; i++) {
|
||||
if (buttons[i].pin == pin) {
|
||||
LOG_WARN("Attempted reuse of GPIO %d. Ignoring assignment whichButton=%d", pin, whichButton);
|
||||
return;
|
||||
}
|
||||
void TwoButton::setWiring(uint8_t whichButton, uint8_t pin, bool internalPullup) {
|
||||
// Prevent the same GPIO being assigned to multiple buttons
|
||||
// Allows an edge case when the user remaps hardware buttons using device settings, due to a broken user button
|
||||
for (uint8_t i = 0; i < whichButton; i++) {
|
||||
if (buttons[i].pin == pin) {
|
||||
LOG_WARN("Attempted reuse of GPIO %d. Ignoring assignment whichButton=%d", pin, whichButton);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].pin = pin;
|
||||
buttons[whichButton].activeLogic = LOW; // Unimplemented
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].pin = pin;
|
||||
buttons[whichButton].activeLogic = LOW; // Unimplemented
|
||||
|
||||
pinMode(buttons[whichButton].pin, internalPullup ? INPUT_PULLUP : INPUT);
|
||||
pinMode(buttons[whichButton].pin, internalPullup ? INPUT_PULLUP : INPUT);
|
||||
}
|
||||
|
||||
void TwoButton::setTiming(uint8_t whichButton, uint32_t debounceMs, uint32_t longpressMs)
|
||||
{
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].debounceLength = debounceMs;
|
||||
buttons[whichButton].longpressLength = longpressMs;
|
||||
void TwoButton::setTiming(uint8_t whichButton, uint32_t debounceMs, uint32_t longpressMs) {
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].debounceLength = debounceMs;
|
||||
buttons[whichButton].longpressLength = longpressMs;
|
||||
}
|
||||
|
||||
// Set what should happen when a button becomes pressed
|
||||
// Use this to implement a "while held" behavior
|
||||
void TwoButton::setHandlerDown(uint8_t whichButton, Callback onDown)
|
||||
{
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].onDown = onDown;
|
||||
void TwoButton::setHandlerDown(uint8_t whichButton, Callback onDown) {
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].onDown = onDown;
|
||||
}
|
||||
|
||||
// Set what should happen when a button becomes unpressed
|
||||
// Use this to implement a "While held" behavior
|
||||
void TwoButton::setHandlerUp(uint8_t whichButton, Callback onUp)
|
||||
{
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].onUp = onUp;
|
||||
void TwoButton::setHandlerUp(uint8_t whichButton, Callback onUp) {
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].onUp = onUp;
|
||||
}
|
||||
|
||||
// Set what should happen when a "short press" event has occurred
|
||||
void TwoButton::setHandlerShortPress(uint8_t whichButton, Callback onShortPress)
|
||||
{
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].onShortPress = onShortPress;
|
||||
void TwoButton::setHandlerShortPress(uint8_t whichButton, Callback onShortPress) {
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].onShortPress = onShortPress;
|
||||
}
|
||||
|
||||
// Set what should happen when a "long press" event has fired
|
||||
// Note: this will occur while the button is still held
|
||||
void TwoButton::setHandlerLongPress(uint8_t whichButton, Callback onLongPress)
|
||||
{
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].onLongPress = onLongPress;
|
||||
void TwoButton::setHandlerLongPress(uint8_t whichButton, Callback onLongPress) {
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].onLongPress = onLongPress;
|
||||
}
|
||||
|
||||
// Handle the start of a press to the primary button
|
||||
// Wakes our button thread
|
||||
void TwoButton::isrPrimary()
|
||||
{
|
||||
static volatile bool isrRunning = false;
|
||||
void TwoButton::isrPrimary() {
|
||||
static volatile bool isrRunning = false;
|
||||
|
||||
if (!isrRunning) {
|
||||
isrRunning = true;
|
||||
TwoButton *b = TwoButton::getInstance();
|
||||
if (b->buttons[0].state == State::REST) {
|
||||
b->buttons[0].state = State::IRQ;
|
||||
b->buttons[0].irqAtMillis = millis();
|
||||
b->startThread();
|
||||
}
|
||||
isrRunning = false;
|
||||
if (!isrRunning) {
|
||||
isrRunning = true;
|
||||
TwoButton *b = TwoButton::getInstance();
|
||||
if (b->buttons[0].state == State::REST) {
|
||||
b->buttons[0].state = State::IRQ;
|
||||
b->buttons[0].irqAtMillis = millis();
|
||||
b->startThread();
|
||||
}
|
||||
isrRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the start of a press to the secondary button
|
||||
// Wakes our button thread
|
||||
void TwoButton::isrSecondary()
|
||||
{
|
||||
static volatile bool isrRunning = false;
|
||||
void TwoButton::isrSecondary() {
|
||||
static volatile bool isrRunning = false;
|
||||
|
||||
if (!isrRunning) {
|
||||
isrRunning = true;
|
||||
TwoButton *b = TwoButton::getInstance();
|
||||
if (b->buttons[1].state == State::REST) {
|
||||
b->buttons[1].state = State::IRQ;
|
||||
b->buttons[1].irqAtMillis = millis();
|
||||
b->startThread();
|
||||
}
|
||||
isrRunning = false;
|
||||
if (!isrRunning) {
|
||||
isrRunning = true;
|
||||
TwoButton *b = TwoButton::getInstance();
|
||||
if (b->buttons[1].state == State::REST) {
|
||||
b->buttons[1].state = State::IRQ;
|
||||
b->buttons[1].irqAtMillis = millis();
|
||||
b->startThread();
|
||||
}
|
||||
isrRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Concise method to start our button thread
|
||||
// Follows an ISR, listening for button release
|
||||
void TwoButton::startThread()
|
||||
{
|
||||
if (!OSThread::enabled) {
|
||||
OSThread::setInterval(10);
|
||||
OSThread::enabled = true;
|
||||
}
|
||||
void TwoButton::startThread() {
|
||||
if (!OSThread::enabled) {
|
||||
OSThread::setInterval(10);
|
||||
OSThread::enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Concise method to stop our button thread
|
||||
// Called when we no longer need to poll for button release
|
||||
void TwoButton::stopThread()
|
||||
{
|
||||
if (OSThread::enabled) {
|
||||
OSThread::disable();
|
||||
}
|
||||
void TwoButton::stopThread() {
|
||||
if (OSThread::enabled) {
|
||||
OSThread::disable();
|
||||
}
|
||||
|
||||
// Reset both buttons manually
|
||||
// Just in case an IRQ fires during the process of resetting the system
|
||||
// Can occur with super rapid presses?
|
||||
buttons[0].state = REST;
|
||||
buttons[1].state = REST;
|
||||
// Reset both buttons manually
|
||||
// Just in case an IRQ fires during the process of resetting the system
|
||||
// Can occur with super rapid presses?
|
||||
buttons[0].state = REST;
|
||||
buttons[1].state = REST;
|
||||
}
|
||||
|
||||
// Our button thread
|
||||
// Started by an IRQ, on either button
|
||||
// Polls for button releases
|
||||
// Stops when both buttons released
|
||||
int32_t TwoButton::runOnce()
|
||||
{
|
||||
constexpr uint8_t BUTTON_COUNT = sizeof(buttons) / sizeof(Button);
|
||||
int32_t TwoButton::runOnce() {
|
||||
constexpr uint8_t BUTTON_COUNT = sizeof(buttons) / sizeof(Button);
|
||||
|
||||
// Allow either button to request that our thread should continue polling
|
||||
bool awaitingRelease = false;
|
||||
// Allow either button to request that our thread should continue polling
|
||||
bool awaitingRelease = false;
|
||||
|
||||
// Check both primary and secondary buttons
|
||||
for (uint8_t i = 0; i < BUTTON_COUNT; i++) {
|
||||
switch (buttons[i].state) {
|
||||
// No action: button has not been pressed
|
||||
case REST:
|
||||
break;
|
||||
// Check both primary and secondary buttons
|
||||
for (uint8_t i = 0; i < BUTTON_COUNT; i++) {
|
||||
switch (buttons[i].state) {
|
||||
// No action: button has not been pressed
|
||||
case REST:
|
||||
break;
|
||||
|
||||
// New press detected by interrupt
|
||||
case IRQ:
|
||||
powerFSM.trigger(EVENT_PRESS); // Tell PowerFSM that press occurred (resets sleep timer)
|
||||
buttons[i].onDown(); // Run callback: press has begun (possible hold behavior)
|
||||
buttons[i].state = State::POLLING_UNFIRED; // Mark that button-down has been handled
|
||||
awaitingRelease = true; // Mark that polling-for-release should continue
|
||||
break;
|
||||
// New press detected by interrupt
|
||||
case IRQ:
|
||||
powerFSM.trigger(EVENT_PRESS); // Tell PowerFSM that press occurred (resets sleep timer)
|
||||
buttons[i].onDown(); // Run callback: press has begun (possible hold behavior)
|
||||
buttons[i].state = State::POLLING_UNFIRED; // Mark that button-down has been handled
|
||||
awaitingRelease = true; // Mark that polling-for-release should continue
|
||||
break;
|
||||
|
||||
// An existing press continues
|
||||
// Not held long enough to register as longpress
|
||||
case POLLING_UNFIRED: {
|
||||
uint32_t length = millis() - buttons[i].irqAtMillis;
|
||||
// An existing press continues
|
||||
// Not held long enough to register as longpress
|
||||
case POLLING_UNFIRED: {
|
||||
uint32_t length = millis() - buttons[i].irqAtMillis;
|
||||
|
||||
// If button released since last thread tick,
|
||||
if (digitalRead(buttons[i].pin) != buttons[i].activeLogic) {
|
||||
buttons[i].onUp(); // Run callback: press has ended (possible release of a hold)
|
||||
buttons[i].state = State::REST; // Mark that the button has reset
|
||||
if (length > buttons[i].debounceLength && length < buttons[i].longpressLength) // If too short for longpress,
|
||||
buttons[i].onShortPress(); // Run callback: short press
|
||||
}
|
||||
// If button released since last thread tick,
|
||||
if (digitalRead(buttons[i].pin) != buttons[i].activeLogic) {
|
||||
buttons[i].onUp(); // Run callback: press has ended (possible release of a hold)
|
||||
buttons[i].state = State::REST; // Mark that the button has reset
|
||||
if (length > buttons[i].debounceLength && length < buttons[i].longpressLength) // If too short for longpress,
|
||||
buttons[i].onShortPress(); // Run callback: short press
|
||||
}
|
||||
|
||||
// If button not yet released
|
||||
else {
|
||||
awaitingRelease = true; // Mark that polling-for-release should continue
|
||||
if (length >= buttons[i].longpressLength) {
|
||||
// Run callback: long press (once)
|
||||
// Then continue waiting for release, to rearm
|
||||
buttons[i].state = State::POLLING_FIRED;
|
||||
buttons[i].onLongPress();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Button still held, but duration long enough that longpress event already fired
|
||||
// Just waiting for release
|
||||
case POLLING_FIRED:
|
||||
// Release detected
|
||||
if (digitalRead(buttons[i].pin) != buttons[i].activeLogic) {
|
||||
buttons[i].state = State::REST;
|
||||
buttons[i].onUp(); // Callback: release of hold (in this case: *after* longpress has fired)
|
||||
}
|
||||
// Not yet released, keep polling
|
||||
else
|
||||
awaitingRelease = true;
|
||||
break;
|
||||
// If button not yet released
|
||||
else {
|
||||
awaitingRelease = true; // Mark that polling-for-release should continue
|
||||
if (length >= buttons[i].longpressLength) {
|
||||
// Run callback: long press (once)
|
||||
// Then continue waiting for release, to rearm
|
||||
buttons[i].state = State::POLLING_FIRED;
|
||||
buttons[i].onLongPress();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// If both buttons are now released
|
||||
// we don't need to waste cpu resources polling
|
||||
// IRQ will restart this thread when we next need it
|
||||
if (!awaitingRelease)
|
||||
stopThread();
|
||||
// Button still held, but duration long enough that longpress event already fired
|
||||
// Just waiting for release
|
||||
case POLLING_FIRED:
|
||||
// Release detected
|
||||
if (digitalRead(buttons[i].pin) != buttons[i].activeLogic) {
|
||||
buttons[i].state = State::REST;
|
||||
buttons[i].onUp(); // Callback: release of hold (in this case: *after* longpress has fired)
|
||||
}
|
||||
// Not yet released, keep polling
|
||||
else
|
||||
awaitingRelease = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Run this method again, or don't..
|
||||
// Use whatever behavior was previously set by stopThread() or startThread()
|
||||
return OSThread::interval;
|
||||
// If both buttons are now released
|
||||
// we don't need to waste cpu resources polling
|
||||
// IRQ will restart this thread when we next need it
|
||||
if (!awaitingRelease)
|
||||
stopThread();
|
||||
|
||||
// Run this method again, or don't..
|
||||
// Use whatever behavior was previously set by stopThread() or startThread()
|
||||
return OSThread::interval;
|
||||
}
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
|
||||
// Detach our class' interrupts before lightsleep
|
||||
// Allows sleep.cpp to configure its own interrupts, which wake the device on user-button press
|
||||
int TwoButton::beforeLightSleep(void *unused)
|
||||
{
|
||||
stop();
|
||||
return 0; // Indicates success
|
||||
int TwoButton::beforeLightSleep(void *unused) {
|
||||
stop();
|
||||
return 0; // Indicates success
|
||||
}
|
||||
|
||||
// Reconfigure our interrupts
|
||||
// Our class' interrupts were disconnected during sleep, to allow the user button to wake the device from sleep
|
||||
int TwoButton::afterLightSleep(esp_sleep_wakeup_cause_t cause)
|
||||
{
|
||||
start();
|
||||
int TwoButton::afterLightSleep(esp_sleep_wakeup_cause_t cause) {
|
||||
start();
|
||||
|
||||
// Manually trigger the button-down ISR
|
||||
// - during light sleep, our ISR is disabled
|
||||
// - if light sleep ends by button press, pretend our own ISR caught it
|
||||
// - need to manually confirm by reading pin ourselves, to avoid occasional false positives
|
||||
// (false positive only when using internal pullup resistors?)
|
||||
if (cause == ESP_SLEEP_WAKEUP_GPIO && digitalRead(buttons[0].pin) == buttons[0].activeLogic)
|
||||
isrPrimary();
|
||||
// Manually trigger the button-down ISR
|
||||
// - during light sleep, our ISR is disabled
|
||||
// - if light sleep ends by button press, pretend our own ISR caught it
|
||||
// - need to manually confirm by reading pin ourselves, to avoid occasional false positives
|
||||
// (false positive only when using internal pullup resistors?)
|
||||
if (cause == ESP_SLEEP_WAKEUP_GPIO && digitalRead(buttons[0].pin) == buttons[0].activeLogic)
|
||||
isrPrimary();
|
||||
|
||||
return 0; // Indicates success
|
||||
return 0; // Indicates success
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -22,81 +22,78 @@ Interrupt driven
|
||||
|
||||
#include "Observer.h"
|
||||
|
||||
namespace NicheGraphics::Inputs
|
||||
{
|
||||
namespace NicheGraphics::Inputs {
|
||||
|
||||
class TwoButton : protected concurrency::OSThread
|
||||
{
|
||||
class TwoButton : protected concurrency::OSThread {
|
||||
public:
|
||||
typedef std::function<void()> Callback;
|
||||
|
||||
static uint8_t getUserButtonPin(); // Resolve the GPIO, considering the various possible source of definition
|
||||
|
||||
static TwoButton *getInstance(); // Create or get the singleton instance
|
||||
void start(); // Start handling button input
|
||||
void stop(); // Stop handling button input (disconnect ISRs for sleep)
|
||||
void setWiring(uint8_t whichButton, uint8_t pin, bool internalPullup = false);
|
||||
void setTiming(uint8_t whichButton, uint32_t debounceMs, uint32_t longpressMs);
|
||||
void setHandlerDown(uint8_t whichButton, Callback onDown);
|
||||
void setHandlerUp(uint8_t whichButton, Callback onUp);
|
||||
void setHandlerShortPress(uint8_t whichButton, Callback onShortPress);
|
||||
void setHandlerLongPress(uint8_t whichButton, Callback onLongPress);
|
||||
|
||||
// Disconnect and reconnect interrupts for light sleep
|
||||
#ifdef ARCH_ESP32
|
||||
int beforeLightSleep(void *unused);
|
||||
int afterLightSleep(esp_sleep_wakeup_cause_t cause);
|
||||
#endif
|
||||
|
||||
private:
|
||||
// Internal state of a specific button
|
||||
enum State {
|
||||
REST, // Up, no activity
|
||||
IRQ, // Down detected, not yet handled
|
||||
POLLING_UNFIRED, // Down handled, polling for release
|
||||
POLLING_FIRED, // Longpress fired, button still held
|
||||
};
|
||||
|
||||
// Contains info about a specific button
|
||||
// (Array of this struct below)
|
||||
class Button {
|
||||
public:
|
||||
typedef std::function<void()> Callback;
|
||||
// Per-button config
|
||||
uint8_t pin = 0xFF; // 0xFF: unset
|
||||
bool activeLogic = LOW; // Active LOW by default. Currently unimplemented.
|
||||
uint32_t debounceLength = 50; // Minimum length for shortpress, in ms
|
||||
uint32_t longpressLength = 500; // How long after button down to fire longpress, in ms
|
||||
volatile State state = State::REST; // Internal state
|
||||
volatile uint32_t irqAtMillis; // millis() when button went down
|
||||
|
||||
static uint8_t getUserButtonPin(); // Resolve the GPIO, considering the various possible source of definition
|
||||
|
||||
static TwoButton *getInstance(); // Create or get the singleton instance
|
||||
void start(); // Start handling button input
|
||||
void stop(); // Stop handling button input (disconnect ISRs for sleep)
|
||||
void setWiring(uint8_t whichButton, uint8_t pin, bool internalPullup = false);
|
||||
void setTiming(uint8_t whichButton, uint32_t debounceMs, uint32_t longpressMs);
|
||||
void setHandlerDown(uint8_t whichButton, Callback onDown);
|
||||
void setHandlerUp(uint8_t whichButton, Callback onUp);
|
||||
void setHandlerShortPress(uint8_t whichButton, Callback onShortPress);
|
||||
void setHandlerLongPress(uint8_t whichButton, Callback onLongPress);
|
||||
|
||||
// Disconnect and reconnect interrupts for light sleep
|
||||
#ifdef ARCH_ESP32
|
||||
int beforeLightSleep(void *unused);
|
||||
int afterLightSleep(esp_sleep_wakeup_cause_t cause);
|
||||
#endif
|
||||
|
||||
private:
|
||||
// Internal state of a specific button
|
||||
enum State {
|
||||
REST, // Up, no activity
|
||||
IRQ, // Down detected, not yet handled
|
||||
POLLING_UNFIRED, // Down handled, polling for release
|
||||
POLLING_FIRED, // Longpress fired, button still held
|
||||
};
|
||||
|
||||
// Contains info about a specific button
|
||||
// (Array of this struct below)
|
||||
class Button
|
||||
{
|
||||
public:
|
||||
// Per-button config
|
||||
uint8_t pin = 0xFF; // 0xFF: unset
|
||||
bool activeLogic = LOW; // Active LOW by default. Currently unimplemented.
|
||||
uint32_t debounceLength = 50; // Minimum length for shortpress, in ms
|
||||
uint32_t longpressLength = 500; // How long after button down to fire longpress, in ms
|
||||
volatile State state = State::REST; // Internal state
|
||||
volatile uint32_t irqAtMillis; // millis() when button went down
|
||||
|
||||
// Per-button event callbacks
|
||||
static void noop(){};
|
||||
std::function<void()> onDown = noop;
|
||||
std::function<void()> onUp = noop;
|
||||
std::function<void()> onShortPress = noop;
|
||||
std::function<void()> onLongPress = noop;
|
||||
};
|
||||
// Per-button event callbacks
|
||||
static void noop(){};
|
||||
std::function<void()> onDown = noop;
|
||||
std::function<void()> onUp = noop;
|
||||
std::function<void()> onShortPress = noop;
|
||||
std::function<void()> onLongPress = noop;
|
||||
};
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
// Get notified when lightsleep begins and ends
|
||||
CallbackObserver<TwoButton, void *> lsObserver = CallbackObserver<TwoButton, void *>(this, &TwoButton::beforeLightSleep);
|
||||
CallbackObserver<TwoButton, esp_sleep_wakeup_cause_t> lsEndObserver =
|
||||
CallbackObserver<TwoButton, esp_sleep_wakeup_cause_t>(this, &TwoButton::afterLightSleep);
|
||||
// Get notified when lightsleep begins and ends
|
||||
CallbackObserver<TwoButton, void *> lsObserver = CallbackObserver<TwoButton, void *>(this, &TwoButton::beforeLightSleep);
|
||||
CallbackObserver<TwoButton, esp_sleep_wakeup_cause_t> lsEndObserver =
|
||||
CallbackObserver<TwoButton, esp_sleep_wakeup_cause_t>(this, &TwoButton::afterLightSleep);
|
||||
#endif
|
||||
|
||||
int32_t runOnce() override; // Timer method. Polls for button release
|
||||
int32_t runOnce() override; // Timer method. Polls for button release
|
||||
|
||||
void startThread(); // Start polling for release
|
||||
void stopThread(); // Stop polling for release
|
||||
void startThread(); // Start polling for release
|
||||
void stopThread(); // Stop polling for release
|
||||
|
||||
static void isrPrimary(); // Detect start of press
|
||||
static void isrSecondary(); // Detect start of press (optional aux button)
|
||||
static void isrPrimary(); // Detect start of press
|
||||
static void isrSecondary(); // Detect start of press (optional aux button)
|
||||
|
||||
TwoButton(); // Constructor made private: force use of Button::instance()
|
||||
TwoButton(); // Constructor made private: force use of Button::instance()
|
||||
|
||||
// Info about both buttons
|
||||
Button buttons[2];
|
||||
// Info about both buttons
|
||||
Button buttons[2];
|
||||
};
|
||||
|
||||
}; // namespace NicheGraphics::Inputs
|
||||
|
||||
@@ -8,514 +8,481 @@
|
||||
|
||||
using namespace NicheGraphics::Inputs;
|
||||
|
||||
TwoButtonExtended::TwoButtonExtended() : concurrency::OSThread("TwoButtonExtended")
|
||||
{
|
||||
// Don't start polling buttons for release immediately
|
||||
// Assume they are in a "released" state at boot
|
||||
OSThread::disable();
|
||||
TwoButtonExtended::TwoButtonExtended() : concurrency::OSThread("TwoButtonExtended") {
|
||||
// Don't start polling buttons for release immediately
|
||||
// Assume they are in a "released" state at boot
|
||||
OSThread::disable();
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
// Register callbacks for before and after lightsleep
|
||||
lsObserver.observe(¬ifyLightSleep);
|
||||
lsEndObserver.observe(¬ifyLightSleepEnd);
|
||||
// Register callbacks for before and after lightsleep
|
||||
lsObserver.observe(¬ifyLightSleep);
|
||||
lsEndObserver.observe(¬ifyLightSleepEnd);
|
||||
#endif
|
||||
|
||||
// Explicitly initialize these, just to keep cppcheck quiet..
|
||||
buttons[0] = Button();
|
||||
buttons[1] = Button();
|
||||
joystick[Direction::UP] = SimpleButton();
|
||||
joystick[Direction::DOWN] = SimpleButton();
|
||||
joystick[Direction::LEFT] = SimpleButton();
|
||||
joystick[Direction::RIGHT] = SimpleButton();
|
||||
// Explicitly initialize these, just to keep cppcheck quiet..
|
||||
buttons[0] = Button();
|
||||
buttons[1] = Button();
|
||||
joystick[Direction::UP] = SimpleButton();
|
||||
joystick[Direction::DOWN] = SimpleButton();
|
||||
joystick[Direction::LEFT] = SimpleButton();
|
||||
joystick[Direction::RIGHT] = SimpleButton();
|
||||
}
|
||||
|
||||
// Get access to (or create) the singleton instance of this class
|
||||
// Accessible inside the ISRs, even though we maybe shouldn't
|
||||
TwoButtonExtended *TwoButtonExtended::getInstance()
|
||||
{
|
||||
// Instantiate the class the first time this method is called
|
||||
static TwoButtonExtended *const singletonInstance = new TwoButtonExtended;
|
||||
TwoButtonExtended *TwoButtonExtended::getInstance() {
|
||||
// Instantiate the class the first time this method is called
|
||||
static TwoButtonExtended *const singletonInstance = new TwoButtonExtended;
|
||||
|
||||
return singletonInstance;
|
||||
return singletonInstance;
|
||||
}
|
||||
|
||||
// Begin receiving button input
|
||||
// We probably need to do this after sleep, as well as at boot
|
||||
void TwoButtonExtended::start()
|
||||
{
|
||||
if (buttons[0].pin != 0xFF)
|
||||
attachInterrupt(buttons[0].pin, TwoButtonExtended::isrPrimary, buttons[0].activeLogic == LOW ? FALLING : RISING);
|
||||
void TwoButtonExtended::start() {
|
||||
if (buttons[0].pin != 0xFF)
|
||||
attachInterrupt(buttons[0].pin, TwoButtonExtended::isrPrimary, buttons[0].activeLogic == LOW ? FALLING : RISING);
|
||||
|
||||
if (buttons[1].pin != 0xFF)
|
||||
attachInterrupt(buttons[1].pin, TwoButtonExtended::isrSecondary, buttons[1].activeLogic == LOW ? FALLING : RISING);
|
||||
if (buttons[1].pin != 0xFF)
|
||||
attachInterrupt(buttons[1].pin, TwoButtonExtended::isrSecondary, buttons[1].activeLogic == LOW ? FALLING : RISING);
|
||||
|
||||
if (joystick[Direction::UP].pin != 0xFF)
|
||||
attachInterrupt(joystick[Direction::UP].pin, TwoButtonExtended::isrJoystickUp,
|
||||
joystickActiveLogic == LOW ? FALLING : RISING);
|
||||
if (joystick[Direction::UP].pin != 0xFF)
|
||||
attachInterrupt(joystick[Direction::UP].pin, TwoButtonExtended::isrJoystickUp, joystickActiveLogic == LOW ? FALLING : RISING);
|
||||
|
||||
if (joystick[Direction::DOWN].pin != 0xFF)
|
||||
attachInterrupt(joystick[Direction::DOWN].pin, TwoButtonExtended::isrJoystickDown,
|
||||
joystickActiveLogic == LOW ? FALLING : RISING);
|
||||
if (joystick[Direction::DOWN].pin != 0xFF)
|
||||
attachInterrupt(joystick[Direction::DOWN].pin, TwoButtonExtended::isrJoystickDown, joystickActiveLogic == LOW ? FALLING : RISING);
|
||||
|
||||
if (joystick[Direction::LEFT].pin != 0xFF)
|
||||
attachInterrupt(joystick[Direction::LEFT].pin, TwoButtonExtended::isrJoystickLeft,
|
||||
joystickActiveLogic == LOW ? FALLING : RISING);
|
||||
if (joystick[Direction::LEFT].pin != 0xFF)
|
||||
attachInterrupt(joystick[Direction::LEFT].pin, TwoButtonExtended::isrJoystickLeft, joystickActiveLogic == LOW ? FALLING : RISING);
|
||||
|
||||
if (joystick[Direction::RIGHT].pin != 0xFF)
|
||||
attachInterrupt(joystick[Direction::RIGHT].pin, TwoButtonExtended::isrJoystickRight,
|
||||
joystickActiveLogic == LOW ? FALLING : RISING);
|
||||
if (joystick[Direction::RIGHT].pin != 0xFF)
|
||||
attachInterrupt(joystick[Direction::RIGHT].pin, TwoButtonExtended::isrJoystickRight, joystickActiveLogic == LOW ? FALLING : RISING);
|
||||
}
|
||||
|
||||
// Stop receiving button input, and run custom sleep code
|
||||
// Called before device sleeps. This might be power-off, or just ESP32 light sleep
|
||||
// Some devices will want to attach interrupts here, for the user button to wake from sleep
|
||||
void TwoButtonExtended::stop()
|
||||
{
|
||||
if (buttons[0].pin != 0xFF)
|
||||
detachInterrupt(buttons[0].pin);
|
||||
void TwoButtonExtended::stop() {
|
||||
if (buttons[0].pin != 0xFF)
|
||||
detachInterrupt(buttons[0].pin);
|
||||
|
||||
if (buttons[1].pin != 0xFF)
|
||||
detachInterrupt(buttons[1].pin);
|
||||
if (buttons[1].pin != 0xFF)
|
||||
detachInterrupt(buttons[1].pin);
|
||||
|
||||
if (joystick[Direction::UP].pin != 0xFF)
|
||||
detachInterrupt(joystick[Direction::UP].pin);
|
||||
if (joystick[Direction::UP].pin != 0xFF)
|
||||
detachInterrupt(joystick[Direction::UP].pin);
|
||||
|
||||
if (joystick[Direction::DOWN].pin != 0xFF)
|
||||
detachInterrupt(joystick[Direction::DOWN].pin);
|
||||
if (joystick[Direction::DOWN].pin != 0xFF)
|
||||
detachInterrupt(joystick[Direction::DOWN].pin);
|
||||
|
||||
if (joystick[Direction::LEFT].pin != 0xFF)
|
||||
detachInterrupt(joystick[Direction::LEFT].pin);
|
||||
if (joystick[Direction::LEFT].pin != 0xFF)
|
||||
detachInterrupt(joystick[Direction::LEFT].pin);
|
||||
|
||||
if (joystick[Direction::RIGHT].pin != 0xFF)
|
||||
detachInterrupt(joystick[Direction::RIGHT].pin);
|
||||
if (joystick[Direction::RIGHT].pin != 0xFF)
|
||||
detachInterrupt(joystick[Direction::RIGHT].pin);
|
||||
}
|
||||
|
||||
// Attempt to resolve a GPIO pin for the user button, honoring userPrefs.jsonc and device settings
|
||||
// This helper method isn't used by the TwoButtonExtended class itself, it could be moved elsewhere.
|
||||
// Intention is to pass this value to TwoButtonExtended::setWiring in the setupNicheGraphics method.
|
||||
uint8_t TwoButtonExtended::getUserButtonPin()
|
||||
{
|
||||
uint8_t pin = 0xFF; // Unset
|
||||
uint8_t TwoButtonExtended::getUserButtonPin() {
|
||||
uint8_t pin = 0xFF; // Unset
|
||||
|
||||
// Use default pin for variant, if no better source
|
||||
// Use default pin for variant, if no better source
|
||||
#ifdef BUTTON_PIN
|
||||
pin = BUTTON_PIN;
|
||||
pin = BUTTON_PIN;
|
||||
#endif
|
||||
|
||||
// From userPrefs.jsonc, if set
|
||||
// From userPrefs.jsonc, if set
|
||||
#ifdef USERPREFS_BUTTON_PIN
|
||||
pin = USERPREFS_BUTTON_PIN;
|
||||
pin = USERPREFS_BUTTON_PIN;
|
||||
#endif
|
||||
|
||||
// From user's override in device settings, if set
|
||||
if (config.device.button_gpio)
|
||||
pin = config.device.button_gpio;
|
||||
// From user's override in device settings, if set
|
||||
if (config.device.button_gpio)
|
||||
pin = config.device.button_gpio;
|
||||
|
||||
return pin;
|
||||
return pin;
|
||||
}
|
||||
|
||||
// Configures the wiring and logic of either button
|
||||
// Called when outlining your NicheGraphics implementation, in variant/nicheGraphics.cpp
|
||||
void TwoButtonExtended::setWiring(uint8_t whichButton, uint8_t pin, bool internalPullup)
|
||||
{
|
||||
// Prevent the same GPIO being assigned to multiple buttons
|
||||
// Allows an edge case when the user remaps hardware buttons using device settings, due to a broken user button
|
||||
for (uint8_t i = 0; i < whichButton; i++) {
|
||||
if (buttons[i].pin == pin) {
|
||||
LOG_WARN("Attempted reuse of GPIO %d. Ignoring assignment whichButton=%d", pin, whichButton);
|
||||
return;
|
||||
}
|
||||
void TwoButtonExtended::setWiring(uint8_t whichButton, uint8_t pin, bool internalPullup) {
|
||||
// Prevent the same GPIO being assigned to multiple buttons
|
||||
// Allows an edge case when the user remaps hardware buttons using device settings, due to a broken user button
|
||||
for (uint8_t i = 0; i < whichButton; i++) {
|
||||
if (buttons[i].pin == pin) {
|
||||
LOG_WARN("Attempted reuse of GPIO %d. Ignoring assignment whichButton=%d", pin, whichButton);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].pin = pin;
|
||||
buttons[whichButton].activeLogic = LOW;
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].pin = pin;
|
||||
buttons[whichButton].activeLogic = LOW;
|
||||
|
||||
pinMode(buttons[whichButton].pin, internalPullup ? INPUT_PULLUP : INPUT);
|
||||
pinMode(buttons[whichButton].pin, internalPullup ? INPUT_PULLUP : INPUT);
|
||||
}
|
||||
|
||||
// Configures the wiring and logic of the joystick buttons
|
||||
// Called when outlining your NicheGraphics implementation, in variant/nicheGraphics.cpp
|
||||
void TwoButtonExtended::setJoystickWiring(uint8_t uPin, uint8_t dPin, uint8_t lPin, uint8_t rPin, bool internalPullup)
|
||||
{
|
||||
if (joystick[Direction::UP].pin == uPin || joystick[Direction::DOWN].pin == dPin || joystick[Direction::LEFT].pin == lPin ||
|
||||
joystick[Direction::RIGHT].pin == rPin) {
|
||||
LOG_WARN("Attempted reuse of Joystick GPIO. Ignoring assignment");
|
||||
return;
|
||||
}
|
||||
void TwoButtonExtended::setJoystickWiring(uint8_t uPin, uint8_t dPin, uint8_t lPin, uint8_t rPin, bool internalPullup) {
|
||||
if (joystick[Direction::UP].pin == uPin || joystick[Direction::DOWN].pin == dPin || joystick[Direction::LEFT].pin == lPin ||
|
||||
joystick[Direction::RIGHT].pin == rPin) {
|
||||
LOG_WARN("Attempted reuse of Joystick GPIO. Ignoring assignment");
|
||||
return;
|
||||
}
|
||||
|
||||
joystick[Direction::UP].pin = uPin;
|
||||
joystick[Direction::DOWN].pin = dPin;
|
||||
joystick[Direction::LEFT].pin = lPin;
|
||||
joystick[Direction::RIGHT].pin = rPin;
|
||||
joystickActiveLogic = LOW;
|
||||
joystick[Direction::UP].pin = uPin;
|
||||
joystick[Direction::DOWN].pin = dPin;
|
||||
joystick[Direction::LEFT].pin = lPin;
|
||||
joystick[Direction::RIGHT].pin = rPin;
|
||||
joystickActiveLogic = LOW;
|
||||
|
||||
pinMode(joystick[Direction::UP].pin, internalPullup ? INPUT_PULLUP : INPUT);
|
||||
pinMode(joystick[Direction::DOWN].pin, internalPullup ? INPUT_PULLUP : INPUT);
|
||||
pinMode(joystick[Direction::LEFT].pin, internalPullup ? INPUT_PULLUP : INPUT);
|
||||
pinMode(joystick[Direction::RIGHT].pin, internalPullup ? INPUT_PULLUP : INPUT);
|
||||
pinMode(joystick[Direction::UP].pin, internalPullup ? INPUT_PULLUP : INPUT);
|
||||
pinMode(joystick[Direction::DOWN].pin, internalPullup ? INPUT_PULLUP : INPUT);
|
||||
pinMode(joystick[Direction::LEFT].pin, internalPullup ? INPUT_PULLUP : INPUT);
|
||||
pinMode(joystick[Direction::RIGHT].pin, internalPullup ? INPUT_PULLUP : INPUT);
|
||||
}
|
||||
|
||||
void TwoButtonExtended::setTiming(uint8_t whichButton, uint32_t debounceMs, uint32_t longpressMs)
|
||||
{
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].debounceLength = debounceMs;
|
||||
buttons[whichButton].longpressLength = longpressMs;
|
||||
void TwoButtonExtended::setTiming(uint8_t whichButton, uint32_t debounceMs, uint32_t longpressMs) {
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].debounceLength = debounceMs;
|
||||
buttons[whichButton].longpressLength = longpressMs;
|
||||
}
|
||||
|
||||
void TwoButtonExtended::setJoystickDebounce(uint32_t debounceMs)
|
||||
{
|
||||
joystickDebounceLength = debounceMs;
|
||||
}
|
||||
void TwoButtonExtended::setJoystickDebounce(uint32_t debounceMs) { joystickDebounceLength = debounceMs; }
|
||||
|
||||
// Set what should happen when a button becomes pressed
|
||||
// Use this to implement a "while held" behavior
|
||||
void TwoButtonExtended::setHandlerDown(uint8_t whichButton, Callback onDown)
|
||||
{
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].onDown = onDown;
|
||||
void TwoButtonExtended::setHandlerDown(uint8_t whichButton, Callback onDown) {
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].onDown = onDown;
|
||||
}
|
||||
|
||||
// Set what should happen when a button becomes unpressed
|
||||
// Use this to implement a "While held" behavior
|
||||
void TwoButtonExtended::setHandlerUp(uint8_t whichButton, Callback onUp)
|
||||
{
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].onUp = onUp;
|
||||
void TwoButtonExtended::setHandlerUp(uint8_t whichButton, Callback onUp) {
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].onUp = onUp;
|
||||
}
|
||||
|
||||
// Set what should happen when a "short press" event has occurred
|
||||
void TwoButtonExtended::setHandlerShortPress(uint8_t whichButton, Callback onPress)
|
||||
{
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].onPress = onPress;
|
||||
void TwoButtonExtended::setHandlerShortPress(uint8_t whichButton, Callback onPress) {
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].onPress = onPress;
|
||||
}
|
||||
|
||||
// Set what should happen when a "long press" event has fired
|
||||
// Note: this will occur while the button is still held
|
||||
void TwoButtonExtended::setHandlerLongPress(uint8_t whichButton, Callback onLongPress)
|
||||
{
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].onLongPress = onLongPress;
|
||||
void TwoButtonExtended::setHandlerLongPress(uint8_t whichButton, Callback onLongPress) {
|
||||
assert(whichButton < 2);
|
||||
buttons[whichButton].onLongPress = onLongPress;
|
||||
}
|
||||
|
||||
// Set what should happen when a joystick button becomes pressed
|
||||
// Use this to implement a "while held" behavior
|
||||
void TwoButtonExtended::setJoystickDownHandlers(Callback uDown, Callback dDown, Callback lDown, Callback rDown)
|
||||
{
|
||||
joystick[Direction::UP].onDown = uDown;
|
||||
joystick[Direction::DOWN].onDown = dDown;
|
||||
joystick[Direction::LEFT].onDown = lDown;
|
||||
joystick[Direction::RIGHT].onDown = rDown;
|
||||
void TwoButtonExtended::setJoystickDownHandlers(Callback uDown, Callback dDown, Callback lDown, Callback rDown) {
|
||||
joystick[Direction::UP].onDown = uDown;
|
||||
joystick[Direction::DOWN].onDown = dDown;
|
||||
joystick[Direction::LEFT].onDown = lDown;
|
||||
joystick[Direction::RIGHT].onDown = rDown;
|
||||
}
|
||||
|
||||
// Set what should happen when a joystick button becomes unpressed
|
||||
// Use this to implement a "while held" behavior
|
||||
void TwoButtonExtended::setJoystickUpHandlers(Callback uUp, Callback dUp, Callback lUp, Callback rUp)
|
||||
{
|
||||
joystick[Direction::UP].onUp = uUp;
|
||||
joystick[Direction::DOWN].onUp = dUp;
|
||||
joystick[Direction::LEFT].onUp = lUp;
|
||||
joystick[Direction::RIGHT].onUp = rUp;
|
||||
void TwoButtonExtended::setJoystickUpHandlers(Callback uUp, Callback dUp, Callback lUp, Callback rUp) {
|
||||
joystick[Direction::UP].onUp = uUp;
|
||||
joystick[Direction::DOWN].onUp = dUp;
|
||||
joystick[Direction::LEFT].onUp = lUp;
|
||||
joystick[Direction::RIGHT].onUp = rUp;
|
||||
}
|
||||
|
||||
// Set what should happen when a "press" event has fired
|
||||
// Note: this will occur while the joystick button is still held
|
||||
void TwoButtonExtended::setJoystickPressHandlers(Callback uPress, Callback dPress, Callback lPress, Callback rPress)
|
||||
{
|
||||
joystick[Direction::UP].onPress = uPress;
|
||||
joystick[Direction::DOWN].onPress = dPress;
|
||||
joystick[Direction::LEFT].onPress = lPress;
|
||||
joystick[Direction::RIGHT].onPress = rPress;
|
||||
void TwoButtonExtended::setJoystickPressHandlers(Callback uPress, Callback dPress, Callback lPress, Callback rPress) {
|
||||
joystick[Direction::UP].onPress = uPress;
|
||||
joystick[Direction::DOWN].onPress = dPress;
|
||||
joystick[Direction::LEFT].onPress = lPress;
|
||||
joystick[Direction::RIGHT].onPress = rPress;
|
||||
}
|
||||
|
||||
// Handle the start of a press to the primary button
|
||||
// Wakes our button thread
|
||||
void TwoButtonExtended::isrPrimary()
|
||||
{
|
||||
static volatile bool isrRunning = false;
|
||||
void TwoButtonExtended::isrPrimary() {
|
||||
static volatile bool isrRunning = false;
|
||||
|
||||
if (!isrRunning) {
|
||||
isrRunning = true;
|
||||
TwoButtonExtended *b = TwoButtonExtended::getInstance();
|
||||
if (b->buttons[0].state == State::REST) {
|
||||
b->buttons[0].state = State::IRQ;
|
||||
b->buttons[0].irqAtMillis = millis();
|
||||
b->startThread();
|
||||
}
|
||||
isrRunning = false;
|
||||
if (!isrRunning) {
|
||||
isrRunning = true;
|
||||
TwoButtonExtended *b = TwoButtonExtended::getInstance();
|
||||
if (b->buttons[0].state == State::REST) {
|
||||
b->buttons[0].state = State::IRQ;
|
||||
b->buttons[0].irqAtMillis = millis();
|
||||
b->startThread();
|
||||
}
|
||||
isrRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the start of a press to the secondary button
|
||||
// Wakes our button thread
|
||||
void TwoButtonExtended::isrSecondary()
|
||||
{
|
||||
static volatile bool isrRunning = false;
|
||||
void TwoButtonExtended::isrSecondary() {
|
||||
static volatile bool isrRunning = false;
|
||||
|
||||
if (!isrRunning) {
|
||||
isrRunning = true;
|
||||
TwoButtonExtended *b = TwoButtonExtended::getInstance();
|
||||
if (b->buttons[1].state == State::REST) {
|
||||
b->buttons[1].state = State::IRQ;
|
||||
b->buttons[1].irqAtMillis = millis();
|
||||
b->startThread();
|
||||
}
|
||||
isrRunning = false;
|
||||
if (!isrRunning) {
|
||||
isrRunning = true;
|
||||
TwoButtonExtended *b = TwoButtonExtended::getInstance();
|
||||
if (b->buttons[1].state == State::REST) {
|
||||
b->buttons[1].state = State::IRQ;
|
||||
b->buttons[1].irqAtMillis = millis();
|
||||
b->startThread();
|
||||
}
|
||||
isrRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the start of a press to the joystick buttons
|
||||
// Also wakes our button thread
|
||||
void TwoButtonExtended::isrJoystickUp()
|
||||
{
|
||||
static volatile bool isrRunning = false;
|
||||
void TwoButtonExtended::isrJoystickUp() {
|
||||
static volatile bool isrRunning = false;
|
||||
|
||||
if (!isrRunning) {
|
||||
isrRunning = true;
|
||||
TwoButtonExtended *b = TwoButtonExtended::getInstance();
|
||||
if (b->joystick[Direction::UP].state == State::REST) {
|
||||
b->joystick[Direction::UP].state = State::IRQ;
|
||||
b->joystick[Direction::UP].irqAtMillis = millis();
|
||||
b->startThread();
|
||||
}
|
||||
isrRunning = false;
|
||||
if (!isrRunning) {
|
||||
isrRunning = true;
|
||||
TwoButtonExtended *b = TwoButtonExtended::getInstance();
|
||||
if (b->joystick[Direction::UP].state == State::REST) {
|
||||
b->joystick[Direction::UP].state = State::IRQ;
|
||||
b->joystick[Direction::UP].irqAtMillis = millis();
|
||||
b->startThread();
|
||||
}
|
||||
isrRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
void TwoButtonExtended::isrJoystickDown()
|
||||
{
|
||||
static volatile bool isrRunning = false;
|
||||
void TwoButtonExtended::isrJoystickDown() {
|
||||
static volatile bool isrRunning = false;
|
||||
|
||||
if (!isrRunning) {
|
||||
isrRunning = true;
|
||||
TwoButtonExtended *b = TwoButtonExtended::getInstance();
|
||||
if (b->joystick[Direction::DOWN].state == State::REST) {
|
||||
b->joystick[Direction::DOWN].state = State::IRQ;
|
||||
b->joystick[Direction::DOWN].irqAtMillis = millis();
|
||||
b->startThread();
|
||||
}
|
||||
isrRunning = false;
|
||||
if (!isrRunning) {
|
||||
isrRunning = true;
|
||||
TwoButtonExtended *b = TwoButtonExtended::getInstance();
|
||||
if (b->joystick[Direction::DOWN].state == State::REST) {
|
||||
b->joystick[Direction::DOWN].state = State::IRQ;
|
||||
b->joystick[Direction::DOWN].irqAtMillis = millis();
|
||||
b->startThread();
|
||||
}
|
||||
isrRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
void TwoButtonExtended::isrJoystickLeft()
|
||||
{
|
||||
static volatile bool isrRunning = false;
|
||||
void TwoButtonExtended::isrJoystickLeft() {
|
||||
static volatile bool isrRunning = false;
|
||||
|
||||
if (!isrRunning) {
|
||||
isrRunning = true;
|
||||
TwoButtonExtended *b = TwoButtonExtended::getInstance();
|
||||
if (b->joystick[Direction::LEFT].state == State::REST) {
|
||||
b->joystick[Direction::LEFT].state = State::IRQ;
|
||||
b->joystick[Direction::LEFT].irqAtMillis = millis();
|
||||
b->startThread();
|
||||
}
|
||||
isrRunning = false;
|
||||
if (!isrRunning) {
|
||||
isrRunning = true;
|
||||
TwoButtonExtended *b = TwoButtonExtended::getInstance();
|
||||
if (b->joystick[Direction::LEFT].state == State::REST) {
|
||||
b->joystick[Direction::LEFT].state = State::IRQ;
|
||||
b->joystick[Direction::LEFT].irqAtMillis = millis();
|
||||
b->startThread();
|
||||
}
|
||||
isrRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
void TwoButtonExtended::isrJoystickRight()
|
||||
{
|
||||
static volatile bool isrRunning = false;
|
||||
void TwoButtonExtended::isrJoystickRight() {
|
||||
static volatile bool isrRunning = false;
|
||||
|
||||
if (!isrRunning) {
|
||||
isrRunning = true;
|
||||
TwoButtonExtended *b = TwoButtonExtended::getInstance();
|
||||
if (b->joystick[Direction::RIGHT].state == State::REST) {
|
||||
b->joystick[Direction::RIGHT].state = State::IRQ;
|
||||
b->joystick[Direction::RIGHT].irqAtMillis = millis();
|
||||
b->startThread();
|
||||
}
|
||||
isrRunning = false;
|
||||
if (!isrRunning) {
|
||||
isrRunning = true;
|
||||
TwoButtonExtended *b = TwoButtonExtended::getInstance();
|
||||
if (b->joystick[Direction::RIGHT].state == State::REST) {
|
||||
b->joystick[Direction::RIGHT].state = State::IRQ;
|
||||
b->joystick[Direction::RIGHT].irqAtMillis = millis();
|
||||
b->startThread();
|
||||
}
|
||||
isrRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Concise method to start our button thread
|
||||
// Follows an ISR, listening for button release
|
||||
void TwoButtonExtended::startThread()
|
||||
{
|
||||
if (!OSThread::enabled) {
|
||||
OSThread::setInterval(10);
|
||||
OSThread::enabled = true;
|
||||
}
|
||||
void TwoButtonExtended::startThread() {
|
||||
if (!OSThread::enabled) {
|
||||
OSThread::setInterval(10);
|
||||
OSThread::enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Concise method to stop our button thread
|
||||
// Called when we no longer need to poll for button release
|
||||
void TwoButtonExtended::stopThread()
|
||||
{
|
||||
if (OSThread::enabled) {
|
||||
OSThread::disable();
|
||||
}
|
||||
void TwoButtonExtended::stopThread() {
|
||||
if (OSThread::enabled) {
|
||||
OSThread::disable();
|
||||
}
|
||||
|
||||
// Reset both buttons manually
|
||||
// Just in case an IRQ fires during the process of resetting the system
|
||||
// Can occur with super rapid presses?
|
||||
buttons[0].state = REST;
|
||||
buttons[1].state = REST;
|
||||
joystick[Direction::UP].state = REST;
|
||||
joystick[Direction::DOWN].state = REST;
|
||||
joystick[Direction::LEFT].state = REST;
|
||||
joystick[Direction::RIGHT].state = REST;
|
||||
// Reset both buttons manually
|
||||
// Just in case an IRQ fires during the process of resetting the system
|
||||
// Can occur with super rapid presses?
|
||||
buttons[0].state = REST;
|
||||
buttons[1].state = REST;
|
||||
joystick[Direction::UP].state = REST;
|
||||
joystick[Direction::DOWN].state = REST;
|
||||
joystick[Direction::LEFT].state = REST;
|
||||
joystick[Direction::RIGHT].state = REST;
|
||||
}
|
||||
|
||||
// Our button thread
|
||||
// Started by an IRQ, on either button
|
||||
// Polls for button releases
|
||||
// Stops when both buttons released
|
||||
int32_t TwoButtonExtended::runOnce()
|
||||
{
|
||||
constexpr uint8_t BUTTON_COUNT = sizeof(buttons) / sizeof(Button);
|
||||
constexpr uint8_t JOYSTICK_COUNT = sizeof(joystick) / sizeof(SimpleButton);
|
||||
int32_t TwoButtonExtended::runOnce() {
|
||||
constexpr uint8_t BUTTON_COUNT = sizeof(buttons) / sizeof(Button);
|
||||
constexpr uint8_t JOYSTICK_COUNT = sizeof(joystick) / sizeof(SimpleButton);
|
||||
|
||||
// Allow either button to request that our thread should continue polling
|
||||
bool awaitingRelease = false;
|
||||
// Allow either button to request that our thread should continue polling
|
||||
bool awaitingRelease = false;
|
||||
|
||||
// Check both primary and secondary buttons
|
||||
for (uint8_t i = 0; i < BUTTON_COUNT; i++) {
|
||||
switch (buttons[i].state) {
|
||||
// No action: button has not been pressed
|
||||
case REST:
|
||||
break;
|
||||
// Check both primary and secondary buttons
|
||||
for (uint8_t i = 0; i < BUTTON_COUNT; i++) {
|
||||
switch (buttons[i].state) {
|
||||
// No action: button has not been pressed
|
||||
case REST:
|
||||
break;
|
||||
|
||||
// New press detected by interrupt
|
||||
case IRQ:
|
||||
powerFSM.trigger(EVENT_PRESS); // Tell PowerFSM that press occurred (resets sleep timer)
|
||||
buttons[i].onDown(); // Run callback: press has begun (possible hold behavior)
|
||||
buttons[i].state = State::POLLING_UNFIRED; // Mark that button-down has been handled
|
||||
awaitingRelease = true; // Mark that polling-for-release should continue
|
||||
break;
|
||||
// New press detected by interrupt
|
||||
case IRQ:
|
||||
powerFSM.trigger(EVENT_PRESS); // Tell PowerFSM that press occurred (resets sleep timer)
|
||||
buttons[i].onDown(); // Run callback: press has begun (possible hold behavior)
|
||||
buttons[i].state = State::POLLING_UNFIRED; // Mark that button-down has been handled
|
||||
awaitingRelease = true; // Mark that polling-for-release should continue
|
||||
break;
|
||||
|
||||
// An existing press continues
|
||||
// Not held long enough to register as longpress
|
||||
case POLLING_UNFIRED: {
|
||||
uint32_t length = millis() - buttons[i].irqAtMillis;
|
||||
// An existing press continues
|
||||
// Not held long enough to register as longpress
|
||||
case POLLING_UNFIRED: {
|
||||
uint32_t length = millis() - buttons[i].irqAtMillis;
|
||||
|
||||
// If button released since last thread tick,
|
||||
if (digitalRead(buttons[i].pin) != buttons[i].activeLogic) {
|
||||
buttons[i].onUp(); // Run callback: press has ended (possible release of a hold)
|
||||
buttons[i].state = State::REST; // Mark that the button has reset
|
||||
if (length > buttons[i].debounceLength && length < buttons[i].longpressLength) // If too short for longpress,
|
||||
buttons[i].onPress(); // Run callback: press
|
||||
}
|
||||
// If button not yet released
|
||||
else {
|
||||
awaitingRelease = true; // Mark that polling-for-release should continue
|
||||
if (length >= buttons[i].longpressLength) {
|
||||
// Run callback: long press (once)
|
||||
// Then continue waiting for release, to rearm
|
||||
buttons[i].state = State::POLLING_FIRED;
|
||||
buttons[i].onLongPress();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Button still held, but duration long enough that longpress event already fired
|
||||
// Just waiting for release
|
||||
case POLLING_FIRED:
|
||||
// Release detected
|
||||
if (digitalRead(buttons[i].pin) != buttons[i].activeLogic) {
|
||||
buttons[i].state = State::REST;
|
||||
buttons[i].onUp(); // Callback: release of hold (in this case: *after* longpress has fired)
|
||||
}
|
||||
// Not yet released, keep polling
|
||||
else
|
||||
awaitingRelease = true;
|
||||
break;
|
||||
// If button released since last thread tick,
|
||||
if (digitalRead(buttons[i].pin) != buttons[i].activeLogic) {
|
||||
buttons[i].onUp(); // Run callback: press has ended (possible release of a hold)
|
||||
buttons[i].state = State::REST; // Mark that the button has reset
|
||||
if (length > buttons[i].debounceLength && length < buttons[i].longpressLength) // If too short for longpress,
|
||||
buttons[i].onPress(); // Run callback: press
|
||||
}
|
||||
// If button not yet released
|
||||
else {
|
||||
awaitingRelease = true; // Mark that polling-for-release should continue
|
||||
if (length >= buttons[i].longpressLength) {
|
||||
// Run callback: long press (once)
|
||||
// Then continue waiting for release, to rearm
|
||||
buttons[i].state = State::POLLING_FIRED;
|
||||
buttons[i].onLongPress();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Check all the joystick directions
|
||||
for (uint8_t i = 0; i < JOYSTICK_COUNT; i++) {
|
||||
switch (joystick[i].state) {
|
||||
// No action: button has not been pressed
|
||||
case REST:
|
||||
break;
|
||||
// Button still held, but duration long enough that longpress event already fired
|
||||
// Just waiting for release
|
||||
case POLLING_FIRED:
|
||||
// Release detected
|
||||
if (digitalRead(buttons[i].pin) != buttons[i].activeLogic) {
|
||||
buttons[i].state = State::REST;
|
||||
buttons[i].onUp(); // Callback: release of hold (in this case: *after* longpress has fired)
|
||||
}
|
||||
// Not yet released, keep polling
|
||||
else
|
||||
awaitingRelease = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// New press detected by interrupt
|
||||
case IRQ:
|
||||
powerFSM.trigger(EVENT_PRESS); // Tell PowerFSM that press occurred (resets sleep timer)
|
||||
joystick[i].onDown(); // Run callback: press has begun (possible hold behavior)
|
||||
joystick[i].state = State::POLLING_UNFIRED; // Mark that button-down has been handled
|
||||
awaitingRelease = true; // Mark that polling-for-release should continue
|
||||
break;
|
||||
// Check all the joystick directions
|
||||
for (uint8_t i = 0; i < JOYSTICK_COUNT; i++) {
|
||||
switch (joystick[i].state) {
|
||||
// No action: button has not been pressed
|
||||
case REST:
|
||||
break;
|
||||
|
||||
// An existing press continues
|
||||
// Not held long enough to register as press
|
||||
case POLLING_UNFIRED: {
|
||||
uint32_t length = millis() - joystick[i].irqAtMillis;
|
||||
// New press detected by interrupt
|
||||
case IRQ:
|
||||
powerFSM.trigger(EVENT_PRESS); // Tell PowerFSM that press occurred (resets sleep timer)
|
||||
joystick[i].onDown(); // Run callback: press has begun (possible hold behavior)
|
||||
joystick[i].state = State::POLLING_UNFIRED; // Mark that button-down has been handled
|
||||
awaitingRelease = true; // Mark that polling-for-release should continue
|
||||
break;
|
||||
|
||||
// If button released since last thread tick,
|
||||
if (digitalRead(joystick[i].pin) != joystickActiveLogic) {
|
||||
joystick[i].onUp(); // Run callback: press has ended (possible release of a hold)
|
||||
joystick[i].state = State::REST; // Mark that the button has reset
|
||||
}
|
||||
// If button not yet released
|
||||
else {
|
||||
awaitingRelease = true; // Mark that polling-for-release should continue
|
||||
if (length >= joystickDebounceLength) {
|
||||
// Run callback: long press (once)
|
||||
// Then continue waiting for release, to rearm
|
||||
joystick[i].state = State::POLLING_FIRED;
|
||||
joystick[i].onPress();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Button still held after press
|
||||
// Just waiting for release
|
||||
case POLLING_FIRED:
|
||||
// Release detected
|
||||
if (digitalRead(joystick[i].pin) != joystickActiveLogic) {
|
||||
joystick[i].state = State::REST;
|
||||
joystick[i].onUp(); // Callback: release of hold
|
||||
}
|
||||
// Not yet released, keep polling
|
||||
else
|
||||
awaitingRelease = true;
|
||||
break;
|
||||
// An existing press continues
|
||||
// Not held long enough to register as press
|
||||
case POLLING_UNFIRED: {
|
||||
uint32_t length = millis() - joystick[i].irqAtMillis;
|
||||
|
||||
// If button released since last thread tick,
|
||||
if (digitalRead(joystick[i].pin) != joystickActiveLogic) {
|
||||
joystick[i].onUp(); // Run callback: press has ended (possible release of a hold)
|
||||
joystick[i].state = State::REST; // Mark that the button has reset
|
||||
}
|
||||
// If button not yet released
|
||||
else {
|
||||
awaitingRelease = true; // Mark that polling-for-release should continue
|
||||
if (length >= joystickDebounceLength) {
|
||||
// Run callback: long press (once)
|
||||
// Then continue waiting for release, to rearm
|
||||
joystick[i].state = State::POLLING_FIRED;
|
||||
joystick[i].onPress();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// If all buttons are now released
|
||||
// we don't need to waste cpu resources polling
|
||||
// IRQ will restart this thread when we next need it
|
||||
if (!awaitingRelease)
|
||||
stopThread();
|
||||
// Button still held after press
|
||||
// Just waiting for release
|
||||
case POLLING_FIRED:
|
||||
// Release detected
|
||||
if (digitalRead(joystick[i].pin) != joystickActiveLogic) {
|
||||
joystick[i].state = State::REST;
|
||||
joystick[i].onUp(); // Callback: release of hold
|
||||
}
|
||||
// Not yet released, keep polling
|
||||
else
|
||||
awaitingRelease = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Run this method again, or don't..
|
||||
// Use whatever behavior was previously set by stopThread() or startThread()
|
||||
return OSThread::interval;
|
||||
// If all buttons are now released
|
||||
// we don't need to waste cpu resources polling
|
||||
// IRQ will restart this thread when we next need it
|
||||
if (!awaitingRelease)
|
||||
stopThread();
|
||||
|
||||
// Run this method again, or don't..
|
||||
// Use whatever behavior was previously set by stopThread() or startThread()
|
||||
return OSThread::interval;
|
||||
}
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
|
||||
// Detach our class' interrupts before lightsleep
|
||||
// Allows sleep.cpp to configure its own interrupts, which wake the device on user-button press
|
||||
int TwoButtonExtended::beforeLightSleep(void *unused)
|
||||
{
|
||||
stop();
|
||||
return 0; // Indicates success
|
||||
int TwoButtonExtended::beforeLightSleep(void *unused) {
|
||||
stop();
|
||||
return 0; // Indicates success
|
||||
}
|
||||
|
||||
// Reconfigure our interrupts
|
||||
// Our class' interrupts were disconnected during sleep, to allow the user button to wake the device from sleep
|
||||
int TwoButtonExtended::afterLightSleep(esp_sleep_wakeup_cause_t cause)
|
||||
{
|
||||
start();
|
||||
int TwoButtonExtended::afterLightSleep(esp_sleep_wakeup_cause_t cause) {
|
||||
start();
|
||||
|
||||
// Manually trigger the button-down ISR
|
||||
// - during light sleep, our ISR is disabled
|
||||
// - if light sleep ends by button press, pretend our own ISR caught it
|
||||
// - need to manually confirm by reading pin ourselves, to avoid occasional false positives
|
||||
// (false positive only when using internal pullup resistors?)
|
||||
if (cause == ESP_SLEEP_WAKEUP_GPIO && digitalRead(buttons[0].pin) == buttons[0].activeLogic)
|
||||
isrPrimary();
|
||||
// Manually trigger the button-down ISR
|
||||
// - during light sleep, our ISR is disabled
|
||||
// - if light sleep ends by button press, pretend our own ISR caught it
|
||||
// - need to manually confirm by reading pin ourselves, to avoid occasional false positives
|
||||
// (false positive only when using internal pullup resistors?)
|
||||
if (cause == ESP_SLEEP_WAKEUP_GPIO && digitalRead(buttons[0].pin) == buttons[0].activeLogic)
|
||||
isrPrimary();
|
||||
|
||||
return 0; // Indicates success
|
||||
return 0; // Indicates success
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -30,105 +30,100 @@ Interrupt driven
|
||||
|
||||
#include "Observer.h"
|
||||
|
||||
namespace NicheGraphics::Inputs
|
||||
{
|
||||
namespace NicheGraphics::Inputs {
|
||||
|
||||
class TwoButtonExtended : protected concurrency::OSThread
|
||||
{
|
||||
class TwoButtonExtended : protected concurrency::OSThread {
|
||||
public:
|
||||
typedef std::function<void()> Callback;
|
||||
|
||||
static uint8_t getUserButtonPin(); // Resolve the GPIO, considering the various possible source of definition
|
||||
|
||||
static TwoButtonExtended *getInstance(); // Create or get the singleton instance
|
||||
void start(); // Start handling button input
|
||||
void stop(); // Stop handling button input (disconnect ISRs for sleep)
|
||||
void setWiring(uint8_t whichButton, uint8_t pin, bool internalPullup = false);
|
||||
void setJoystickWiring(uint8_t uPin, uint8_t dPin, uint8_t lPin, uint8_t rPin, bool internalPullup = false);
|
||||
void setTiming(uint8_t whichButton, uint32_t debounceMs, uint32_t longpressMs);
|
||||
void setJoystickDebounce(uint32_t debounceMs);
|
||||
void setHandlerDown(uint8_t whichButton, Callback onDown);
|
||||
void setHandlerUp(uint8_t whichButton, Callback onUp);
|
||||
void setHandlerShortPress(uint8_t whichButton, Callback onShortPress);
|
||||
void setHandlerLongPress(uint8_t whichButton, Callback onLongPress);
|
||||
void setJoystickDownHandlers(Callback uDown, Callback dDown, Callback ldown, Callback rDown);
|
||||
void setJoystickUpHandlers(Callback uUp, Callback dUp, Callback lUp, Callback rUp);
|
||||
void setJoystickPressHandlers(Callback uPress, Callback dPress, Callback lPress, Callback rPress);
|
||||
|
||||
// Disconnect and reconnect interrupts for light sleep
|
||||
#ifdef ARCH_ESP32
|
||||
int beforeLightSleep(void *unused);
|
||||
int afterLightSleep(esp_sleep_wakeup_cause_t cause);
|
||||
#endif
|
||||
|
||||
private:
|
||||
// Internal state of a specific button
|
||||
enum State {
|
||||
REST, // Up, no activity
|
||||
IRQ, // Down detected, not yet handled
|
||||
POLLING_UNFIRED, // Down handled, polling for release
|
||||
POLLING_FIRED, // Longpress fired, button still held
|
||||
};
|
||||
|
||||
// Joystick Directions
|
||||
enum Direction { UP = 0, DOWN, LEFT, RIGHT };
|
||||
|
||||
// Data used for direction (single-action) buttons
|
||||
class SimpleButton {
|
||||
public:
|
||||
typedef std::function<void()> Callback;
|
||||
// Per-button config
|
||||
uint8_t pin = 0xFF; // 0xFF: unset
|
||||
volatile State state = State::REST; // Internal state
|
||||
volatile uint32_t irqAtMillis; // millis() when button went down
|
||||
|
||||
static uint8_t getUserButtonPin(); // Resolve the GPIO, considering the various possible source of definition
|
||||
// Per-button event callbacks
|
||||
static void noop(){};
|
||||
std::function<void()> onDown = noop;
|
||||
std::function<void()> onUp = noop;
|
||||
std::function<void()> onPress = noop;
|
||||
};
|
||||
|
||||
static TwoButtonExtended *getInstance(); // Create or get the singleton instance
|
||||
void start(); // Start handling button input
|
||||
void stop(); // Stop handling button input (disconnect ISRs for sleep)
|
||||
void setWiring(uint8_t whichButton, uint8_t pin, bool internalPullup = false);
|
||||
void setJoystickWiring(uint8_t uPin, uint8_t dPin, uint8_t lPin, uint8_t rPin, bool internalPullup = false);
|
||||
void setTiming(uint8_t whichButton, uint32_t debounceMs, uint32_t longpressMs);
|
||||
void setJoystickDebounce(uint32_t debounceMs);
|
||||
void setHandlerDown(uint8_t whichButton, Callback onDown);
|
||||
void setHandlerUp(uint8_t whichButton, Callback onUp);
|
||||
void setHandlerShortPress(uint8_t whichButton, Callback onShortPress);
|
||||
void setHandlerLongPress(uint8_t whichButton, Callback onLongPress);
|
||||
void setJoystickDownHandlers(Callback uDown, Callback dDown, Callback ldown, Callback rDown);
|
||||
void setJoystickUpHandlers(Callback uUp, Callback dUp, Callback lUp, Callback rUp);
|
||||
void setJoystickPressHandlers(Callback uPress, Callback dPress, Callback lPress, Callback rPress);
|
||||
// Data used for double-action buttons
|
||||
class Button : public SimpleButton {
|
||||
public:
|
||||
// Per-button extended config
|
||||
bool activeLogic = LOW; // Active LOW by default.
|
||||
uint32_t debounceLength = 50; // Minimum length for shortpress in ms
|
||||
uint32_t longpressLength = 500; // Time until longpress in ms
|
||||
|
||||
// Disconnect and reconnect interrupts for light sleep
|
||||
#ifdef ARCH_ESP32
|
||||
int beforeLightSleep(void *unused);
|
||||
int afterLightSleep(esp_sleep_wakeup_cause_t cause);
|
||||
#endif
|
||||
|
||||
private:
|
||||
// Internal state of a specific button
|
||||
enum State {
|
||||
REST, // Up, no activity
|
||||
IRQ, // Down detected, not yet handled
|
||||
POLLING_UNFIRED, // Down handled, polling for release
|
||||
POLLING_FIRED, // Longpress fired, button still held
|
||||
};
|
||||
|
||||
// Joystick Directions
|
||||
enum Direction { UP = 0, DOWN, LEFT, RIGHT };
|
||||
|
||||
// Data used for direction (single-action) buttons
|
||||
class SimpleButton
|
||||
{
|
||||
public:
|
||||
// Per-button config
|
||||
uint8_t pin = 0xFF; // 0xFF: unset
|
||||
volatile State state = State::REST; // Internal state
|
||||
volatile uint32_t irqAtMillis; // millis() when button went down
|
||||
|
||||
// Per-button event callbacks
|
||||
static void noop(){};
|
||||
std::function<void()> onDown = noop;
|
||||
std::function<void()> onUp = noop;
|
||||
std::function<void()> onPress = noop;
|
||||
};
|
||||
|
||||
// Data used for double-action buttons
|
||||
class Button : public SimpleButton
|
||||
{
|
||||
public:
|
||||
// Per-button extended config
|
||||
bool activeLogic = LOW; // Active LOW by default.
|
||||
uint32_t debounceLength = 50; // Minimum length for shortpress in ms
|
||||
uint32_t longpressLength = 500; // Time until longpress in ms
|
||||
|
||||
// Per-button event callbacks
|
||||
std::function<void()> onLongPress = noop;
|
||||
};
|
||||
// Per-button event callbacks
|
||||
std::function<void()> onLongPress = noop;
|
||||
};
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
// Get notified when lightsleep begins and ends
|
||||
CallbackObserver<TwoButtonExtended, void *> lsObserver =
|
||||
CallbackObserver<TwoButtonExtended, void *>(this, &TwoButtonExtended::beforeLightSleep);
|
||||
CallbackObserver<TwoButtonExtended, esp_sleep_wakeup_cause_t> lsEndObserver =
|
||||
CallbackObserver<TwoButtonExtended, esp_sleep_wakeup_cause_t>(this, &TwoButtonExtended::afterLightSleep);
|
||||
// Get notified when lightsleep begins and ends
|
||||
CallbackObserver<TwoButtonExtended, void *> lsObserver = CallbackObserver<TwoButtonExtended, void *>(this, &TwoButtonExtended::beforeLightSleep);
|
||||
CallbackObserver<TwoButtonExtended, esp_sleep_wakeup_cause_t> lsEndObserver =
|
||||
CallbackObserver<TwoButtonExtended, esp_sleep_wakeup_cause_t>(this, &TwoButtonExtended::afterLightSleep);
|
||||
#endif
|
||||
|
||||
int32_t runOnce() override; // Timer method. Polls for button release
|
||||
int32_t runOnce() override; // Timer method. Polls for button release
|
||||
|
||||
void startThread(); // Start polling for release
|
||||
void stopThread(); // Stop polling for release
|
||||
void startThread(); // Start polling for release
|
||||
void stopThread(); // Stop polling for release
|
||||
|
||||
static void isrPrimary(); // User Button ISR
|
||||
static void isrSecondary(); // optional aux button or joystick center
|
||||
static void isrJoystickUp();
|
||||
static void isrJoystickDown();
|
||||
static void isrJoystickLeft();
|
||||
static void isrJoystickRight();
|
||||
static void isrPrimary(); // User Button ISR
|
||||
static void isrSecondary(); // optional aux button or joystick center
|
||||
static void isrJoystickUp();
|
||||
static void isrJoystickDown();
|
||||
static void isrJoystickLeft();
|
||||
static void isrJoystickRight();
|
||||
|
||||
TwoButtonExtended(); // Constructor made private: force use of Button::instance()
|
||||
TwoButtonExtended(); // Constructor made private: force use of Button::instance()
|
||||
|
||||
// Info about both buttons
|
||||
Button buttons[2];
|
||||
bool joystickActiveLogic = LOW; // Active LOW by default
|
||||
uint32_t joystickDebounceLength = 50; // time until press in ms
|
||||
SimpleButton joystick[4];
|
||||
// Info about both buttons
|
||||
Button buttons[2];
|
||||
bool joystickActiveLogic = LOW; // Active LOW by default
|
||||
uint32_t joystickDebounceLength = 50; // time until press in ms
|
||||
SimpleButton joystick[4];
|
||||
};
|
||||
|
||||
}; // namespace NicheGraphics::Inputs
|
||||
|
||||
Reference in New Issue
Block a user