This commit is contained in:
Wilson 2025-08-28 07:00:16 +00:00 committed by GitHub
commit f846a132d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 236 additions and 55 deletions

View File

@ -33,7 +33,9 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
break; break;
case INPUT_BROKER_UP: case INPUT_BROKER_UP:
case INPUT_BROKER_UP_LONG:
case INPUT_BROKER_DOWN: case INPUT_BROKER_DOWN:
case INPUT_BROKER_DOWN_LONG:
case INPUT_BROKER_LEFT: case INPUT_BROKER_LEFT:
case INPUT_BROKER_RIGHT: case INPUT_BROKER_RIGHT:
playChirp(); // Navigation feedback playChirp(); // Navigation feedback

View File

@ -7,10 +7,18 @@
#include "graphics/ScreenFonts.h" #include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h" #include "graphics/SharedUIDisplay.h"
#include "graphics/images.h" #include "graphics/images.h"
#include "input/RotaryEncoderInterruptImpl1.h"
#include "input/UpDownInterruptImpl1.h"
#if HAS_BUTTON
#include "input/ButtonThread.h"
#endif
#include "main.h" #include "main.h"
#include <algorithm> #include <algorithm>
#include <string> #include <string>
#include <vector> #include <vector>
#if HAS_TRACKBALL
#include "input/TrackballInterruptImpl1.h"
#endif
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
#include "esp_task_wdt.h" #include "esp_task_wdt.h"
@ -18,6 +26,11 @@
using namespace meshtastic; using namespace meshtastic;
#if HAS_BUTTON
// Global button thread pointer defined in main.cpp
extern ::ButtonThread *UserButtonThread;
#endif
// External references to global variables from Screen.cpp // External references to global variables from Screen.cpp
extern std::vector<std::string> functionSymbol; extern std::vector<std::string> functionSymbol;
extern std::string functionSymbolString; extern std::string functionSymbolString;
@ -288,12 +301,9 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
if (nodeDB->getMeshNodeByIndex(i + 1)->has_user) { if (nodeDB->getMeshNodeByIndex(i + 1)->has_user) {
std::string sanitized = sanitizeString(nodeDB->getMeshNodeByIndex(i + 1)->user.long_name); std::string sanitized = sanitizeString(nodeDB->getMeshNodeByIndex(i + 1)->user.long_name);
strncpy(temp_name, sanitized.c_str(), sizeof(temp_name) - 1); strncpy(temp_name, sanitized.c_str(), sizeof(temp_name) - 1);
} else { } else {
snprintf(temp_name, sizeof(temp_name), "(%04X)", (uint16_t)(nodeDB->getMeshNodeByIndex(i + 1)->num & 0xFFFF)); snprintf(temp_name, sizeof(temp_name), "(%04X)", (uint16_t)(nodeDB->getMeshNodeByIndex(i + 1)->num & 0xFFFF));
} }
// make temp buffer for name
// fi
if (i == curSelected) { if (i == curSelected) {
selectedNodenum = nodeDB->getMeshNodeByIndex(i + 1)->num; selectedNodenum = nodeDB->getMeshNodeByIndex(i + 1)->num;
if (isHighResolution) { if (isHighResolution) {
@ -307,7 +317,8 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
} }
scratchLineBuffer[scratchLineNum][39] = '\0'; scratchLineBuffer[scratchLineNum][39] = '\0';
} else { } else {
strncpy(scratchLineBuffer[scratchLineNum], temp_name, 36); strncpy(scratchLineBuffer[scratchLineNum], temp_name, 39);
scratchLineBuffer[scratchLineNum][39] = '\0';
} }
linePointers[linesShown] = scratchLineBuffer[scratchLineNum++]; linePointers[linesShown] = scratchLineBuffer[scratchLineNum++];
} }
@ -623,59 +634,68 @@ void NotificationRenderer::drawTextInput(OLEDDisplay *display, OLEDDisplayUiStat
return; return;
} }
// Handle input events for virtual keyboard navigation
if (inEvent.inputEvent != INPUT_BROKER_NONE) { if (inEvent.inputEvent != INPUT_BROKER_NONE) {
if (inEvent.inputEvent == INPUT_BROKER_UP) { if (inEvent.inputEvent == INPUT_BROKER_UP) {
virtualKeyboard->moveCursorUp(); // high frequency for move cursor left/right than up/down with encoders
extern ::RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1;
extern ::UpDownInterruptImpl1 *upDownInterruptImpl1;
if (::rotaryEncoderInterruptImpl1 || ::upDownInterruptImpl1) {
virtualKeyboard->moveCursorLeft();
} else {
virtualKeyboard->moveCursorUp();
}
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN) { } else if (inEvent.inputEvent == INPUT_BROKER_DOWN) {
virtualKeyboard->moveCursorDown(); extern ::RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1;
extern ::UpDownInterruptImpl1 *upDownInterruptImpl1;
if (::rotaryEncoderInterruptImpl1 || ::upDownInterruptImpl1) {
virtualKeyboard->moveCursorRight();
} else {
virtualKeyboard->moveCursorDown();
}
} else if (inEvent.inputEvent == INPUT_BROKER_LEFT) { } else if (inEvent.inputEvent == INPUT_BROKER_LEFT) {
virtualKeyboard->moveCursorLeft(); virtualKeyboard->moveCursorLeft();
} else if (inEvent.inputEvent == INPUT_BROKER_RIGHT) { } else if (inEvent.inputEvent == INPUT_BROKER_RIGHT) {
virtualKeyboard->moveCursorRight(); virtualKeyboard->moveCursorRight();
} else if (inEvent.inputEvent == INPUT_BROKER_UP_LONG) {
virtualKeyboard->moveCursorUp();
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN_LONG) {
virtualKeyboard->moveCursorDown();
} else if (inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) { } else if (inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
// Long press UP = move left
virtualKeyboard->moveCursorLeft(); virtualKeyboard->moveCursorLeft();
} else if (inEvent.inputEvent == INPUT_BROKER_USER_PRESS) { } else if (inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
// Long press DOWN = move right
virtualKeyboard->moveCursorRight(); virtualKeyboard->moveCursorRight();
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT) { } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
virtualKeyboard->handlePress(); virtualKeyboard->handlePress();
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT_LONG) { } else if (inEvent.inputEvent == INPUT_BROKER_SELECT_LONG) {
virtualKeyboard->handleLongPress(); virtualKeyboard->handleLongPress();
} else if (inEvent.inputEvent == INPUT_BROKER_CANCEL) { } else if (inEvent.inputEvent == INPUT_BROKER_CANCEL) {
// Cancel virtual keyboard - call callback with empty string auto callback = textInputCallback;
auto callback = textInputCallback; // Store callback before clearing
// Clean up first to prevent re-entry
delete virtualKeyboard; delete virtualKeyboard;
virtualKeyboard = nullptr; virtualKeyboard = nullptr;
textInputCallback = nullptr; textInputCallback = nullptr;
resetBanner(); resetBanner();
// Call callback after cleanup
if (callback) { if (callback) {
callback(""); callback("");
} }
// Restore normal overlays
if (screen) { if (screen) {
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
} }
return; return;
} }
// Reset input event after processing // Consume the event after processing for virtual keyboard
inEvent.inputEvent = INPUT_BROKER_NONE; inEvent.inputEvent = INPUT_BROKER_NONE;
} }
// Clear the display and draw virtual keyboard // Clear the screen to avoid overlapping with underlying frames or overlays
display->setColor(BLACK); display->setColor(BLACK);
display->fillRect(0, 0, display->getWidth(), display->getHeight()); display->fillRect(0, 0, display->getWidth(), display->getHeight());
display->setColor(WHITE); display->setColor(WHITE);
// Draw the virtual keyboard
virtualKeyboard->draw(display, 0, 0); virtualKeyboard->draw(display, 0, 0);
} else { } else {
// If virtualKeyboard is null, reset the banner to avoid getting stuck // If virtualKeyboard is null, reset the banner to avoid getting stuck
LOG_INFO("Virtual keyboard is null - resetting banner");
resetBanner(); resetBanner();
} }
} }

