Files
pyxis/lib/tdeck_ui/UI/LVGL/LVGLInit.cpp
T
torlando-agent[bot]andClaude Opus 4.8 70d4aa6be9 feat: graft pyxis onto upstream microReticulum 0.4.1
Repins microReticulum + microLXMF onto the upstream-0.4.1 graft and adapts
pyxis to the new src/microReticulum/ layout and 0.4.x APIs. The far-diverged
0.3.0 fork's Resource/Transport/Identity work is subsumed by upstream's
reimplementation; only the still-needed fixes ride on the pinned branches
(PKCS7/HMAC/X25519 crypto -- proven byte-identical to python RNS 1.3.1 --
Packet link-proof callback, Identity short-sig guard, and the bz2 layer +
decompress-on-receive in Resource::assemble()).

Consumer-side changes:
- platformio.ini: pin microReticulum @2f21fee (pyxis-fixes-on-0.4.1) and
  microLXMF @33760d0 (chore/microreticulum-0.4.1-layout); bump microStore
  ceea8f5 -> c5fb69d (0.4.x requires the new BasicFileStore::init API);
  -std=gnu++11 -> gnu++17 (upstream requires C++17).
- Namespace all microReticulum includes (angle + quote) to <microReticulum/...>
  for the relocated layout; shim-local Utilities/Stream.h|Print.h preserved.
- Interface::send_outgoing now returns bool: update TCP/BLE/SX1262/Auto
  overrides with correct success/failure returns.
- SDArchiveFileSystem::init(bool reformatOnFail=true) to match new microStore.
- Static Transport::get_path_table() -> path_table(); instance getter unchanged.
- Remove duplicate shim Cryptography/BZ2 (microReticulum provides it now; keep
  lib/libbz2 as the ESP32 bzlib provider).
- patch_littlefs_paths.py: normalize microStore's LittleFS adapter paths to a
  leading "/" -- ESP32 Arduino LittleFS rejects "./"-prefixed paths, which
  silently broke the path store (no peer paths learned, all messaging blocked).

Validated on T-Deck Plus: builds (RAM 27.5% / Flash 77.7%), boots stable
(no WDT/panic), and a full on-device LXMF e2e (DIRECT + OPPORTUNISTIC +
bz2-compressed-Resource receive) passes 5/5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
2026-06-19 15:49:44 -04:00

331 lines
8.1 KiB
C++

// Copyright (c) 2024 microReticulum contributors
// SPDX-License-Identifier: MIT
#include "LVGLInit.h"
#ifdef ARDUINO
#include "esp_task_wdt.h"
#include <microReticulum/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