431 lines
14 KiB
C++
431 lines
14 KiB
C++
#include <stdio.h>
|
|
#include <inttypes.h>
|
|
#include <stdexcept>
|
|
#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 <tick/lv_tick.h>
|
|
#include "esp_lvgl_port.h"
|
|
#include "lvgl.h"
|
|
#include "network.h"
|
|
#include <esp_task_wdt.h>
|
|
|
|
#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 EInk_Checkerboard(
|
|
EInkDisplayHandler* display_handler
|
|
) {
|
|
struct CheckerboardTaskParams {
|
|
EInkDisplayHandler* display_handler;
|
|
};
|
|
auto checkerboard_task_fn = [](void* pvParameters) {
|
|
CheckerboardTaskParams* params = static_cast<CheckerboardTaskParams*>(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<int32_t>((y >= YIELD_INTERVAL - 1) ? (y - (YIELD_INTERVAL - 1)) : 0);
|
|
int32_t y_end = static_cast<int32_t>(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<void*>(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<CheckerboardTaskParams*>(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<LVGLHandler*>(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(5000 / 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_task",
|
|
8192,
|
|
static_cast<void*>(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<WifiHandler>(
|
|
// std::unique_ptr<KVStorageHandler>(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<EInkDisplayHandler> 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<MtrApp*>(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<ShutdownApp*>(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();
|
|
}
|