Skip to content
Merged
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
5 changes: 4 additions & 1 deletion Data/data/settings/system.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
language=en-US
timeFormat24h=true
timeFormat24h=true
dateFormat=MM/DD/YYYY
region=US
timezone=America/Los_Angeles
4 changes: 4 additions & 0 deletions Devices/lilygo-tdeck/Source/Configuration.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#include "devices/Display.h"
#include "devices/KeyboardBacklight.h"
#include "devices/Power.h"
#include "devices/Sdcard.h"
#include "devices/TdeckKeyboard.h"
#include "devices/TrackballDevice.h"

#include <Tactility/hal/Configuration.h>
#include <Tactility/lvgl/LvglSync.h>
Expand All @@ -15,6 +17,8 @@ static std::vector<std::shared_ptr<Device>> createDevices() {
createPower(),
createDisplay(),
std::make_shared<TdeckKeyboard>(),
std::make_shared<KeyboardBacklightDevice>(),
std::make_shared<TrackballDevice>(),
createSdCard()
};
}
Expand Down
46 changes: 46 additions & 0 deletions Devices/lilygo-tdeck/Source/Init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

#include <Tactility/TactilityCore.h>
#include <Tactility/hal/gps/GpsConfiguration.h>
#include <Tactility/settings/KeyboardSettings.h>

#include "devices/KeyboardBacklight.h"
#include "devices/TrackballDevice.h"
#include <KeyboardBacklight/KeyboardBacklight.h>
#include <Trackball/Trackball.h>

#define TAG "tdeck"

Expand Down Expand Up @@ -59,5 +65,45 @@ bool initBoot() {
}
}
});

tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent event) {
auto kbBacklight = tt::hal::findDevice("Keyboard Backlight");
if (kbBacklight != nullptr) {
TT_LOG_I(TAG, "%s starting", kbBacklight->getName().c_str());
auto kbDevice = std::static_pointer_cast<KeyboardBacklightDevice>(kbBacklight);
if (kbDevice->start()) {
TT_LOG_I(TAG, "%s started", kbBacklight->getName().c_str());
} else {
TT_LOG_E(TAG, "%s start failed", kbBacklight->getName().c_str());
}
}

auto trackball = tt::hal::findDevice("Trackball");
if (trackball != nullptr) {
TT_LOG_I(TAG, "%s starting", trackball->getName().c_str());
auto tbDevice = std::static_pointer_cast<TrackballDevice>(trackball);
if (tbDevice->start()) {
TT_LOG_I(TAG, "%s started", trackball->getName().c_str());
} else {
TT_LOG_E(TAG, "%s start failed", trackball->getName().c_str());
}
}

// Backlight doesn't seem to turn on until toggled on and off from keyboard settings...
// Or let the display and backlight sleep then wake it up.
// Then it works fine...until reboot, then you need to toggle again.
// The current keyboard firmware sets backlight duty to 0 on boot.
// https://github.com/Xinyuan-LilyGO/T-Deck/blob/master/firmware/T-Keyboard_Keyboard_ESP32C3_250620.bin
// https://github.com/Xinyuan-LilyGO/T-Deck/blob/master/examples/Keyboard_ESP32C3/Keyboard_ESP32C3.ino#L25
// https://github.com/Xinyuan-LilyGO/T-Deck/blob/master/examples/Keyboard_ESP32C3/Keyboard_ESP32C3.ino#L217
auto kbSettings = tt::settings::keyboard::loadOrGetDefault();
bool result = keyboardbacklight::setBrightness(kbSettings.backlightEnabled ? kbSettings.backlightBrightness : 0);
if (!result) {
TT_LOG_W(TAG, "Failed to set keyboard backlight brightness");
}

trackball::setEnabled(kbSettings.trackballEnabled);
});

return true;
}
109 changes: 109 additions & 0 deletions Devices/lilygo-tdeck/Source/KeyboardBacklight/KeyboardBacklight.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#include "KeyboardBacklight.h"
#include <esp_log.h>
#include <cstring>

static const char* TAG = "KeyboardBacklight";

