mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-08-18 07:55:06 +00:00
Added the newest variant to the tab5, St7121. Fixed variant detection reliability Fixed tab5 camera WHO_AM_I failing sometimes Fixed tab5 keyboard live rotation on boot and after stopping/starting lvgl
158 lines
6.4 KiB
C++
158 lines
6.4 KiB
C++
#include "tab5_keyboard_attach_detect.h"
|
|
|
|
#include "tab5_keyboard.h"
|
|
|
|
#include <tactility/device.h>
|
|
#include <tactility/log.h>
|
|
|
|
#include <lvgl/lvgl.h>
|
|
#include <lvgl.h>
|
|
|
|
#include <freertos/FreeRTOS.h>
|
|
#include <freertos/timers.h>
|
|
|
|
constexpr auto* TAG = "Tab5";
|
|
|
|
// Hot-plug attach-state check interval. I2C probes can false-positive on a floating/half-connected
|
|
// bus (e.g. mid-unplug), so a state change is only acted on once it's seen on two consecutive
|
|
// checks in a row.
|
|
constexpr auto ATTACH_CHECK_INTERVAL_MS = 1000;
|
|
|
|
static TimerHandle_t attach_detect_timer = nullptr;
|
|
|
|
static bool was_attached = false;
|
|
static bool pending_attach_state = false;
|
|
static uint8_t pending_attach_confirm_count = 0;
|
|
|
|
// Tracks LVGL's own readiness so a restart (lvgl_is_running() going from false back to true -
|
|
// e.g. an app that took over the display for direct rendering, stopping and letting LVGL rebind)
|
|
// can be told apart from the keyboard itself attaching/detaching. See apply_state()'s comment for
|
|
// why that distinction matters.
|
|
static bool was_lvgl_ready = false;
|
|
|
|
static lv_display_rotation_t saved_rotation = LV_DISPLAY_ROTATION_0;
|
|
static bool rotation_override_active = false;
|
|
|
|
// Applies the current attach state to LVGL (landscape rotation while attached, restoring the
|
|
// prior rotation on detach unless the user changed it manually since attaching) and to the
|
|
// keyboard device's own register state (reinit on attach - RGB mode/interrupt config are volatile
|
|
// across an unplug/replug on this chip). Ported as-is from the deprecated HAL's
|
|
// Tab5Keyboard::applyAutoRotation() / the pre-refactor tab5_keyboard.cpp driver logic.
|
|
// @return true once handled; false to be retried on the next tick (e.g. LVGL lock busy).
|
|
static bool apply_state(Device* keyboard_device, bool attached) {
|
|
if (!lvgl_try_lock(pdMS_TO_TICKS(100))) {
|
|
return false; // retry next tick
|
|
}
|
|
|
|
// Resolved inside the lock, not before: the default display can start/stop between an
|
|
// unlocked probe and actually taking the lock, and lv_display_get_default() itself isn't
|
|
// safe to call without holding it (unlike lvgl_is_running(), used for the readiness check in
|
|
// attach_detect_callback()).
|
|
auto* display = lv_display_get_default();
|
|
if (display == nullptr) {
|
|
lvgl_unlock();
|
|
return false; // LVGL not ready yet - retry next tick
|
|
}
|
|
|
|
if (attached) {
|
|
tab5_keyboard_reinit(keyboard_device);
|
|
|
|
if (lv_display_get_rotation(display) != LV_DISPLAY_ROTATION_90) {
|
|
saved_rotation = lv_display_get_rotation(display);
|
|
rotation_override_active = 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 (rotation_override_active && lv_display_get_rotation(display) == LV_DISPLAY_ROTATION_90) {
|
|
lv_display_set_rotation(display, saved_rotation);
|
|
}
|
|
rotation_override_active = false;
|
|
}
|
|
|
|
lvgl_unlock();
|
|
return true;
|
|
}
|
|
|
|
static void attach_detect_callback(TimerHandle_t /*timer*/) {
|
|
Device* keyboard_device = nullptr;
|
|
if (device_get_by_name("keyboard0", &keyboard_device) != ERROR_NONE) {
|
|
return; // Not constructed yet - will retry on next tick
|
|
}
|
|
|
|
// LVGL restarting is a distinct event from the keyboard physically attaching/detaching: the
|
|
// accessory may never have moved, but whatever apply_state() last set (rotation) may have
|
|
// been reset in the meantime by the restart. Forcing was_attached false makes the block below
|
|
// see a fresh "attached" transition (still going through the normal 2-check debounce) so
|
|
// apply_state() re-announces the current state instead of staying silent forever, waiting for
|
|
// an edge that will never come because the keyboard was never actually unplugged.
|
|
// lvgl_is_running() is safe to call unlocked (unlike lv_display_get_default(), resolved inside
|
|
// the lock in apply_state() instead).
|
|
const bool lvgl_ready = lvgl_is_running();
|
|
if (lvgl_ready && !was_lvgl_ready) {
|
|
was_attached = false;
|
|
pending_attach_confirm_count = 0;
|
|
}
|
|
was_lvgl_ready = lvgl_ready;
|
|
|
|
const bool attached = tab5_keyboard_is_attached(keyboard_device);
|
|
if (attached != was_attached) {
|
|
// Require the new state to be confirmed on a second consecutive check before acting - a
|
|
// single probe on a floating/half-connected bus (e.g. mid-unplug) can false-positive.
|
|
if (attached != pending_attach_state || pending_attach_confirm_count == 0) {
|
|
pending_attach_state = attached;
|
|
pending_attach_confirm_count = 1;
|
|
} else {
|
|
pending_attach_confirm_count = 0;
|
|
if (apply_state(keyboard_device, attached)) {
|
|
was_attached = attached;
|
|
}
|
|
// else: not handled yet (e.g. LVGL lock busy) - retry on the next confirmed check
|
|
}
|
|
} else {
|
|
pending_attach_confirm_count = 0;
|
|
}
|
|
|
|
device_put(keyboard_device);
|
|
}
|
|
|
|
void tab5_keyboard_attach_detect_start() {
|
|
if (attach_detect_timer != nullptr) {
|
|
LOG_W(TAG, "keyboard attach-detect timer already running");
|
|
return;
|
|
}
|
|
|
|
was_attached = false;
|
|
pending_attach_confirm_count = 0;
|
|
was_lvgl_ready = false;
|
|
rotation_override_active = false;
|
|
|
|
attach_detect_timer = xTimerCreate("kb_attach_detect", pdMS_TO_TICKS(ATTACH_CHECK_INTERVAL_MS), pdTRUE, nullptr, attach_detect_callback);
|
|
if (!attach_detect_timer) {
|
|
LOG_E(TAG, "Failed to create keyboard attach-detect timer");
|
|
return;
|
|
}
|
|
if (xTimerStart(attach_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
|
|
LOG_E(TAG, "Failed to start keyboard attach-detect timer");
|
|
xTimerDelete(attach_detect_timer, pdMS_TO_TICKS(100));
|
|
attach_detect_timer = nullptr;
|
|
}
|
|
}
|
|
|
|
void tab5_keyboard_attach_detect_stop() {
|
|
if (attach_detect_timer == nullptr) {
|
|
return;
|
|
}
|
|
|
|
if (xTimerStop(attach_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
|
|
LOG_W(TAG, "Failed to stop keyboard attach-detect timer");
|
|
}
|
|
if (xTimerDelete(attach_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
|
|
LOG_E(TAG, "Failed to delete keyboard attach-detect timer");
|
|
}
|
|
// Always clear the handle - stale non-null handle is worse than a resource leak, as it would
|
|
// cause tab5_keyboard_attach_detect_start() to silently skip re-creating the timer.
|
|
attach_detect_timer = nullptr;
|
|
}
|