View File

@ -76,6 +76,9 @@ class ButtonThread : public Observable<const InputEvent *>, public concurrency::
return digitalRead(buttonPin); // Most buttons are active low by default return digitalRead(buttonPin); // Most buttons are active low by default
} }
// Returns true while this thread's button is physically held down
bool isHeld() { return isButtonPressed(_pinNum); }
// Disconnect and reconnect interrupts for light sleep // Disconnect and reconnect interrupts for light sleep
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
int beforeLightSleep(void *unused); int beforeLightSleep(void *unused);

View File

@ -4,7 +4,9 @@
enum input_broker_event { enum input_broker_event {
INPUT_BROKER_NONE = 0, INPUT_BROKER_NONE = 0,
INPUT_BROKER_SELECT = 10, INPUT_BROKER_SELECT = 10,
INPUT_BROKER_SELECT_LONG, INPUT_BROKER_SELECT_LONG = 11,
INPUT_BROKER_UP_LONG = 12,
INPUT_BROKER_DOWN_LONG = 13,
INPUT_BROKER_UP = 17, INPUT_BROKER_UP = 17,
INPUT_BROKER_DOWN = 18, INPUT_BROKER_DOWN = 18,
INPUT_BROKER_LEFT = 19, INPUT_BROKER_LEFT = 19,

View File

@ -8,15 +8,17 @@ RotaryEncoderInterruptBase::RotaryEncoderInterruptBase(const char *name) : concu
void RotaryEncoderInterruptBase::init( void RotaryEncoderInterruptBase::init(
uint8_t pinA, uint8_t pinB, uint8_t pinPress, input_broker_event eventCw, input_broker_event eventCcw, uint8_t pinA, uint8_t pinB, uint8_t pinPress, input_broker_event eventCw, input_broker_event eventCcw,
input_broker_event eventPressed, input_broker_event eventPressed, input_broker_event eventPressedLong,
// std::function<void(void)> onIntA, std::function<void(void)> onIntB, std::function<void(void)> onIntPress) : // std::function<void(void)> onIntA, std::function<void(void)> onIntB, std::function<void(void)> onIntPress) :
void (*onIntA)(), void (*onIntB)(), void (*onIntPress)()) void (*onIntA)(), void (*onIntB)(), void (*onIntPress)())
{ {
this->_pinA = pinA; this->_pinA = pinA;
this->_pinB = pinB; this->_pinB = pinB;
this->_pinPress = pinPress;
this->_eventCw = eventCw; this->_eventCw = eventCw;
this->_eventCcw = eventCcw; this->_eventCcw = eventCcw;
this->_eventPressed = eventPressed; this->_eventPressed = eventPressed;
this->_eventPressedLong = eventPressedLong;
bool isRAK = false; bool isRAK = false;
#ifdef RAK_4631 #ifdef RAK_4631
@ -46,10 +48,37 @@ int32_t RotaryEncoderInterruptBase::runOnce()
InputEvent e; InputEvent e;
e.inputEvent = INPUT_BROKER_NONE; e.inputEvent = INPUT_BROKER_NONE;
e.source = this->_originName; e.source = this->_originName;
unsigned long now = millis();
// Handle press long/short detection
if (this->action == ROTARY_ACTION_PRESSED) { if (this->action == ROTARY_ACTION_PRESSED) {
LOG_DEBUG("Rotary event Press"); bool buttonPressed = !digitalRead(_pinPress);
e.inputEvent = this->_eventPressed; if (!pressDetected && buttonPressed) {
pressDetected = true;
pressStartTime = now;
}
if (pressDetected) {
uint32_t duration = now - pressStartTime;
if (!buttonPressed) {
// released -> if short press, send short, else already sent long
if (duration < LONG_PRESS_DURATION && now - lastPressKeyTime >= pressDebounceMs) {
lastPressKeyTime = now;
LOG_DEBUG("Rotary event Press short");
e.inputEvent = this->_eventPressed;
}
pressDetected = false;
pressStartTime = 0;
lastPressLongEventTime = 0;
this->action = ROTARY_ACTION_NONE;
} else if (duration >= LONG_PRESS_DURATION && this->_eventPressedLong != INPUT_BROKER_NONE &&
lastPressLongEventTime == 0) {
// fire single-shot long press
lastPressLongEventTime = now;
LOG_DEBUG("Rotary event Press long");
e.inputEvent = this->_eventPressedLong;
}
}
} else if (this->action == ROTARY_ACTION_CW) { } else if (this->action == ROTARY_ACTION_CW) {
LOG_DEBUG("Rotary event CW"); LOG_DEBUG("Rotary event CW");
e.inputEvent = this->_eventCw; e.inputEvent = this->_eventCw;
@ -62,7 +91,9 @@ int32_t RotaryEncoderInterruptBase::runOnce()
this->notifyObservers(&e); this->notifyObservers(&e);
} }
this->action = ROTARY_ACTION_NONE; if (!pressDetected) {
this->action = ROTARY_ACTION_NONE;
}
return INT32_MAX; return INT32_MAX;
} }
@ -70,7 +101,7 @@ int32_t RotaryEncoderInterruptBase::runOnce()
void RotaryEncoderInterruptBase::intPressHandler() void RotaryEncoderInterruptBase::intPressHandler()
{ {
this->action = ROTARY_ACTION_PRESSED; this->action = ROTARY_ACTION_PRESSED;
setIntervalFromNow(20); // TODO: this modifies a non-volatile variable! setIntervalFromNow(20); // start checking for long/short
} }
void RotaryEncoderInterruptBase::intAHandler() void RotaryEncoderInterruptBase::intAHandler()

View File

@ -13,7 +13,7 @@ class RotaryEncoderInterruptBase : public Observable<const InputEvent *>, public
public: public:
explicit RotaryEncoderInterruptBase(const char *name); explicit RotaryEncoderInterruptBase(const char *name);
void init(uint8_t pinA, uint8_t pinB, uint8_t pinPress, input_broker_event eventCw, input_broker_event eventCcw, void init(uint8_t pinA, uint8_t pinB, uint8_t pinPress, input_broker_event eventCw, input_broker_event eventCcw,
input_broker_event eventPressed, input_broker_event eventPressed, input_broker_event eventPressedLong,
// std::function<void(void)> onIntA, std::function<void(void)> onIntB, std::function<void(void)> onIntPress); // std::function<void(void)> onIntA, std::function<void(void)> onIntB, std::function<void(void)> onIntPress);
void (*onIntA)(), void (*onIntB)(), void (*onIntPress)()); void (*onIntA)(), void (*onIntB)(), void (*onIntPress)());
void intPressHandler(); void intPressHandler();
@ -33,10 +33,22 @@ class RotaryEncoderInterruptBase : public Observable<const InputEvent *>, public
volatile RotaryEncoderInterruptBaseActionType action = ROTARY_ACTION_NONE; volatile RotaryEncoderInterruptBaseActionType action = ROTARY_ACTION_NONE;
private: private:
// pins and events
uint8_t _pinA = 0; uint8_t _pinA = 0;
uint8_t _pinB = 0; uint8_t _pinB = 0;
uint8_t _pinPress = 0;
input_broker_event _eventCw = INPUT_BROKER_NONE; input_broker_event _eventCw = INPUT_BROKER_NONE;
input_broker_event _eventCcw = INPUT_BROKER_NONE; input_broker_event _eventCcw = INPUT_BROKER_NONE;
input_broker_event _eventPressed = INPUT_BROKER_NONE; input_broker_event _eventPressed = INPUT_BROKER_NONE;
input_broker_event _eventPressedLong = INPUT_BROKER_NONE;
const char *_originName; const char *_originName;
// Long press detection variables
uint32_t pressStartTime = 0;
bool pressDetected = false;
uint32_t lastPressLongEventTime = 0;
unsigned long lastPressKeyTime = 0;
static const uint32_t LONG_PRESS_DURATION = 300; // ms
static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 0; // 0 = single-shot for rotary select
const unsigned long pressDebounceMs = 200; // ms
}; };

