Skip to content
Open
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
6 changes: 4 additions & 2 deletions Devices/m5stack-tab5/Source/Configuration.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#include "devices/Display.h"
#include "devices/SdCard.h"
#include "devices/Power.h"
#include "devices/Tab5Keyboard.h"

Expand All @@ -8,6 +7,10 @@

#include <Tactility/hal/Configuration.h>
#include <Tactility/hal/i2c/I2c.h>
#include <Tactility/hal/keyboard/KeyboardDevice.h>
#include <Tactility/kernel/SystemEvents.h>
#include <Tactility/lvgl/LvglSync.h>
#include <Tactility/settings/DisplaySettings.h>

using namespace tt::hal;

Expand All @@ -18,7 +21,6 @@ static DeviceVector createDevices() {
return {
createPower(),
createDisplay(),
createSdCard(),
std::make_shared<Tab5Keyboard>(i2c2)
};
}
Expand Down
2 changes: 1 addition & 1 deletion Devices/m5stack-tab5/Source/devices/Ili9881cDisplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ bool Ili9881cDisplay::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, cons
.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
.in_color_format = LCD_COLOR_FMT_RGB565,
.out_color_format = LCD_COLOR_FMT_RGB565,
.num_fbs = 1, // TODO: 2?
.num_fbs = 2,
.video_timing =
{
.h_size = 720,
Expand Down
26 changes: 0 additions & 26 deletions Devices/m5stack-tab5/Source/devices/SdCard.cpp

This file was deleted.

8 changes: 0 additions & 8 deletions Devices/m5stack-tab5/Source/devices/SdCard.h

This file was deleted.

2 changes: 1 addition & 1 deletion Devices/m5stack-tab5/Source/devices/St7123Display.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ bool St7123Display::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, const
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
.dpi_clock_freq_mhz = 70,
.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
.num_fbs = 1,
.num_fbs = 2,
.video_timing = {
.h_size = 720,
.v_size = 1280,
Expand Down
147 changes: 131 additions & 16 deletions Devices/m5stack-tab5/Source/devices/Tab5Keyboard.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "Tab5Keyboard.h"
#include <Tactility/app/App.h>
#include <Tactility/lvgl/Keyboard.h>
#include <Tactility/lvgl/LvglSync.h>
#include <tactility/drivers/i2c_controller.h>
#include <tactility/log.h>
#include <esp_timer.h>
Expand Down Expand Up @@ -329,6 +331,103 @@ void Tab5Keyboard::processKeyboard() {
}
}
}

checkAttachState();
}

// ---------------------------------------------------------------------------
// applyAutoRotation - on attach, switches to landscape if not already (saving
// the prior rotation); on detach, restores the saved rotation if we were the
// ones who changed it. Only affects the live LVGL rotation, never persisted
// display settings.
// ---------------------------------------------------------------------------
bool Tab5Keyboard::applyAutoRotation(bool keyboardAttached) {
auto* display = lv_indev_get_display(kbHandle);
if (display == nullptr) {
return false;
}

if (!tt::lvgl::lock(pdMS_TO_TICKS(100))) {
return false; // retry next poll
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

if (keyboardAttached) {
if (lv_display_get_rotation(display) != LV_DISPLAY_ROTATION_90) {
savedRotation = lv_display_get_rotation(display);
rotationOverrideActive = true;
lv_display_set_rotation(display, LV_DISPLAY_ROTATION_90);
}
} else {
// Only restore if rotation is still what we set it to - if the user manually
// changed it since attaching, respect their choice instead.
if (rotationOverrideActive && lv_display_get_rotation(display) == LV_DISPLAY_ROTATION_90) {
lv_display_set_rotation(display, savedRotation);
}
rotationOverrideActive = false;
}

tt::lvgl::unlock();
return true;
}

// ---------------------------------------------------------------------------
// checkAttachState - throttled (~1s) hot-plug detection. Reapplies device
// register configuration and auto-rotation on detach/attach transitions.
// ---------------------------------------------------------------------------
void Tab5Keyboard::checkAttachState() {
static constexpr uint32_t ATTACH_CHECK_TICKS = 50; // ~1s at 20ms/tick

if (++attachCheckTickCounter < ATTACH_CHECK_TICKS) {
return;
}
attachCheckTickCounter = 0;

const bool attached = isAttached();
if (attached == wasAttached) {
return;
}

if (attached) {
reinitDevice();
}
if (!applyAutoRotation(attached)) {
return; // keep prior state so transition is retried
}
wasAttached = attached;
}

// ---------------------------------------------------------------------------
// lateStart - see header comment. Brings up LVGL input handling for a keyboard
// that wasn't attached at boot (startLvgl() wasn't called from attachDevices()).
// ---------------------------------------------------------------------------
bool Tab5Keyboard::lateStart() {
if (kbHandle != nullptr) {
return true; // already started
}

auto* display = lv_display_get_default();
if (display == nullptr) {
return false; // LVGL not ready yet
}

if (!tt::lvgl::lock(pdMS_TO_TICKS(100))) {
return false; // try again on the next attach-state check
}

bool started = startLvgl(display);
if (started) {
tt::lvgl::hardware_keyboard_set_indev(kbHandle);

// redraw() assigns every indev that exists at the time to the active screen's
// input group. This indev didn't exist yet at the last redraw(), so it has no
// group and won't deliver key events until the next app switch. Join the
// current default group now so input works immediately on the visible screen.
lv_indev_set_group(kbHandle, lv_group_get_default());
}

tt::lvgl::unlock();

return started;
}

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -359,21 +458,31 @@ Tab5Keyboard::~Tab5Keyboard() {
}
}