namespace keyboardbacklight {

static const uint8_t CMD_BRIGHTNESS = 0x01;
static const uint8_t CMD_DEFAULT_BRIGHTNESS = 0x02;

static i2c_port_t g_i2cPort = I2C_NUM_MAX;
static uint8_t g_slaveAddress = 0x55;
static uint8_t g_currentBrightness = 127;

// TODO: Umm...something. Calls xxxBrightness, ignores return values.
bool init(i2c_port_t i2cPort, uint8_t slaveAddress) {
g_i2cPort = i2cPort;
g_slaveAddress = slaveAddress;

ESP_LOGI(TAG, "Keyboard backlight initialized on I2C port %d, address 0x%02X", g_i2cPort, g_slaveAddress);

// Set a reasonable default brightness
if (!setDefaultBrightness(127)) {
ESP_LOGE(TAG, "Failed to set default brightness");
return false;
}

if (!setBrightness(127)) {
ESP_LOGE(TAG, "Failed to set brightness");
return false;
}

return true;
}

bool setBrightness(uint8_t brightness) {
if (g_i2cPort >= I2C_NUM_MAX) {
ESP_LOGE(TAG, "Keyboard backlight not initialized");
return false;
}

// Skip if brightness is already at target value (avoid I2C spam on every keypress)
if (brightness == g_currentBrightness) {
return true;
}

ESP_LOGI(TAG, "Setting brightness to %d on I2C port %d, address 0x%02X", brightness, g_i2cPort, g_slaveAddress);

i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (g_slaveAddress << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, CMD_BRIGHTNESS, true);
i2c_master_write_byte(cmd, brightness, true);
i2c_master_stop(cmd);

esp_err_t ret = i2c_master_cmd_begin(g_i2cPort, cmd, pdMS_TO_TICKS(100));
i2c_cmd_link_delete(cmd);

if (ret == ESP_OK) {
g_currentBrightness = brightness;
ESP_LOGI(TAG, "Successfully set brightness to %d", brightness);
return true;
} else {
ESP_LOGE(TAG, "Failed to set brightness: %s (0x%x)", esp_err_to_name(ret), ret);
return false;
}
}

bool setDefaultBrightness(uint8_t brightness) {
if (g_i2cPort >= I2C_NUM_MAX) {
ESP_LOGE(TAG, "Keyboard backlight not initialized");
return false;
}

// Clamp to valid range for default brightness
if (brightness < 30) {
brightness = 30;
}

i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (g_slaveAddress << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, CMD_DEFAULT_BRIGHTNESS, true);
i2c_master_write_byte(cmd, brightness, true);
i2c_master_stop(cmd);

esp_err_t ret = i2c_master_cmd_begin(g_i2cPort, cmd, pdMS_TO_TICKS(100));
i2c_cmd_link_delete(cmd);

if (ret == ESP_OK) {
ESP_LOGD(TAG, "Set default brightness to %d", brightness);
return true;
} else {
ESP_LOGE(TAG, "Failed to set default brightness: %s", esp_err_to_name(ret));
return false;
}
}

uint8_t getBrightness() {
if (g_i2cPort >= I2C_NUM_MAX) {
ESP_LOGE(TAG, "Keyboard backlight not initialized");
return 0;
}

return g_currentBrightness;
}

}
36 changes: 36 additions & 0 deletions Devices/lilygo-tdeck/Source/KeyboardBacklight/KeyboardBacklight.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#pragma once

#include <driver/i2c.h>
#include <cstdint>

namespace keyboardbacklight {

/**
* @brief Initialize keyboard backlight control
* @param i2cPort I2C port number (I2C_NUM_0 or I2C_NUM_1)
* @param slaveAddress I2C slave address (default 0x55 for T-Deck keyboard)
* @return true if initialization succeeded
*/
bool init(i2c_port_t i2cPort, uint8_t slaveAddress = 0x55);

/**
* @brief Set keyboard backlight brightness
* @param brightness Brightness level (0-255, 0=off, 255=max)
* @return true if command succeeded
*/
bool setBrightness(uint8_t brightness);

/**
* @brief Set default keyboard backlight brightness for ALT+B toggle
* @param brightness Default brightness level (30-255)
* @return true if command succeeded
*/
bool setDefaultBrightness(uint8_t brightness);

/**
* @brief Get current keyboard backlight brightness
* @return Current brightness level (0-255)
*/
uint8_t getBrightness();

}
145 changes: 145 additions & 0 deletions Devices/lilygo-tdeck/Source/Trackball/Trackball.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#include "Trackball.h"
#include <esp_log.h>

