mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-27 21:20:33 +00:00
* TwoButtonExtened mirrors TwoButton but added joystick functionality * basic ui navigation with a joystick settings->joystick.enabled setting added and SETTINGS_VERSION incremented by one in InkHUD/Persistence.h in seeed_wio_tracker_L1_eink/nicheGraphics.h enable joystick and disable "Next Tile" menu item in implement prevTile and prevApplet functions in InkHUD/WindowManager.h,cpp and InkHUD/InkHUD.h,cpp onStickCenterShort, onStickCenterLong, onStickUp, onStickDown, onStickLeft, and onStickRight functions added to: - InkHUD/InkHUD.h,cpp - InkHUD/Events.h,cpp - InkHUD/Applet.h change navigation actions in InkHUD/Events.cpp events based on whether the joystick is enabled or not in seeed_wio_tracker_L1_eink/nicheGraphics.h connect joystick events to the new joystick handler functions * handle joystick input in NotificationApplet and TipsApplet Both the joystick center short press and the user button short press can be used to advance through the Tips applet. dismiss notifications with any joystick input * MenuApplet controls allows menu navigation including a back button * add AlignStickApplet for aligning the joystick with the screen add joystick.aligned and joystick.alignment to InkHUD/Persistence.h for storing alignment status and relative angle create AlignStick applet that prompts the user for a joystick input and rotates the controls to align with the screen AlignStick applet is run after the tips applet if the joystick is enabled and not aligned add menu item for opening the AlignStick applet * update tips applet with joystick controls * format InkHUD additions * fix stroke consistency when resizing joystick graphic * tweak button tips for order consistency * increase joystick debounce * fix comments * remove unnecessary '+' * remap joystick controls to match standard inkHUD behavior Input with a joystick now behaves as follows User Button (joystick center): - short press in applet -> opens menu - long press in applet -> opens menu - short press in menu -> selects - long press in menu -> selects Exit Button: - short press in applet -> switches tile - long press in applet -> nothing for now - short press in menu -> closes menu - long press in menu -> nothing for now --------- Co-authored-by: scobert <scobert57@gmail.com> Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
524 lines
19 KiB
C++
524 lines
19 KiB
C++
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
|
|
|
#include "./TwoButtonExtended.h"
|
|
|
|
#include "NodeDB.h" // For the helper function TwoButtonExtended::getUserButtonPin
|
|
#include "PowerFSM.h"
|
|
#include "sleep.h"
|
|
|
|
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();
|
|
|
|
#ifdef ARCH_ESP32
|
|
// 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();
|
|
}
|
|
|
|
// 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;
|
|
|
|
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);
|
|
|
|
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::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::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);
|
|
|
|
if (buttons[1].pin != 0xFF)
|
|
detachInterrupt(buttons[1].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::LEFT].pin != 0xFF)
|
|
detachInterrupt(joystick[Direction::LEFT].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
|
|
|
|
// Use default pin for variant, if no better source
|
|
#ifdef BUTTON_PIN
|
|
pin = BUTTON_PIN;
|
|
#endif
|
|
|
|
// From userPrefs.jsonc, if set
|
|
#ifdef 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;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
assert(whichButton < 2);
|
|
buttons[whichButton].pin = pin;
|
|
buttons[whichButton].activeLogic = LOW;
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Handle the start of a press to the primary button
|
|
// Wakes our button thread
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Handle the start of a press to the secondary button
|
|
// Wakes our button thread
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Handle the start of a press to the joystick buttons
|
|
// Also wakes our button thread
|
|
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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
|
|
// 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);
|
|
|
|
// 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;
|
|
|
|
// 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;
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
|
|
// 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;
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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();
|
|
|
|
// 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
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif
|