// ---------------------------------------------------------------------------
// reinitDevice - (re)applies the device register configuration. Used at
// startLvgl() and again on hot-plug reattach, since the device's RGB mode and
// interrupt configuration are volatile and reset to power-on defaults when
// the keyboard is unplugged and reconnected.
// ---------------------------------------------------------------------------
void Tab5Keyboard::reinitDevice() {
writeReg(REG_KEYBOARD_MODE, 0x00); // Normal mode
writeReg(REG_EVENT_NUM, 0x00); // flush event queue
writeReg(REG_INT_STAT, 0x00); // clear pending INT
writeReg(REG_RGB_MODE, 0x01); // Custom RGB mode (manual LED control)
writeReg(REG_BRIGHTNESS, 50); // 50% brightness
updateLeds(); // restore current LED state

if (irqConfigured) {
writeReg(REG_INT_CFG, 0x01); // re-enable Normal-mode interrupt (bit 0)
}
}

bool Tab5Keyboard::startLvgl(lv_display_t* display) {
if (!queue) {
LOG_E("Tab5Keyboard", "Input queue allocation failed — cannot start");
return false;
}

// Set Normal mode explicitly — device may power up in a different mode
if (!writeReg(REG_KEYBOARD_MODE, 0x00)) {
LOG_E("Tab5Keyboard", "Failed to set keyboard mode");
return false;
}
writeReg(REG_EVENT_NUM, 0x00); // flush event queue
writeReg(REG_INT_STAT, 0x00); // clear pending INT
writeReg(REG_RGB_MODE, 0x01); // Custom RGB mode (manual LED control)
writeReg(REG_BRIGHTNESS, 50); // 50% brightness
symActive = false;
aaSticky = false;
aaHeld = false;
Expand All @@ -383,28 +492,34 @@ bool Tab5Keyboard::startLvgl(lv_display_t* display) {
repeatRow = 0xFF;
repeatCol = 0xFF;
repeatLastMs = 0;
updateLeds(); // both LEDs off initially

// Enable Normal-mode interrupt (bit 0)
if (!writeReg(REG_INT_CFG, 0x01)) {
LOG_E("Tab5Keyboard", "Failed to configure interrupt register");
return false;
}
configureIrqPin(); // best-effort; falls back to polling if it fails. Must run before
// reinitDevice() so REG_INT_CFG is written if IRQ setup succeeded.

// Best-effort: if the keyboard isn't attached yet (e.g. started speculatively at
// boot so it can be detected later via hot-plug), these I2C writes fail silently
// and reinitDevice() runs again once attach is detected.
reinitDevice();

kbHandle = lv_indev_create();
lv_indev_set_type(kbHandle, LV_INDEV_TYPE_KEYPAD);
lv_indev_set_read_cb(kbHandle, readCallback);
lv_indev_set_display(kbHandle, display);
lv_indev_set_user_data(kbHandle, this);

configureIrqPin(); // best-effort; falls back to polling if it fails
wasAttached = isAttached();
rotationOverrideActive = false;

assert(inputTimer == nullptr);
inputTimer = std::make_unique<tt::Timer>(tt::Timer::Type::Periodic, pdMS_TO_TICKS(20), [this] {
processKeyboard();
});
inputTimer->start();

if (wasAttached) {
applyAutoRotation(true);
}

return true;
}

Expand Down
16 changes: 16 additions & 0 deletions Devices/m5stack-tab5/Source/devices/Tab5Keyboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ class Tab5Keyboard final : public tt::hal::keyboard::KeyboardDevice {
volatile bool irqPending = false;
bool irqConfigured = false;

// Hot-plug attach-state polling (piggybacks on the 20ms inputTimer)
bool wasAttached = false;
uint32_t attachCheckTickCounter = 0;
lv_display_rotation_t savedRotation = LV_DISPLAY_ROTATION_0;
bool rotationOverrideActive = false;

// Software key-repeat state (tracked by position to survive modifier changes)
uint32_t repeatKey = 0;
uint8_t repeatRow = 0xFF;
Expand All @@ -44,6 +50,10 @@ class Tab5Keyboard final : public tt::hal::keyboard::KeyboardDevice {
void removeIrqPin();
static void IRAM_ATTR irqHandler(void* arg);

void reinitDevice();
bool applyAutoRotation(bool keyboardAttached);
void checkAttachState();

void drainEvents();
void processKeyboard();
static void readCallback(lv_indev_t* indev, lv_indev_data_t* data);
Expand All @@ -62,4 +72,10 @@ class Tab5Keyboard final : public tt::hal::keyboard::KeyboardDevice {
bool stopLvgl() override;
bool isAttached() const override;
lv_indev_t* getLvglIndev() override { return kbHandle; }

// Starts LVGL input handling and registers the hardware keyboard indev for a device
// that wasn't attached at boot (so startLvgl() was never called from Lvgl.cpp's
// attachDevices()). Called from the device module's attach-detection timer once the
// keyboard is first detected post-boot. No-op if LVGL input is already started.
bool lateStart();
};
Loading
Loading