mirror of
https://github.com/meshtastic/firmware.git
synced 2025-10-27 06:54:37 +00:00
Merge de69e3554e into 13c4c2037d
This commit is contained in:
commit
02d907528e
@ -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();
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
#include "TCA8418KeyboardBase.h"
|
||||
#include "configuration.h"
|
||||
#include "sleep.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
@ -32,8 +33,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),
|
||||
writeCallback(nullptr)
|
||||
: rows(rows), columns(columns), queue(""), m_wire(nullptr), m_addr(0), readCallback(nullptr), writeCallback(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@ -43,6 +43,20 @@ void TCA8418KeyboardBase::begin(uint8_t addr, TwoWire *wire)
|
||||
m_wire = wire;
|
||||
m_wire->begin();
|
||||
reset();
|
||||
|
||||
#ifdef KB_INT
|
||||
::pinMode(KB_INT, INPUT_PULLUP);
|
||||
attachInterruptHandler();
|
||||
enableInterrupts();
|
||||
|
||||
#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)
|
||||
@ -82,9 +96,43 @@ void TCA8418KeyboardBase::reset()
|
||||
matrix(rows, columns);
|
||||
enableDebounce();
|
||||
flush();
|
||||
state = Idle;
|
||||
}
|
||||
|
||||
#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)
|
||||
@ -148,27 +196,21 @@ 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();
|
||||
}
|
||||
|
||||
#ifdef KB_INT
|
||||
// Reset interrupt mask so we can receive future interrupts
|
||||
clearInt();
|
||||
#endif
|
||||
}
|
||||
|
||||
void TCA8418KeyboardBase::pressed(uint8_t key)
|
||||
@ -177,7 +219,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");
|
||||
@ -309,6 +351,8 @@ void TCA8418KeyboardBase::disableInterrupts()
|
||||
writeRegister(TCA8418_REG_CFG, value);
|
||||
};
|
||||
|
||||
TCA8418KeyboardBase *TCA8418KeyboardBase::interruptInstance;
|
||||
|
||||
void TCA8418KeyboardBase::enableMatrixOverflow()
|
||||
{
|
||||
uint8_t value = readRegister(TCA8418_REG_CFG);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
// Based on the MPR121 Keyboard and Adafruit TCA8418 library
|
||||
#include "configuration.h"
|
||||
#include "kbInterrupt.h"
|
||||
#include <Wire.h>
|
||||
|
||||
/**
|
||||
@ -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 {
|
||||
@ -48,8 +49,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,
|
||||
@ -123,7 +122,7 @@ class TCA8418KeyboardBase
|
||||
};
|
||||
|
||||
virtual void pressed(uint8_t key);
|
||||
virtual void released(void);
|
||||
virtual void released(uint8_t key);
|
||||
|
||||
virtual void queueEvent(char);
|
||||
|
||||
@ -146,6 +145,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();
|
||||
@ -161,12 +161,31 @@ class TCA8418KeyboardBase
|
||||
protected:
|
||||
uint8_t rows;
|
||||
uint8_t columns;
|
||||
KeyState state;
|
||||
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<TCA8418KeyboardBase, void *> lsObserver =
|
||||
CallbackObserver<TCA8418KeyboardBase, void *>(this, &TCA8418KeyboardBase::beforeLightSleep);
|
||||
CallbackObserver<TCA8418KeyboardBase, esp_sleep_wakeup_cause_t> lsEndObserver =
|
||||
CallbackObserver<TCA8418KeyboardBase, esp_sleep_wakeup_cause_t>(this, &TCA8418KeyboardBase::afterLightSleep);
|
||||
#endif
|
||||
};
|
||||
|
||||
@ -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,11 @@ 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), pressedKeysCount(0), onlyOneModifierPressed(false),
|
||||
persistedPreviousModifier(false)
|
||||
{
|
||||
}
|
||||
|
||||
@ -74,91 +74,73 @@ 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;
|
||||
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)
|
||||
{
|
||||
int8_t key_index = keyToIndex(key);
|
||||
if (key_index < 0)
|
||||
return;
|
||||
|
||||
if (!TDeckProHeldMap[key_index]) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t now = millis();
|
||||
last_tap = now;
|
||||
|
||||
if (TDeckProTapMap[last_key][modifierFlag % TDeckProTapMod[last_key]] == Key::BL_TOGGLE) {
|
||||
if (TDeckProTapMap[key_index][modifierFlag % TDeckProTapMod[key_index]] == Key::BL_TOGGLE) {
|
||||
toggleBacklight();
|
||||
return;
|
||||
} else {
|
||||
queueEvent(TDeckProTapMap[key_index][modifierFlag % TDeckProTapMod[key_index]]);
|
||||
}
|
||||
|
||||
queueEvent(TDeckProTapMap[last_key][modifierFlag % TDeckProTapMod[last_key]]);
|
||||
if (isModifierKey(last_key) == false)
|
||||
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)
|
||||
@ -175,22 +157,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
|
||||
@ -5,23 +5,20 @@ 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;
|
||||
uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed
|
||||
uint8_t pressedKeysCount;
|
||||
bool onlyOneModifierPressed;
|
||||
bool persistedPreviousModifier;
|
||||
};
|
||||
|
||||
@ -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,48 @@ 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), 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);
|
||||
@ -85,24 +87,6 @@ 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)
|
||||
{
|
||||
uint32_t _brightness = 0;
|
||||
@ -115,78 +99,78 @@ void TLoraPagerKeyboard::setBacklight(bool on)
|
||||
#endif
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
TLoraPagerHeldMap[key_index] = true;
|
||||
pressedKeysCount++;
|
||||
|
||||
last_key = next_key;
|
||||
last_tap = now;
|
||||
uint8_t key_modifier = keyToModifierFlag(key_index);
|
||||
if (key_modifier && pressedKeysCount == 1) {
|
||||
onlyOneModifierPressed = true;
|
||||
} else {
|
||||
onlyOneModifierPressed = false;
|
||||
}
|
||||
modifierFlag |= key_modifier;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
} else {
|
||||
queueEvent(TLoraPagerTapMap[key_index][modifierFlag % TLoraPagerTapMod[key_index]]);
|
||||
}
|
||||
|
||||
queueEvent(TLoraPagerTapMap[last_key][modifierFlag % TLoraPagerTapMod[last_key]]);
|
||||
if (isModifierKey(last_key) == false)
|
||||
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()
|
||||
@ -215,18 +199,19 @@ void TLoraPagerKeyboard::toggleBacklight(bool off)
|
||||
setBacklight(true);
|
||||
}
|
||||
|
||||
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
|
||||
@ -5,26 +5,23 @@ 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;
|
||||
uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed
|
||||
uint8_t pressedKeysCount;
|
||||
bool onlyOneModifierPressed;
|
||||
bool persistedPreviousModifier;
|
||||
uint32_t brightness = 0;
|
||||
};
|
||||
|
||||
@ -44,8 +44,106 @@ 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
|
||||
#ifdef KB_INT
|
||||
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);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_WARN("Unknown kb_model 0x%02x", kb_model);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t KbI2cBase::runOnce()
|
||||
{
|
||||
// Called periodically at 300ms interval
|
||||
if (!i2cBus) {
|
||||
switch (cardkb_found.port) {
|
||||
case ScanI2C::WIRE1:
|
||||
@ -61,6 +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
|
||||
@ -76,6 +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:
|
||||
@ -249,6 +357,7 @@ int32_t KbI2cBase::runOnce()
|
||||
break;
|
||||
}
|
||||
case 0x84: { // Adafruit TCA8418
|
||||
#ifndef KB_INT
|
||||
TCAKeyboard.trigger();
|
||||
InputEvent e = {};
|
||||
while (TCAKeyboard.hasEvent()) {
|
||||
@ -331,9 +440,8 @@ int32_t KbI2cBase::runOnce()
|
||||
LOG_DEBUG("TCA8418 Notifying: %i Char: %c", e.inputEvent, e.kbchar);
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
TCAKeyboard.trigger();
|
||||
}
|
||||
TCAKeyboard.clearInt();
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 0x02: {
|
||||
@ -528,3 +636,12 @@ void KbI2cBase::toggleBacklight(bool on)
|
||||
TCAKeyboard.setBacklight(on);
|
||||
#endif
|
||||
}
|
||||
|
||||
int KbI2cBase::onNotify(KbInterruptObservable *src)
|
||||
{
|
||||
// Called from interrupt context, request polling after exiting the ISR
|
||||
#ifdef KB_INT
|
||||
inputBroker->requestPollSoon(this);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
@ -5,16 +5,22 @@
|
||||
#include "MPR121Keyboard.h"
|
||||
#include "Wire.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "kbInterrupt.h"
|
||||
|
||||
class TCA8418KeyboardBase;
|
||||
|
||||
class KbI2cBase : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||
class KbI2cBase : public Observable<const InputEvent *>,
|
||||
public InputPollable,
|
||||
public KbInterruptObserver,
|
||||
public concurrency::OSThread
|
||||
{
|
||||
public:
|
||||
explicit KbI2cBase(const char *name);
|
||||
void toggleBacklight(bool on);
|
||||
virtual void pollOnce() override;
|
||||
|
||||
protected:
|
||||
virtual int onNotify(KbInterruptObservable *src) override;
|
||||
virtual int32_t runOnce() override;
|
||||
|
||||
private:
|
||||
|
||||
11
src/input/kbInterrupt.h
Normal file
11
src/input/kbInterrupt.h
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "Observer.h"
|
||||
|
||||
class KbInterruptObservable : public Observable<KbInterruptObservable *>
|
||||
{
|
||||
};
|
||||
|
||||
class KbInterruptObserver : public Observer<KbInterruptObservable *>
|
||||
{
|
||||
};
|
||||
@ -43,6 +43,7 @@
|
||||
|
||||
// TCA8418 keyboard
|
||||
#define KB_BL_PIN 42
|
||||
#define KB_INT 15
|
||||
#define CANNED_MESSAGE_MODULE_ENABLE 1
|
||||
|
||||
// microphone PCM5102A
|
||||
|
||||
Loading…
Reference in New Issue
Block a user