From 4816f45552085699e46a7037e0dd9adb2ccf5b44 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Fri, 12 Sep 2025 05:54:10 +0200 Subject: [PATCH 01/18] TCA8418: N-key rollover, simplify code greatly - Pass key parameter to release(), allows tracking multiple held keys - Move KeyState enum and member out of TCA8418KeyboardBase class - Process all FIFO events in trigger(), remove overrides of trigger() - Remove all unused/unrelated code from TDeckPro/TLoraPager Keyboard - TDeckPro/TLoraPager Keyboard track held keys individually, can press multiple keys together and they are registered in order of release, helps greatly with typing fast registering inputs correctly - TDeckPro/TLoraPager Keyboard modifiers persist while held, even if a non-modifier key is pressed, like you would expect from a keyboard - Keep TCA8418Keyboard (multitap) handling one key at a time, wouldn't make much sense to handle multiple held keys on a multitap keyboard - Make event maps const so they do not take up RAM --- src/input/TCA8418Keyboard.cpp | 21 ++-- src/input/TCA8418Keyboard.h | 8 +- src/input/TCA8418KeyboardBase.cpp | 22 +--- src/input/TCA8418KeyboardBase.h | 5 +- src/input/TDeckProKeyboard.cpp | 116 +++++++------------ src/input/TDeckProKeyboard.h | 12 +- src/input/TLoraPagerKeyboard.cpp | 182 ++++++++++++------------------ src/input/TLoraPagerKeyboard.h | 12 +- 8 files changed, 143 insertions(+), 235 deletions(-) diff --git a/src/input/TCA8418Keyboard.cpp b/src/input/TCA8418Keyboard.cpp index bd8338acf..463bd514c 100644 --- a/src/input/TCA8418Keyboard.cpp +++ b/src/input/TCA8418Keyboard.cpp @@ -10,9 +10,9 @@ using Key = TCA8418KeyboardBase::TCA8418Key; // Num chars per key, Modulus for rotating through characters -static uint8_t TCA8418TapMod[_TCA8418_NUM_KEYS] = {13, 7, 7, 7, 7, 7, 9, 7, 9, 2, 2, 2}; +static const uint8_t TCA8418TapMod[_TCA8418_NUM_KEYS] = {13, 7, 7, 7, 7, 7, 9, 7, 9, 2, 2, 2}; -static unsigned char TCA8418TapMap[_TCA8418_NUM_KEYS][13] = { +static const uint8_t TCA8418TapMap[_TCA8418_NUM_KEYS][13] = { {'1', '.', ',', '?', '!', ':', ';', '-', '_', '\\', '/', '(', ')'}, // 1 {'2', 'a', 'b', 'c', 'A', 'B', 'C'}, // 2 {'3', 'd', 'e', 'f', 'D', 'E', 'F'}, // 3 @@ -27,7 +27,7 @@ static unsigned char TCA8418TapMap[_TCA8418_NUM_KEYS][13] = { {'#', '@'}, // # }; -static unsigned char TCA8418LongPressMap[_TCA8418_NUM_KEYS] = { +static const uint8_t TCA8418LongPressMap[_TCA8418_NUM_KEYS] = { Key::ESC, // 1 Key::UP, // 2 Key::NONE, // 3 @@ -43,7 +43,7 @@ static unsigned char TCA8418LongPressMap[_TCA8418_NUM_KEYS] = { }; TCA8418Keyboard::TCA8418Keyboard() - : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), last_key(-1), next_key(-1), last_tap(0L), char_idx(0), tap_interval(0), + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), state(Init), last_key(-1), last_tap(0L), char_idx(0), tap_interval(0), should_backspace(false) { } @@ -56,11 +56,12 @@ void TCA8418Keyboard::reset() writeRegister(TCA8418_REG_GPIO_DIR_3, 0x02); // Switch off keyboard backlight (COL9 = LOW) writeRegister(TCA8418_REG_GPIO_DAT_OUT_3, 0x00); + state = Idle; } void TCA8418Keyboard::pressed(uint8_t key) { - if (state == Init || state == Busy) { + if (state != Idle) { return; } uint8_t next_key = 0; @@ -100,15 +101,21 @@ void TCA8418Keyboard::pressed(uint8_t key) last_tap = now; } -void TCA8418Keyboard::released() +void TCA8418Keyboard::released(uint8_t key) { + // Only handle last pressed key, as this is a multitap keyboard (press same key multiple + // times to cycle what character it produces, eg flip phones) it wouldn't be that useful + // to track multiple keys being held at the same time + (void)key; + if (state != Held) { return; } + state = Idle; + if (last_key < 0 || last_key > _TCA8418_NUM_KEYS) { // reset to idle if last_key out of bounds last_key = -1; - state = Idle; return; } uint32_t now = millis(); diff --git a/src/input/TCA8418Keyboard.h b/src/input/TCA8418Keyboard.h index b76916643..339adc8ef 100644 --- a/src/input/TCA8418Keyboard.h +++ b/src/input/TCA8418Keyboard.h @@ -11,11 +11,13 @@ class TCA8418Keyboard : public TCA8418KeyboardBase void setBacklight(bool on) override; protected: - void pressed(uint8_t key) override; - void released(void) override; + enum KeyState { Init, Idle, Held, Busy }; + void pressed(uint8_t key) override; + void released(uint8_t key) override; + + KeyState state; int8_t last_key; - int8_t next_key; uint32_t last_tap; uint8_t char_idx; int32_t tap_interval; diff --git a/src/input/TCA8418KeyboardBase.cpp b/src/input/TCA8418KeyboardBase.cpp index aafc4c36c..e7dec41f5 100644 --- a/src/input/TCA8418KeyboardBase.cpp +++ b/src/input/TCA8418KeyboardBase.cpp @@ -32,7 +32,7 @@ #define _TCA8418_REG_LCK_EC_KLEC_0 0x01 // Key event count bit 0 TCA8418KeyboardBase::TCA8418KeyboardBase(uint8_t rows, uint8_t columns) - : rows(rows), columns(columns), state(Init), queue(""), m_wire(nullptr), m_addr(0), readCallback(nullptr), + : rows(rows), columns(columns), queue(""), m_wire(nullptr), m_addr(0), readCallback(nullptr), writeCallback(nullptr) { } @@ -82,7 +82,6 @@ void TCA8418KeyboardBase::reset() matrix(rows, columns); enableDebounce(); flush(); - state = Idle; } bool TCA8418KeyboardBase::matrix(uint8_t rows, uint8_t columns) @@ -148,26 +147,15 @@ char TCA8418KeyboardBase::dequeueEvent() void TCA8418KeyboardBase::trigger() { - if (keyCount() == 0) { - return; - } - if (state != Init) { + while (keyCount()) { // Read the key register uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A); uint8_t key = k & 0x7F; if (k & 0x80) { - if (state == Idle) - pressed(key); - return; + pressed(key); } else { - if (state == Held) { - released(); - } - state = Idle; - return; + released(key); } - } else { - reset(); } } @@ -177,7 +165,7 @@ void TCA8418KeyboardBase::pressed(uint8_t key) LOG_ERROR("pressed() not implemented in derived class"); } -void TCA8418KeyboardBase::released() +void TCA8418KeyboardBase::released(uint8_t key) { // must be defined in derived class LOG_ERROR("released() not implemented in derived class"); diff --git a/src/input/TCA8418KeyboardBase.h b/src/input/TCA8418KeyboardBase.h index 5d6c4f7e9..81b8a6b35 100644 --- a/src/input/TCA8418KeyboardBase.h +++ b/src/input/TCA8418KeyboardBase.h @@ -46,8 +46,6 @@ class TCA8418KeyboardBase virtual char dequeueEvent(void); protected: - enum KeyState { Init, Idle, Held, Busy }; - enum TCA8418Register : uint8_t { TCA8418_REG_RESERVED = 0x00, TCA8418_REG_CFG = 0x01, @@ -121,7 +119,7 @@ class TCA8418KeyboardBase }; virtual void pressed(uint8_t key); - virtual void released(void); + virtual void released(uint8_t key); virtual void queueEvent(char); @@ -159,7 +157,6 @@ class TCA8418KeyboardBase protected: uint8_t rows; uint8_t columns; - KeyState state; String queue; private: diff --git a/src/input/TDeckProKeyboard.cpp b/src/input/TDeckProKeyboard.cpp index 098e0804a..d7e006e66 100644 --- a/src/input/TDeckProKeyboard.cpp +++ b/src/input/TDeckProKeyboard.cpp @@ -6,8 +6,6 @@ #define _TCA8418_ROWS 4 #define _TCA8418_NUM_KEYS 35 -#define _TCA8418_MULTI_TAP_THRESHOLD 1500 - using Key = TCA8418KeyboardBase::TCA8418Key; constexpr uint8_t modifierRightShiftKey = 31 - 1; // keynum -1 @@ -20,10 +18,10 @@ constexpr uint8_t modifierAltKey = 30 - 1; constexpr uint8_t modifierAlt = 0b0100; // Num chars per key, Modulus for rotating through characters -static uint8_t TDeckProTapMod[_TCA8418_NUM_KEYS] = {5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5}; +static const uint8_t TDeckProTapMod[_TCA8418_NUM_KEYS] = {5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5}; -static unsigned char TDeckProTapMap[_TCA8418_NUM_KEYS][5] = { +static const uint8_t TDeckProTapMap[_TCA8418_NUM_KEYS][5] = { {'p', 'P', '@', 0x00, Key::SEND_PING}, {'o', 'O', '+'}, {'i', 'I', '-'}, @@ -61,9 +59,10 @@ static unsigned char TDeckProTapMap[_TCA8418_NUM_KEYS][5] = { {0x00, 0x00, 0x00} // R_Shift, sym, space, mic, L_Shift }; +static bool TDeckProHeldMap[_TCA8418_NUM_KEYS] = {}; + TDeckProKeyboard::TDeckProKeyboard() - : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1), - last_tap(0L), char_idx(0), tap_interval(0) + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0) { } @@ -74,91 +73,53 @@ void TDeckProKeyboard::reset() setBacklight(false); } -// handle multi-key presses (shift and alt) -void TDeckProKeyboard::trigger() +int8_t TDeckProKeyboard::keyToIndex(uint8_t key) { - uint8_t count = keyCount(); - if (count == 0) - return; - for (uint8_t i = 0; i < count; ++i) { - uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i); - uint8_t key = k & 0x7F; - if (k & 0x80) { - pressed(key); - } else { - released(); - state = Idle; - } - } -} - -void TDeckProKeyboard::pressed(uint8_t key) -{ - if (state == Init || state == Busy) { - return; - } - if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) { - modifierFlag = 0; - } - - uint8_t next_key = 0; + uint8_t key_index = 0; int row = (key - 1) / 10; int col = (key - 1) % 10; if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) { - return; // Invalid key + return -1; // Invalid key } - next_key = row * _TCA8418_COLS + col; - state = Held; - - uint32_t now = millis(); - tap_interval = now - last_tap; - - updateModifierFlag(next_key); - if (isModifierKey(next_key)) { - last_modifier_time = now; - } - - if (tap_interval < 0) { - last_tap = 0; - state = Busy; - return; - } - - if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) { - char_idx = 0; - } else { - char_idx += 1; - } - - last_key = next_key; - last_tap = now; + key_index = row * _TCA8418_COLS + col; + return key_index; } -void TDeckProKeyboard::released() +void TDeckProKeyboard::pressed(uint8_t key) { - if (state != Held) { + int8_t key_index = keyToIndex(key); + if (key_index < 0) + return; + + if (TDeckProHeldMap[key_index]) { return; } - if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) { - last_key = -1; - state = Idle; + TDeckProHeldMap[key_index] = true; + modifierFlag |= keyToModifierFlag(key_index); +} + +void TDeckProKeyboard::released(uint8_t key) +{ + int8_t key_index = keyToIndex(key); + if (key_index < 0) + return; + + if (!TDeckProHeldMap[key_index]) { return; } - uint32_t now = millis(); - last_tap = now; + TDeckProHeldMap[key_index] = false; + modifierFlag &= ~keyToModifierFlag(key_index); - if (TDeckProTapMap[last_key][modifierFlag % TDeckProTapMod[last_key]] == Key::BL_TOGGLE) { + if (TDeckProTapMap[key_index][modifierFlag % TDeckProTapMod[key_index]] == Key::BL_TOGGLE) { toggleBacklight(); return; } - queueEvent(TDeckProTapMap[last_key][modifierFlag % TDeckProTapMod[last_key]]); - if (isModifierKey(last_key) == false) - modifierFlag = 0; + queueEvent(TDeckProTapMap[key_index][modifierFlag % TDeckProTapMod[key_index]]); } void TDeckProKeyboard::setBacklight(bool on) @@ -175,22 +136,23 @@ void TDeckProKeyboard::toggleBacklight(void) digitalWrite(KB_BL_PIN, !digitalRead(KB_BL_PIN)); } -void TDeckProKeyboard::updateModifierFlag(uint8_t key) +uint8_t TDeckProKeyboard::keyToModifierFlag(uint8_t key) { if (key == modifierRightShiftKey) { - modifierFlag ^= modifierRightShift; + return modifierRightShift; } else if (key == modifierLeftShiftKey) { - modifierFlag ^= modifierLeftShift; + return modifierLeftShift; } else if (key == modifierSymKey) { - modifierFlag ^= modifierSym; + return modifierSym; } else if (key == modifierAltKey) { - modifierFlag ^= modifierAlt; + return modifierAlt; } + return 0; } bool TDeckProKeyboard::isModifierKey(uint8_t key) { - return (key == modifierRightShiftKey || key == modifierLeftShiftKey || key == modifierAltKey || key == modifierSymKey); + return keyToModifierFlag(key) != 0; } #endif // T_DECK_PRO \ No newline at end of file diff --git a/src/input/TDeckProKeyboard.h b/src/input/TDeckProKeyboard.h index 617f3f20b..2698fd7a9 100644 --- a/src/input/TDeckProKeyboard.h +++ b/src/input/TDeckProKeyboard.h @@ -5,23 +5,17 @@ class TDeckProKeyboard : public TCA8418KeyboardBase public: TDeckProKeyboard(); void reset(void) override; - void trigger(void) override; void setBacklight(bool on) override; protected: void pressed(uint8_t key) override; - void released(void) override; + void released(uint8_t key) override; + int8_t keyToIndex(uint8_t key); - void updateModifierFlag(uint8_t key); + uint8_t keyToModifierFlag(uint8_t key); bool isModifierKey(uint8_t key); void toggleBacklight(void); private: uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed - uint32_t last_modifier_time; // Timestamp of the last modifier key press - int8_t last_key; - int8_t next_key; - uint32_t last_tap; - uint8_t char_idx; - int32_t tap_interval; }; diff --git a/src/input/TLoraPagerKeyboard.cpp b/src/input/TLoraPagerKeyboard.cpp index b3f62a36c..4de09dd5c 100644 --- a/src/input/TLoraPagerKeyboard.cpp +++ b/src/input/TLoraPagerKeyboard.cpp @@ -19,8 +19,6 @@ #define _TCA8418_ROWS 4 #define _TCA8418_NUM_KEYS 31 -#define _TCA8418_MULTI_TAP_THRESHOLD 1500 - using Key = TCA8418KeyboardBase::TCA8418Key; constexpr uint8_t modifierRightShiftKey = 29 - 1; // keynum -1 @@ -29,44 +27,47 @@ constexpr uint8_t modifierSymKey = 21 - 1; constexpr uint8_t modifierSym = 0b0010; // Num chars per key, Modulus for rotating through characters -static uint8_t TLoraPagerTapMod[_TCA8418_NUM_KEYS] = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}; +static const uint8_t TLoraPagerTapMod[_TCA8418_NUM_KEYS] = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}; -static unsigned char TLoraPagerTapMap[_TCA8418_NUM_KEYS][3] = {{'q', 'Q', '1'}, - {'w', 'W', '2'}, - {'e', 'E', '3'}, - {'r', 'R', '4'}, - {'t', 'T', '5'}, - {'y', 'Y', '6'}, - {'u', 'U', '7'}, - {'i', 'I', '8'}, - {'o', 'O', '9'}, - {'p', 'P', '0'}, - {'a', 'A', '*'}, - {'s', 'S', '/'}, - {'d', 'D', '+'}, - {'f', 'F', '-'}, - {'g', 'G', '='}, - {'h', 'H', ':'}, - {'j', 'J', '\''}, - {'k', 'K', '"'}, - {'l', 'L', '@'}, - {Key::SELECT, 0x00, Key::TAB}, - {0x00, 0x00, 0x00}, - {'z', 'Z', '_'}, - {'x', 'X', '$'}, - {'c', 'C', ';'}, - {'v', 'V', '?'}, - {'b', 'B', '!'}, - {'n', 'N', ','}, - {'m', 'M', '.'}, - {0x00, 0x00, 0x00}, - {Key::BSP, 0x00, Key::ESC}, - {' ', 0x00, Key::BL_TOGGLE}}; +static const uint8_t TLoraPagerTapMap[_TCA8418_NUM_KEYS][3] = { + {'q', 'Q', '1'}, + {'w', 'W', '2'}, + {'e', 'E', '3'}, + {'r', 'R', '4'}, + {'t', 'T', '5'}, + {'y', 'Y', '6'}, + {'u', 'U', '7'}, + {'i', 'I', '8'}, + {'o', 'O', '9'}, + {'p', 'P', '0'}, + {'a', 'A', '*'}, + {'s', 'S', '/'}, + {'d', 'D', '+'}, + {'f', 'F', '-'}, + {'g', 'G', '='}, + {'h', 'H', ':'}, + {'j', 'J', '\''}, + {'k', 'K', '"'}, + {'l', 'L', '@'}, + {Key::SELECT, 0x00, Key::TAB}, + {0x00, 0x00, 0x00}, + {'z', 'Z', '_'}, + {'x', 'X', '$'}, + {'c', 'C', ';'}, + {'v', 'V', '?'}, + {'b', 'B', '!'}, + {'n', 'N', ','}, + {'m', 'M', '.'}, + {0x00, 0x00, 0x00}, + {Key::BSP, 0x00, Key::ESC}, + {' ', 0x00, Key::BL_TOGGLE}, +}; + +static bool TLoraPagerHeldMap[_TCA8418_NUM_KEYS] = {}; TLoraPagerKeyboard::TLoraPagerKeyboard() - : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1), - last_tap(0L), char_idx(0), tap_interval(0) + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0) { #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) ledcAttach(KB_BL_PIN, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH); @@ -85,101 +86,63 @@ void TLoraPagerKeyboard::reset(void) setBacklight(false); } -// handle multi-key presses (shift and alt) -void TLoraPagerKeyboard::trigger() -{ - uint8_t count = keyCount(); - if (count == 0) - return; - for (uint8_t i = 0; i < count; ++i) { - uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i); - uint8_t key = k & 0x7F; - if (k & 0x80) { - pressed(key); - } else { - released(); - state = Idle; - } - } -} - void TLoraPagerKeyboard::setBacklight(bool on) { toggleBacklight(!on); } +int8_t TLoraPagerKeyboard::keyToIndex(uint8_t key) +{ + uint8_t key_index = 0; + int row = (key - 1) / 10; + int col = (key - 1) % 10; + + if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) { + return -1; // Invalid key + } + + key_index = row * _TCA8418_COLS + col; + return key_index; +} + void TLoraPagerKeyboard::pressed(uint8_t key) { - if (state == Init || state == Busy) { - return; - } if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_ALL_ENABLED || config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_SYSTEM_ONLY) { hapticFeedback(); } - if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) { - modifierFlag = 0; - } + int8_t key_index = keyToIndex(key); + if (key_index < 0) + return; - uint8_t next_key = 0; - int row = (key - 1) / 10; - int col = (key - 1) % 10; - - if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) { - return; // Invalid key - } - - next_key = row * _TCA8418_COLS + col; - state = Held; - - uint32_t now = millis(); - tap_interval = now - last_tap; - - updateModifierFlag(next_key); - if (isModifierKey(next_key)) { - last_modifier_time = now; - } - - if (tap_interval < 0) { - last_tap = 0; - state = Busy; + if (TLoraPagerHeldMap[key_index]) { return; } - if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) { - char_idx = 0; - } else { - char_idx += 1; - } - - last_key = next_key; - last_tap = now; + TLoraPagerHeldMap[key_index] = true; + modifierFlag |= keyToModifierFlag(key_index); } -void TLoraPagerKeyboard::released() +void TLoraPagerKeyboard::released(uint8_t key) { - if (state != Held) { + int8_t key_index = keyToIndex(key); + if (key_index < 0) + return; + + if (!TLoraPagerHeldMap[key_index]) { return; } - if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) { - last_key = -1; - state = Idle; - return; - } + TLoraPagerHeldMap[key_index] = false; + modifierFlag &= ~keyToModifierFlag(key_index); - uint32_t now = millis(); - last_tap = now; - - if (TLoraPagerTapMap[last_key][modifierFlag % TLoraPagerTapMod[last_key]] == Key::BL_TOGGLE) { + if (TLoraPagerTapMap[key_index][modifierFlag % TLoraPagerTapMod[key_index]] == Key::BL_TOGGLE) { toggleBacklight(); return; } - queueEvent(TLoraPagerTapMap[last_key][modifierFlag % TLoraPagerTapMod[last_key]]); - if (isModifierKey(last_key) == false) - modifierFlag = 0; + queueEvent(TLoraPagerTapMap[key_index][modifierFlag % TLoraPagerTapMod[key_index]]); } void TLoraPagerKeyboard::hapticFeedback() @@ -213,18 +176,19 @@ void TLoraPagerKeyboard::toggleBacklight(bool off) #endif } -void TLoraPagerKeyboard::updateModifierFlag(uint8_t key) +uint8_t TLoraPagerKeyboard::keyToModifierFlag(uint8_t key) { if (key == modifierRightShiftKey) { - modifierFlag ^= modifierRightShift; + return modifierRightShift; } else if (key == modifierSymKey) { - modifierFlag ^= modifierSym; + return modifierSym; } + return 0; } bool TLoraPagerKeyboard::isModifierKey(uint8_t key) { - return (key == modifierRightShiftKey || key == modifierSymKey); + return keyToModifierFlag(key) != 0; } #endif \ No newline at end of file diff --git a/src/input/TLoraPagerKeyboard.h b/src/input/TLoraPagerKeyboard.h index 4dabbac64..7e16d05ca 100644 --- a/src/input/TLoraPagerKeyboard.h +++ b/src/input/TLoraPagerKeyboard.h @@ -5,25 +5,19 @@ class TLoraPagerKeyboard : public TCA8418KeyboardBase public: TLoraPagerKeyboard(); void reset(void); - void trigger(void) override; void setBacklight(bool on) override; virtual ~TLoraPagerKeyboard() {} protected: void pressed(uint8_t key) override; - void released(void) override; + void released(uint8_t key) override; + int8_t keyToIndex(uint8_t key); void hapticFeedback(void); - void updateModifierFlag(uint8_t key); + uint8_t keyToModifierFlag(uint8_t key); bool isModifierKey(uint8_t key); void toggleBacklight(bool off = false); private: uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed - uint32_t last_modifier_time; // Timestamp of the last modifier key press - int8_t last_key; - int8_t next_key; - uint32_t last_tap; - uint8_t char_idx; - int32_t tap_interval; }; From 63468df93fa58cbcfc5b3942ddc33fe023150059 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 13 Sep 2025 04:04:40 +0200 Subject: [PATCH 02/18] TCA8418: Interrupt based notify of new key events --- src/input/TCA8418KeyboardBase.cpp | 19 +++++++++++++++++++ src/input/TCA8418KeyboardBase.h | 4 +++- src/input/kbI2cBase.cpp | 19 +++++++++++++++++-- src/input/kbI2cBase.h | 5 ++++- src/input/kbInterrupt.h | 11 +++++++++++ 5 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 src/input/kbInterrupt.h diff --git a/src/input/TCA8418KeyboardBase.cpp b/src/input/TCA8418KeyboardBase.cpp index e7dec41f5..47c4cfbb0 100644 --- a/src/input/TCA8418KeyboardBase.cpp +++ b/src/input/TCA8418KeyboardBase.cpp @@ -43,6 +43,18 @@ void TCA8418KeyboardBase::begin(uint8_t addr, TwoWire *wire) m_wire = wire; m_wire->begin(); reset(); + +#ifdef KB_INT + interruptInstance = this; + auto interruptHandler = []() { + interruptInstance->notifyObservers(interruptInstance); + }; + + ::pinMode(KB_INT, INPUT_PULLUP); + attachInterrupt(KB_INT, interruptHandler, FALLING); + + enableInterrupts(); +#endif } void TCA8418KeyboardBase::begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr) @@ -157,6 +169,11 @@ void TCA8418KeyboardBase::trigger() released(key); } } + +#ifdef KB_INT + // Reset interrupt mask so we can receive future interrupts + writeRegister(TCA8418_REG_INT_STAT, 3); +#endif } void TCA8418KeyboardBase::pressed(uint8_t key) @@ -292,6 +309,8 @@ void TCA8418KeyboardBase::disableInterrupts() writeRegister(TCA8418_REG_CFG, value); }; +TCA8418KeyboardBase* TCA8418KeyboardBase::interruptInstance; + void TCA8418KeyboardBase::enableMatrixOverflow() { uint8_t value = readRegister(TCA8418_REG_CFG); diff --git a/src/input/TCA8418KeyboardBase.h b/src/input/TCA8418KeyboardBase.h index 81b8a6b35..83527f9dc 100644 --- a/src/input/TCA8418KeyboardBase.h +++ b/src/input/TCA8418KeyboardBase.h @@ -1,6 +1,7 @@ // Based on the MPR121 Keyboard and Adafruit TCA8418 library #include "configuration.h" #include +#include "kbInterrupt.h" /** * @brief TCA8418KeyboardBase is the base class for TCA8418 keyboard handling. @@ -8,7 +9,7 @@ * and handling key states. It is designed to be extended for specific keyboard implementations. * It supports both I2C communication and function pointers for custom I2C operations. */ -class TCA8418KeyboardBase +class TCA8418KeyboardBase : public KbInterruptObservable { public: enum TCA8418Key : uint8_t { @@ -142,6 +143,7 @@ class TCA8418KeyboardBase // enable / disable interrupts for matrix and GPI pins void enableInterrupts(); void disableInterrupts(); + static TCA8418KeyboardBase* interruptInstance; // ignore key events when FIFO buffer is full or not. void enableMatrixOverflow(); diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 5db1e39a9..8e5c312a2 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -46,6 +46,7 @@ uint8_t read_from_14004(TwoWire *i2cBus, uint8_t reg, uint8_t *data, uint8_t len int32_t KbI2cBase::runOnce() { + int32_t interval = 300; if (!i2cBus) { switch (cardkb_found.port) { case ScanI2C::WIRE1: @@ -61,6 +62,7 @@ int32_t KbI2cBase::runOnce() } if (cardkb_found.address == TCA8418_KB_ADDR) { TCAKeyboard.begin(TCA8418_KB_ADDR, &Wire1); + observe(&TCAKeyboard); } break; #endif @@ -76,6 +78,7 @@ int32_t KbI2cBase::runOnce() } if (cardkb_found.address == TCA8418_KB_ADDR) { TCAKeyboard.begin(TCA8418_KB_ADDR, &Wire); + observe(&TCAKeyboard); } break; case ScanI2C::NO_I2C: @@ -249,6 +252,7 @@ int32_t KbI2cBase::runOnce() break; } case 0x84: { // Adafruit TCA8418 + interval = 3000; // Less polling, we have interrupts with onNotify() TCAKeyboard.trigger(); InputEvent e; while (TCAKeyboard.hasEvent()) { @@ -331,7 +335,6 @@ int32_t KbI2cBase::runOnce() LOG_DEBUG("TCA8418 Notifying: %i Char: %c", e.inputEvent, e.kbchar); this->notifyObservers(&e); } - TCAKeyboard.trigger(); } break; } @@ -518,5 +521,17 @@ int32_t KbI2cBase::runOnce() default: LOG_WARN("Unknown kb_model 0x%02x", kb_model); } - return 300; + + // If new interrupt triggered while we were processing the previous, reinvoke with 0 interval right away + if (pendingInterruptCount) pendingInterruptCount--; + if (pendingInterruptCount) return 0; + + return interval; +} + +int KbI2cBase::onNotify(KbInterruptObservable* src) +{ + pendingInterruptCount++; + setInterval(0); + return 0; } \ No newline at end of file diff --git a/src/input/kbI2cBase.h b/src/input/kbI2cBase.h index af7602979..556ca13ca 100644 --- a/src/input/kbI2cBase.h +++ b/src/input/kbI2cBase.h @@ -5,16 +5,18 @@ #include "MPR121Keyboard.h" #include "Wire.h" #include "concurrency/OSThread.h" +#include "kbInterrupt.h" class TCA8418KeyboardBase; -class KbI2cBase : public Observable, public concurrency::OSThread +class KbI2cBase : public Observable, public KbInterruptObserver, public concurrency::OSThread { public: explicit KbI2cBase(const char *name); protected: virtual int32_t runOnce() override; + virtual int onNotify(KbInterruptObservable* src) override; private: const char *_originName; @@ -24,5 +26,6 @@ class KbI2cBase : public Observable, public concurrency::OST BBQ10Keyboard Q10keyboard; MPR121Keyboard MPRkeyboard; TCA8418KeyboardBase &TCAKeyboard; + volatile uint8_t pendingInterruptCount = 0; bool is_sym = false; }; \ No newline at end of file diff --git a/src/input/kbInterrupt.h b/src/input/kbInterrupt.h new file mode 100644 index 000000000..dc63ed338 --- /dev/null +++ b/src/input/kbInterrupt.h @@ -0,0 +1,11 @@ +#pragma once + +#include "Observer.h" + +class KbInterruptObservable : public Observable +{ +}; + +class KbInterruptObserver : public Observer +{ +}; \ No newline at end of file From f6d5e1bdb6ff37a250b315088a50f92b191e3b48 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Mon, 15 Sep 2025 03:08:12 +0200 Subject: [PATCH 03/18] T-Lora Pager, T-Deck Pro: One modifier to next key - Restore behavior of single modifier keypress influencing next keypress - Holding modifier and clicking other keys/modifiers still combines them as you would expect from a real keyboard - But for ease of use, can press just one modifier on its own to make it apply to the next keypress --- src/input/TDeckProKeyboard.cpp | 34 +++++++++++++++++++++++++------- src/input/TDeckProKeyboard.h | 3 +++ src/input/TLoraPagerKeyboard.cpp | 34 +++++++++++++++++++++++++------- src/input/TLoraPagerKeyboard.h | 3 +++ 4 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/input/TDeckProKeyboard.cpp b/src/input/TDeckProKeyboard.cpp index d7e006e66..e99af8b8d 100644 --- a/src/input/TDeckProKeyboard.cpp +++ b/src/input/TDeckProKeyboard.cpp @@ -62,7 +62,7 @@ static const uint8_t TDeckProTapMap[_TCA8418_NUM_KEYS][5] = { static bool TDeckProHeldMap[_TCA8418_NUM_KEYS] = {}; TDeckProKeyboard::TDeckProKeyboard() - : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0) + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), pressedKeysCount(0), onlyOneModifierPressed(false), persistedPreviousModifier(false) { } @@ -98,7 +98,15 @@ void TDeckProKeyboard::pressed(uint8_t key) } TDeckProHeldMap[key_index] = true; - modifierFlag |= keyToModifierFlag(key_index); + pressedKeysCount++; + + uint8_t key_modifier = keyToModifierFlag(key_index); + if (key_modifier && pressedKeysCount == 1) { + onlyOneModifierPressed = true; + } else { + onlyOneModifierPressed = false; + } + modifierFlag |= key_modifier; } void TDeckProKeyboard::released(uint8_t key) @@ -111,15 +119,27 @@ void TDeckProKeyboard::released(uint8_t key) return; } - TDeckProHeldMap[key_index] = false; - modifierFlag &= ~keyToModifierFlag(key_index); - if (TDeckProTapMap[key_index][modifierFlag % TDeckProTapMod[key_index]] == Key::BL_TOGGLE) { toggleBacklight(); - return; + } else { + queueEvent(TDeckProTapMap[key_index][modifierFlag % TDeckProTapMod[key_index]]); } - queueEvent(TDeckProTapMap[key_index][modifierFlag % TDeckProTapMod[key_index]]); + TDeckProHeldMap[key_index] = false; + pressedKeysCount--; + + if (onlyOneModifierPressed) { + onlyOneModifierPressed = false; + if (persistedPreviousModifier) { + modifierFlag = 0; + } + persistedPreviousModifier = !persistedPreviousModifier; + } else if (persistedPreviousModifier && pressedKeysCount == 0) { + modifierFlag = 0; + persistedPreviousModifier = false; + } else { + modifierFlag &= ~keyToModifierFlag(key_index); + } } void TDeckProKeyboard::setBacklight(bool on) diff --git a/src/input/TDeckProKeyboard.h b/src/input/TDeckProKeyboard.h index 2698fd7a9..74a5df0a8 100644 --- a/src/input/TDeckProKeyboard.h +++ b/src/input/TDeckProKeyboard.h @@ -18,4 +18,7 @@ class TDeckProKeyboard : public TCA8418KeyboardBase private: uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed + uint8_t pressedKeysCount; + bool onlyOneModifierPressed; + bool persistedPreviousModifier; }; diff --git a/src/input/TLoraPagerKeyboard.cpp b/src/input/TLoraPagerKeyboard.cpp index 4de09dd5c..f1ec14695 100644 --- a/src/input/TLoraPagerKeyboard.cpp +++ b/src/input/TLoraPagerKeyboard.cpp @@ -67,7 +67,7 @@ static const uint8_t TLoraPagerTapMap[_TCA8418_NUM_KEYS][3] = { static bool TLoraPagerHeldMap[_TCA8418_NUM_KEYS] = {}; TLoraPagerKeyboard::TLoraPagerKeyboard() - : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0) + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), pressedKeysCount(0), onlyOneModifierPressed(false), persistedPreviousModifier(false) { #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) ledcAttach(KB_BL_PIN, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH); @@ -121,7 +121,15 @@ void TLoraPagerKeyboard::pressed(uint8_t key) } TLoraPagerHeldMap[key_index] = true; - modifierFlag |= keyToModifierFlag(key_index); + pressedKeysCount++; + + uint8_t key_modifier = keyToModifierFlag(key_index); + if (key_modifier && pressedKeysCount == 1) { + onlyOneModifierPressed = true; + } else { + onlyOneModifierPressed = false; + } + modifierFlag |= key_modifier; } void TLoraPagerKeyboard::released(uint8_t key) @@ -134,15 +142,27 @@ void TLoraPagerKeyboard::released(uint8_t key) return; } - TLoraPagerHeldMap[key_index] = false; - modifierFlag &= ~keyToModifierFlag(key_index); - if (TLoraPagerTapMap[key_index][modifierFlag % TLoraPagerTapMod[key_index]] == Key::BL_TOGGLE) { toggleBacklight(); - return; + } else { + queueEvent(TLoraPagerTapMap[key_index][modifierFlag % TLoraPagerTapMod[key_index]]); } - queueEvent(TLoraPagerTapMap[key_index][modifierFlag % TLoraPagerTapMod[key_index]]); + TLoraPagerHeldMap[key_index] = false; + pressedKeysCount--; + + if (onlyOneModifierPressed) { + onlyOneModifierPressed = false; + if (persistedPreviousModifier) { + modifierFlag = 0; + } + persistedPreviousModifier = !persistedPreviousModifier; + } else if (persistedPreviousModifier && pressedKeysCount == 0) { + modifierFlag = 0; + persistedPreviousModifier = false; + } else { + modifierFlag &= ~keyToModifierFlag(key_index); + } } void TLoraPagerKeyboard::hapticFeedback() diff --git a/src/input/TLoraPagerKeyboard.h b/src/input/TLoraPagerKeyboard.h index 7e16d05ca..6b4001c21 100644 --- a/src/input/TLoraPagerKeyboard.h +++ b/src/input/TLoraPagerKeyboard.h @@ -20,4 +20,7 @@ class TLoraPagerKeyboard : public TCA8418KeyboardBase private: uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed + uint8_t pressedKeysCount; + bool onlyOneModifierPressed; + bool persistedPreviousModifier; }; From a5c98316063a797bea027794f7841d8a92066ad9 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Mon, 15 Sep 2025 18:08:24 +0200 Subject: [PATCH 04/18] Avoid class member name shadowing --- src/input/kbI2cBase.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 8e5c312a2..454224802 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -46,7 +46,7 @@ uint8_t read_from_14004(TwoWire *i2cBus, uint8_t reg, uint8_t *data, uint8_t len int32_t KbI2cBase::runOnce() { - int32_t interval = 300; + int32_t newInterval = 300; if (!i2cBus) { switch (cardkb_found.port) { case ScanI2C::WIRE1: @@ -252,7 +252,7 @@ int32_t KbI2cBase::runOnce() break; } case 0x84: { // Adafruit TCA8418 - interval = 3000; // Less polling, we have interrupts with onNotify() + newInterval = 3000; // Less polling, we have interrupts with onNotify() TCAKeyboard.trigger(); InputEvent e; while (TCAKeyboard.hasEvent()) { @@ -526,7 +526,7 @@ int32_t KbI2cBase::runOnce() if (pendingInterruptCount) pendingInterruptCount--; if (pendingInterruptCount) return 0; - return interval; + return newInterval; } int KbI2cBase::onNotify(KbInterruptObservable* src) From e59a53c5ec1b32accae6d3145f8fb2befe1578db Mon Sep 17 00:00:00 2001 From: WillyJL Date: Fri, 19 Sep 2025 22:06:09 +0200 Subject: [PATCH 05/18] InputPollable: System for polling after interrupts --- src/input/InputBroker.cpp | 46 +++++++++++++++++++++++++++++++++++++-- src/input/InputBroker.h | 22 +++++++++++++++++++ src/main.cpp | 3 +++ 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/input/InputBroker.cpp b/src/input/InputBroker.cpp index ef6d8df91..5ca890b43 100644 --- a/src/input/InputBroker.cpp +++ b/src/input/InputBroker.cpp @@ -3,16 +3,58 @@ InputBroker *inputBroker = nullptr; -InputBroker::InputBroker(){}; +InputBroker::InputBroker() +{ +#ifdef HAS_FREE_RTOS + inputEventQueue = xQueueCreate(5, sizeof(InputEvent)); + pollSoonQueue = xQueueCreate(5, sizeof(InputPollable *)); + xTaskCreate(pollSoonWorker, "input-pollSoon", 2 * 1024, this, 10, &pollSoonTask); +#endif +} void InputBroker::registerSource(Observable *source) { this->inputEventObserver.observe(source); } +#ifdef HAS_FREE_RTOS +void InputBroker::pollSoonRequestFromIsr(InputPollable *pollable) +{ + xQueueSendFromISR(pollSoonQueue, &pollable, NULL); +} + +void InputBroker::queueInputEvent(const InputEvent *event) +{ + xQueueSend(inputEventQueue, event, portMAX_DELAY); +} + +void InputBroker::processInputEventQueue() +{ + InputEvent event; + while (xQueueReceive(inputEventQueue, &event, 0)) { + handleInputEvent(&event); + } +} +#endif + int InputBroker::handleInputEvent(const InputEvent *event) { powerFSM.trigger(EVENT_INPUT); // todo: not every input should wake, like long hold release this->notifyObservers(event); return 0; -} \ No newline at end of file +} + +#ifdef HAS_FREE_RTOS +void InputBroker::pollSoonWorker(void *p) +{ + InputBroker *instance = (InputBroker *)p; + while (true) { + InputPollable *pollable = NULL; + xQueueReceive(instance->pollSoonQueue, &pollable, portMAX_DELAY); + if (pollable) { + pollable->pollOnce(); + } + } + vTaskDelete(NULL); +} +#endif diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h index 2cdfa2ae2..82af184f3 100644 --- a/src/input/InputBroker.h +++ b/src/input/InputBroker.h @@ -1,5 +1,7 @@ #pragma once + #include "Observer.h" +#include "freertosinc.h" enum input_broker_event { INPUT_BROKER_NONE = 0, @@ -41,6 +43,13 @@ typedef struct _InputEvent { uint16_t touchX; uint16_t touchY; } InputEvent; + +class InputPollable +{ + public: + virtual void pollOnce() = 0; +}; + class InputBroker : public Observable { CallbackObserver inputEventObserver = @@ -50,9 +59,22 @@ class InputBroker : public Observable InputBroker(); void registerSource(Observable *source); void injectInputEvent(const InputEvent *event) { handleInputEvent(event); } +#ifdef HAS_FREE_RTOS + void pollSoonRequestFromIsr(InputPollable *pollable); + void queueInputEvent(const InputEvent *event); + void processInputEventQueue(); +#endif protected: int handleInputEvent(const InputEvent *event); + + private: +#ifdef HAS_FREE_RTOS + QueueHandle_t inputEventQueue; + QueueHandle_t pollSoonQueue; + TaskHandle_t pollSoonTask; + static void pollSoonWorker(void *p); +#endif }; extern InputBroker *inputBroker; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 3f84a5e66..bc84b7fc5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1595,6 +1595,9 @@ void loop() #endif service->loop(); +#if !MESHTASTIC_EXCLUDE_INPUTBROKER + inputBroker->processInputEventQueue(); +#endif #if defined(LGFX_SDL) if (screen) { auto dispdev = screen->getDisplayDevice(); From 15a22ee50d213c26d73454fdc1ad611344047828 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Fri, 19 Sep 2025 22:06:22 +0200 Subject: [PATCH 06/18] KbI2cBase: Use InputPollable for responsiveness --- src/input/kbI2cBase.cpp | 201 +++++++++++++++++++++------------------- src/input/kbI2cBase.h | 9 +- 2 files changed, 110 insertions(+), 100 deletions(-) diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index d8c341049..951bcd2bd 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -44,9 +44,104 @@ uint8_t read_from_14004(TwoWire *i2cBus, uint8_t reg, uint8_t *data, uint8_t len return readflag; } +void KbI2cBase::pollOnce() +{ + // Called right after an interrupt by InputBroker + switch (kb_model) { + case 0x84: { // Adafruit TCA8418 + TCAKeyboard.trigger(); + InputEvent e = {}; + while (TCAKeyboard.hasEvent()) { + char nextEvent = TCAKeyboard.dequeueEvent(); + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = 0x00; + e.source = this->_originName; + switch (nextEvent) { + case TCA8418KeyboardBase::NONE: + e.inputEvent = INPUT_BROKER_NONE; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::REBOOT: + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = INPUT_BROKER_MSG_REBOOT; + break; + case TCA8418KeyboardBase::LEFT: + e.inputEvent = INPUT_BROKER_LEFT; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::UP: + e.inputEvent = INPUT_BROKER_UP; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::DOWN: + e.inputEvent = INPUT_BROKER_DOWN; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::RIGHT: + e.inputEvent = INPUT_BROKER_RIGHT; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::BSP: + e.inputEvent = INPUT_BROKER_BACK; + e.kbchar = 0x08; + break; + case TCA8418KeyboardBase::SELECT: + e.inputEvent = INPUT_BROKER_SELECT; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::ESC: + e.inputEvent = INPUT_BROKER_CANCEL; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::GPS_TOGGLE: + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = INPUT_BROKER_GPS_TOGGLE; + break; + case TCA8418KeyboardBase::SEND_PING: + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = INPUT_BROKER_SEND_PING; + break; + case TCA8418KeyboardBase::MUTE_TOGGLE: + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = INPUT_BROKER_MSG_MUTE_TOGGLE; + break; + case TCA8418KeyboardBase::BT_TOGGLE: + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = INPUT_BROKER_MSG_BLUETOOTH_TOGGLE; + break; + case TCA8418KeyboardBase::BL_TOGGLE: + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = INPUT_BROKER_MSG_BLUETOOTH_TOGGLE; + break; + case TCA8418KeyboardBase::TAB: + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = INPUT_BROKER_MSG_TAB; + break; + default: + if (nextEvent > 127) { + e.inputEvent = INPUT_BROKER_NONE; + e.kbchar = 0x00; + break; + } + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = nextEvent; + break; + } + if (e.inputEvent != INPUT_BROKER_NONE) { + LOG_DEBUG("TCA8418 Notifying: %i Char: %c", e.inputEvent, e.kbchar); + inputBroker->queueInputEvent(&e); + } + } + break; + } + default: + LOG_WARN("Unknown kb_model 0x%02x", kb_model); + } +} + int32_t KbI2cBase::runOnce() { - int32_t newInterval = 300; + // Called periodically at 300ms interval if (!i2cBus) { switch (cardkb_found.port) { case ScanI2C::WIRE1: @@ -62,6 +157,8 @@ int32_t KbI2cBase::runOnce() } if (cardkb_found.address == TCA8418_KB_ADDR) { TCAKeyboard.begin(TCA8418_KB_ADDR, &Wire1); + // Disable polling via OSThread, we use interrupts instead + disable(); observe(&TCAKeyboard); } break; @@ -78,6 +175,8 @@ int32_t KbI2cBase::runOnce() } if (cardkb_found.address == TCA8418_KB_ADDR) { TCAKeyboard.begin(TCA8418_KB_ADDR, &Wire); + // Disable polling via OSThread, we use interrupts instead + disable(); observe(&TCAKeyboard); } break; @@ -251,93 +350,6 @@ int32_t KbI2cBase::runOnce() } break; } - case 0x84: { // Adafruit TCA8418 - newInterval = 3000; // Less polling, we have interrupts with onNotify() - TCAKeyboard.trigger(); - InputEvent e = {}; - while (TCAKeyboard.hasEvent()) { - char nextEvent = TCAKeyboard.dequeueEvent(); - e.inputEvent = INPUT_BROKER_ANYKEY; - e.kbchar = 0x00; - e.source = this->_originName; - switch (nextEvent) { - case TCA8418KeyboardBase::NONE: - e.inputEvent = INPUT_BROKER_NONE; - e.kbchar = 0x00; - break; - case TCA8418KeyboardBase::REBOOT: - e.inputEvent = INPUT_BROKER_ANYKEY; - e.kbchar = INPUT_BROKER_MSG_REBOOT; - break; - case TCA8418KeyboardBase::LEFT: - e.inputEvent = INPUT_BROKER_LEFT; - e.kbchar = 0x00; - break; - case TCA8418KeyboardBase::UP: - e.inputEvent = INPUT_BROKER_UP; - e.kbchar = 0x00; - break; - case TCA8418KeyboardBase::DOWN: - e.inputEvent = INPUT_BROKER_DOWN; - e.kbchar = 0x00; - break; - case TCA8418KeyboardBase::RIGHT: - e.inputEvent = INPUT_BROKER_RIGHT; - e.kbchar = 0x00; - break; - case TCA8418KeyboardBase::BSP: - e.inputEvent = INPUT_BROKER_BACK; - e.kbchar = 0x08; - break; - case TCA8418KeyboardBase::SELECT: - e.inputEvent = INPUT_BROKER_SELECT; - e.kbchar = 0x00; - break; - case TCA8418KeyboardBase::ESC: - e.inputEvent = INPUT_BROKER_CANCEL; - e.kbchar = 0x00; - break; - case TCA8418KeyboardBase::GPS_TOGGLE: - e.inputEvent = INPUT_BROKER_ANYKEY; - e.kbchar = INPUT_BROKER_GPS_TOGGLE; - break; - case TCA8418KeyboardBase::SEND_PING: - e.inputEvent = INPUT_BROKER_ANYKEY; - e.kbchar = INPUT_BROKER_SEND_PING; - break; - case TCA8418KeyboardBase::MUTE_TOGGLE: - e.inputEvent = INPUT_BROKER_ANYKEY; - e.kbchar = INPUT_BROKER_MSG_MUTE_TOGGLE; - break; - case TCA8418KeyboardBase::BT_TOGGLE: - e.inputEvent = INPUT_BROKER_ANYKEY; - e.kbchar = INPUT_BROKER_MSG_BLUETOOTH_TOGGLE; - break; - case TCA8418KeyboardBase::BL_TOGGLE: - e.inputEvent = INPUT_BROKER_ANYKEY; - e.kbchar = INPUT_BROKER_MSG_BLUETOOTH_TOGGLE; - break; - case TCA8418KeyboardBase::TAB: - e.inputEvent = INPUT_BROKER_ANYKEY; - e.kbchar = INPUT_BROKER_MSG_TAB; - break; - default: - if (nextEvent > 127) { - e.inputEvent = INPUT_BROKER_NONE; - e.kbchar = 0x00; - break; - } - e.inputEvent = INPUT_BROKER_ANYKEY; - e.kbchar = nextEvent; - break; - } - if (e.inputEvent != INPUT_BROKER_NONE) { - LOG_DEBUG("TCA8418 Notifying: %i Char: %c", e.inputEvent, e.kbchar); - this->notifyObservers(&e); - } - } - break; - } case 0x02: { // RAK14004 uint8_t rDataBuf[8] = {0}; @@ -521,17 +533,12 @@ int32_t KbI2cBase::runOnce() default: LOG_WARN("Unknown kb_model 0x%02x", kb_model); } - - // If new interrupt triggered while we were processing the previous, reinvoke with 0 interval right away - if (pendingInterruptCount) pendingInterruptCount--; - if (pendingInterruptCount) return 0; - - return newInterval; + return 300; } -int KbI2cBase::onNotify(KbInterruptObservable* src) +int KbI2cBase::onNotify(KbInterruptObservable *src) { - pendingInterruptCount++; - setInterval(0); + // Called from interrupt context, request polling after exiting the ISR + inputBroker->pollSoonRequestFromIsr(this); return 0; } \ No newline at end of file diff --git a/src/input/kbI2cBase.h b/src/input/kbI2cBase.h index 556ca13ca..87cdfe263 100644 --- a/src/input/kbI2cBase.h +++ b/src/input/kbI2cBase.h @@ -9,14 +9,18 @@ class TCA8418KeyboardBase; -class KbI2cBase : public Observable, public KbInterruptObserver, public concurrency::OSThread +class KbI2cBase : public Observable, + public InputPollable, + public KbInterruptObserver, + public concurrency::OSThread { public: explicit KbI2cBase(const char *name); + virtual void pollOnce() override; protected: + virtual int onNotify(KbInterruptObservable *src) override; virtual int32_t runOnce() override; - virtual int onNotify(KbInterruptObservable* src) override; private: const char *_originName; @@ -26,6 +30,5 @@ class KbI2cBase : public Observable, public KbInterruptObser BBQ10Keyboard Q10keyboard; MPR121Keyboard MPRkeyboard; TCA8418KeyboardBase &TCAKeyboard; - volatile uint8_t pendingInterruptCount = 0; bool is_sym = false; }; \ No newline at end of file From 2c6414827ea6dc482211a910de7c99916f7f1a5c Mon Sep 17 00:00:00 2001 From: WillyJL Date: Fri, 19 Sep 2025 23:43:54 +0200 Subject: [PATCH 07/18] Format --- src/input/TCA8418KeyboardBase.cpp | 9 +++------ src/input/TCA8418KeyboardBase.h | 4 ++-- src/input/TDeckProKeyboard.cpp | 3 ++- src/input/TDeckProKeyboard.h | 2 +- src/input/TLoraPagerKeyboard.cpp | 3 ++- src/input/TLoraPagerKeyboard.h | 2 +- src/input/kbInterrupt.h | 4 ++-- 7 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/input/TCA8418KeyboardBase.cpp b/src/input/TCA8418KeyboardBase.cpp index 47c4cfbb0..4932ead80 100644 --- a/src/input/TCA8418KeyboardBase.cpp +++ b/src/input/TCA8418KeyboardBase.cpp @@ -32,8 +32,7 @@ #define _TCA8418_REG_LCK_EC_KLEC_0 0x01 // Key event count bit 0 TCA8418KeyboardBase::TCA8418KeyboardBase(uint8_t rows, uint8_t columns) - : rows(rows), columns(columns), queue(""), m_wire(nullptr), m_addr(0), readCallback(nullptr), - writeCallback(nullptr) + : rows(rows), columns(columns), queue(""), m_wire(nullptr), m_addr(0), readCallback(nullptr), writeCallback(nullptr) { } @@ -46,9 +45,7 @@ void TCA8418KeyboardBase::begin(uint8_t addr, TwoWire *wire) #ifdef KB_INT interruptInstance = this; - auto interruptHandler = []() { - interruptInstance->notifyObservers(interruptInstance); - }; + auto interruptHandler = []() { interruptInstance->notifyObservers(interruptInstance); }; ::pinMode(KB_INT, INPUT_PULLUP); attachInterrupt(KB_INT, interruptHandler, FALLING); @@ -309,7 +306,7 @@ void TCA8418KeyboardBase::disableInterrupts() writeRegister(TCA8418_REG_CFG, value); }; -TCA8418KeyboardBase* TCA8418KeyboardBase::interruptInstance; +TCA8418KeyboardBase *TCA8418KeyboardBase::interruptInstance; void TCA8418KeyboardBase::enableMatrixOverflow() { diff --git a/src/input/TCA8418KeyboardBase.h b/src/input/TCA8418KeyboardBase.h index 83527f9dc..3bec766a1 100644 --- a/src/input/TCA8418KeyboardBase.h +++ b/src/input/TCA8418KeyboardBase.h @@ -1,7 +1,7 @@ // Based on the MPR121 Keyboard and Adafruit TCA8418 library #include "configuration.h" -#include #include "kbInterrupt.h" +#include /** * @brief TCA8418KeyboardBase is the base class for TCA8418 keyboard handling. @@ -143,7 +143,7 @@ class TCA8418KeyboardBase : public KbInterruptObservable // enable / disable interrupts for matrix and GPI pins void enableInterrupts(); void disableInterrupts(); - static TCA8418KeyboardBase* interruptInstance; + static TCA8418KeyboardBase *interruptInstance; // ignore key events when FIFO buffer is full or not. void enableMatrixOverflow(); diff --git a/src/input/TDeckProKeyboard.cpp b/src/input/TDeckProKeyboard.cpp index e99af8b8d..b4893bb16 100644 --- a/src/input/TDeckProKeyboard.cpp +++ b/src/input/TDeckProKeyboard.cpp @@ -62,7 +62,8 @@ static const uint8_t TDeckProTapMap[_TCA8418_NUM_KEYS][5] = { static bool TDeckProHeldMap[_TCA8418_NUM_KEYS] = {}; TDeckProKeyboard::TDeckProKeyboard() - : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), pressedKeysCount(0), onlyOneModifierPressed(false), persistedPreviousModifier(false) + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), pressedKeysCount(0), onlyOneModifierPressed(false), + persistedPreviousModifier(false) { } diff --git a/src/input/TDeckProKeyboard.h b/src/input/TDeckProKeyboard.h index 74a5df0a8..b10b93edf 100644 --- a/src/input/TDeckProKeyboard.h +++ b/src/input/TDeckProKeyboard.h @@ -17,7 +17,7 @@ class TDeckProKeyboard : public TCA8418KeyboardBase void toggleBacklight(void); private: - uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed + uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed uint8_t pressedKeysCount; bool onlyOneModifierPressed; bool persistedPreviousModifier; diff --git a/src/input/TLoraPagerKeyboard.cpp b/src/input/TLoraPagerKeyboard.cpp index f1ec14695..0386f90ce 100644 --- a/src/input/TLoraPagerKeyboard.cpp +++ b/src/input/TLoraPagerKeyboard.cpp @@ -67,7 +67,8 @@ static const uint8_t TLoraPagerTapMap[_TCA8418_NUM_KEYS][3] = { static bool TLoraPagerHeldMap[_TCA8418_NUM_KEYS] = {}; TLoraPagerKeyboard::TLoraPagerKeyboard() - : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), pressedKeysCount(0), onlyOneModifierPressed(false), persistedPreviousModifier(false) + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), pressedKeysCount(0), onlyOneModifierPressed(false), + persistedPreviousModifier(false) { #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) ledcAttach(KB_BL_PIN, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH); diff --git a/src/input/TLoraPagerKeyboard.h b/src/input/TLoraPagerKeyboard.h index 6b4001c21..6adba9d0b 100644 --- a/src/input/TLoraPagerKeyboard.h +++ b/src/input/TLoraPagerKeyboard.h @@ -19,7 +19,7 @@ class TLoraPagerKeyboard : public TCA8418KeyboardBase void toggleBacklight(bool off = false); private: - uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed + uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed uint8_t pressedKeysCount; bool onlyOneModifierPressed; bool persistedPreviousModifier; diff --git a/src/input/kbInterrupt.h b/src/input/kbInterrupt.h index dc63ed338..2f6c84a9f 100644 --- a/src/input/kbInterrupt.h +++ b/src/input/kbInterrupt.h @@ -2,10 +2,10 @@ #include "Observer.h" -class KbInterruptObservable : public Observable +class KbInterruptObservable : public Observable { }; -class KbInterruptObserver : public Observer +class KbInterruptObserver : public Observer { }; \ No newline at end of file From 1c02e6bed95be4c85bbfc12b608512d26f92afbd Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Sat, 20 Sep 2025 14:21:09 +0200 Subject: [PATCH 08/18] added KB_INT for T-Deck Pro --- variants/esp32s3/t-deck-pro/variant.h | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/esp32s3/t-deck-pro/variant.h b/variants/esp32s3/t-deck-pro/variant.h index 35cb99435..665c1d30d 100644 --- a/variants/esp32s3/t-deck-pro/variant.h +++ b/variants/esp32s3/t-deck-pro/variant.h @@ -43,6 +43,7 @@ // TCA8418 keyboard #define KB_BL_PIN 42 +#define KB_INT 15 #define CANNED_MESSAGE_MODULE_ENABLE 1 // microphone PCM5102A From c30e24c99350ae55ab076df823498ce0a085bef7 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 20 Sep 2025 18:10:31 +0200 Subject: [PATCH 09/18] Allow TCA8418 polling when KB_INT is missing --- src/input/kbI2cBase.cpp | 94 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 951bcd2bd..af99a4bd6 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -48,6 +48,7 @@ void KbI2cBase::pollOnce() { // Called right after an interrupt by InputBroker switch (kb_model) { +#ifdef KB_INT case 0x84: { // Adafruit TCA8418 TCAKeyboard.trigger(); InputEvent e = {}; @@ -134,6 +135,7 @@ void KbI2cBase::pollOnce() } break; } +#endif default: LOG_WARN("Unknown kb_model 0x%02x", kb_model); } @@ -157,9 +159,11 @@ int32_t KbI2cBase::runOnce() } if (cardkb_found.address == TCA8418_KB_ADDR) { TCAKeyboard.begin(TCA8418_KB_ADDR, &Wire1); +#ifdef KB_INT // Disable polling via OSThread, we use interrupts instead disable(); observe(&TCAKeyboard); +#endif } break; #endif @@ -175,9 +179,11 @@ int32_t KbI2cBase::runOnce() } if (cardkb_found.address == TCA8418_KB_ADDR) { TCAKeyboard.begin(TCA8418_KB_ADDR, &Wire); +#ifdef KB_INT // Disable polling via OSThread, we use interrupts instead disable(); observe(&TCAKeyboard); +#endif } break; case ScanI2C::NO_I2C: @@ -350,6 +356,94 @@ int32_t KbI2cBase::runOnce() } break; } +#ifndef KB_INT + case 0x84: { // Adafruit TCA8418 + TCAKeyboard.trigger(); + InputEvent e = {}; + while (TCAKeyboard.hasEvent()) { + char nextEvent = TCAKeyboard.dequeueEvent(); + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = 0x00; + e.source = this->_originName; + switch (nextEvent) { + case TCA8418KeyboardBase::NONE: + e.inputEvent = INPUT_BROKER_NONE; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::REBOOT: + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = INPUT_BROKER_MSG_REBOOT; + break; + case TCA8418KeyboardBase::LEFT: + e.inputEvent = INPUT_BROKER_LEFT; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::UP: + e.inputEvent = INPUT_BROKER_UP; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::DOWN: + e.inputEvent = INPUT_BROKER_DOWN; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::RIGHT: + e.inputEvent = INPUT_BROKER_RIGHT; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::BSP: + e.inputEvent = INPUT_BROKER_BACK; + e.kbchar = 0x08; + break; + case TCA8418KeyboardBase::SELECT: + e.inputEvent = INPUT_BROKER_SELECT; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::ESC: + e.inputEvent = INPUT_BROKER_CANCEL; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::GPS_TOGGLE: + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = INPUT_BROKER_GPS_TOGGLE; + break; + case TCA8418KeyboardBase::SEND_PING: + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = INPUT_BROKER_SEND_PING; + break; + case TCA8418KeyboardBase::MUTE_TOGGLE: + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = INPUT_BROKER_MSG_MUTE_TOGGLE; + break; + case TCA8418KeyboardBase::BT_TOGGLE: + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = INPUT_BROKER_MSG_BLUETOOTH_TOGGLE; + break; + case TCA8418KeyboardBase::BL_TOGGLE: + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = INPUT_BROKER_MSG_BLUETOOTH_TOGGLE; + break; + case TCA8418KeyboardBase::TAB: + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = INPUT_BROKER_MSG_TAB; + break; + default: + if (nextEvent > 127) { + e.inputEvent = INPUT_BROKER_NONE; + e.kbchar = 0x00; + break; + } + e.inputEvent = INPUT_BROKER_ANYKEY; + e.kbchar = nextEvent; + break; + } + if (e.inputEvent != INPUT_BROKER_NONE) { + LOG_DEBUG("TCA8418 Notifying: %i Char: %c", e.inputEvent, e.kbchar); + this->notifyObservers(&e); + } + } + break; + } +#endif case 0x02: { // RAK14004 uint8_t rDataBuf[8] = {0}; From eaee05437e27adb5265298d1c46645f171f5ec62 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Tue, 23 Sep 2025 22:30:01 +0200 Subject: [PATCH 10/18] Fix desktop build --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 3ef4bded9..1efc6f12e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1596,7 +1596,7 @@ void loop() #endif service->loop(); -#if !MESHTASTIC_EXCLUDE_INPUTBROKER +#if !MESHTASTIC_EXCLUDE_INPUTBROKER && defined(HAS_FREE_RTOS) inputBroker->processInputEventQueue(); #endif #if defined(LGFX_SDL) From f4bdf2f2482b3d7e7e4775ab4f8ce88d000772cb Mon Sep 17 00:00:00 2001 From: WillyJL Date: Wed, 24 Sep 2025 02:04:58 +0200 Subject: [PATCH 11/18] Skip unknown model warning --- src/input/kbI2cBase.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 3711e9fd3..201cc7720 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -48,8 +48,8 @@ void KbI2cBase::pollOnce() { // Called right after an interrupt by InputBroker switch (kb_model) { -#ifdef KB_INT case 0x84: { // Adafruit TCA8418 +#ifdef KB_INT TCAKeyboard.trigger(); InputEvent e = {}; while (TCAKeyboard.hasEvent()) { @@ -133,9 +133,9 @@ void KbI2cBase::pollOnce() inputBroker->queueInputEvent(&e); } } +#endif break; } -#endif default: LOG_WARN("Unknown kb_model 0x%02x", kb_model); } @@ -356,8 +356,8 @@ int32_t KbI2cBase::runOnce() } break; } -#ifndef KB_INT case 0x84: { // Adafruit TCA8418 +#ifndef KB_INT TCAKeyboard.trigger(); InputEvent e = {}; while (TCAKeyboard.hasEvent()) { @@ -442,9 +442,9 @@ int32_t KbI2cBase::runOnce() } } TCAKeyboard.clearInt(); +#endif break; } -#endif case 0x02: { // RAK14004 uint8_t rDataBuf[8] = {0}; From 696a391cb8c502bc3ea9e4ea88ada09ea6844fbe Mon Sep 17 00:00:00 2001 From: WillyJL Date: Wed, 24 Sep 2025 03:05:26 +0200 Subject: [PATCH 12/18] More flexible InputPollable paradigm --- src/input/InputBroker.cpp | 14 +++++++++++--- src/input/InputBroker.h | 2 +- src/input/kbI2cBase.cpp | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/input/InputBroker.cpp b/src/input/InputBroker.cpp index 5ca890b43..c588a9a0f 100644 --- a/src/input/InputBroker.cpp +++ b/src/input/InputBroker.cpp @@ -18,14 +18,22 @@ void InputBroker::registerSource(Observable *source) } #ifdef HAS_FREE_RTOS -void InputBroker::pollSoonRequestFromIsr(InputPollable *pollable) +void InputBroker::requestPollSoon(InputPollable *pollable) { - xQueueSendFromISR(pollSoonQueue, &pollable, NULL); + if (xPortInIsrContext() == pdTRUE) { + xQueueSendFromISR(pollSoonQueue, &pollable, NULL); + } else { + xQueueSend(pollSoonQueue, &pollable, 0); + } } void InputBroker::queueInputEvent(const InputEvent *event) { - xQueueSend(inputEventQueue, event, portMAX_DELAY); + if (xPortInIsrContext() == pdTRUE) { + xQueueSendFromISR(inputEventQueue, event, NULL); + } else { + xQueueSend(inputEventQueue, event, portMAX_DELAY); + } } void InputBroker::processInputEventQueue() diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h index 82af184f3..192bd7e8b 100644 --- a/src/input/InputBroker.h +++ b/src/input/InputBroker.h @@ -60,7 +60,7 @@ class InputBroker : public Observable void registerSource(Observable *source); void injectInputEvent(const InputEvent *event) { handleInputEvent(event); } #ifdef HAS_FREE_RTOS - void pollSoonRequestFromIsr(InputPollable *pollable); + void requestPollSoon(InputPollable *pollable); void queueInputEvent(const InputEvent *event); void processInputEventQueue(); #endif diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 201cc7720..5202f5b71 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -641,6 +641,6 @@ void KbI2cBase::toggleBacklight(bool on) int KbI2cBase::onNotify(KbInterruptObservable *src) { // Called from interrupt context, request polling after exiting the ISR - inputBroker->pollSoonRequestFromIsr(this); + inputBroker->requestPollSoon(this); return 0; } \ No newline at end of file From a37133214f42f9e6bfc0c0235cc4d435d709f385 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Wed, 24 Sep 2025 03:06:36 +0200 Subject: [PATCH 13/18] Properly handle TCA8418 interrupts in sleep mode --- src/input/TCA8418KeyboardBase.cpp | 54 +++++++++++++++++++++++++++---- src/input/TCA8418KeyboardBase.h | 20 ++++++++++++ src/input/kbI2cBase.cpp | 1 - 3 files changed, 67 insertions(+), 8 deletions(-) diff --git a/src/input/TCA8418KeyboardBase.cpp b/src/input/TCA8418KeyboardBase.cpp index 1abd05e36..a5ecbd0a0 100644 --- a/src/input/TCA8418KeyboardBase.cpp +++ b/src/input/TCA8418KeyboardBase.cpp @@ -2,6 +2,7 @@ #include "TCA8418KeyboardBase.h" #include "configuration.h" +#include "sleep.h" #include @@ -44,14 +45,18 @@ void TCA8418KeyboardBase::begin(uint8_t addr, TwoWire *wire) reset(); #ifdef KB_INT - interruptInstance = this; - auto interruptHandler = []() { interruptInstance->notifyObservers(interruptInstance); }; - ::pinMode(KB_INT, INPUT_PULLUP); - attachInterrupt(KB_INT, interruptHandler, FALLING); - + attachInterruptHandler(); enableInterrupts(); -#endif + +#ifdef ARCH_ESP32 + // Register callbacks for before and after lightsleep + // Used to detach and reattach interrupts + lsObserver.observe(¬ifyLightSleep); + lsEndObserver.observe(¬ifyLightSleepEnd); +#endif // ARCH_ESP32 + +#endif // KB_INT } void TCA8418KeyboardBase::begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr) @@ -93,6 +98,41 @@ void TCA8418KeyboardBase::reset() flush(); } +#ifdef KB_INT +void TCA8418KeyboardBase::attachInterruptHandler() +{ + interruptInstance = this; + auto interruptHandler = []() { interruptInstance->notifyObservers(interruptInstance); }; + attachInterrupt(KB_INT, interruptHandler, FALLING); +} + +void TCA8418KeyboardBase::detachInterruptHandler() +{ + detachInterrupt(KB_INT); + interruptInstance = nullptr; +} + +#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 TCA8418KeyboardBase::beforeLightSleep(void *unused) +{ + detachInterruptHandler(); + 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 TCA8418KeyboardBase::afterLightSleep(esp_sleep_wakeup_cause_t cause) +{ + attachInterruptHandler(); + this->notifyObservers(this); // Trigger a one-off poll in case a keypress woke us + return 0; // Indicates success +} +#endif // ARCH_ESP32 + +#endif // KB_INT + bool TCA8418KeyboardBase::matrix(uint8_t rows, uint8_t columns) { if (rows < 1 || rows > 8 || columns < 1 || columns > 10) @@ -169,7 +209,7 @@ void TCA8418KeyboardBase::trigger() #ifdef KB_INT // Reset interrupt mask so we can receive future interrupts - writeRegister(TCA8418_REG_INT_STAT, 3); + clearInt(); #endif } diff --git a/src/input/TCA8418KeyboardBase.h b/src/input/TCA8418KeyboardBase.h index 9f68bc885..0321ffe70 100644 --- a/src/input/TCA8418KeyboardBase.h +++ b/src/input/TCA8418KeyboardBase.h @@ -163,9 +163,29 @@ class TCA8418KeyboardBase : public KbInterruptObservable uint8_t columns; String queue; +#ifdef KB_INT + void attachInterruptHandler(); + void detachInterruptHandler(); + +#ifdef ARCH_ESP32 + // Disconnect and reconnect interrupts for light sleep + int beforeLightSleep(void *unused); + int afterLightSleep(esp_sleep_wakeup_cause_t cause); +#endif // ARCH_ESP32 + +#endif // KB_INT + private: TwoWire *m_wire; uint8_t m_addr; i2c_com_fptr_t readCallback; i2c_com_fptr_t writeCallback; + +#if defined(KB_INT) && defined(ARCH_ESP32) + // Get notified when lightsleep begins and ends + CallbackObserver lsObserver = + CallbackObserver(this, &TCA8418KeyboardBase::beforeLightSleep); + CallbackObserver lsEndObserver = + CallbackObserver(this, &TCA8418KeyboardBase::afterLightSleep); +#endif }; diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 5202f5b71..dbc1f713f 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -441,7 +441,6 @@ int32_t KbI2cBase::runOnce() this->notifyObservers(&e); } } - TCAKeyboard.clearInt(); #endif break; } From 3171937f4d9ec91dab3e2041905b0dca69bb1990 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Wed, 24 Sep 2025 03:21:06 +0200 Subject: [PATCH 14/18] Fix desktop build --- src/input/kbI2cBase.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index dbc1f713f..2aae45399 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -640,6 +640,8 @@ void KbI2cBase::toggleBacklight(bool on) int KbI2cBase::onNotify(KbInterruptObservable *src) { // Called from interrupt context, request polling after exiting the ISR +#ifdef KB_INT inputBroker->requestPollSoon(this); +#endif return 0; } \ No newline at end of file From cea129c1640a2347b6be175c31d9ad9d686ceb7f Mon Sep 17 00:00:00 2001 From: WillyJL Date: Wed, 24 Sep 2025 03:54:51 +0200 Subject: [PATCH 15/18] No xPortInIsrContext() on nRF52... --- src/input/InputBroker.cpp | 8 ++++---- src/input/InputBroker.h | 4 ++-- src/input/TCA8418KeyboardBase.cpp | 6 +++--- src/input/kbI2cBase.cpp | 6 +++--- src/input/kbI2cBase.h | 2 +- src/input/kbInterrupt.h | 4 ++-- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/input/InputBroker.cpp b/src/input/InputBroker.cpp index c588a9a0f..36aaea3db 100644 --- a/src/input/InputBroker.cpp +++ b/src/input/InputBroker.cpp @@ -18,18 +18,18 @@ void InputBroker::registerSource(Observable *source) } #ifdef HAS_FREE_RTOS -void InputBroker::requestPollSoon(InputPollable *pollable) +void InputBroker::requestPollSoon(InputPollable *pollable, bool fromIsr) { - if (xPortInIsrContext() == pdTRUE) { + if (fromIsr) { xQueueSendFromISR(pollSoonQueue, &pollable, NULL); } else { xQueueSend(pollSoonQueue, &pollable, 0); } } -void InputBroker::queueInputEvent(const InputEvent *event) +void InputBroker::queueInputEvent(const InputEvent *event, bool fromIsr) { - if (xPortInIsrContext() == pdTRUE) { + if (fromIsr) { xQueueSendFromISR(inputEventQueue, event, NULL); } else { xQueueSend(inputEventQueue, event, portMAX_DELAY); diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h index 192bd7e8b..f4d1eb606 100644 --- a/src/input/InputBroker.h +++ b/src/input/InputBroker.h @@ -60,8 +60,8 @@ class InputBroker : public Observable void registerSource(Observable *source); void injectInputEvent(const InputEvent *event) { handleInputEvent(event); } #ifdef HAS_FREE_RTOS - void requestPollSoon(InputPollable *pollable); - void queueInputEvent(const InputEvent *event); + void requestPollSoon(InputPollable *pollable, bool fromIsr); + void queueInputEvent(const InputEvent *event, bool fromIsr); void processInputEventQueue(); #endif diff --git a/src/input/TCA8418KeyboardBase.cpp b/src/input/TCA8418KeyboardBase.cpp index a5ecbd0a0..6fd59dde2 100644 --- a/src/input/TCA8418KeyboardBase.cpp +++ b/src/input/TCA8418KeyboardBase.cpp @@ -102,7 +102,7 @@ void TCA8418KeyboardBase::reset() void TCA8418KeyboardBase::attachInterruptHandler() { interruptInstance = this; - auto interruptHandler = []() { interruptInstance->notifyObservers(interruptInstance); }; + auto interruptHandler = []() { interruptInstance->notifyObservers(true); }; attachInterrupt(KB_INT, interruptHandler, FALLING); } @@ -126,8 +126,8 @@ int TCA8418KeyboardBase::beforeLightSleep(void *unused) int TCA8418KeyboardBase::afterLightSleep(esp_sleep_wakeup_cause_t cause) { attachInterruptHandler(); - this->notifyObservers(this); // Trigger a one-off poll in case a keypress woke us - return 0; // Indicates success + this->notifyObservers(false); // Trigger a one-off poll in case a keypress woke us + return 0; // Indicates success } #endif // ARCH_ESP32 diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 2aae45399..28f0867e6 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -130,7 +130,7 @@ void KbI2cBase::pollOnce() } if (e.inputEvent != INPUT_BROKER_NONE) { LOG_DEBUG("TCA8418 Notifying: %i Char: %c", e.inputEvent, e.kbchar); - inputBroker->queueInputEvent(&e); + inputBroker->queueInputEvent(&e, false); } } #endif @@ -637,11 +637,11 @@ void KbI2cBase::toggleBacklight(bool on) #endif } -int KbI2cBase::onNotify(KbInterruptObservable *src) +int KbI2cBase::onNotify(bool fromIsr) { // Called from interrupt context, request polling after exiting the ISR #ifdef KB_INT - inputBroker->requestPollSoon(this); + inputBroker->requestPollSoon(this, fromIsr); #endif return 0; } \ No newline at end of file diff --git a/src/input/kbI2cBase.h b/src/input/kbI2cBase.h index ddc174e9c..379072c4a 100644 --- a/src/input/kbI2cBase.h +++ b/src/input/kbI2cBase.h @@ -20,7 +20,7 @@ class KbI2cBase : public Observable, virtual void pollOnce() override; protected: - virtual int onNotify(KbInterruptObservable *src) override; + virtual int onNotify(bool fromIsr) override; virtual int32_t runOnce() override; private: diff --git a/src/input/kbInterrupt.h b/src/input/kbInterrupt.h index 2f6c84a9f..c9595b2c0 100644 --- a/src/input/kbInterrupt.h +++ b/src/input/kbInterrupt.h @@ -2,10 +2,10 @@ #include "Observer.h" -class KbInterruptObservable : public Observable +class KbInterruptObservable : public Observable { }; -class KbInterruptObserver : public Observer +class KbInterruptObserver : public Observer { }; \ No newline at end of file From 81c703dfc1d9d01ce161d4968d0984c0c6fd0cda Mon Sep 17 00:00:00 2001 From: WillyJL Date: Wed, 24 Sep 2025 04:12:38 +0200 Subject: [PATCH 16/18] Revert "No xPortInIsrContext() on nRF52..." This reverts commit cea129c1640a2347b6be175c31d9ad9d686ceb7f. --- src/input/InputBroker.cpp | 8 ++++---- src/input/InputBroker.h | 4 ++-- src/input/TCA8418KeyboardBase.cpp | 6 +++--- src/input/kbI2cBase.cpp | 6 +++--- src/input/kbI2cBase.h | 2 +- src/input/kbInterrupt.h | 4 ++-- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/input/InputBroker.cpp b/src/input/InputBroker.cpp index 36aaea3db..c588a9a0f 100644 --- a/src/input/InputBroker.cpp +++ b/src/input/InputBroker.cpp @@ -18,18 +18,18 @@ void InputBroker::registerSource(Observable *source) } #ifdef HAS_FREE_RTOS -void InputBroker::requestPollSoon(InputPollable *pollable, bool fromIsr) +void InputBroker::requestPollSoon(InputPollable *pollable) { - if (fromIsr) { + if (xPortInIsrContext() == pdTRUE) { xQueueSendFromISR(pollSoonQueue, &pollable, NULL); } else { xQueueSend(pollSoonQueue, &pollable, 0); } } -void InputBroker::queueInputEvent(const InputEvent *event, bool fromIsr) +void InputBroker::queueInputEvent(const InputEvent *event) { - if (fromIsr) { + if (xPortInIsrContext() == pdTRUE) { xQueueSendFromISR(inputEventQueue, event, NULL); } else { xQueueSend(inputEventQueue, event, portMAX_DELAY); diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h index f4d1eb606..192bd7e8b 100644 --- a/src/input/InputBroker.h +++ b/src/input/InputBroker.h @@ -60,8 +60,8 @@ class InputBroker : public Observable void registerSource(Observable *source); void injectInputEvent(const InputEvent *event) { handleInputEvent(event); } #ifdef HAS_FREE_RTOS - void requestPollSoon(InputPollable *pollable, bool fromIsr); - void queueInputEvent(const InputEvent *event, bool fromIsr); + void requestPollSoon(InputPollable *pollable); + void queueInputEvent(const InputEvent *event); void processInputEventQueue(); #endif diff --git a/src/input/TCA8418KeyboardBase.cpp b/src/input/TCA8418KeyboardBase.cpp index 6fd59dde2..a5ecbd0a0 100644 --- a/src/input/TCA8418KeyboardBase.cpp +++ b/src/input/TCA8418KeyboardBase.cpp @@ -102,7 +102,7 @@ void TCA8418KeyboardBase::reset() void TCA8418KeyboardBase::attachInterruptHandler() { interruptInstance = this; - auto interruptHandler = []() { interruptInstance->notifyObservers(true); }; + auto interruptHandler = []() { interruptInstance->notifyObservers(interruptInstance); }; attachInterrupt(KB_INT, interruptHandler, FALLING); } @@ -126,8 +126,8 @@ int TCA8418KeyboardBase::beforeLightSleep(void *unused) int TCA8418KeyboardBase::afterLightSleep(esp_sleep_wakeup_cause_t cause) { attachInterruptHandler(); - this->notifyObservers(false); // Trigger a one-off poll in case a keypress woke us - return 0; // Indicates success + this->notifyObservers(this); // Trigger a one-off poll in case a keypress woke us + return 0; // Indicates success } #endif // ARCH_ESP32 diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 28f0867e6..2aae45399 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -130,7 +130,7 @@ void KbI2cBase::pollOnce() } if (e.inputEvent != INPUT_BROKER_NONE) { LOG_DEBUG("TCA8418 Notifying: %i Char: %c", e.inputEvent, e.kbchar); - inputBroker->queueInputEvent(&e, false); + inputBroker->queueInputEvent(&e); } } #endif @@ -637,11 +637,11 @@ void KbI2cBase::toggleBacklight(bool on) #endif } -int KbI2cBase::onNotify(bool fromIsr) +int KbI2cBase::onNotify(KbInterruptObservable *src) { // Called from interrupt context, request polling after exiting the ISR #ifdef KB_INT - inputBroker->requestPollSoon(this, fromIsr); + inputBroker->requestPollSoon(this); #endif return 0; } \ No newline at end of file diff --git a/src/input/kbI2cBase.h b/src/input/kbI2cBase.h index 379072c4a..ddc174e9c 100644 --- a/src/input/kbI2cBase.h +++ b/src/input/kbI2cBase.h @@ -20,7 +20,7 @@ class KbI2cBase : public Observable, virtual void pollOnce() override; protected: - virtual int onNotify(bool fromIsr) override; + virtual int onNotify(KbInterruptObservable *src) override; virtual int32_t runOnce() override; private: diff --git a/src/input/kbInterrupt.h b/src/input/kbInterrupt.h index c9595b2c0..2f6c84a9f 100644 --- a/src/input/kbInterrupt.h +++ b/src/input/kbInterrupt.h @@ -2,10 +2,10 @@ #include "Observer.h" -class KbInterruptObservable : public Observable +class KbInterruptObservable : public Observable { }; -class KbInterruptObserver : public Observer +class KbInterruptObserver : public Observer { }; \ No newline at end of file From 42bbbaa72bad990244ebbb06ccf57c67d235172e Mon Sep 17 00:00:00 2001 From: WillyJL Date: Wed, 24 Sep 2025 04:19:11 +0200 Subject: [PATCH 17/18] Custom xPortInIsrContext() for nRF52 --- src/platform/nrf52/architecture.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index c9938062e..b1a2439e6 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -149,3 +149,5 @@ // No serial ports on this board - ONLY use segger in memory console #define USE_SEGGER #endif + +#define xPortInIsrContext() ((SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) == 0 ? pdFALSE : pdTRUE) From b0ea51af3202c1a1d23132b833aa018ba9e068ce Mon Sep 17 00:00:00 2001 From: WillyJL Date: Wed, 24 Sep 2025 04:59:21 +0200 Subject: [PATCH 18/18] Custom xPortInIsrContext() for RP2xx0 --- src/platform/nrf52/architecture.h | 1 + src/platform/rp2xx0/architecture.h | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index b1a2439e6..56b46088a 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -150,4 +150,5 @@ #define USE_SEGGER #endif +// Detect if running in ISR context (ARM Cortex-M4) #define xPortInIsrContext() ((SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) == 0 ? pdFALSE : pdTRUE) diff --git a/src/platform/rp2xx0/architecture.h b/src/platform/rp2xx0/architecture.h index 506c19c83..0c168ceee 100644 --- a/src/platform/rp2xx0/architecture.h +++ b/src/platform/rp2xx0/architecture.h @@ -35,4 +35,7 @@ #define HW_VENDOR meshtastic_HardwareModel_RP2040_FEATHER_RFM95 #elif defined(PRIVATE_HW) #define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW -#endif \ No newline at end of file +#endif + +// Detect if running in ISR context (ARM Cortex-M33 / RISC-V) +#define xPortInIsrContext() (__get_current_exception() == 0 ? pdFALSE : pdTRUE)