static const char* TAG = "Trackball";

namespace trackball {

static TrackballConfig g_config;
static lv_indev_t* g_indev = nullptr;
static bool g_initialized = false;
static bool g_enabled = true;

// Track last GPIO states for edge detection
static bool g_lastState[5] = {false, false, false, false, false};

static void read_cb(lv_indev_t* indev, lv_indev_data_t* data) {
if (!g_initialized || !g_enabled) {
data->state = LV_INDEV_STATE_RELEASED;
data->enc_diff = 0;
return;
}

const gpio_num_t pins[5] = {
g_config.pinRight,
g_config.pinUp,
g_config.pinLeft,
g_config.pinDown,
g_config.pinClick
};

// Read GPIO states and detect changes (active low with pull-up)
bool currentStates[5];
for (int i = 0; i < 5; i++) {
currentStates[i] = gpio_get_level(pins[i]) == 0;
}

// Process directional inputs as encoder steps
// Right/Down = positive diff (next item), Left/Up = negative diff (prev item)
int16_t diff = 0;

// Right pressed (rising edge)
if (currentStates[0] && !g_lastState[0]) {
diff += g_config.movementStep;
}
// Up pressed (rising edge)
if (currentStates[1] && !g_lastState[1]) {
diff -= g_config.movementStep;
}
// Left pressed (rising edge)
if (currentStates[2] && !g_lastState[2]) {
diff -= g_config.movementStep;
}
// Down pressed (rising edge)
if (currentStates[3] && !g_lastState[3]) {
diff += g_config.movementStep;
}

// Update last states
for (int i = 0; i < 5; i++) {
g_lastState[i] = currentStates[i];
}

// Update encoder diff and button state
data->enc_diff = diff;
data->state = currentStates[4] ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;

// Trigger activity for wake-on-trackball
if (diff != 0 || currentStates[4]) {
lv_disp_trig_activity(nullptr);
}
}

lv_indev_t* init(const TrackballConfig& config) {
if (g_initialized) {
ESP_LOGW(TAG, "Trackball already initialized");
return g_indev;
}

g_config = config;

// Set default movement step if not specified
if (g_config.movementStep == 0) {
g_config.movementStep = 10;
}

// Configure all GPIO pins as inputs with pull-ups (active low)
const gpio_num_t pins[5] = {
config.pinRight,
config.pinUp,
config.pinLeft,
config.pinDown,
config.pinClick
};

gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;

for (int i = 0; i < 5; i++) {
io_conf.pin_bit_mask = (1ULL << pins[i]);
gpio_config(&io_conf);
g_lastState[i] = gpio_get_level(pins[i]) == 0;
}

// Register as LVGL encoder input device for group navigation
g_indev = lv_indev_create();
lv_indev_set_type(g_indev, LV_INDEV_TYPE_ENCODER);
lv_indev_set_read_cb(g_indev, read_cb);

if (g_indev) {
g_initialized = true;
ESP_LOGI(TAG, "Trackball initialized as encoder (R:%d U:%d L:%d D:%d Click:%d)",
config.pinRight, config.pinUp, config.pinLeft, config.pinDown,
config.pinClick);
return g_indev;
} else {
ESP_LOGE(TAG, "Failed to register LVGL input device");
return nullptr;
}
}

void deinit() {
if (g_indev) {
lv_indev_delete(g_indev);
g_indev = nullptr;
}
g_initialized = false;
ESP_LOGI(TAG, "Trackball deinitialized");
}

void setMovementStep(uint8_t step) {
if (step > 0) {
g_config.movementStep = step;
ESP_LOGD(TAG, "Movement step set to %d", step);
}
}

void setEnabled(bool enabled) {
g_enabled = enabled;
ESP_LOGI(TAG, "Trackball %s", enabled ? "enabled" : "disabled");
}

}
Loading