diff --git a/main/display/eink_display_handler.cpp b/main/display/eink_display_handler.cpp index 7e76a84..77727da 100644 --- a/main/display/eink_display_handler.cpp +++ b/main/display/eink_display_handler.cpp @@ -151,7 +151,7 @@ esp_err_t EInkDisplayHandler::full_write(const uint8_t* framebuffer) { return ESP_OK; } -esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* framebuffer, const RefreshArea& area) { +esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* partial_framebuffer, const RefreshArea& area) { ESP_LOGI(TAG, "Starting partial refresh (0.3 seconds)..."); esp_err_t err = ESP_OK; @@ -179,13 +179,18 @@ esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* framebuffer, const } // Step 3: Set partial window { + if (area.x1 % 8 != 0 || area.x2 % 8 != 7) { + ESP_LOGE(TAG, "Partial refresh area x1 and x2 must be byte-aligned (x1 %% 8 == 0 and x2 %% 8 == 7)"); + return ESP_ERR_INVALID_ARG; + } + // no rounding needed, area is expected to be aligned std::vector window_data = { // x start static_cast((area.x1 >> 8) & 0xFF), // x start high byte - static_cast(area.x1 & 0xFF), // x start low byte + static_cast(area.x1 & 0x07), // x start low byte // x end static_cast((area.x2 >> 8) & 0xFF), - static_cast(area.x2 & 0xFF), + static_cast(area.x2 & 0x07), // y start static_cast((area.y1 >> 8) & 0xFF), static_cast(area.y1 & 0xFF), @@ -209,9 +214,9 @@ esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* framebuffer, const return err; } - err = transfer_spi_data(framebuffer, DISPLAY_BUFFER_SIZE, transaction_guard.transaction_id()); // Send new framebuffer data + err = transfer_spi_data(partial_framebuffer, DISPLAY_BUFFER_SIZE, transaction_guard.transaction_id()); // Send new framebuffer data if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to send framebuffer data for partial refresh: %s", esp_err_to_name(err)); + ESP_LOGE(TAG, "Failed to send partial_framebuffer data for partial refresh: %s", esp_err_to_name(err)); return err; } } diff --git a/main/display/lvgl_handler.cpp b/main/display/lvgl_handler.cpp new file mode 100644 index 0000000..b23f2d4 --- /dev/null +++ b/main/display/lvgl_handler.cpp @@ -0,0 +1,354 @@ +#include "display/lvgl_handler.h" +#include "esp_log.h" +#include "common/semaphore_guard.h" +#include "common/constants.h" +#include + +#define DISPLAY_BUFFER_SIZE (DISPLAY_WIDTH * DISPLAY_HEIGHT) / 8 // 1 bit per pixels +#define LVGL_BUFFER_SIZE (DISPLAY_BUFFER_SIZE + 8) // 1 bit per pixels + 8 bytes for palette +#define TAG "LVGLHandler" + +LVGLHandler::LVGLHandler( + std::unique_ptr display_handler_in +) : display_handler_(std::move(display_handler_in)) { + lvgl_mutex_ = xSemaphoreCreateMutex(); + if (lvgl_mutex_ == nullptr) { + ESP_LOGE(TAG, "Failed to create LVGL mutex"); + } +} + +LVGLHandler::~LVGLHandler() { + if (lvgl_display_ != nullptr) { + lv_display_delete(lvgl_display_); + lvgl_display_ = nullptr; + } + if (lvgl_touch_indev_ != nullptr) { + lvgl_port_remove_touch(lvgl_touch_indev_); + lvgl_touch_indev_ = nullptr; + } + if (lvgl_draw_buf_ != nullptr) { + lv_draw_buf_destroy(lvgl_draw_buf_); + lvgl_draw_buf_ = nullptr; + } + if (framebuffer_ != nullptr) { + heap_caps_free(framebuffer_); + framebuffer_ = nullptr; + } + if (lvgl_mutex_ != nullptr) { + vSemaphoreDelete(lvgl_mutex_); + lvgl_mutex_ = nullptr; + } +} + +esp_err_t LVGLHandler::initLVGL(EventGroupHandle_t system_event_group) { + esp_err_t err = initLVGLPort_(); + if (err != ESP_OK) { + return err; + } + err = initLVGLDisplay_(); + if (err != ESP_OK) { + return err; + } + err = registerLVGLTouch_(); + if (err != ESP_OK) { + return err; + } + + + auto lvgl_tick_timer_callback = [](TimerHandle_t xTimer) { + lv_tick_inc(5); + }; + TickType_t lvgl_tick_period = pdMS_TO_TICKS(5); + if (lvgl_tick_period == 0) { + lvgl_tick_period = 1; // ensure at least 1 tick to avoid FreeRTOS assert + } + ESP_LOGV(TAG, "Creating LVGL tick timer with period %u ticks...\n", (unsigned)lvgl_tick_period); + TimerHandle_t lvgl_tick_timer = xTimerCreate( + "lvgl_tick_timer", + lvgl_tick_period, + pdTRUE, + NULL, + lvgl_tick_timer_callback + ); + if (lvgl_tick_timer == NULL) { + ESP_LOGE("Main", "Failed to create LVGL tick timer"); + vTaskDelay(5000 / portTICK_PERIOD_MS); + return ESP_ERR_NO_MEM; + } + ESP_LOGV(TAG, "Starting LVGL tick timer...\n"); + xTimerStart(lvgl_tick_timer, 0); + + if (system_event_group != nullptr) { + xEventGroupSetBits(system_event_group, DISPLAY_READY_BIT | TOUCH_CALIBRATED_BIT); + } + + return ESP_OK; +} + +// +// Private methods +// + +void LVGLHandler::rounder_cb_(lv_display_t* disp, lv_area_t* area) { + // align x to byte boundary + area->x1 = (area->x1 & ~0x7); + area->x2 = (area->x2 | 0x7); +} + +void LVGLHandler::flush_cb_(lv_display_t* disp, const lv_area_t* area, uint8_t* px_map) { + LVGLHandler* handler = static_cast(lv_display_get_user_data(disp)); + if (handler == nullptr || handler->display_handler_ == nullptr) { + ESP_LOGE(TAG, "Invalid handler in flush callback"); + lv_display_flush_ready(disp); + return; + } + uint8_t* pixel_data = px_map + 8; // Skip palette + // + ESP_LOGI(TAG, "Flush callback: x1=%d, y1=%d, x2=%d, y2=%d", area->x1, area->y1, area->x2, area->y2); + // take mutex + SemaphoreGuard guard(handler->lvgl_mutex_); + if (!guard.take(pdMS_TO_TICKS(5000))) { + ESP_LOGE(TAG, "LVGL mutex timeout in flush callback"); + lv_display_flush_ready(disp); + return; + } + + // copy data to framebuffer + int32_t area_w = lv_area_get_width(area); + int32_t area_h = lv_area_get_height(area); + if (area->x1 == 0 && area->y1 == 0 && area_w == DISPLAY_WIDTH && area_h == DISPLAY_HEIGHT) { + ESP_LOGI(TAG, "Full screen update"); + memcpy(framebuffer_, pixel_data, DISPLAY_BUFFER_SIZE); + // request full refresh + esp_err_t err = handler->display_handler_->refresh_display(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Full refresh request failed: %s", esp_err_to_name(err)); + } + } else { + // partial update + ESP_LOGI(TAG, "Partial update: x1=%d, y1=%d, w=%d, h=%d", area->x1, area->y1, area_w, area_h); + + // 1. Calculate Strides + int32_t fb_stride_bytes = DISPLAY_WIDTH / 8; // Stride of the full framebuffer + int32_t src_stride_bytes = area_w / 8; // Stride of the LVGL partial buffer + + // 2. Safety: Ensure we don't write out of bounds + // (The rounder_cb should prevent this, but clipping is safe practice) + int32_t safe_h = area_h; + if (area->y1 + safe_h > DISPLAY_HEIGHT) { + safe_h = DISPLAY_HEIGHT - area->y1; + ESP_LOGW(TAG, "Clipping height to %d to prevent OOB", safe_h); + } + + // 3. Iterate Rows + uint8_t* partial_buffer = new uint8_t[src_stride_bytes * safe_h]; + if (partial_buffer == nullptr) { + ESP_LOGE(TAG, "Failed to allocate partial buffer for refresh"); + lv_display_flush_ready(disp); + return; + } + for (int32_t y = 0; y < safe_h; y++) { + // Calculate Absolute Y in Framebuffer + int32_t fb_y = area->y1 + y; + + // Calculate Source Pointer (Start of current line in partial buffer) + // NOTE: Use src_stride_bytes, NOT fb_stride_bytes + uint8_t* src_line = pixel_data + (y * src_stride_bytes); + + // Calculate Destination Pointer (Start of current line in full framebuffer) + // Offset = (Row * Stride) + (X_Start_Byte) + uint8_t* dst_line = handler->framebuffer_ + (fb_y * fb_stride_bytes) + (area->x1 / 8); + + // 4. Block Copy + // Since x1 is byte-aligned by rounder_cb, we can copy the whole row directly. + memcpy(dst_line, src_line, src_stride_bytes); + // also copy to partial_buffer for refresh + memcpy(partial_buffer + (y * src_stride_bytes), src_line, src_stride_bytes); + } + // 5. Request partial refresh + esp_err_t err = handler->display_handler_->partial_refresh(partial_buffer, + RefreshArea(area->x1, area->y1, area->x2, area->y2)); + delete[] partial_buffer; + if (err != ESP_OK) { + ESP_LOGE(TAG, "Partial refresh request failed: %s", esp_err_to_name(err)); + } + } + // + lv_display_flush_ready(disp); +} + +void LVGLHandler::touch_read_cb_(lv_indev_t* indev, lv_indev_data_t* data) { + LVGLHandler* handler = static_cast(lv_indev_get_user_data(indev)); + if (handler == nullptr || handler->display_handler_ == nullptr) { + data->state = LV_INDEV_STATE_RELEASED; + ESP_LOGE(TAG, "Invalid handler in touch read callback"); + return; + } + + // Disable touch input during display refresh (BUSY) + if (handler->display_handler_->is_busy()) { + data->state = LV_INDEV_STATE_RELEASED; + data->continue_reading = false; + return; + } + + esp_lcd_touch_handle_t tp_handle = handler->display_handler_->get_touch_handle(); + if (tp_handle == nullptr) { + data->state = LV_INDEV_STATE_RELEASED; + return; + } + + // Read touch data from GT911 + esp_err_t ret = esp_lcd_touch_read_data(tp_handle); + if (ret == ESP_OK) { + uint8_t touch_cnt = 0; + // Get touch data using new API + esp_lcd_touch_point_data_t point_data[1]; + esp_lcd_touch_get_data(tp_handle, point_data, &touch_cnt, 1); + + if (touch_cnt > 0) { + ESP_LOGI(TAG, "Touch data read successfully: x=%d, y=%d", point_data[0].x, point_data[0].y); + data->point.x = point_data[0].x; + data->point.y = point_data[0].y; + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->state = LV_INDEV_STATE_RELEASED; + } + } else { + data->state = LV_INDEV_STATE_RELEASED; + } + + data->continue_reading = false; +} + +esp_err_t LVGLHandler::initLVGLDisplay_() { + if (display_handler_ == nullptr) { + return ESP_ERR_INVALID_STATE; + } + esp_err_t err = ESP_OK; + + // Lock LVGL to prevent the timer task from accessing partially initialized display + if (!lvgl_port_lock(pdMS_TO_TICKS(1000))) { + ESP_LOGE(TAG, "Failed to lock LVGL port for display initialization"); + return ESP_ERR_TIMEOUT; + } + + // Create LVGL display + lvgl_display_ = lv_display_create(DISPLAY_WIDTH, DISPLAY_HEIGHT); + if (lvgl_display_ == nullptr) { + ESP_LOGE(TAG, "Failed to create LVGL display"); + return ESP_FAIL; + } + + // set framebuffer + framebuffer_ = (uint8_t*)heap_caps_malloc(LVGL_BUFFER_SIZE, MALLOC_CAP_SPIRAM); + if (framebuffer_ != nullptr) { + framebuffer_in_psram_ = true; + ESP_LOGI(TAG, "Framebuffer allocated in PSRAM (%zu bytes)", LVGL_BUFFER_SIZE); + } else { + ESP_LOGW(TAG, "PSRAM not available, allocating framebuffer in internal RAM"); + framebuffer_ = (uint8_t*)heap_caps_malloc(LVGL_BUFFER_SIZE, MALLOC_CAP_INTERNAL); + framebuffer_in_psram_ = false; + if (framebuffer_ == nullptr) { + ESP_LOGE(TAG, "Failed to allocate framebuffer"); + lvgl_port_unlock(); + return ESP_FAIL; + } + ESP_LOGI(TAG, "Framebuffer allocated in internal RAM (%zu bytes)", LVGL_BUFFER_SIZE); + } + memset(framebuffer_, 0xFF, LVGL_BUFFER_SIZE); // Initialize to white + // Create a draw buffer covering the entire display + lvgl_draw_buf_ = lv_draw_buf_create(DISPLAY_WIDTH, DISPLAY_HEIGHT, LV_COLOR_FORMAT_I1, LV_STRIDE_AUTO); + if (lvgl_draw_buf_ == nullptr) { + ESP_LOGE(TAG, "Failed to create LVGL draw buffer"); + lv_display_delete(lvgl_display_); + lvgl_port_unlock(); + return ESP_FAIL; + } + lv_display_set_draw_buffers(lvgl_display_, lvgl_draw_buf_, nullptr); + lv_display_set_render_mode(lvgl_display_, LV_DISPLAY_RENDER_MODE_DIRECT); + // + ESP_LOGI(TAG, "Performing initial display write..."); + err = display_handler_->full_write(framebuffer_); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Initial display write failed: %d", err); + } else { + ESP_LOGI(TAG, "Initial display write complete"); + } + // Configure LVGL display + lv_display_set_color_format(lvgl_display_, LV_COLOR_FORMAT_I1); + lv_display_set_user_data(lvgl_display_, this); + + lv_display_add_event_cb(lvgl_display_, [](lv_event_t* e) { + LVGLHandler* handler = static_cast(lv_display_get_user_data(static_cast(lv_event_get_target(e)))); + if (handler != nullptr) { + handler->rounder_cb_(static_cast(lv_event_get_target(e)), + static_cast(lv_event_get_param(e))); + } else { + ESP_LOGE(TAG, "Invalid handler in rounder callback"); + } + }, LV_EVENT_INVALIDATE_AREA, lvgl_display_); + + lv_display_set_flush_cb(lvgl_display_, [](lv_display_t* disp, const lv_area_t* area, uint8_t* px_map) { + LVGLHandler* handler = static_cast(lv_display_get_user_data(disp)); + if (handler != nullptr) { + handler->flush_cb_(disp, area, px_map); + } else { + lv_display_flush_ready(disp); + } + }); + + // Unlock LVGL now that display is fully initialized + lvgl_port_unlock(); + + ESP_LOGI(TAG, "LVGL display registered"); + return err; +} + +esp_err_t LVGLHandler::registerLVGLTouch_() { + if (display_handler_ == nullptr) { + return ESP_ERR_INVALID_STATE; + } + esp_lcd_touch_handle_t tp_handle = display_handler_->get_touch_handle(); + if (tp_handle == nullptr) { + ESP_LOGE(TAG, "Touch handle is NULL — touch initialization failed; skipping LVGL touch registration"); + return ESP_FAIL; + } + + const lvgl_port_touch_cfg_t touch_cfg = { + .disp = lvgl_display_, + .handle = tp_handle, + .scale = {}, // Default scaling + }; + + lvgl_touch_indev_ = lvgl_port_add_touch(&touch_cfg); + if (lvgl_touch_indev_ == nullptr) { + ESP_LOGE(TAG, "Failed to register LVGL touch input"); + return ESP_FAIL; + } + + lv_indev_set_user_data(lvgl_touch_indev_, this); + lv_indev_set_read_cb(lvgl_touch_indev_, [](lv_indev_t* indev, lv_indev_data_t* data) { + LVGLHandler* handler = static_cast(lv_indev_get_user_data(indev)); + if (handler != nullptr) { + handler->touch_read_cb_(indev, data); + } else { + data->state = LV_INDEV_STATE_RELEASED; + } + }); + + ESP_LOGI(TAG, "LVGL touch input registered"); + return ESP_OK; +} + +esp_err_t LVGLHandler::initLVGLPort_() { + const lvgl_port_cfg_t lvgl_cfg = ESP_LVGL_PORT_INIT_CONFIG(); + esp_err_t err = lvgl_port_init(&lvgl_cfg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "LVGL port initialization failed: %s", esp_err_to_name(err)); + vTaskDelay(5000 / portTICK_PERIOD_MS); + return ESP_ERR_INVALID_STATE; + } + ESP_LOGI(TAG, "LVGL port initialized successfully.\n"); + return ESP_OK; +} diff --git a/main/display/lvgl_handler.h b/main/display/lvgl_handler.h new file mode 100644 index 0000000..73c2587 --- /dev/null +++ b/main/display/lvgl_handler.h @@ -0,0 +1,39 @@ +#pragma once +#include "lvgl.h" +#include "esp_lvgl_port.h" +#include "display/eink_display_handler.h" +#include "freertos/semphr.h" +#include "freertos/event_groups.h" +#include "esp_err.h" +#include + + +class LVGLHandler { +public: + LVGLHandler( + // an owning pointer to the display handler + // The display handler must outlive the LVGLHandler + // The display handler must be fully initialized before calling initLVGLDisplay + std::unique_ptr display_handler_in + ); + ~LVGLHandler(); + esp_err_t initLVGL(EventGroupHandle_t system_event_group = nullptr); + +private: + void rounder_cb_(lv_display_t* disp, lv_area_t* area); + void flush_cb_(lv_display_t* disp, const lv_area_t* area, uint8_t* px_map); + void touch_read_cb_(lv_indev_t* indev, lv_indev_data_t* data); + esp_err_t initLVGLDisplay_(); + esp_err_t registerLVGLTouch_(); + esp_err_t initLVGLPort_(); + + std::unique_ptr display_handler_ = nullptr; + + lv_display_t* lvgl_display_ = nullptr; + lv_indev_t* lvgl_touch_indev_ = nullptr; + lv_draw_buf_t* lvgl_draw_buf_ = nullptr; + uint8_t* framebuffer_ = nullptr; + bool framebuffer_in_psram_ = false; + + SemaphoreHandle_t lvgl_mutex_ = nullptr; +}; diff --git a/main/main.cpp b/main/main.cpp index 8edc750..b14369f 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -15,6 +15,7 @@ #include "io/nvs_handler.h" #include "info/info.h" #include "display/eink_display_handler.h" +#include "display/lvgl_handler.h" #include "ui/ui_handler.h" #include "ui/app_registry.h" #include "ui/apps/shutdown_app.h" @@ -26,6 +27,8 @@ #include "network.h" #include +#include "lvgl.h" + // nvs storage namespaces, 15 characters max #define DEFAULT_STORAGE_NAMESPACE "storage" #define WIFI_CREDENTIALS_STORAGE_NAMESPACE "wifi_cred" @@ -40,120 +43,10 @@ void init_queues( ); -void app_main(void) { - display_chip_info(); - QueueHandle_t touch_event_queue = NULL; - EventGroupHandle_t system_event_group = NULL, system_lifecycle_event_group = NULL; - - init_queues(touch_event_queue, system_event_group, system_lifecycle_event_group); - if (touch_event_queue == NULL || system_event_group == NULL || system_lifecycle_event_group == NULL) { - ESP_LOGE("Main", "Failed to create one or more queues/event groups"); - vTaskDelay(5000 / portTICK_PERIOD_MS); - return esp_restart(); - } - ESP_LOGI(TAG, "Queues initialized.\n"); - - // Initialize LVGL - const lvgl_port_cfg_t lvgl_cfg = ESP_LVGL_PORT_INIT_CONFIG(); - esp_err_t err = lvgl_port_init(&lvgl_cfg); - if (err != ESP_OK) { - ESP_LOGE(TAG, "LVGL port initialization failed: %s", esp_err_to_name(err)); - vTaskDelay(5000 / portTICK_PERIOD_MS); - return esp_restart(); - } - ESP_LOGI(TAG, "LVGL port initialized successfully.\n"); - - SemaphoreHandle_t lvgl_mutex = xSemaphoreCreateMutex(); - if (lvgl_mutex == NULL) { - ESP_LOGE("Main", "Failed to create LVGL mutex"); - vTaskDelay(5000 / portTICK_PERIOD_MS); - return esp_restart(); - } - // - // KVStorageHandler* kv_storage_handler = new NVSStorageHandler( - // DEFAULT_STORAGE_NAMESPACE - // ); - - // auto wifi_handler = std::make_unique( - // std::unique_ptr(new NVSStorageHandler(WIFI_CREDENTIALS_STORAGE_NAMESPACE)) - // ); - // NetworkHandler* network_handler = new NetworkHandler(std::move(wifi_handler)); - EInkDisplayHandler* display_handler = new EInkDisplayHandler(); - // - // kv_storage_handler->init(system_event_group); - // network_handler->init(system_event_group); - - // Initialize display and touch - display_handler->init_devices(system_event_group); - display_handler->clear_display(); - // ESP_LOGV(TAG, "Starting touch task...\n"); - // display_handler->start_touch_task(); - // ESP_LOGV(TAG, "Touch task started.\n"); - // - // LVGL tick timer - // auto lvgl_tick_timer_callback = [](TimerHandle_t xTimer) { - // lv_tick_inc(5); - // }; - // TickType_t lvgl_tick_period = pdMS_TO_TICKS(5); - // if (lvgl_tick_period == 0) { - // lvgl_tick_period = 1; // ensure at least 1 tick to avoid FreeRTOS assert - // } - // ESP_LOGV(TAG, "Creating LVGL tick timer with period %u ticks...\n", (unsigned)lvgl_tick_period); - // TimerHandle_t lvgl_tick_timer = xTimerCreate( - // "lvgl_tick_timer", - // lvgl_tick_period, - // pdTRUE, - // NULL, - // lvgl_tick_timer_callback - // ); - // if (lvgl_tick_timer == NULL) { - // ESP_LOGE("Main", "Failed to create LVGL tick timer"); - // vTaskDelay(5000 / portTICK_PERIOD_MS); - // return esp_restart(); - // } - // ESP_LOGV(TAG, "Starting LVGL tick timer...\n"); - // xTimerStart(lvgl_tick_timer, 0); - - // - ESP_LOGI(TAG, "Waiting for system to be ready...\n"); - xEventGroupWaitBits( - system_event_group, - // DISPLAY_READY_BIT | STORAGE_READY_BIT | NETWORK_READY_BIT, - DISPLAY_READY_BIT, - // do not clear on exit, require explicit reset - pdFALSE, - pdTRUE, - portMAX_DELAY - ); - ESP_LOGI(TAG, "System is ready. Starting main application...\n"); - - // Register apps with AppRegistry by creating their descriptors - // Each descriptor will create and register the app instance - // DemoAppDescriptor* demo_descriptor = new DemoAppDescriptor(); - // ShutdownAppDescriptor* shutdown_descriptor = new ShutdownAppDescriptor(); - // DiscordAppDescriptor::instance(); // Use singleton pattern for Discord app - // MtrAppDescriptor* mtr_descriptor = new MtrAppDescriptor(); - - // Pass network handler to MtrApp so it can fetch arrival data - // MtrApp* mtr_app = dynamic_cast(mtr_descriptor->get_app_instance()); - // if (mtr_app) { - // mtr_app->set_network_handler(network_handler); - // } - - // ESP_LOGI(TAG, "Apps registered with AppRegistry\n"); - - // Initialize UI Handler (will render app icons from registry) - // UIHandler ui_handler; - // if (ui_handler.init() != ESP_OK) { - // ESP_LOGE(TAG, "Failed to initialize UI handler"); - // vTaskDelay(5000 / portTICK_PERIOD_MS); - // return esp_restart(); - // } - // ESP_LOGI(TAG, "UI handler initialized successfully\n"); - // ESP_LOGI(TAG, "Main screen displayed with app icons. Tap an icon to launch an app.\n"); - - // Run checkerboard draw in its own FreeRTOS task to avoid watchdog triggers +void EInk_Checkerboard( + EInkDisplayHandler* display_handler +) { struct CheckerboardTaskParams { EInkDisplayHandler* display_handler; }; @@ -230,6 +123,181 @@ void app_main(void) { ESP_LOGE(TAG, "Failed to create checkerboard task"); delete checker_params; } +} + +void LVGL_Checkerboard( + LVGLHandler* lvgl_handler +) { + struct CheckerboardTaskParams { + LVGLHandler* lvgl_handler; + }; + auto checkerboard_task_fn = [](void* pvParameters) { + CheckerboardTaskParams* params = static_cast(pvParameters); + if (params == nullptr || params->lvgl_handler == nullptr) { + ESP_LOGE(TAG, "Invalid parameters for LVGL checkerboard task"); + delete params; + vTaskDelete(NULL); + return; + } + auto* handler = static_cast(params->lvgl_handler); + + // Add safety checks + if (!handler) { + ESP_LOGE("LVGL", "Handler is null!"); + return; + } + + ESP_LOGI("HEAP", "Free: %d", esp_get_free_heap_size()); + + // Acquire LVGL lock with proper timeout + if (!lvgl_port_lock(pdMS_TO_TICKS(1000))) { + ESP_LOGE(TAG, "Failed to acquire LVGL lock for checkerboard"); + delete params; + vTaskDelete(NULL); + return; + } + + // Create LVGL objects for checkerboard + lv_obj_t* scr = lv_scr_act(); + if (scr == nullptr) { + ESP_LOGE(TAG, "Failed to get active LVGL screen"); + lvgl_port_unlock(); + delete params; + vTaskDelete(NULL); + return; + } + lv_obj_t* checkerboard = lv_obj_create(scr); + if (checkerboard == nullptr) { + ESP_LOGE(TAG, "Failed to create LVGL checkerboard object"); + lvgl_port_unlock(); + delete params; + vTaskDelete(NULL); + return; + } + lv_obj_set_size(checkerboard, DISPLAY_WIDTH, DISPLAY_HEIGHT); + lv_obj_center(checkerboard); + const int CELL_SIZE = 40; + // Create checkerboard pattern using LVGL + for (int y = 0; y < DISPLAY_HEIGHT; y += CELL_SIZE) { + for (int x = 0; x < DISPLAY_WIDTH; x += CELL_SIZE) { + lv_color_t color = (((x / CELL_SIZE) % 2) == ((y / CELL_SIZE) % 2)) ? lv_color_hex(0xFFFFFF) : lv_color_hex(0x000000); + lv_obj_t* cell = lv_obj_create(checkerboard); + if (cell == nullptr) { + ESP_LOGE(TAG, "Failed to create LVGL checkerboard cell"); + continue; + } + lv_obj_set_size(cell, CELL_SIZE, CELL_SIZE); + lv_obj_set_style_bg_color(cell, color, 0); + lv_obj_set_pos(cell, x, y); + } + } + + ESP_LOGI(TAG, "LVGL Checkerboard pattern displayed successfully."); + lvgl_port_unlock(); + delete params; + vTaskDelete(NULL); + }; + CheckerboardTaskParams* checker_params = new CheckerboardTaskParams(); + checker_params->lvgl_handler = lvgl_handler; + BaseType_t res = xTaskCreate( + checkerboard_task_fn, + "lvgl_checkerboard_task", + 8192, + static_cast(checker_params), + tskIDLE_PRIORITY + 1, + NULL + ); + if (res != pdPASS) { + ESP_LOGE(TAG, "Failed to create LVGL checkerboard task"); + delete checker_params; + } +} + + +void app_main(void) { + display_chip_info(); + + QueueHandle_t touch_event_queue = NULL; + EventGroupHandle_t system_event_group = NULL, system_lifecycle_event_group = NULL; + + init_queues(touch_event_queue, system_event_group, system_lifecycle_event_group); + if (touch_event_queue == NULL || system_event_group == NULL || system_lifecycle_event_group == NULL) { + ESP_LOGE("Main", "Failed to create one or more queues/event groups"); + vTaskDelay(5000 / portTICK_PERIOD_MS); + return esp_restart(); + } + ESP_LOGI(TAG, "Queues initialized.\n"); + + // + // KVStorageHandler* kv_storage_handler = new NVSStorageHandler( + // DEFAULT_STORAGE_NAMESPACE + // ); + + // auto wifi_handler = std::make_unique( + // std::unique_ptr(new NVSStorageHandler(WIFI_CREDENTIALS_STORAGE_NAMESPACE)) + // ); + // NetworkHandler* network_handler = new NetworkHandler(std::move(wifi_handler)); + EInkDisplayHandler* display_handler = new EInkDisplayHandler(); + // Initialize display and touch + display_handler->init_devices(); + display_handler->clear_display(); + ESP_LOGI(TAG, "E-Ink display handler initialized.\n"); + // LVGL Handler + std::unique_ptr display_uptr(display_handler); + LVGLHandler lvgl_handler(std::move(display_uptr)); + esp_err_t err = lvgl_handler.initLVGL(system_event_group); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize LVGL handler: %s", esp_err_to_name(err)); + vTaskDelay(5000 / portTICK_PERIOD_MS); + return esp_restart(); + } + + // + // kv_storage_handler->init(system_event_group); + // network_handler->init(system_event_group); + + // + ESP_LOGI(TAG, "Waiting for system to be ready...\n"); + xEventGroupWaitBits( + system_event_group, + // DISPLAY_READY_BIT | STORAGE_READY_BIT | NETWORK_READY_BIT, + DISPLAY_READY_BIT, + // do not clear on exit, require explicit reset + pdFALSE, + pdTRUE, + portMAX_DELAY + ); + ESP_LOGI(TAG, "System is ready. Starting main application...\n"); + + // Show checkerboard pattern on display for testing + LVGL_Checkerboard(&lvgl_handler); + + // Register apps with AppRegistry by creating their descriptors + // Each descriptor will create and register the app instance + // DemoAppDescriptor* demo_descriptor = new DemoAppDescriptor(); + // ShutdownAppDescriptor* shutdown_descriptor = new ShutdownAppDescriptor(); + // DiscordAppDescriptor::instance(); // Use singleton pattern for Discord app + // MtrAppDescriptor* mtr_descriptor = new MtrAppDescriptor(); + + // Pass network handler to MtrApp so it can fetch arrival data + // MtrApp* mtr_app = dynamic_cast(mtr_descriptor->get_app_instance()); + // if (mtr_app) { + // mtr_app->set_network_handler(network_handler); + // } + + // ESP_LOGI(TAG, "Apps registered with AppRegistry\n"); + + // Initialize UI Handler (will render app icons from registry) + // UIHandler ui_handler; + // if (ui_handler.init() != ESP_OK) { + // ESP_LOGE(TAG, "Failed to initialize UI handler"); + // vTaskDelay(5000 / portTICK_PERIOD_MS); + // return esp_restart(); + // } + // ESP_LOGI(TAG, "UI handler initialized successfully\n"); + // ESP_LOGI(TAG, "Main screen displayed with app icons. Tap an icon to launch an app.\n"); + + // wait for shutdown signal ESP_LOGI(TAG, "Waiting for shutdown signal...\n"); @@ -255,8 +323,7 @@ void app_main(void) { // delete demo_descriptor; // delete shutdown_descriptor; // delete mtr_descriptor; - delete display_handler; - vSemaphoreDelete(lvgl_mutex); + vEventGroupDelete(system_event_group); vQueueDelete(touch_event_queue); ESP_LOGI(TAG, "Cleanup complete.\n");