View File

@ -1,5 +1,6 @@
#include "RotaryEncoderInterruptImpl1.h" #include "RotaryEncoderInterruptImpl1.h"
#include "InputBroker.h" #include "InputBroker.h"
extern bool osk_found;
RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1; RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1;
@ -19,12 +20,14 @@ bool RotaryEncoderInterruptImpl1::init()
input_broker_event eventCw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_cw); input_broker_event eventCw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_cw);
input_broker_event eventCcw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_ccw); input_broker_event eventCcw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_ccw);
input_broker_event eventPressed = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_press); input_broker_event eventPressed = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_press);
input_broker_event eventPressedLong = INPUT_BROKER_SELECT_LONG;
// moduleConfig.canned_message.ext_notification_module_output // moduleConfig.canned_message.ext_notification_module_output
RotaryEncoderInterruptBase::init(pinA, pinB, pinPress, eventCw, eventCcw, eventPressed, RotaryEncoderInterruptBase::init(pinA, pinB, pinPress, eventCw, eventCcw, eventPressed, eventPressedLong,
RotaryEncoderInterruptImpl1::handleIntA, RotaryEncoderInterruptImpl1::handleIntB, RotaryEncoderInterruptImpl1::handleIntA, RotaryEncoderInterruptImpl1::handleIntB,
RotaryEncoderInterruptImpl1::handleIntPressed); RotaryEncoderInterruptImpl1::handleIntPressed);
inputBroker->registerSource(this); inputBroker->registerSource(this);
osk_found = true;
return true; return true;
} }

