mirror of
https://github.com/meshtastic/firmware.git
synced 2025-09-06 03:29:17 +00:00
Merge ba11d57c8f
into 8685436cbb
This commit is contained in:
commit
f846a132d7
@ -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
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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,
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user