Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/input/InputBroker.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ enum input_broker_event {
#define INPUT_BROKER_MSG_FN_SYMBOL_OFF 0xf2
#define INPUT_BROKER_MSG_BLUETOOTH_TOGGLE 0xAA
#define INPUT_BROKER_MSG_TAB 0x09
#define INPUT_BROKER_MSG_OPEN_FREETEXT 0x8E
#define INPUT_BROKER_MSG_EMOTE_LIST 0x8F

typedef struct _InputEvent {
Expand Down
134 changes: 105 additions & 29 deletions src/input/TCA8418Keyboard.cpp
Original file line number Diff line number Diff line change
@@ -1,51 +1,88 @@
#include "TCA8418Keyboard.h"
#include "modules/CannedMessageModule.h"

#define _TCA8418_COLS 3
#define _TCA8418_ROWS 4
#define _TCA8418_NUM_KEYS 12

#define _TCA8418_LONG_PRESS_THRESHOLD 2000
#define _TCA8418_LONG_PRESS_THRESHOLD 1000
#define _TCA8418_LONG_PRESS_REPEAT_INTERVAL 250
#define _TCA8418_MULTI_TAP_THRESHOLD 750

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 uint8_t TCA8418TapMod[_TCA8418_NUM_KEYS] = {13, 7, 7, 7, 7, 7, 9, 7, 9, 1, 2, 4};

static unsigned char TCA8418TapMap[_TCA8418_NUM_KEYS][13] = {
{'1', '.', ',', '?', '!', ':', ';', '-', '_', '\\', '/', '(', ')'}, // 1
{'2', 'a', 'b', 'c', 'A', 'B', 'C'}, // 2
{'3', 'd', 'e', 'f', 'D', 'E', 'F'}, // 3
{'4', 'g', 'h', 'i', 'G', 'H', 'I'}, // 4
{'5', 'j', 'k', 'l', 'J', 'K', 'L'}, // 5
{'6', 'm', 'n', 'o', 'M', 'N', 'O'}, // 6
{'7', 'p', 'q', 'r', 's', 'P', 'Q', 'R', 'S'}, // 7
{'8', 't', 'u', 'v', 'T', 'U', 'V'}, // 8
{'9', 'w', 'x', 'y', 'z', 'W', 'X', 'Y', 'Z'}, // 9
{'*', '+'}, // *
{'0', ' '}, // 0
{'#', '@'}, // #
{'.', ',', '?', '!', '1', ':', ';', '-', '_', '\\', '/', '(', ')'}, // 1
{'a', 'b', 'c', '2', 'A', 'B', 'C'}, // 2
{'d', 'e', 'f', '3', 'D', 'E', 'F'}, // 3
{'g', 'h', 'i', '4', 'G', 'H', 'I'}, // 4
{'j', 'k', 'l', '5', 'J', 'K', 'L'}, // 5
{'m', 'n', 'o', '6', 'M', 'N', 'O'}, // 6
{'p', 'q', 'r', 's', '7', 'P', 'Q', 'R', 'S'}, // 7
{'t', 'u', 'v', '8', 'T', 'U', 'V'}, // 8
{'w', 'x', 'y', 'z', '9', 'W', 'X', 'Y', 'Z'}, // 9
{Key::BSP}, // *
{' ', '0'}, // 0
{'#', '@', '*', '+'}, // #
};

static unsigned char TCA8418LongPressMap[_TCA8418_NUM_KEYS] = {
Key::ESC, // 1
Key::UP, // 2
Key::NONE, // 3
Key::TAB, // 3
Key::LEFT, // 4
Key::NONE, // 5
Key::SELECT, // 5
Key::RIGHT, // 6
Key::NONE, // 7
Key::DOWN, // 8
Key::NONE, // 9
Key::BSP, // *
Key::NONE, // 0
Key::NONE, // #
Key::NONE, // 0
Key::NONE // #
};

// key assignment when not in a free text entering state
static unsigned char TCA8418NavMap[_TCA8418_NUM_KEYS] = {
Key::ESC, // 1
Key::UP, // 2
Key::OPEN_FREETEXT, // 3
Key::LEFT, // 4
Key::SELECT, // 5
Key::RIGHT, // 6
Key::NONE, // 7
Key::DOWN, // 8
Key::NONE, // 9
Key::NONE, // *
Key::GPS_TOGGLE, // 0
Key::MUTE_TOGGLE // #
};


static bool isRepeatable(Key key)
{
return key == Key::UP || key == Key::DOWN || key == Key::LEFT || key == Key::RIGHT || key == Key::BSP;
}

static Key getRepeatKey(uint8_t key_index, bool is_char_input_allowed)
{
if (key_index >= _TCA8418_NUM_KEYS) {
return Key::NONE;
}

Key repeat_key = static_cast<Key>(is_char_input_allowed ? TCA8418LongPressMap[key_index] : TCA8418NavMap[key_index]);
return isRepeatable(repeat_key) ? repeat_key : Key::NONE;
}

TCA8418Keyboard::TCA8418Keyboard()
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), last_key(UINT8_MAX), next_key(UINT8_MAX), last_tap(0L), char_idx(0),
tap_interval(0), should_backspace(false)
tap_interval(0), should_backspace(false)
{
press_started_at = 0;
last_repeat = 0;
is_repeating_long_press = false;
}

void TCA8418Keyboard::reset()
Expand Down Expand Up @@ -97,6 +134,34 @@ void TCA8418Keyboard::pressed(uint8_t key)
// Store the current key as the last key
last_key = next_key;
last_tap = now;
press_started_at = now;
last_repeat = now;
is_repeating_long_press = false;
}