View File

@ -7,14 +7,22 @@ UpDownInterruptBase::UpDownInterruptBase(const char *name) : concurrency::OSThre
} }
void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, input_broker_event eventDown, void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, input_broker_event eventDown,
input_broker_event eventUp, input_broker_event eventPressed, void (*onIntDown)(), input_broker_event eventUp, input_broker_event eventPressed, input_broker_event eventPressedLong,
input_broker_event eventUpLong, input_broker_event eventDownLong, void (*onIntDown)(),
void (*onIntUp)(), void (*onIntPress)(), unsigned long updownDebounceMs) void (*onIntUp)(), void (*onIntPress)(), unsigned long updownDebounceMs)
{ {
this->_pinDown = pinDown; this->_pinDown = pinDown;
this->_pinUp = pinUp; this->_pinUp = pinUp;
this->_pinPress = pinPress;
this->_eventDown = eventDown; this->_eventDown = eventDown;
this->_eventUp = eventUp; this->_eventUp = eventUp;
this->_eventPressed = eventPressed; this->_eventPressed = eventPressed;
this->_eventPressedLong = eventPressedLong;
this->_eventUpLong = eventUpLong;
this->_eventDownLong = eventDownLong;
// Store debounce configuration passed by caller
this->updownDebounceMs = updownDebounceMs;
bool isRAK = false; bool isRAK = false;
#ifdef RAK_4631 #ifdef RAK_4631
isRAK = true; isRAK = true;
@ -22,20 +30,20 @@ void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress,
if (!isRAK || pinPress != 0) { if (!isRAK || pinPress != 0) {
pinMode(pinPress, INPUT_PULLUP); pinMode(pinPress, INPUT_PULLUP);
attachInterrupt(pinPress, onIntPress, RISING); attachInterrupt(pinPress, onIntPress, FALLING);
} }
if (!isRAK || this->_pinDown != 0) { if (!isRAK || this->_pinDown != 0) {
pinMode(this->_pinDown, INPUT_PULLUP); pinMode(this->_pinDown, INPUT_PULLUP);
attachInterrupt(this->_pinDown, onIntDown, RISING); attachInterrupt(this->_pinDown, onIntDown, FALLING);
} }
if (!isRAK || this->_pinUp != 0) { if (!isRAK || this->_pinUp != 0) {
pinMode(this->_pinUp, INPUT_PULLUP); pinMode(this->_pinUp, INPUT_PULLUP);
attachInterrupt(this->_pinUp, onIntUp, RISING); attachInterrupt(this->_pinUp, onIntUp, FALLING);
} }
LOG_DEBUG("Up/down/press GPIO initialized (%d, %d, %d)", this->_pinUp, this->_pinDown, pinPress); LOG_DEBUG("Up/down/press GPIO initialized (%d, %d, %d)", this->_pinUp, this->_pinDown, pinPress);
this->setInterval(100); this->setInterval(20);
} }
int32_t UpDownInterruptBase::runOnce() int32_t UpDownInterruptBase::runOnce()
@ -43,23 +51,88 @@ int32_t UpDownInterruptBase::runOnce()
InputEvent e; InputEvent e;
e.inputEvent = INPUT_BROKER_NONE; e.inputEvent = INPUT_BROKER_NONE;
unsigned long now = millis(); unsigned long now = millis();
if (this->action == UPDOWN_ACTION_PRESSED) {
if (now - lastPressKeyTime >= pressDebounceMs) { // Read all button states once at the beginning
lastPressKeyTime = now; bool pressButtonPressed = !digitalRead(_pinPress);
LOG_DEBUG("GPIO event Press"); bool upButtonPressed = !digitalRead(_pinUp);
e.inputEvent = this->_eventPressed; bool downButtonPressed = !digitalRead(_pinDown);
// Handle initial button press detection - only if not already detected
if (this->action == UPDOWN_ACTION_PRESSED && pressButtonPressed && !pressDetected) {
pressDetected = true;
pressStartTime = now;
} else if (this->action == UPDOWN_ACTION_UP && upButtonPressed && !upDetected) {
upDetected = true;
upStartTime = now;
} else if (this->action == UPDOWN_ACTION_DOWN && downButtonPressed && !downDetected) {
downDetected = true;
downStartTime = now;
}
// Handle long press detection for press button
if (pressDetected && pressStartTime > 0) {
uint32_t pressDuration = now - pressStartTime;
if (!pressButtonPressed) {
// Button released
if (pressDuration < LONG_PRESS_DURATION && now - lastPressKeyTime >= pressDebounceMs) {
lastPressKeyTime = now;
e.inputEvent = this->_eventPressed;
}
// Reset state
pressDetected = false;
pressStartTime = 0;
lastPressLongEventTime = 0;
} else if (pressDuration >= LONG_PRESS_DURATION && lastPressLongEventTime == 0) {
// First long press event only - avoid repeated events causing lag
e.inputEvent = this->_eventPressedLong;
lastPressLongEventTime = now;
} }
} else if (this->action == UPDOWN_ACTION_UP) { }
if (now - lastUpKeyTime >= updownDebounceMs) {
lastUpKeyTime = now; // Handle long press detection for up button
LOG_DEBUG("GPIO event Up"); if (upDetected && upStartTime > 0) {
e.inputEvent = this->_eventUp; uint32_t upDuration = now - upStartTime;
if (!upButtonPressed) {
// Button released
if (upDuration < LONG_PRESS_DURATION && now - lastUpKeyTime >= updownDebounceMs) {
lastUpKeyTime = now;
e.inputEvent = this->_eventUp;
}
// Reset state
upDetected = false;
upStartTime = 0;
lastUpLongEventTime = 0;
} else if (upDuration >= LONG_PRESS_DURATION) {
// Auto-repeat long press events
if (lastUpLongEventTime == 0 || (now - lastUpLongEventTime) >= LONG_PRESS_REPEAT_INTERVAL) {
e.inputEvent = this->_eventUpLong;
lastUpLongEventTime = now;
}
} }
} else if (this->action == UPDOWN_ACTION_DOWN) { }
if (now - lastDownKeyTime >= updownDebounceMs) {
lastDownKeyTime = now; // Handle long press detection for down button
LOG_DEBUG("GPIO event Down"); if (downDetected && downStartTime > 0) {
e.inputEvent = this->_eventDown; uint32_t downDuration = now - downStartTime;
if (!downButtonPressed) {
// Button released
if (downDuration < LONG_PRESS_DURATION && now - lastDownKeyTime >= updownDebounceMs) {
lastDownKeyTime = now;
e.inputEvent = this->_eventDown;
}
// Reset state
downDetected = false;
downStartTime = 0;
lastDownLongEventTime = 0;
} else if (downDuration >= LONG_PRESS_DURATION) {
// Auto-repeat long press events
if (lastDownLongEventTime == 0 || (now - lastDownLongEventTime) >= LONG_PRESS_REPEAT_INTERVAL) {
e.inputEvent = this->_eventDownLong;
lastDownLongEventTime = now;
}
} }
} }
@ -69,8 +142,11 @@ int32_t UpDownInterruptBase::runOnce()
this->notifyObservers(&e); this->notifyObservers(&e);
} }
this->action = UPDOWN_ACTION_NONE; if (!pressDetected && !upDetected && !downDetected) {
return 100; this->action = UPDOWN_ACTION_NONE;
}
return 20; // This will control how the input frequency
} }
void UpDownInterruptBase::intPressHandler() void UpDownInterruptBase::intPressHandler()

