mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-03-30 13:45:38 +00:00
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>
294 lines
6.3 KiB
C++
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
|