mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-08-05 18:29:41 +00:00
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>
This commit is contained in:
@@ -0,0 +1,330 @@
|
||||
// Copyright (c) 2024 microReticulum contributors
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "LVGLInit.h"
|
||||
|
||||
#ifdef ARDUINO
|
||||
|
||||
#include "esp_task_wdt.h"
|
||||
|
||||
#include "Log.h"
|
||||
#include "../../Hardware/TDeck/Display.h"
|
||||
#include "../../Hardware/TDeck/Keyboard.h"
|
||||
#include "../../Hardware/TDeck/Touch.h"
|
||||
#include "../../Hardware/TDeck/Trackball.h"
|
||||
|
||||
using namespace RNS;
|
||||
using namespace Hardware::TDeck;
|
||||
|
||||
namespace UI {
|
||||
namespace LVGL {
|
||||
|
||||
bool LVGLInit::_initialized = false;
|
||||
lv_disp_t* LVGLInit::_display = nullptr;
|
||||
lv_indev_t* LVGLInit::_keyboard = nullptr;
|
||||
lv_indev_t* LVGLInit::_touch = nullptr;
|
||||
lv_indev_t* LVGLInit::_trackball = nullptr;
|
||||
lv_group_t* LVGLInit::_default_group = nullptr;
|
||||
TaskHandle_t LVGLInit::_task_handle = nullptr;
|
||||
SemaphoreHandle_t LVGLInit::_mutex = nullptr;
|
||||
|
||||
bool LVGLInit::init() {
|
||||
if (_initialized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
INFO("Initializing LVGL");
|
||||
|
||||
// Create recursive mutex for thread-safe LVGL access
|
||||
// Recursive because LVGL callbacks may call other LVGL functions
|
||||
_mutex = xSemaphoreCreateRecursiveMutex();
|
||||
if (!_mutex) {
|
||||
ERROR("Failed to create LVGL mutex");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize LVGL library
|
||||
lv_init();
|
||||
|
||||
// LVGL 8.x logging is configured via lv_conf.h LV_USE_LOG
|
||||
// No runtime callback registration needed
|
||||
|
||||
// Initialize display (this also sets up LVGL display driver)
|
||||
if (!Display::init()) {
|
||||
ERROR("Failed to initialize display for LVGL");
|
||||
return false;
|
||||
}
|
||||
_display = lv_disp_get_default();
|
||||
|
||||
INFO(" Display initialized");
|
||||
|
||||
// Create default input group for keyboard navigation
|
||||
_default_group = lv_group_create();
|
||||
if (!_default_group) {
|
||||
ERROR("Failed to create input group");
|
||||
return false;
|
||||
}
|
||||
lv_group_set_default(_default_group);
|
||||
|
||||
// Initialize keyboard input
|
||||
if (Keyboard::init()) {
|
||||
_keyboard = Keyboard::get_indev();
|
||||
// Associate keyboard with input group
|
||||
if (_keyboard) {
|
||||
lv_indev_set_group(_keyboard, _default_group);
|
||||
INFO(" Keyboard registered with input group");
|
||||
}
|
||||
} else {
|
||||
WARNING(" Keyboard initialization failed");
|
||||
}
|
||||
|
||||
// Initialize touch input
|
||||
if (Touch::init()) {
|
||||
_touch = lv_indev_get_next(_keyboard);
|
||||
INFO(" Touch registered");
|
||||
} else {
|
||||
WARNING(" Touch initialization failed");
|
||||
}
|
||||
|
||||
// Initialize trackball input
|
||||
if (Trackball::init()) {
|
||||
_trackball = Trackball::get_indev();
|
||||
// Associate trackball with input group for focus navigation
|
||||
if (_trackball) {
|
||||
lv_indev_set_group(_trackball, _default_group);
|
||||
INFO(" Trackball registered with input group");
|
||||
}
|
||||
} else {
|
||||
WARNING(" Trackball initialization failed");
|
||||
}
|
||||
|
||||
// Set default dark theme
|
||||
set_theme(true);
|
||||
|
||||
_initialized = true;
|
||||
INFO("LVGL initialized successfully");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LVGLInit::init_display_only() {
|
||||
if (_initialized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
INFO("Initializing LVGL (display only)");
|
||||
|
||||
// Initialize LVGL library
|
||||
lv_init();
|
||||
|
||||
// LVGL 8.x logging is configured via lv_conf.h LV_USE_LOG
|
||||
// No runtime callback registration needed
|
||||
|
||||
// Initialize display
|
||||
if (!Display::init()) {
|
||||
ERROR("Failed to initialize display for LVGL");
|
||||
return false;
|
||||
}
|
||||
_display = lv_disp_get_default();
|
||||
|
||||
INFO(" Display initialized");
|
||||
|
||||
// Set default dark theme
|
||||
set_theme(true);
|
||||
|
||||
_initialized = true;
|
||||
INFO("LVGL initialized (display only)");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LVGLInit::task_handler() {
|
||||
if (!_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If running as a task, this is a no-op
|
||||
if (_task_handle != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
lv_task_handler();
|
||||
}
|
||||
|
||||
void LVGLInit::lvgl_task(void* param) {
|
||||
Serial.printf("LVGL task started on core %d\n", xPortGetCoreID());
|
||||
|
||||
// Subscribe this task to Task Watchdog Timer
|
||||
esp_task_wdt_add(nullptr); // nullptr = current task
|
||||
|
||||
while (true) {
|
||||
// Acquire mutex before calling LVGL
|
||||
#ifndef NDEBUG
|
||||
// Debug builds: 5-second timeout for stuck task detection
|
||||
BaseType_t result = xSemaphoreTakeRecursive(_mutex, pdMS_TO_TICKS(5000));
|
||||
if (result != pdTRUE) {
|
||||
WARNING("LVGL task mutex timeout (5s) - possible deadlock or stuck task");
|
||||
// Per Phase 8 context: log warning and continue waiting (don't break functionality)
|
||||
xSemaphoreTakeRecursive(_mutex, portMAX_DELAY);
|
||||
}
|
||||
#else
|
||||
xSemaphoreTakeRecursive(_mutex, portMAX_DELAY);
|
||||
#endif
|
||||
lv_task_handler();
|
||||
xSemaphoreGiveRecursive(_mutex);
|
||||
|
||||
// Feed watchdog and yield to other tasks
|
||||
esp_task_wdt_reset();
|
||||
vTaskDelay(pdMS_TO_TICKS(5));
|
||||
}
|
||||
}
|
||||
|
||||
bool LVGLInit::start_task(int priority, int core) {
|
||||
if (!_initialized) {
|
||||
ERROR("Cannot start LVGL task - not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_task_handle != nullptr) {
|
||||
WARNING("LVGL task already running");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create the task
|
||||
BaseType_t result;
|
||||
if (core >= 0) {
|
||||
result = xTaskCreatePinnedToCore(
|
||||
lvgl_task,
|
||||
"lvgl",
|
||||
8192, // Stack size (8KB should be enough for LVGL)
|
||||
nullptr,
|
||||
priority,
|
||||
&_task_handle,
|
||||
core
|
||||
);
|
||||
} else {
|
||||
result = xTaskCreate(
|
||||
lvgl_task,
|
||||
"lvgl",
|
||||
8192,
|
||||
nullptr,
|
||||
priority,
|
||||
&_task_handle
|
||||
);
|
||||
}
|
||||
|
||||
if (result != pdPASS) {
|
||||
ERROR("Failed to create LVGL task");
|
||||
return false;
|
||||
}
|
||||
|
||||
Serial.printf("LVGL task created with priority %d%s%d\n",
|
||||
priority,
|
||||
core >= 0 ? " on core " : "",
|
||||
core >= 0 ? core : 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LVGLInit::is_task_running() {
|
||||
return _task_handle != nullptr;
|
||||
}
|
||||
|
||||
SemaphoreHandle_t LVGLInit::get_mutex() {
|
||||
return _mutex;
|
||||
}
|
||||
|
||||
uint32_t LVGLInit::get_tick() {
|
||||
return millis();
|
||||
}
|
||||
|
||||
bool LVGLInit::is_initialized() {
|
||||
return _initialized;
|
||||
}
|
||||
|
||||
void LVGLInit::set_theme(bool dark) {
|
||||
if (!_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
lv_theme_t* theme;
|
||||
|
||||
if (dark) {
|
||||
// Dark theme with blue accents
|
||||
theme = lv_theme_default_init(
|
||||
_display,
|
||||
lv_palette_main(LV_PALETTE_BLUE), // Primary color
|
||||
lv_palette_main(LV_PALETTE_RED), // Secondary color
|
||||
true, // Dark mode
|
||||
&lv_font_montserrat_14 // Default font
|
||||
);
|
||||
} else {
|
||||
// Light theme
|
||||
theme = lv_theme_default_init(
|
||||
_display,
|
||||
lv_palette_main(LV_PALETTE_BLUE),
|
||||
lv_palette_main(LV_PALETTE_RED),
|
||||
false, // Light mode
|
||||
&lv_font_montserrat_14
|
||||
);
|
||||
}
|
||||
|
||||
lv_disp_set_theme(_display, theme);
|
||||
}
|
||||
|
||||
lv_disp_t* LVGLInit::get_display() {
|
||||
return _display;
|
||||
}
|
||||
|
||||
lv_indev_t* LVGLInit::get_keyboard() {
|
||||
return _keyboard;
|
||||
}
|
||||
|
||||
lv_indev_t* LVGLInit::get_touch() {
|
||||
return _touch;
|
||||
}
|
||||
|
||||
lv_indev_t* LVGLInit::get_trackball() {
|
||||
return _trackball;
|
||||
}
|
||||
|
||||
lv_group_t* LVGLInit::get_default_group() {
|
||||
return _default_group;
|
||||
}
|
||||
|
||||
void LVGLInit::focus_widget(lv_obj_t* obj) {
|
||||
if (!_default_group || !obj) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove from group first if already there (to avoid duplicates)
|
||||
lv_group_remove_obj(obj);
|
||||
|
||||
// Add to group and focus
|
||||
lv_group_add_obj(_default_group, obj);
|
||||
lv_group_focus_obj(obj);
|
||||
}
|
||||
|
||||
void LVGLInit::log_print(const char* buf) {
|
||||
// Forward LVGL logs to our logging system
|
||||
// LVGL logs include newlines, so strip them
|
||||
String msg(buf);
|
||||
msg.trim();
|
||||
|
||||
if (msg.length() > 0) {
|
||||
// LVGL log levels: Trace, Info, Warn, Error
|
||||
if (msg.indexOf("[Error]") >= 0) {
|
||||
ERROR(msg.c_str());
|
||||
} else if (msg.indexOf("[Warn]") >= 0) {
|
||||
WARNING(msg.c_str());
|
||||
} else if (msg.indexOf("[Info]") >= 0) {
|
||||
INFO(msg.c_str());
|
||||
} else {
|
||||
TRACE(msg.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LVGL
|
||||
} // namespace UI
|
||||
|
||||
#endif // ARDUINO
|
||||
Reference in New Issue
Block a user