View File

@ -8,7 +8,8 @@ class UpDownInterruptBase : public Observable<const InputEvent *>, public concur
public: public:
explicit UpDownInterruptBase(const char *name); explicit UpDownInterruptBase(const char *name);
void init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, input_broker_event eventDown, input_broker_event eventUp, void init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, input_broker_event eventDown, input_broker_event eventUp,
input_broker_event eventPressed, void (*onIntDown)(), void (*onIntUp)(), void (*onIntPress)(), input_broker_event eventPressed, input_broker_event eventPressedLong, input_broker_event eventUpLong,
input_broker_event eventDownLong, void (*onIntDown)(), void (*onIntUp)(), void (*onIntPress)(),
unsigned long updownDebounceMs = 50); unsigned long updownDebounceMs = 50);
void intPressHandler(); void intPressHandler();
void intDownHandler(); void intDownHandler();
@ -17,16 +18,41 @@ class UpDownInterruptBase : public Observable<const InputEvent *>, public concur
int32_t runOnce() override; int32_t runOnce() override;
protected: protected:
enum UpDownInterruptBaseActionType { UPDOWN_ACTION_NONE, UPDOWN_ACTION_PRESSED, UPDOWN_ACTION_UP, UPDOWN_ACTION_DOWN }; enum UpDownInterruptBaseActionType {
UPDOWN_ACTION_NONE,
UPDOWN_ACTION_PRESSED,
UPDOWN_ACTION_PRESSED_LONG,
UPDOWN_ACTION_UP,
UPDOWN_ACTION_UP_LONG,
UPDOWN_ACTION_DOWN,
UPDOWN_ACTION_DOWN_LONG
};
volatile UpDownInterruptBaseActionType action = UPDOWN_ACTION_NONE; volatile UpDownInterruptBaseActionType action = UPDOWN_ACTION_NONE;
// Long press detection variables
uint32_t pressStartTime = 0;
uint32_t upStartTime = 0;
uint32_t downStartTime = 0;
bool pressDetected = false;
bool upDetected = false;
bool downDetected = false;
uint32_t lastPressLongEventTime = 0;
uint32_t lastUpLongEventTime = 0;
uint32_t lastDownLongEventTime = 0;
static const uint32_t LONG_PRESS_DURATION = 300;
static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 300;
private: private:
uint8_t _pinDown = 0; uint8_t _pinDown = 0;
uint8_t _pinUp = 0; uint8_t _pinUp = 0;
uint8_t _pinPress = 0;
input_broker_event _eventDown = INPUT_BROKER_NONE; input_broker_event _eventDown = INPUT_BROKER_NONE;
input_broker_event _eventUp = INPUT_BROKER_NONE; input_broker_event _eventUp = INPUT_BROKER_NONE;
input_broker_event _eventPressed = INPUT_BROKER_NONE; input_broker_event _eventPressed = INPUT_BROKER_NONE;
input_broker_event _eventPressedLong = INPUT_BROKER_NONE;
input_broker_event _eventUpLong = INPUT_BROKER_NONE;
input_broker_event _eventDownLong = INPUT_BROKER_NONE;
const char *_originName; const char *_originName;
unsigned long lastUpKeyTime = 0; unsigned long lastUpKeyTime = 0;

