Files
pyxis/lib/tdeck_ui/Hardware/TDeck/Keyboard.cpp
torlando-tech ac6ceca9f8 Initial commit: standalone Pyxis T-Deck firmware
Split T-Deck firmware from microReticulum examples/lxmf_tdeck/ into its
own repo. microReticulum is consumed as a git submodule dependency pinned
to feat/t-deck. All include paths updated from relative symlinks to bare
includes resolved via library build flags.

Both tdeck (NimBLE) and tdeck-bluedroid environments compile successfully.
Licensed under AGPLv3.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 19:48:33 -05:00

294 lines
6.3 KiB
C++

// Copyright (c) 2024 microReticulum contributors
// SPDX-License-Identifier: MIT
#include "Keyboard.h"
#ifdef ARDUINO
#include "Log.h"
using namespace RNS;
namespace Hardware {
namespace TDeck {
TwoWire* Keyboard::_wire = nullptr;
bool Keyboard::_initialized = false;
lv_indev_t* Keyboard::_indev = nullptr;
char Keyboard::_key_buffer[Kbd::MAX_KEYS_BUFFERED];
uint8_t Keyboard::_buffer_head = 0;
uint8_t Keyboard::_buffer_tail = 0;
uint8_t Keyboard::_buffer_count = 0;
uint32_t Keyboard::_last_poll_time = 0;
uint32_t Keyboard::_last_key_time = 0;
bool Keyboard::init(TwoWire& wire) {
if (_initialized) {
return true;
}
INFO("Initializing T-Deck keyboard");
// Initialize hardware first
if (!init_hardware_only(wire)) {
return false;
}
// Register LVGL input device
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_KEYPAD;
indev_drv.read_cb = lvgl_read_cb;
_indev = lv_indev_drv_register(&indev_drv);
if (!_indev) {
ERROR("Failed to register keyboard with LVGL");
return false;
}
INFO("Keyboard initialized successfully");
return true;
}
lv_indev_t* Keyboard::get_indev() {
return _indev;
}
bool Keyboard::init_hardware_only(TwoWire& wire) {
if (_initialized) {
return true;
}
INFO("Initializing keyboard hardware");
_wire = &wire;
// Verify keyboard is present by checking I2C address
_wire->beginTransmission(I2C::KEYBOARD_ADDR);
uint8_t result = _wire->endTransmission();
if (result != 0) {
WARNING(" Keyboard not found on I2C bus");
_wire = nullptr;
return false;
}
// Clear any pending keys by reading and discarding
_wire->requestFrom(I2C::KEYBOARD_ADDR, (uint8_t)1);
while (_wire->available()) {
_wire->read();
}
// Clear buffer
clear_buffer();
_initialized = true;
INFO(" Keyboard hardware ready");
return true;
}
uint8_t Keyboard::poll() {
if (!_initialized || !_wire) {
return 0;
}
uint32_t now = millis();
if (now - _last_poll_time < Kbd::POLL_INTERVAL_MS) {
return 0; // Don't poll too frequently
}
_last_poll_time = now;
// T-Deck keyboard uses simple protocol: just read 1 byte directly
// Returns ASCII key code if pressed, 0 if no key
uint8_t bytes_read = _wire->requestFrom(I2C::KEYBOARD_ADDR, (uint8_t)1);
if (bytes_read != 1) {
return 0;
}
uint8_t key = _wire->read();
// No key pressed or invalid
if (key == KEY_NONE || key == 0xFF) {
return 0;
}
// Add key to buffer
buffer_push((char)key);
return 1;
}
char Keyboard::read_key() {
if (_buffer_count == 0) {
return 0;
}
return buffer_pop();
}
bool Keyboard::available() {
return _buffer_count > 0;
}
uint8_t Keyboard::get_key_count() {
return _buffer_count;
}
void Keyboard::clear_buffer() {
_buffer_head = 0;
_buffer_tail = 0;
_buffer_count = 0;
}
uint8_t Keyboard::get_firmware_version() {
uint8_t version = 0;
if (!read_register(Register::REG_VERSION, &version)) {
return 0;
}
return version;
}
void Keyboard::set_backlight(uint8_t brightness) {
if (!_wire || !_initialized) {
return;
}
// Command 0x01 = LILYGO_KB_BRIGHTNESS_CMD
_wire->beginTransmission(I2C::KEYBOARD_ADDR);
_wire->write(0x01);
_wire->write(brightness);
_wire->endTransmission();
}
void Keyboard::backlight_on() {
set_backlight(255);
}
void Keyboard::backlight_off() {
set_backlight(0);
}
void Keyboard::lvgl_read_cb(lv_indev_drv_t* drv, lv_indev_data_t* data) {
// Safety check - LVGL should never pass null but be defensive
if (!data) {
return;
}
// Skip if not properly initialized
if (!_initialized || !_wire) {
data->state = LV_INDEV_STATE_RELEASED;
data->key = 0;
data->continue_reading = false;
return;
}
// Poll for new keys
poll();
// Read next key from buffer
char key = read_key();
if (key != 0) {
data->state = LV_INDEV_STATE_PRESSED;
data->key = key;
} else {
data->state = LV_INDEV_STATE_RELEASED;
data->key = 0;
}
// Continue reading is set automatically by LVGL based on state
data->continue_reading = (available() > 0);
}
bool Keyboard::write_register(uint8_t reg, uint8_t value) {
if (!_wire) {
return false;
}
_wire->beginTransmission(I2C::KEYBOARD_ADDR);
_wire->write(reg);
_wire->write(value);
uint8_t result = _wire->endTransmission();
return (result == 0);
}
bool Keyboard::read_register(uint8_t reg, uint8_t* value) {
if (!_wire) {
return false;
}
// Write register address
_wire->beginTransmission(I2C::KEYBOARD_ADDR);
_wire->write(reg);
uint8_t result = _wire->endTransmission(false); // Send repeated start
if (result != 0) {
return false;
}
// Read register value
if (_wire->requestFrom(I2C::KEYBOARD_ADDR, (uint8_t)1) != 1) {
return false;
}
*value = _wire->read();
return true;
}
bool Keyboard::read_registers(uint8_t reg, uint8_t* buffer, size_t len) {
if (!_wire) {
return false;
}
// Write register address
_wire->beginTransmission(I2C::KEYBOARD_ADDR);
_wire->write(reg);
uint8_t result = _wire->endTransmission(false); // Send repeated start
if (result != 0) {
return false;
}
// Read register values
if (_wire->requestFrom(I2C::KEYBOARD_ADDR, (uint8_t)len) != len) {
return false;
}
for (size_t i = 0; i < len; i++) {
buffer[i] = _wire->read();
}
return true;
}
void Keyboard::buffer_push(char key) {
if (_buffer_count >= Kbd::MAX_KEYS_BUFFERED) {
// Buffer full, drop oldest key
buffer_pop();
}
_key_buffer[_buffer_tail] = key;
_buffer_tail = (_buffer_tail + 1) % Kbd::MAX_KEYS_BUFFERED;
_buffer_count++;
_last_key_time = millis();
}
uint32_t Keyboard::get_last_key_time() {
return _last_key_time;
}
char Keyboard::buffer_pop() {
if (_buffer_count == 0) {
return 0;
}
char key = _key_buffer[_buffer_head];
_buffer_head = (_buffer_head + 1) % Kbd::MAX_KEYS_BUFFERED;
_buffer_count--;
return key;
}
} // namespace TDeck
} // namespace Hardware
#endif // ARDUINO