void TCA8418Keyboard::held()
{
if (state != Held || last_key >= _TCA8418_NUM_KEYS) {
return;
}

bool is_char_input_allowed = cannedMessageModule && cannedMessageModule->isCharInputAllowed();
unsigned char repeat_key = getRepeatKey(last_key, is_char_input_allowed);
if (repeat_key == Key::NONE) {
return;
}

uint32_t now = millis();
uint32_t held_interval = now - press_started_at;
if (held_interval <= _TCA8418_LONG_PRESS_THRESHOLD) {
return;
}

if (!is_repeating_long_press || (now - last_repeat) >= _TCA8418_LONG_PRESS_REPEAT_INTERVAL) {
queueEvent(repeat_key);
is_repeating_long_press = true;
last_repeat = now;
}
}

void TCA8418Keyboard::released()
Expand All @@ -110,20 +175,31 @@ void TCA8418Keyboard::released()
state = Idle;
return;
}

uint32_t now = millis();
int32_t held_interval = now - last_tap;
int32_t held_interval = now - press_started_at;
last_tap = now;
if (tap_interval < _TCA8418_MULTI_TAP_THRESHOLD && should_backspace) {
queueEvent(BSP);
}
if (held_interval > _TCA8418_LONG_PRESS_THRESHOLD) {
queueEvent(TCA8418LongPressMap[last_key]);

bool is_char_input_allowed = cannedMessageModule && cannedMessageModule->isCharInputAllowed();
if (is_char_input_allowed) {
if (tap_interval < _TCA8418_MULTI_TAP_THRESHOLD && should_backspace && TCA8418TapMap[last_key][(char_idx % TCA8418TapMod[last_key])] != Key::BSP) {
queueEvent(BSP);
}
if (held_interval > _TCA8418_LONG_PRESS_THRESHOLD) {
if (!is_repeating_long_press) {
queueEvent(TCA8418LongPressMap[last_key]);
// LOG_DEBUG("Long Press Key: %i Map: %i", last_key, TCA8418LongPressMap[last_key]);
}
} else {
queueEvent(TCA8418TapMap[last_key][(char_idx % TCA8418TapMod[last_key])]);
// LOG_DEBUG("Key Press: %i Index:%i if %i Map: %c", last_key, char_idx, TCA8418TapMod[last_key],
// TCA8418TapMap[last_key][(char_idx % TCA8418TapMod[last_key])]);
}
} else if (!is_repeating_long_press) {
queueEvent(TCA8418NavMap[last_key]);
// LOG_DEBUG("Long Press Key: %i Map: %i", last_key, TCA8418LongPressMap[last_key]);
} else {
queueEvent(TCA8418TapMap[last_key][(char_idx % TCA8418TapMod[last_key])]);
// LOG_DEBUG("Key Press: %i Index:%i if %i Map: %c", last_key, char_idx, TCA8418TapMod[last_key],
// TCA8418TapMap[last_key][(char_idx % TCA8418TapMod[last_key])]);
}
is_repeating_long_press = false;
}

void TCA8418Keyboard::setBacklight(bool on)
Expand Down
4 changes: 4 additions & 0 deletions src/input/TCA8418Keyboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ class TCA8418Keyboard : public TCA8418KeyboardBase

protected:
void pressed(uint8_t key) override;
void held(void) override;
void released(void) override;

uint8_t last_key;
uint8_t next_key;
uint32_t last_tap;
uint32_t press_started_at;
uint32_t last_repeat;
uint8_t char_idx;
int32_t tap_interval;
bool should_backspace;
bool is_repeating_long_press;
};
42 changes: 26 additions & 16 deletions src/input/TCA8418KeyboardBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,26 +148,31 @@ char TCA8418KeyboardBase::dequeueEvent()