View File

@ -1,5 +1,6 @@
#include "UpDownInterruptImpl1.h" #include "UpDownInterruptImpl1.h"
#include "InputBroker.h" #include "InputBroker.h"
extern bool osk_found;
UpDownInterruptImpl1 *upDownInterruptImpl1; UpDownInterruptImpl1 *upDownInterruptImpl1;
@ -17,13 +18,18 @@ bool UpDownInterruptImpl1::init()
uint8_t pinDown = moduleConfig.canned_message.inputbroker_pin_b; uint8_t pinDown = moduleConfig.canned_message.inputbroker_pin_b;
uint8_t pinPress = moduleConfig.canned_message.inputbroker_pin_press; uint8_t pinPress = moduleConfig.canned_message.inputbroker_pin_press;
input_broker_event eventDown = INPUT_BROKER_DOWN; input_broker_event eventDown = INPUT_BROKER_USER_PRESS; // acts like RIGHT/DOWN
input_broker_event eventUp = INPUT_BROKER_UP; input_broker_event eventUp = INPUT_BROKER_ALT_PRESS; // acts like LEFT/UP
input_broker_event eventPressed = INPUT_BROKER_SELECT; input_broker_event eventPressed = INPUT_BROKER_SELECT;
input_broker_event eventPressedLong = INPUT_BROKER_SELECT_LONG;
input_broker_event eventUpLong = INPUT_BROKER_UP_LONG;
input_broker_event eventDownLong = INPUT_BROKER_DOWN_LONG;
UpDownInterruptBase::init(pinDown, pinUp, pinPress, eventDown, eventUp, eventPressed, UpDownInterruptImpl1::handleIntDown, UpDownInterruptBase::init(pinDown, pinUp, pinPress, eventDown, eventUp, eventPressed, eventPressedLong, eventUpLong,
UpDownInterruptImpl1::handleIntUp, UpDownInterruptImpl1::handleIntPressed); eventDownLong, UpDownInterruptImpl1::handleIntDown, UpDownInterruptImpl1::handleIntUp,
UpDownInterruptImpl1::handleIntPressed);
inputBroker->registerSource(this); inputBroker->registerSource(this);
osk_found = true;
return true; return true;
} }