#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 "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 // 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 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 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 if (y % 50 == 0) { if (wdt_err == ESP_OK) { esp_task_wdt_reset(); } vTaskDelay(1 / 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; } // 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; delete display_handler; vSemaphoreDelete(lvgl_mutex); 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(); }