diff --git a/lib/kodedot_bsp/include/kodedot/display_manager.h b/lib/kodedot_bsp/include/kodedot/display_manager.h index eeb3249..23dc82e 100644 --- a/lib/kodedot_bsp/include/kodedot/display_manager.h +++ b/lib/kodedot_bsp/include/kodedot/display_manager.h @@ -7,72 +7,37 @@ #include /** - * @brief High-level manager that initializes and wires up the display, LVGL, and touch input. - * - * Responsibilities: - * - Bring up the panel using Arduino_GFX - * - Allocate LVGL draw buffers (prefer PSRAM, fallback to SRAM) - * - Register LVGL display and input drivers - * - Provide simple helpers for brightness and touch reading + * @brief High-level manager for display, LVGL 9 and touch on Kode Dot. */ class DisplayManager { private: - // Hardware interfaces Arduino_DataBus *bus; - Arduino_CO5300 *gfx; - BBCapTouch bbct; - - // LVGL draw buffer and driver handles - lv_disp_draw_buf_t draw_buf; - lv_color_t *buf; - lv_color_t *buf2; - lv_disp_drv_t disp_drv; - - // Static callbacks required by LVGL - static void disp_flush_callback(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p); - static void touchpad_read_callback(lv_indev_drv_t *indev_drv, lv_indev_data_t *data); - - // Singleton-like back-reference used by static callbacks - static DisplayManager* instance; + Arduino_CO5300 *gfx; + BBCapTouch bbct; + + // LVGL 9 handles + lv_display_t *lv_disp; + lv_indev_t *lv_touch; + uint8_t *buf1; + uint8_t *buf2; + + // Static callbacks + static void disp_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map); + static void touch_read_cb(lv_indev_t *indev, lv_indev_data_t *data); + + static DisplayManager *instance; public: DisplayManager(); ~DisplayManager(); - - /** - * @brief Fully initialize display, LVGL, and touch. - * @return true on success, false otherwise - */ + bool init(); - - /** - * @brief Get the underlying Arduino_GFX panel instance. - */ - Arduino_CO5300* getGfx() { return gfx; } - - /** - * @brief Get the touch controller instance. - */ - BBCapTouch* getTouch() { return &bbct; } - - /** - * @brief Pump LVGL timers and tick. Call frequently in loop(). - */ void update(); - - /** - * @brief Set backlight brightness. - * @param brightness Range 0-255 - */ void setBrightness(uint8_t brightness); - - /** - * @brief Read current touch coordinates. - * @param x Output X coordinate - * @param y Output Y coordinate - * @return true if there is an active touch - */ bool getTouchCoordinates(int16_t &x, int16_t &y); + + Arduino_CO5300 *getGfx() { return gfx; } + BBCapTouch *getTouch() { return &bbct; } }; diff --git a/lib/kodedot_bsp/src/custom_ota_override.cpp b/lib/kodedot_bsp/src/custom_ota_override.cpp index 0591814..9de419d 100644 --- a/lib/kodedot_bsp/src/custom_ota_override.cpp +++ b/lib/kodedot_bsp/src/custom_ota_override.cpp @@ -1,10 +1,4 @@ // src/custom_ota_override.cpp -// Strong override of the weak boot/OTA hook in the core. -// Returning true means: "do NOT auto-validate the running OTA image". -// After any reset, the bootloader will roll back to the last valid app (your factory). +// OTA hooks are now in display_manager.cpp to avoid duplicate symbol errors. +// This file is kept empty intentionally. -extern "C" bool verifyRollbackLater(); // must use C linkage to match the weak symbol - -extern "C" bool verifyRollbackLater() { - return true; // never auto-validate OTAs -} diff --git a/lib/kodedot_bsp/src/display_manager.cpp b/lib/kodedot_bsp/src/display_manager.cpp index 2b427dc..c29d8f5 100644 --- a/lib/kodedot_bsp/src/display_manager.cpp +++ b/lib/kodedot_bsp/src/display_manager.cpp @@ -1,199 +1,139 @@ #include #include -// Forward declarations for internal helpers -extern "C" void __wrap_esp_ota_mark_app_valid_cancel_rollback(void); -void init_nvs(); -void lvgl_port_rounder_callback(lv_disp_drv_t *disp_drv, lv_area_t *area); - -// Static instance used by LVGL callbacks -DisplayManager* DisplayManager::instance = nullptr; - -// --- Internal system utilities --- -extern "C" void __wrap_esp_ota_mark_app_valid_cancel_rollback(void) { - // no-op: always disables OTA validation -} +/* ── OTA override ─────────────────────────────────────────────── */ +extern "C" void __wrap_esp_ota_mark_app_valid_cancel_rollback(void) { /* no-op */ } +extern "C" bool verifyRollbackLater() { return true; } +/* ── NVS ──────────────────────────────────────────────────────── */ static Preferences prefs; +static void init_nvs() { prefs.begin("kode_storage", false); } -void init_nvs() { - // Initialize NVS namespace for application storage - prefs.begin("kode_storage", false); -} +/* ── Singleton ────────────────────────────────────────────────── */ +DisplayManager *DisplayManager::instance = nullptr; -// --- LVGL helper --- -void lvgl_port_rounder_callback(lv_disp_drv_t *disp_drv, lv_area_t *area) { - // Efficiently align area to even/odd boundaries as required by the panel - area->x1 &= ~1; // round down to even - area->y1 &= ~1; - area->x2 = (area->x2 & ~1) + 1; // round up to odd - area->y2 = (area->y2 & ~1) + 1; -} +/* ── LVGL tick source (v9: callback returning ms) ─────────────── */ +static uint32_t lv_tick_cb(void) { return (uint32_t)millis(); } -DisplayManager::DisplayManager() : bus(nullptr), gfx(nullptr), buf(nullptr), buf2(nullptr) { +/* ── Constructor / destructor ─────────────────────────────────── */ +DisplayManager::DisplayManager() + : bus(nullptr), gfx(nullptr), lv_disp(nullptr), lv_touch(nullptr), + buf1(nullptr), buf2(nullptr) { instance = this; } DisplayManager::~DisplayManager() { - if (buf) free(buf); + if (buf1) free(buf1); if (buf2) free(buf2); - if (gfx) { - delete gfx; - } - if (bus) { - delete bus; - } + delete gfx; + delete bus; instance = nullptr; } +/* ── init ─────────────────────────────────────────────────────── */ bool DisplayManager::init() { - Serial.println("Bringing up display subsystem..."); - - // Initialize NVS (preferences) + Serial.println("[DISP] Bringing up display subsystem (LVGL 9)..."); init_nvs(); - - // Power on the panel if an enable pin is provided - if (LCD_EN >= 0) { - pinMode(LCD_EN, OUTPUT); - digitalWrite(LCD_EN, HIGH); - } - bus = new Arduino_ESP32QSPI( - LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3 - ); - - gfx = new Arduino_CO5300( - bus, LCD_RST, 0, LCD_WIDTH, LCD_HEIGHT, - 22, 0, 0, 0 - ); + /* Panel power */ + if (LCD_EN >= 0) { pinMode(LCD_EN, OUTPUT); digitalWrite(LCD_EN, HIGH); } - if (!gfx->begin()) { - Serial.println("Error: failed to initialize panel"); - return false; - } - + /* QSPI bus + CO5300 panel */ + bus = new Arduino_ESP32QSPI(LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3); + gfx = new Arduino_CO5300(bus, LCD_RST, 0, LCD_WIDTH, LCD_HEIGHT, 22, 0, 0, 0); + + if (!gfx->begin()) { Serial.println("[DISP] Panel init FAILED"); return false; } gfx->setRotation(0); - // Load brightness percentage (0-100) from NVS and apply to panel (0-255) + + /* Brightness from NVS */ { - uint8_t saved_pct = prefs.getUChar("brightness_pct", 100); - if (saved_pct > 100) saved_pct = 100; - uint8_t saved_brightness = (uint8_t)(((uint16_t)saved_pct * 255 + 50) / 100); // round - gfx->setBrightness(saved_brightness); - Serial.printf("Brightness (pct=%u) applied from NVS\n", (unsigned)saved_pct); + uint8_t pct = prefs.getUChar("brightness_pct", 100); + if (pct > 100) pct = 100; + gfx->setBrightness((uint8_t)(((uint16_t)pct * 255 + 50) / 100)); } gfx->fillScreen(BLACK); - Serial.println("Panel initialized"); + Serial.println("[DISP] Panel OK"); - // Initialize LVGL core + /* ── LVGL 9 init ───────────────────────────────────────────── */ lv_init(); + lv_tick_set_cb(lv_tick_cb); - // Allocate draw buffers (prefer PSRAM; fallback to internal SRAM) - size_t draw_buf_pixels = (size_t)LCD_WIDTH * (size_t)LCD_DRAW_BUFF_HEIGHT; - size_t draw_buf_bytes = draw_buf_pixels * sizeof(lv_color_t); - Serial.printf("Requesting LVGL draw buffer: %u bytes\n", (unsigned)draw_buf_bytes); + /* Draw buffers — prefer PSRAM double-buffer (RGB565 = 2 bytes/px) */ + size_t buf_pixels = (size_t)LCD_WIDTH * LCD_DRAW_BUFF_HEIGHT; + size_t buf_bytes = buf_pixels * 2; /* RGB565 */ - buf = (lv_color_t*)heap_caps_malloc(draw_buf_bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - if (!buf) { - // Fallback to internal SRAM with reduced height window - size_t fallback_height = 100; // small window to avoid exhausting SRAM - draw_buf_pixels = (size_t)LCD_WIDTH * fallback_height; - draw_buf_bytes = draw_buf_pixels * sizeof(lv_color_t); - Serial.printf("PSRAM not available, using SRAM fallback: %u bytes\n", (unsigned)draw_buf_bytes); - buf = (lv_color_t*)heap_caps_malloc(draw_buf_bytes, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); - if (!buf) { - Serial.println("Error: unable to allocate draw buffer in SRAM"); - return false; - } + buf1 = (uint8_t *)heap_caps_malloc(buf_bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + if (!buf1) { + buf_pixels = (size_t)LCD_WIDTH * 100; + buf_bytes = buf_pixels * 2; + buf1 = (uint8_t *)heap_caps_malloc(buf_bytes, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + if (!buf1) { Serial.println("[DISP] Buffer alloc FAILED"); return false; } } + buf2 = (uint8_t *)heap_caps_malloc(buf_bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - // Try double buffering in PSRAM if there is room - buf2 = (lv_color_t*)heap_caps_malloc(draw_buf_bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - if (!buf2) { - // If not enough memory, keep single buffer - buf2 = NULL; - } + /* Create display */ + lv_disp = lv_display_create(LCD_WIDTH, LCD_HEIGHT); + lv_display_set_flush_cb(lv_disp, disp_flush_cb); + lv_display_set_buffers(lv_disp, buf1, buf2, buf_bytes, + LV_DISPLAY_RENDER_MODE_PARTIAL); + lv_display_set_color_format(lv_disp, LV_COLOR_FORMAT_RGB565); + Serial.println("[DISP] LVGL 9 display created"); - // Configure LVGL display driver - lv_disp_draw_buf_init(&draw_buf, buf, buf2, (uint32_t)draw_buf_pixels); - - // Initialize the display driver - lv_disp_drv_init(&disp_drv); - disp_drv.hor_res = LCD_WIDTH; - disp_drv.ver_res = LCD_HEIGHT; - disp_drv.flush_cb = disp_flush_callback; - disp_drv.rounder_cb = lvgl_port_rounder_callback; // avoid visual artifacts on odd/even alignment - disp_drv.draw_buf = &draw_buf; - lv_disp_drv_register(&disp_drv); - Serial.println("LVGL initialized"); - - // Initialize capacitive touch - if(bbct.init(TOUCH_I2C_SDA, TOUCH_I2C_SCL, TOUCH_RST, TOUCH_INT) != CT_SUCCESS) { - Serial.println("Error: failed to initialize touch"); + /* ── Touch ─────────────────────────────────────────────────── */ + if (bbct.init(TOUCH_I2C_SDA, TOUCH_I2C_SCL, TOUCH_RST, TOUCH_INT) != CT_SUCCESS) { + Serial.println("[DISP] Touch init FAILED"); return false; - } else { - Serial.println("Touch initialized"); } + Serial.println("[DISP] Touch OK"); - // Register LVGL input driver (touch) - static lv_indev_drv_t indev_drv; - lv_indev_drv_init(&indev_drv); - indev_drv.type = LV_INDEV_TYPE_POINTER; - indev_drv.read_cb = touchpad_read_callback; - lv_indev_drv_register(&indev_drv); + lv_touch = lv_indev_create(); + lv_indev_set_type(lv_touch, LV_INDEV_TYPE_POINTER); + lv_indev_set_read_cb(lv_touch, touch_read_cb); - Serial.println("Display subsystem ready"); + Serial.println("[DISP] Display subsystem ready (LVGL 9.5)"); return true; } +/* ── update (call from loop) ──────────────────────────────────── */ void DisplayManager::update() { - // Advance LVGL tick by 5 ms per iteration - lv_tick_inc(5); - // Let LVGL process timers lv_timer_handler(); } +/* ── brightness ───────────────────────────────────────────────── */ void DisplayManager::setBrightness(uint8_t brightness) { - if (gfx) { - gfx->setBrightness(brightness); - } - // Persist brightness as percentage (0-100) in NVS - uint8_t pct = (uint8_t)(((uint16_t)brightness * 100 + 127) / 255); // round + if (gfx) gfx->setBrightness(brightness); + uint8_t pct = (uint8_t)(((uint16_t)brightness * 100 + 127) / 255); if (pct > 100) pct = 100; prefs.putUChar("brightness_pct", pct); } +/* ── touch coords ─────────────────────────────────────────────── */ bool DisplayManager::getTouchCoordinates(int16_t &x, int16_t &y) { TOUCHINFO ti; - if(bbct.getSamples(&ti) && ti.count > 0) { - x = ti.x[0]; - y = ti.y[0]; - return true; - } + if (bbct.getSamples(&ti) && ti.count > 0) { x = ti.x[0]; y = ti.y[0]; return true; } return false; } -// LVGL display flush callback -void DisplayManager::disp_flush_callback(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) { +/* ── LVGL flush callback (v9) ─────────────────────────────────── */ +void DisplayManager::disp_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) { if (!instance || !instance->gfx) return; - - uint32_t w = (area->x2 - area->x1 + 1); - uint32_t h = (area->y2 - area->y1 + 1); + uint32_t w = lv_area_get_width(area); + uint32_t h = lv_area_get_height(area); instance->gfx->startWrite(); instance->gfx->writeAddrWindow(area->x1, area->y1, w, h); - instance->gfx->writePixels((uint16_t *)&color_p->full, w * h); + instance->gfx->writePixels((uint16_t *)px_map, w * h); instance->gfx->endWrite(); - lv_disp_flush_ready(disp); + lv_display_flush_ready(disp); } -// LVGL touch read callback -void DisplayManager::touchpad_read_callback(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) { +/* ── LVGL touch callback (v9) ─────────────────────────────────── */ +void DisplayManager::touch_read_cb(lv_indev_t *indev, lv_indev_data_t *data) { if (!instance) return; - TOUCHINFO ti; - if(instance->bbct.getSamples(&ti) && ti.count > 0) { - data->state = LV_INDEV_STATE_PRESSED; + if (instance->bbct.getSamples(&ti) && ti.count > 0) { + data->state = LV_INDEV_STATE_PRESSED; data->point.x = ti.x[0]; data->point.y = ti.y[0]; } else { diff --git a/platformio.ini b/platformio.ini index ce014f1..5f99757 100644 --- a/platformio.ini +++ b/platformio.ini @@ -56,7 +56,7 @@ upload_command = esptool --chip esp32s3 --port ${UPLOAD_PORT} --baud ${UPLOAD_SP ; Libraries lib_deps = moononournation/GFX Library for Arduino @ ^1.6.0 - lvgl/lvgl @ ^8.3.9 + lvgl/lvgl @ ^9 adafruit/Adafruit NeoPixel https://github.com/RobTillaart/TCA9555.git#0.4.3 https://github.com/bitbank2/bb_captouch.git diff --git a/src/lv_conf.h b/src/lv_conf.h index 7444848..75ae942 100644 --- a/src/lv_conf.h +++ b/src/lv_conf.h @@ -1,92 +1,144 @@ +/** + * @file lv_conf.h + * Configuration file for LVGL v9.5.0 — Kode Dot FTP Explorer + */ + #ifndef LV_CONF_H #define LV_CONF_H -#define LV_USE_DEV_VERSION 1 +/*==================== + COLOR SETTINGS + *====================*/ +#define LV_COLOR_DEPTH 16 -/* Color settings */ -#define LV_COLOR_DEPTH 16 -#define LV_COLOR_16_SWAP 0 +/*========================= + STDLIB WRAPPER SETTINGS + *=========================*/ +#define LV_USE_STDLIB_MALLOC LV_STDLIB_BUILTIN +#define LV_USE_STDLIB_STRING LV_STDLIB_BUILTIN +#define LV_USE_STDLIB_SPRINTF LV_STDLIB_BUILTIN -/* Memory settings */ -#define LV_MEM_CUSTOM 0 -#define LV_MEM_SIZE (48U * 1024U) +#define LV_MEM_SIZE (48U * 1024U) -/* HAL settings */ -#define LV_DISP_DEF_REFR_PERIOD 30 -#define LV_INDEV_DEF_READ_PERIOD 30 +/*==================== + HAL SETTINGS + *====================*/ +#define LV_DEF_REFR_PERIOD 33 +#define LV_DPI_DEF 130 -/* Feature usage */ -#define LV_USE_ANIMATION 1 -#define LV_USE_SHADOW 0 -#define LV_USE_BLEND_MODES 0 -#define LV_USE_OPA_SCALE 0 -#define LV_USE_IMG_TRANSFORM 0 +/*================= + * OPERATING SYSTEM + *=================*/ +#define LV_USE_OS LV_OS_NONE -/* Widget usage */ -#define LV_USE_ARC 0 -#define LV_USE_ANIMIMG 0 -#define LV_USE_BAR 1 -#define LV_USE_BTN 0 -#define LV_USE_BTNMATRIX 0 -#define LV_USE_CANVAS 0 -#define LV_USE_CHECKBOX 0 -#define LV_USE_DROPDOWN 0 -#define LV_USE_IMG 0 -#define LV_USE_LABEL 1 -#define LV_USE_LINE 1 -#define LV_USE_LIST 0 -#define LV_USE_METER 0 -#define LV_USE_MSGBOX 0 -#define LV_USE_ROLLER 0 -#define LV_USE_SLIDER 0 -#define LV_USE_SPAN 0 -#define LV_USE_SPINBOX 0 -#define LV_USE_SPINNER 0 -#define LV_USE_SWITCH 0 -#define LV_USE_TEXTAREA 0 -#define LV_USE_TABLE 0 -#define LV_USE_TABVIEW 0 -#define LV_USE_TILEVIEW 0 -#define LV_USE_WIN 0 -#define LV_USE_KEYBOARD 0 -#define LV_USE_MENU 0 -#define LV_USE_COLORWHEEL 0 -#define LV_USE_LED 0 -#define LV_USE_IMGBTN 0 -#define LV_USE_CALENDAR 0 -#define LV_USE_CHART 0 +/*======================== + * RENDERING CONFIGURATION + *========================*/ +#define LV_DRAW_BUF_STRIDE_ALIGN 4 +#define LV_DRAW_BUF_ALIGN 4 +#define LV_DRAW_LAYER_SIMPLE_BUF_SIZE (24 * 1024) +#define LV_USE_DRAW_SW 1 +#define LV_DRAW_SW_DRAW_UNIT_CNT 1 -/* Themes */ -#define LV_USE_THEME_DEFAULT 1 -#define LV_USE_THEME_BASIC 0 +/*======================= + * FEATURE CONFIGURATION + *=======================*/ +#define LV_USE_LOG 0 +#define LV_USE_ASSERT_NULL 1 +#define LV_USE_ASSERT_MALLOC 1 +#define LV_USE_ASSERT_STYLE 0 +#define LV_USE_ASSERT_MEM_INTEGRITY 0 +#define LV_USE_ASSERT_OBJ 0 -/* Font usage */ +#define LV_USE_REFR_DEBUG 0 +#define LV_USE_LAYER_DEBUG 0 +#define LV_USE_PARALLEL_DRAW_DEBUG 0 + +/*------------- + * Others + *-----------*/ +#define LV_USE_SYSMON 0 +#define LV_USE_PERF_MONITOR 0 +#define LV_USE_MEM_MONITOR 0 +#define LV_USE_OBJ_ID 0 + +/*===================== + * WIDGET USAGE + *====================*/ +#define LV_USE_ARC 1 +#define LV_USE_BAR 1 +#define LV_USE_BUTTON 1 +#define LV_USE_BUTTONMATRIX 0 +#define LV_USE_CALENDAR 0 +#define LV_USE_CANVAS 0 +#define LV_USE_CHART 0 +#define LV_USE_CHECKBOX 0 +#define LV_USE_DROPDOWN 0 +#define LV_USE_IMAGE 1 +#define LV_USE_IMAGEBUTTON 0 +#define LV_USE_KEYBOARD 0 +#define LV_USE_LABEL 1 +#define LV_USE_LED 0 +#define LV_USE_LINE 1 +#define LV_USE_LIST 0 +#define LV_USE_MENU 0 +#define LV_USE_MSGBOX 0 +#define LV_USE_ROLLER 0 +#define LV_USE_SCALE 0 +#define LV_USE_SLIDER 0 +#define LV_USE_SPAN 0 +#define LV_USE_SPINBOX 0 +#define LV_USE_SPINNER 0 +#define LV_USE_SWITCH 0 +#define LV_USE_TABLE 0 +#define LV_USE_TABVIEW 0 +#define LV_USE_TEXTAREA 0 +#define LV_USE_TILEVIEW 0 +#define LV_USE_WIN 0 + +/*================== + * THEMES + *==================*/ +#define LV_USE_THEME_DEFAULT 1 +#define LV_USE_THEME_SIMPLE 0 + +/*================== + * FONT USAGE + *==================*/ +#define LV_FONT_MONTSERRAT_8 0 +#define LV_FONT_MONTSERRAT_10 0 +#define LV_FONT_MONTSERRAT_12 1 #define LV_FONT_MONTSERRAT_14 1 +#define LV_FONT_MONTSERRAT_16 1 #define LV_FONT_MONTSERRAT_18 1 +#define LV_FONT_MONTSERRAT_20 1 #define LV_FONT_MONTSERRAT_22 1 +#define LV_FONT_MONTSERRAT_24 1 #define LV_FONT_MONTSERRAT_26 1 +#define LV_FONT_MONTSERRAT_28 1 #define LV_FONT_MONTSERRAT_30 1 +#define LV_FONT_MONTSERRAT_32 0 #define LV_FONT_MONTSERRAT_34 0 +#define LV_FONT_MONTSERRAT_36 0 #define LV_FONT_MONTSERRAT_38 0 -#define LV_FONT_MONTSERRAT_42 1 +#define LV_FONT_MONTSERRAT_40 1 +#define LV_FONT_MONTSERRAT_42 0 +#define LV_FONT_MONTSERRAT_44 0 #define LV_FONT_MONTSERRAT_46 0 #define LV_FONT_MONTSERRAT_48 0 -/* Others */ -#define LV_USE_PERF_MONITOR 0 -#define LV_USE_MEM_MONITOR 0 -#define LV_USE_REFR_DEBUG 0 +#define LV_FONT_DEFAULT &lv_font_montserrat_14 -/* Compiler settings */ -#define LV_ATTRIBUTE_TICK_INC -#define LV_ATTRIBUTE_TIMER_HANDLER -#define LV_ATTRIBUTE_FLUSH_READY -#define LV_ATTRIBUTE_MEM_ALIGN -#define LV_ATTRIBUTE_LARGE_CONST -#define LV_ATTRIBUTE_LARGE_RAM_ARRAY +#define LV_USE_FONT_PLACEHOLDER 1 -/* HAL settings */ -#define LV_TICK_CUSTOM 0 -#define LV_DPI_DEF 130 +/*================== + * TEXT SETTINGS + *==================*/ +#define LV_TXT_ENC LV_TXT_ENC_UTF8 -#endif /*LV_CONF_H*/ \ No newline at end of file +/*================== + * V8 COMPATIBILITY + *==================*/ +#define LV_USE_OBJ_PROPERTY 0 + +#endif /*LV_CONF_H*/ diff --git a/src/main.cpp b/src/main.cpp index 22d07d9..66748eb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -161,7 +161,7 @@ void ftpTransferCallback(FtpTransferOperation ftpOperation, const char *name, ui // Time display // --------------------------------------------------------------------------- -/** Update the UI time label once per second */ +/** Update the UI time and date labels once per second */ static void updateTimeDisplay() { unsigned long now = millis(); if (now - last_time_update < 1000) return; @@ -172,6 +172,10 @@ static void updateTimeDisplay() { char buf[16]; strftime(buf, sizeof(buf), "%H:%M:%S", &timeinfo); ui_set_time(buf); + + char datebuf[32]; + strftime(datebuf, sizeof(datebuf), "%a, %d %b %Y", &timeinfo); + ui_set_date(datebuf); } } diff --git a/src/ui.cpp b/src/ui.cpp index 63f3ad3..fc1e3e9 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -1,20 +1,23 @@ #include "ui.h" #include -/* ── colour palette ──────────────────────────────────────────────── */ -#define CLR_BG 0x111111 -#define CLR_PANEL_BG 0x1A1A1A -#define CLR_PANEL_BRD 0x333333 +/* ── colour palette (AMOLED-optimised) ───────────────────────────── */ +#define CLR_BG 0x0A0A0F +#define CLR_PANEL_BG 0x141420 +#define CLR_PANEL_BRD 0x2A2A3A #define CLR_WHITE 0xFFFFFF #define CLR_GRAY 0x888888 -#define CLR_CYAN 0x00E5FF +#define CLR_CYAN 0x00BCD4 #define CLR_GREEN 0x4CAF50 #define CLR_RED 0xF44336 -#define CLR_BAR_BG 0x2A2A2A +#define CLR_BLUE 0x2196F3 +#define CLR_PURPLE 0xAB47BC +#define CLR_BAR_BG 0x1A1A2E #define CLR_BAR_FILL 0x4CAF50 -#define CLR_ERROR_BG 0x330000 -#define CLR_DIMMED 0x777777 -#define CLR_SEPARATOR 0x333333 +#define CLR_ERROR_BG 0x2D0A0A +#define CLR_DIMMED 0x666677 +#define CLR_SEPARATOR 0x1E1E2E +#define CLR_TITLE_ACCENT 0x00BCD4 /* ── screen dimensions ───────────────────────────────────────────── */ #define SCR_W 410 @@ -26,23 +29,27 @@ static lv_obj_t *line_sep; static lv_obj_t *lbl_wifi; static lv_obj_t *lbl_ip; static lv_obj_t *panel_ftp; +static lv_obj_t *dot_ftp_status; +static lv_obj_t *lbl_ftp_title; static lv_obj_t *lbl_ftp_status; static lv_obj_t *lbl_ftp_url; static lv_obj_t *lbl_ftp_creds; -static lv_obj_t *lbl_sd_title; +static lv_obj_t *lbl_storage_title; static lv_obj_t *bar_sd; -static lv_obj_t *lbl_sd_text; +static lv_obj_t *lbl_sd_info; +static lv_obj_t *lbl_sd_pct; static lv_obj_t *lbl_time; static lv_obj_t *lbl_date; +static lv_obj_t *obj_error_bar; static lv_obj_t *lbl_error; /* ── cached IP for FTP URL ───────────────────────────────────────── */ static char cached_ip[48] = "0.0.0.0"; -/* ── helpers ─────────────────────────────────────────────────────── */ +/* ── helper: create a styled label ───────────────────────────────── */ static lv_obj_t *make_label(lv_obj_t *parent, const lv_font_t *font, uint32_t color, lv_align_t align, - lv_coord_t x_ofs, lv_coord_t y_ofs, + int32_t x_ofs, int32_t y_ofs, const char *text) { lv_obj_t *lbl = lv_label_create(parent); @@ -57,112 +64,162 @@ static lv_obj_t *make_label(lv_obj_t *parent, const lv_font_t *font, void ui_init() { - lv_obj_t *scr = lv_scr_act(); + lv_obj_t *scr = lv_screen_active(); - /* screen background */ + /* ── screen background: pure AMOLED black ────────────────────── */ lv_obj_set_style_bg_color(scr, lv_color_hex(CLR_BG), 0); lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0); - /* ── title ───────────────────────────────────────────────────── */ - lbl_title = make_label(scr, &lv_font_montserrat_26, CLR_WHITE, - LV_ALIGN_TOP_MID, 0, 20, "FTP Explorer"); + /* ────────────────────────────────────────────────────────────── */ + /* HEADER (y = 0 … 60) */ + /* ────────────────────────────────────────────────────────────── */ + lbl_title = make_label(scr, &lv_font_montserrat_24, CLR_WHITE, + LV_ALIGN_TOP_MID, 0, 16, "FTP Explorer"); + lv_obj_set_style_text_letter_space(lbl_title, 2, 0); - /* thin separator line */ - static lv_point_t line_pts[] = {{55, 0}, {355, 0}}; + /* subtle separator line */ + static lv_point_precise_t line_pts[] = {{40, 0}, {370, 0}}; line_sep = lv_line_create(scr); lv_line_set_points(line_sep, line_pts, 2); lv_obj_set_style_line_color(line_sep, lv_color_hex(CLR_SEPARATOR), 0); lv_obj_set_style_line_width(line_sep, 1, 0); - lv_obj_align(line_sep, LV_ALIGN_TOP_LEFT, 0, 54); + lv_obj_set_style_line_opa(line_sep, LV_OPA_70, 0); + lv_obj_align(line_sep, LV_ALIGN_TOP_LEFT, 0, 55); - /* ── WiFi section ────────────────────────────────────────────── */ - lbl_wifi = make_label(scr, &lv_font_montserrat_18, CLR_GREEN, - LV_ALIGN_TOP_LEFT, 24, 70, + /* ────────────────────────────────────────────────────────────── */ + /* WI-FI SECTION (y = 65 … 140) */ + /* ────────────────────────────────────────────────────────────── */ + lbl_wifi = make_label(scr, &lv_font_montserrat_16, CLR_GREEN, + LV_ALIGN_TOP_LEFT, 28, 68, LV_SYMBOL_WIFI " Disconnected"); lbl_ip = make_label(scr, &lv_font_montserrat_30, CLR_CYAN, - LV_ALIGN_TOP_LEFT, 24, 100, "0.0.0.0"); + LV_ALIGN_TOP_LEFT, 28, 96, "0.0.0.0"); - /* ── FTP panel ───────────────────────────────────────────────── */ + /* ────────────────────────────────────────────────────────────── */ + /* FTP SERVER PANEL (y = 148 … 288) */ + /* ────────────────────────────────────────────────────────────── */ panel_ftp = lv_obj_create(scr); - lv_obj_set_size(panel_ftp, SCR_W - 40, 120); - lv_obj_align(panel_ftp, LV_ALIGN_TOP_MID, 0, 145); + lv_obj_set_size(panel_ftp, SCR_W - 40, 138); + lv_obj_align(panel_ftp, LV_ALIGN_TOP_MID, 0, 148); lv_obj_set_style_bg_color(panel_ftp, lv_color_hex(CLR_PANEL_BG), 0); lv_obj_set_style_bg_opa(panel_ftp, LV_OPA_COVER, 0); lv_obj_set_style_border_color(panel_ftp, lv_color_hex(CLR_PANEL_BRD), 0); lv_obj_set_style_border_width(panel_ftp, 1, 0); - lv_obj_set_style_radius(panel_ftp, 10, 0); - lv_obj_set_style_pad_all(panel_ftp, 12, 0); + lv_obj_set_style_border_opa(panel_ftp, LV_OPA_80, 0); + lv_obj_set_style_radius(panel_ftp, 14, 0); + lv_obj_set_style_pad_left(panel_ftp, 16, 0); + lv_obj_set_style_pad_right(panel_ftp, 16, 0); + lv_obj_set_style_pad_top(panel_ftp, 14, 0); + lv_obj_set_style_pad_bottom(panel_ftp, 10, 0); lv_obj_clear_flag(panel_ftp, LV_OBJ_FLAG_SCROLLABLE); + /* subtle vertical gradient on the panel */ + lv_obj_set_style_bg_grad_color(panel_ftp, lv_color_hex(0x1A1A30), 0); + lv_obj_set_style_bg_grad_dir(panel_ftp, LV_GRAD_DIR_VER, 0); /* panel title */ - lv_obj_t *lbl_ftp_title = lv_label_create(panel_ftp); + lbl_ftp_title = lv_label_create(panel_ftp); lv_label_set_text(lbl_ftp_title, "FTP Server"); lv_obj_set_style_text_font(lbl_ftp_title, &lv_font_montserrat_18, 0); lv_obj_set_style_text_color(lbl_ftp_title, lv_color_hex(CLR_WHITE), 0); lv_obj_align(lbl_ftp_title, LV_ALIGN_TOP_LEFT, 0, 0); - lbl_ftp_status = lv_label_create(panel_ftp); - lv_label_set_text(lbl_ftp_status, "Status: Idle"); - lv_obj_set_style_text_font(lbl_ftp_status, &lv_font_montserrat_18, 0); - lv_obj_set_style_text_color(lbl_ftp_status, lv_color_hex(CLR_GREEN), 0); - lv_obj_align(lbl_ftp_status, LV_ALIGN_TOP_LEFT, 0, 28); + /* status dot (8x8 circle) */ + dot_ftp_status = lv_obj_create(panel_ftp); + lv_obj_set_size(dot_ftp_status, 8, 8); + lv_obj_set_style_radius(dot_ftp_status, 4, 0); + lv_obj_set_style_bg_color(dot_ftp_status, lv_color_hex(CLR_GREEN), 0); + lv_obj_set_style_bg_opa(dot_ftp_status, LV_OPA_COVER, 0); + lv_obj_set_style_border_width(dot_ftp_status, 0, 0); + lv_obj_clear_flag(dot_ftp_status, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_align(dot_ftp_status, LV_ALIGN_TOP_LEFT, 0, 33); + /* status label next to dot */ + lbl_ftp_status = lv_label_create(panel_ftp); + lv_label_set_text(lbl_ftp_status, "Idle"); + lv_obj_set_style_text_font(lbl_ftp_status, &lv_font_montserrat_14, 0); + lv_obj_set_style_text_color(lbl_ftp_status, lv_color_hex(CLR_GREEN), 0); + lv_obj_align(lbl_ftp_status, LV_ALIGN_TOP_LEFT, 14, 28); + + /* FTP URL */ lbl_ftp_url = lv_label_create(panel_ftp); lv_label_set_text(lbl_ftp_url, "ftp://0.0.0.0"); - lv_obj_set_style_text_font(lbl_ftp_url, &lv_font_montserrat_18, 0); + lv_obj_set_style_text_font(lbl_ftp_url, &lv_font_montserrat_16, 0); lv_obj_set_style_text_color(lbl_ftp_url, lv_color_hex(CLR_DIMMED), 0); lv_obj_align(lbl_ftp_url, LV_ALIGN_TOP_LEFT, 0, 54); + /* credentials */ lbl_ftp_creds = lv_label_create(panel_ftp); lv_label_set_text(lbl_ftp_creds, "User: kode Pass: kode"); - lv_obj_set_style_text_font(lbl_ftp_creds, &lv_font_montserrat_14, 0); + lv_obj_set_style_text_font(lbl_ftp_creds, &lv_font_montserrat_12, 0); lv_obj_set_style_text_color(lbl_ftp_creds, lv_color_hex(CLR_GRAY), 0); - lv_obj_align(lbl_ftp_creds, LV_ALIGN_TOP_LEFT, 0, 80); + lv_obj_align(lbl_ftp_creds, LV_ALIGN_TOP_LEFT, 0, 82); - /* ── SD card section ─────────────────────────────────────────── */ - lbl_sd_title = make_label(scr, &lv_font_montserrat_18, CLR_WHITE, - LV_ALIGN_TOP_LEFT, 24, 280, "SD Card"); + /* ────────────────────────────────────────────────────────────── */ + /* STORAGE SECTION (y = 300 … 370) */ + /* ────────────────────────────────────────────────────────────── */ + lbl_storage_title = make_label(scr, &lv_font_montserrat_18, CLR_WHITE, + LV_ALIGN_TOP_LEFT, 28, 300, "Storage"); bar_sd = lv_bar_create(scr); - lv_obj_set_size(bar_sd, SCR_W - 48, 14); - lv_obj_align(bar_sd, LV_ALIGN_TOP_LEFT, 24, 310); + lv_obj_set_size(bar_sd, SCR_W - 56, 14); + lv_obj_align(bar_sd, LV_ALIGN_TOP_LEFT, 28, 330); lv_bar_set_range(bar_sd, 0, 100); lv_bar_set_value(bar_sd, 0, LV_ANIM_OFF); + /* bar track */ lv_obj_set_style_bg_color(bar_sd, lv_color_hex(CLR_BAR_BG), 0); lv_obj_set_style_bg_opa(bar_sd, LV_OPA_COVER, 0); + lv_obj_set_style_radius(bar_sd, 7, 0); + /* bar indicator */ lv_obj_set_style_bg_color(bar_sd, lv_color_hex(CLR_BAR_FILL), LV_PART_INDICATOR); lv_obj_set_style_bg_opa(bar_sd, LV_OPA_COVER, LV_PART_INDICATOR); - lv_obj_set_style_radius(bar_sd, 4, 0); - lv_obj_set_style_radius(bar_sd, 4, LV_PART_INDICATOR); + lv_obj_set_style_radius(bar_sd, 7, LV_PART_INDICATOR); - lbl_sd_text = make_label(scr, &lv_font_montserrat_14, CLR_GRAY, - LV_ALIGN_TOP_LEFT, 24, 330, "0.0 / 0.0 GB"); + /* size text left-aligned */ + lbl_sd_info = make_label(scr, &lv_font_montserrat_14, CLR_GRAY, + LV_ALIGN_TOP_LEFT, 28, 350, "0.0 / 0.0 GB"); - /* ── time / date ─────────────────────────────────────────────── */ - lbl_time = make_label(scr, &lv_font_montserrat_42, CLR_WHITE, - LV_ALIGN_TOP_MID, 0, 400, "--:--:--"); + /* percentage right-aligned */ + lbl_sd_pct = make_label(scr, &lv_font_montserrat_14, CLR_GRAY, + LV_ALIGN_TOP_RIGHT, -28, 350, "0%"); + + /* ────────────────────────────────────────────────────────────── */ + /* CLOCK AREA (y = 385 … 490) */ + /* ────────────────────────────────────────────────────────────── */ + lbl_time = make_label(scr, &lv_font_montserrat_40, CLR_WHITE, + LV_ALIGN_TOP_MID, 0, 398, "--:--"); lbl_date = make_label(scr, &lv_font_montserrat_18, CLR_GRAY, - LV_ALIGN_TOP_MID, 0, 452, "--.--.----"); + LV_ALIGN_TOP_MID, 0, 456, "--- --, ----"); - /* ── error overlay (hidden) ──────────────────────────────────── */ - lbl_error = lv_label_create(scr); + /* ────────────────────────────────────────────────────────────── */ + /* ERROR OVERLAY (hidden by default) */ + /* ────────────────────────────────────────────────────────────── */ + obj_error_bar = lv_obj_create(scr); + lv_obj_set_size(obj_error_bar, SCR_W - 40, LV_SIZE_CONTENT); + lv_obj_align(obj_error_bar, LV_ALIGN_BOTTOM_MID, 0, -6); + lv_obj_set_style_bg_color(obj_error_bar, lv_color_hex(CLR_ERROR_BG), 0); + lv_obj_set_style_bg_opa(obj_error_bar, LV_OPA_COVER, 0); + lv_obj_set_style_border_color(obj_error_bar, lv_color_hex(CLR_RED), 0); + lv_obj_set_style_border_width(obj_error_bar, 1, 0); + lv_obj_set_style_border_opa(obj_error_bar, LV_OPA_60, 0); + lv_obj_set_style_radius(obj_error_bar, 10, 0); + lv_obj_set_style_pad_all(obj_error_bar, 10, 0); + lv_obj_clear_flag(obj_error_bar, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_add_flag(obj_error_bar, LV_OBJ_FLAG_HIDDEN); + + lbl_error = lv_label_create(obj_error_bar); lv_label_set_text(lbl_error, ""); - lv_obj_set_width(lbl_error, SCR_W - 48); - lv_obj_set_style_text_font(lbl_error, &lv_font_montserrat_18, 0); + lv_obj_set_width(lbl_error, SCR_W - 72); + lv_obj_set_style_text_font(lbl_error, &lv_font_montserrat_14, 0); lv_obj_set_style_text_color(lbl_error, lv_color_hex(CLR_RED), 0); - lv_obj_set_style_bg_color(lbl_error, lv_color_hex(CLR_ERROR_BG), 0); - lv_obj_set_style_bg_opa(lbl_error, LV_OPA_COVER, 0); - lv_obj_set_style_pad_all(lbl_error, 8, 0); - lv_obj_set_style_radius(lbl_error, 6, 0); lv_obj_set_style_text_align(lbl_error, LV_TEXT_ALIGN_CENTER, 0); lv_label_set_long_mode(lbl_error, LV_LABEL_LONG_WRAP); - lv_obj_align(lbl_error, LV_ALIGN_TOP_MID, 0, 360); - lv_obj_add_flag(lbl_error, LV_OBJ_FLAG_HIDDEN); + lv_obj_align(lbl_error, LV_ALIGN_CENTER, 0, 0); } +/* ── ui_set_wifi ─────────────────────────────────────────────────── */ void ui_set_wifi(const char *ssid, const char *ip) { if (ssid && ssid[0]) { @@ -185,17 +242,36 @@ void ui_set_wifi(const char *ssid, const char *ip) lv_label_set_text_fmt(lbl_ftp_url, "ftp://%s", cached_ip); } +/* ── ui_set_ftp ──────────────────────────────────────────────────── */ void ui_set_ftp(const char *status) { - lv_label_set_text_fmt(lbl_ftp_status, "Status: %s", status); + if (!status) return; + + lv_label_set_text(lbl_ftp_status, status); + + /* colour the dot + text based on status keyword */ + uint32_t clr = CLR_GREEN; /* default: Idle */ + if (strstr(status, "Connect")) clr = CLR_BLUE; + if (strstr(status, "Transfer")) clr = CLR_PURPLE; + if (strstr(status, "Error")) clr = CLR_RED; + + lv_obj_set_style_bg_color(dot_ftp_status, lv_color_hex(clr), 0); + lv_obj_set_style_text_color(lbl_ftp_status, lv_color_hex(clr), 0); } +/* ── ui_set_time ─────────────────────────────────────────────────── */ void ui_set_time(const char *time_str) { - /* expects "HH:MM:SS" — split into time and derive date display */ lv_label_set_text(lbl_time, time_str); } +/* ── ui_set_date ─────────────────────────────────────────────────── */ +void ui_set_date(const char *date_str) +{ + lv_label_set_text(lbl_date, date_str); +} + +/* ── ui_set_sd ───────────────────────────────────────────────────── */ void ui_set_sd(uint64_t used_mb, uint64_t total_mb) { int pct = 0; @@ -205,13 +281,13 @@ void ui_set_sd(uint64_t used_mb, uint64_t total_mb) } lv_bar_set_value(bar_sd, pct, LV_ANIM_OFF); - /* show in GB with one decimal */ float used_gb = used_mb / 1024.0f; float total_gb = total_mb / 1024.0f; - lv_label_set_text_fmt(lbl_sd_text, "%.1f / %.1f GB", (double)used_gb, - (double)total_gb); + lv_label_set_text_fmt(lbl_sd_info, "%.1f / %.1f GB", + (double)used_gb, (double)total_gb); + lv_label_set_text_fmt(lbl_sd_pct, "%d%%", pct); - /* colour the bar red when > 90 % */ + /* bar turns red when storage > 90% */ if (pct > 90) { lv_obj_set_style_bg_color(bar_sd, lv_color_hex(CLR_RED), LV_PART_INDICATOR); @@ -221,13 +297,14 @@ void ui_set_sd(uint64_t used_mb, uint64_t total_mb) } } +/* ── ui_set_error ────────────────────────────────────────────────── */ void ui_set_error(const char *msg) { if (msg && msg[0]) { lv_label_set_text(lbl_error, msg); - lv_obj_clear_flag(lbl_error, LV_OBJ_FLAG_HIDDEN); + lv_obj_clear_flag(obj_error_bar, LV_OBJ_FLAG_HIDDEN); } else { lv_label_set_text(lbl_error, ""); - lv_obj_add_flag(lbl_error, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(obj_error_bar, LV_OBJ_FLAG_HIDDEN); } } diff --git a/src/ui.h b/src/ui.h index ec2e062..e5ed3e8 100644 --- a/src/ui.h +++ b/src/ui.h @@ -6,5 +6,6 @@ void ui_init(); void ui_set_wifi(const char *ssid, const char *ip); void ui_set_ftp(const char *status); void ui_set_time(const char *time_str); +void ui_set_date(const char *date_str); void ui_set_sd(uint64_t used_mb, uint64_t total_mb); void ui_set_error(const char *msg);