#include #include #include #include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_chip_info.h" #include "esp_flash.h" #include "esp_system.h" #include "esp_log.h" // #include "common/constants.h" #include "common/queue_defs.h" #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" #include "ui/apps/discord_app.h" #include "ui/apps/mtr_app.h" #include #include "esp_lvgl_port.h" #include "lvgl.h" #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" #define TAG "Main" extern "C" void app_main(void); void init_queues( QueueHandle_t& touch_queue, EventGroupHandle_t& system_event_group, EventGroupHandle_t& system_lifecycle_event_group ); void random_draw_rect( LVGLHandler* lvgl_handler ) { // Draw a random rectangle on the display using LVGL every 2 seconds until 100 rectangles have been drawn static int rect_count = 0; do { rect_count++; LVGLHandler* handler = lvgl_handler; if (handler == nullptr) { return; } lvgl_port_lock(pdMS_TO_TICKS(5000)); // Create a random rectangle lv_obj_t* rect = lv_obj_create(lv_scr_act()); int x = esp_random() % (DISPLAY_WIDTH - 50); int y = esp_random() % (DISPLAY_HEIGHT - 50); lv_obj_set_pos(rect, x, y); int w = 20 + (esp_random() % 100); int h = 20 + (esp_random() % 100); lv_obj_set_size(rect, w, h); // white or black fill for the rect bool is_white = (esp_random() % 2 == 0); lv_obj_set_style_bg_color(rect, is_white ? lv_color_hex(0xFFFFFF) : lv_color_hex(0x000000), 0); lvgl_port_unlock(); ESP_LOGI(TAG, "Drawn %s rectangle %d at (%d,%d) size (%d x %d)", is_white ? "white" : "black", rect_count, x, y, w, h); // Schedule next rectangle draw vTaskDelay(pdMS_TO_TICKS(2000)); } while (rect_count < 100); } void EInk_Checkerboard( EInkDisplayHandler* display_handler ) { struct CheckerboardTaskParams { EInkDisplayHandler* display_handler; }; auto checkerboard_task_fn = [](void* pvParameters) { CheckerboardTaskParams* params = static_cast(pvParameters); if (params != nullptr && params->display_handler != nullptr) { // Add this task to the watchdog timer esp_err_t wdt_err = esp_task_wdt_add(NULL); if (wdt_err != ESP_OK) { ESP_LOGW(TAG, "Failed to add checkerboard task to watchdog: %s", esp_err_to_name(wdt_err)); } EInkDisplayHandler* display_handler = params->display_handler; const size_t DISPLAY_BUFFER_SIZE = DISPLAY_WIDTH * DISPLAY_HEIGHT / 8; uint8_t* framebuffer = new uint8_t[DISPLAY_BUFFER_SIZE]; if (framebuffer == nullptr) { ESP_LOGE(TAG, "Failed to allocate framebuffer for checkerboard task"); if (wdt_err == ESP_OK) { esp_task_wdt_delete(NULL); } vTaskDelete(NULL); return; } // Create checkerboard pattern for (size_t y = 0; y < DISPLAY_HEIGHT; y++) { for (size_t x = 0; x < DISPLAY_WIDTH; x++) { size_t byte_index = (y * DISPLAY_WIDTH + x) / 8; size_t bit_index = 7 - (x % 8); bool is_white = ((x / 20) % 2) == ((y / 20) % 2); if (is_white) { framebuffer[byte_index] |= (1 << bit_index); // Set bit to 1 for white } else { framebuffer[byte_index] &= ~(1 << bit_index); // Clear bit to 0 for black } } // Yield and reset watchdog periodically const size_t YIELD_INTERVAL = 16; if (y % YIELD_INTERVAL == 0) { if (wdt_err == ESP_OK) { esp_task_wdt_reset(); } vTaskDelay(1 / portTICK_PERIOD_MS); // partial refresh to show progress int32_t y_start = static_cast((y >= YIELD_INTERVAL - 1) ? (y - (YIELD_INTERVAL - 1)) : 0); int32_t y_end = static_cast(y); // get the partial framebuffer for this area uint8_t* partial_framebuffer = &framebuffer[y_start * (DISPLAY_WIDTH / 8)]; esp_err_t err = display_handler->partial_refresh(partial_framebuffer, RefreshArea { 0, y_start, DISPLAY_WIDTH - 1, y_end }); if (err != ESP_OK) { ESP_LOGE(TAG, "Partial refresh failed at y=%d: %s", y, esp_err_to_name(err)); } // wait for 4 seconds to prevent spamming the display // vTaskDelay(2000 / portTICK_PERIOD_MS); } } // Perform full write to display // esp_err_t err = display_handler->full_write(framebuffer); // if (err != ESP_OK) { // ESP_LOGE(TAG, "Checkerboard full write failed: %s", esp_err_to_name(err)); // } else { // ESP_LOGI(TAG, "Checkerboard pattern displayed successfully."); // } delete[] framebuffer; // Remove task from watchdog before deletion if (wdt_err == ESP_OK) { esp_task_wdt_delete(NULL); } } else { ESP_LOGE(TAG, "Invalid parameters for checkerboard task"); } vTaskDelete(NULL); }; CheckerboardTaskParams* checker_params = new CheckerboardTaskParams(); checker_params->display_handler = display_handler; BaseType_t res = xTaskCreate( checkerboard_task_fn, "checkerboard_task", 8192, static_cast(checker_params), tskIDLE_PRIORITY + 1, NULL ); if (res != pdPASS) { 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!"); delete params; vTaskDelete(NULL); return; } ESP_LOGI("HEAP", "Free: %d", esp_get_free_heap_size()); // Wait for LVGL system to fully initialize vTaskDelay(pdMS_TO_TICKS(200)); // Acquire LVGL lock with proper timeout if (!lvgl_port_lock(pdMS_TO_TICKS(5000))) { ESP_LOGE(TAG, "Failed to acquire LVGL lock for checkerboard"); delete params; vTaskDelete(NULL); return; } // Verify LVGL is properly initialized if (lv_display_get_default() == nullptr) { ESP_LOGE(TAG, "LVGL default display not available"); lvgl_port_unlock(); 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); // remove border and padding lv_obj_set_style_pad_all(checkerboard, 0, 0); lv_obj_set_style_border_width(checkerboard, 0, 0); const int CELL_SIZE = 40; lvgl_port_unlock(); // Create checkerboard pattern using LVGL for (int y = 0; y < DISPLAY_HEIGHT; y += CELL_SIZE) { lvgl_port_lock(pdMS_TO_TICKS(1000)); 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"); lvgl_port_unlock(); 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); // remove border and padding lv_obj_set_style_pad_all(cell, 0, 0); lv_obj_set_style_border_width(cell, 0, 0); lv_obj_t* label = lv_label_create(cell); if (label != nullptr) { lv_label_set_text_fmt(label, "(%d,%d)", x, y); lv_obj_center(label); } } lvgl_port_unlock(); // Yield to allow LVGL to process rendering vTaskDelay(500 / portTICK_PERIOD_MS); } ESP_LOGI(TAG, "LVGL Checkerboard pattern displayed successfully."); delete params; vTaskDelete(NULL); }; CheckerboardTaskParams* checker_params = new CheckerboardTaskParams(); checker_params->lvgl_handler = lvgl_handler; BaseType_t res = xTaskCreate( checkerboard_task_fn, "lvgl_checkerboard_task0", 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(system_event_group); display_handler->init_devices(); 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"); // Allow LVGL system to stabilize before creating objects vTaskDelay(pdMS_TO_TICKS(100)); // Show checkerboard pattern on display for testing // EInk_Checkerboard(display_handler); // 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"); EventBits_t bits = xEventGroupWaitBits( system_lifecycle_event_group, SYSTEM_SHUTDOWN_BIT | SYSTEM_RESTART_BIT, // do not clear on exit, require explicit reset pdFALSE, pdFALSE, portMAX_DELAY ); ESP_LOGI(TAG, "Shutdown signal received. Cleaning up...\n"); // Show shutdown screen using the shutdown descriptor's app instance // ShutdownApp* shutdown_app = dynamic_cast(shutdown_descriptor->get_app_instance()); // if (shutdown_app) { // ui_handler.switch_app(shutdown_app); // } vTaskDelay(1000 / portTICK_PERIOD_MS); // Display shutdown message briefly // Cleanup // ui_handler.deinit(); // delete demo_descriptor; // delete shutdown_descriptor; // delete mtr_descriptor; vEventGroupDelete(system_event_group); vQueueDelete(touch_event_queue); ESP_LOGI(TAG, "Cleanup complete.\n"); // handle shutdown or restart if (bits & SYSTEM_SHUTDOWN_BIT) { // if (shutdown_display_handler != nullptr) { // ESP_LOGI(TAG, "Calling display shutdown handler...\n"); // shutdown_display_handler(); // } else { // ESP_LOGI(TAG, "No display shutdown handler to call.\n"); // } ESP_LOGI(TAG, "System is shutting down.\n"); fflush(stdout); // wait for start bit to be set again if future restart is desired, else expect manual power cycle EventBits_t bits = xEventGroupWaitBits( system_lifecycle_event_group, SYSTEM_START_BIT, pdFALSE, pdFALSE, portMAX_DELAY ); if (bits & SYSTEM_START_BIT) { ESP_LOGI(TAG, "SYSTEM_START_BIT received, restarting system.\n"); } else { ESP_LOGW(TAG, "No restart signal received, waiting for manual power cycle.\n"); while (true) { vTaskDelay(portMAX_DELAY); } } } else if (bits & SYSTEM_RESTART_BIT) { // if (restart_display_handler != nullptr) { // ESP_LOGI(TAG, "Calling display restart handler...\n"); // restart_display_handler(); // } else { // ESP_LOGI(TAG, "No display restart handler to call.\n"); // } ESP_LOGI(TAG, "System is restarting.\n"); fflush(stdout); } else { ESP_LOGW(TAG, "Unknown shutdown signal received. Restarting by default.\n"); fflush(stdout); } return esp_restart(); } void init_queues( QueueHandle_t& touch_queue, EventGroupHandle_t& system_event_group, EventGroupHandle_t& system_lifecycle_event_group ) { // Implementation of queue initialization touch_queue = xQueueCreate(10, sizeof(touch_event_t)); system_event_group = xEventGroupCreate(); system_lifecycle_event_group = xEventGroupCreate(); }