void TCA8418KeyboardBase::trigger()
{
if (keyCount() == 0) {
if (state == Init) {
reset();
return;
}
if (state != Init) {
// 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;
} else {
if (state == Held) {
released();
}
state = Idle;
return;

if (keyCount() == 0) {
if (state == Held) {
held();
}
return;
}

// 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;
} else {
reset();
if (state == Held) {
released();
}
state = Idle;
return;
}
}

Expand All @@ -177,6 +182,11 @@ void TCA8418KeyboardBase::pressed(uint8_t key)
LOG_ERROR("pressed() not implemented in derived class");
}

void TCA8418KeyboardBase::held()
{
// optional in derived class
}

void TCA8418KeyboardBase::released()
{
// must be defined in derived class
Expand Down
2 changes: 2 additions & 0 deletions src/input/TCA8418KeyboardBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class TCA8418KeyboardBase
MUTE_TOGGLE = 0xAC,
SEND_PING = 0xAF,
BL_TOGGLE = 0xAB,
OPEN_FREETEXT = 0x8E,
FUNCTION_F1 = 0xF1,
FUNCTION_F2 = 0xF2,
FUNCTION_F3 = 0xF3,
Expand Down Expand Up @@ -128,6 +129,7 @@ class TCA8418KeyboardBase
};

virtual void pressed(uint8_t key);
virtual void held(void);
virtual void released(void);

virtual void queueEvent(char);
Expand Down
4 changes: 4 additions & 0 deletions src/input/kbI2cBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,10 @@ int32_t KbI2cBase::runOnce()
e.inputEvent = INPUT_BROKER_ANYKEY;
e.kbchar = INPUT_BROKER_MSG_TAB;
break;
case TCA8418KeyboardBase::OPEN_FREETEXT:
e.inputEvent = INPUT_BROKER_ANYKEY;
e.kbchar = INPUT_BROKER_MSG_OPEN_FREETEXT;
break;
case TCA8418KeyboardBase::FUNCTION_F1:
e.inputEvent = INPUT_BROKER_FN_F1;
e.kbchar = 0x00;
Expand Down
41 changes: 41 additions & 0 deletions src/modules/CannedMessageModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,31 @@ static NodeNum lastDest = NODENUM_BROADCAST;
static uint8_t lastChannel = 0;
static bool lastDestSet = false;

static void applyComposeContextFromCurrentThread(NodeNum &dest, uint8_t &channel)
{
switch (graphics::MessageRenderer::getThreadMode()) {
case graphics::MessageRenderer::ThreadMode::DIRECT: {
const uint32_t peer = graphics::MessageRenderer::getThreadPeer();
if (peer != 0) {
dest = peer;
channel = 0;
}
break;
}
case graphics::MessageRenderer::ThreadMode::CHANNEL: {
const int threadChannel = graphics::MessageRenderer::getThreadChannel();
if (threadChannel >= 0 && threadChannel < channels.getNumChannels()) {
dest = NODENUM_BROADCAST;
channel = static_cast<uint8_t>(threadChannel);
}
break;
}
case graphics::MessageRenderer::ThreadMode::ALL:
default:
break;
}
}

meshtastic_CannedMessageModuleConfig cannedMessageModuleConfig;

CannedMessageModule *cannedMessageModule;
Expand Down Expand Up @@ -452,8 +477,24 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event)
LaunchWithDestination(NODENUM_BROADCAST);
return 1;
}
// Allow opening free text compose without printing a char
if (event->kbchar == INPUT_BROKER_MSG_OPEN_FREETEXT) {
applyComposeContextFromCurrentThread(dest, channel);
lastDest = dest;
lastChannel = channel;
lastDestSet = true;
updateState(CANNED_MESSAGE_RUN_STATE_FREETEXT, true);
UIFrameEvent e;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
notifyObservers(&e);
return 1;
}
// Printable char (ASCII) opens free text compose
if (event->kbchar >= 32 && event->kbchar <= 126) {
applyComposeContextFromCurrentThread(dest, channel);
lastDest = dest;
lastChannel = channel;
lastDestSet = true;
updateState(CANNED_MESSAGE_RUN_STATE_FREETEXT, true);
UIFrameEvent e;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
Expand Down
2 changes: 1 addition & 1 deletion variants/esp32s3/heltec_v4/variant.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,4 @@
// Seems to be missing on this new board
#define GPS_TX_PIN (38) // This is for bits going TOWARDS the CPU
#define GPS_RX_PIN (39) // This is for bits going TOWARDS the GPS
#define GPS_THREAD_INTERVAL 50
#define GPS_THREAD_INTERVAL 50