feat: LVGL 9.5 upgrade, FTP server, premium AMOLED UI

- Upgraded LVGL from 8.4 to 9.5.0 (new display/input API)
- Rewrote DisplayManager for LVGL 9 (lv_display_create, tick callback)
- New lv_conf.h for v9.5 with optimized widget selection
- Premium dark AMOLED UI with gradient panels, status dots, storage bar
- Backend: Wi-Fi from SD JSON, NTP Vienna, SimpleFTPServer on SD_MMC
- Removed 6 unused sensor libs, added ArduinoJson + SimpleFTPServer
- Build: 19% RAM, 4.6% Flash — verified compilation success
This commit is contained in:
Lago
2026-04-03 15:18:02 +02:00
parent 4510cfe16c
commit 6062083b5e
8 changed files with 370 additions and 337 deletions
@@ -7,72 +7,37 @@
#include <bb_captouch.h> #include <bb_captouch.h>
/** /**
* @brief High-level manager that initializes and wires up the display, LVGL, and touch input. * @brief High-level manager for display, LVGL 9 and touch on Kode Dot.
*
* 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
*/ */
class DisplayManager { class DisplayManager {
private: private:
// Hardware interfaces
Arduino_DataBus *bus; Arduino_DataBus *bus;
Arduino_CO5300 *gfx; Arduino_CO5300 *gfx;
BBCapTouch bbct; BBCapTouch bbct;
// LVGL draw buffer and driver handles // LVGL 9 handles
lv_disp_draw_buf_t draw_buf; lv_display_t *lv_disp;
lv_color_t *buf; lv_indev_t *lv_touch;
lv_color_t *buf2; uint8_t *buf1;
lv_disp_drv_t disp_drv; uint8_t *buf2;
// Static callbacks required by LVGL // Static callbacks
static void disp_flush_callback(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p); static void disp_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map);
static void touchpad_read_callback(lv_indev_drv_t *indev_drv, lv_indev_data_t *data); static void touch_read_cb(lv_indev_t *indev, lv_indev_data_t *data);
// Singleton-like back-reference used by static callbacks static DisplayManager *instance;
static DisplayManager* instance;
public: public:
DisplayManager(); DisplayManager();
~DisplayManager(); ~DisplayManager();
/**
* @brief Fully initialize display, LVGL, and touch.
* @return true on success, false otherwise
*/
bool init(); 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(); void update();
/**
* @brief Set backlight brightness.
* @param brightness Range 0-255
*/
void setBrightness(uint8_t brightness); 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); bool getTouchCoordinates(int16_t &x, int16_t &y);
Arduino_CO5300 *getGfx() { return gfx; }
BBCapTouch *getTouch() { return &bbct; }
}; };
+2 -8
View File
@@ -1,10 +1,4 @@
// src/custom_ota_override.cpp // src/custom_ota_override.cpp
// Strong override of the weak boot/OTA hook in the core. // OTA hooks are now in display_manager.cpp to avoid duplicate symbol errors.
// Returning true means: "do NOT auto-validate the running OTA image". // This file is kept empty intentionally.
// After any reset, the bootloader will roll back to the last valid app (your factory).
extern "C" bool verifyRollbackLater(); // must use C linkage to match the weak symbol
extern "C" bool verifyRollbackLater() {
return true; // never auto-validate OTAs
}
+74 -134
View File
@@ -1,199 +1,139 @@
#include <kodedot/display_manager.h> #include <kodedot/display_manager.h>
#include <Preferences.h> #include <Preferences.h>
// Forward declarations for internal helpers /* ── OTA override ─────────────────────────────────────────────── */
extern "C" void __wrap_esp_ota_mark_app_valid_cancel_rollback(void); extern "C" void __wrap_esp_ota_mark_app_valid_cancel_rollback(void) { /* no-op */ }
void init_nvs(); extern "C" bool verifyRollbackLater() { return true; }
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
}
/* ── NVS ──────────────────────────────────────────────────────── */
static Preferences prefs; static Preferences prefs;
static void init_nvs() { prefs.begin("kode_storage", false); }
void init_nvs() { /* ── Singleton ────────────────────────────────────────────────── */
// Initialize NVS namespace for application storage DisplayManager *DisplayManager::instance = nullptr;
prefs.begin("kode_storage", false);
}
// --- LVGL helper --- /* ── LVGL tick source (v9: callback returning ms) ─────────────── */
void lvgl_port_rounder_callback(lv_disp_drv_t *disp_drv, lv_area_t *area) { static uint32_t lv_tick_cb(void) { return (uint32_t)millis(); }
// 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;
}
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; instance = this;
} }
DisplayManager::~DisplayManager() { DisplayManager::~DisplayManager() {
if (buf) free(buf); if (buf1) free(buf1);
if (buf2) free(buf2); if (buf2) free(buf2);
if (gfx) { delete gfx;
delete gfx; delete bus;
}
if (bus) {
delete bus;
}
instance = nullptr; instance = nullptr;
} }
/* ── init ─────────────────────────────────────────────────────── */
bool DisplayManager::init() { bool DisplayManager::init() {
Serial.println("Bringing up display subsystem..."); Serial.println("[DISP] Bringing up display subsystem (LVGL 9)...");
// Initialize NVS (preferences)
init_nvs(); 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( /* Panel power */
LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3 if (LCD_EN >= 0) { pinMode(LCD_EN, OUTPUT); digitalWrite(LCD_EN, HIGH); }
);
gfx = new Arduino_CO5300(
bus, LCD_RST, 0, LCD_WIDTH, LCD_HEIGHT,
22, 0, 0, 0
);
if (!gfx->begin()) { /* QSPI bus + CO5300 panel */
Serial.println("Error: failed to initialize panel"); bus = new Arduino_ESP32QSPI(LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3);
return false; 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); 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); uint8_t pct = prefs.getUChar("brightness_pct", 100);
if (saved_pct > 100) saved_pct = 100; if (pct > 100) pct = 100;
uint8_t saved_brightness = (uint8_t)(((uint16_t)saved_pct * 255 + 50) / 100); // round gfx->setBrightness((uint8_t)(((uint16_t)pct * 255 + 50) / 100));
gfx->setBrightness(saved_brightness);
Serial.printf("Brightness (pct=%u) applied from NVS\n", (unsigned)saved_pct);
} }
gfx->fillScreen(BLACK); gfx->fillScreen(BLACK);
Serial.println("Panel initialized"); Serial.println("[DISP] Panel OK");
// Initialize LVGL core /* ── LVGL 9 init ───────────────────────────────────────────── */
lv_init(); lv_init();
lv_tick_set_cb(lv_tick_cb);
// Allocate draw buffers (prefer PSRAM; fallback to internal SRAM) /* Draw buffers prefer PSRAM double-buffer (RGB565 = 2 bytes/px) */
size_t draw_buf_pixels = (size_t)LCD_WIDTH * (size_t)LCD_DRAW_BUFF_HEIGHT; size_t buf_pixels = (size_t)LCD_WIDTH * LCD_DRAW_BUFF_HEIGHT;
size_t draw_buf_bytes = draw_buf_pixels * sizeof(lv_color_t); size_t buf_bytes = buf_pixels * 2; /* RGB565 */
Serial.printf("Requesting LVGL draw buffer: %u bytes\n", (unsigned)draw_buf_bytes);
buf = (lv_color_t*)heap_caps_malloc(draw_buf_bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); buf1 = (uint8_t *)heap_caps_malloc(buf_bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (!buf) { if (!buf1) {
// Fallback to internal SRAM with reduced height window buf_pixels = (size_t)LCD_WIDTH * 100;
size_t fallback_height = 100; // small window to avoid exhausting SRAM buf_bytes = buf_pixels * 2;
draw_buf_pixels = (size_t)LCD_WIDTH * fallback_height; buf1 = (uint8_t *)heap_caps_malloc(buf_bytes, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
draw_buf_bytes = draw_buf_pixels * sizeof(lv_color_t); if (!buf1) { Serial.println("[DISP] Buffer alloc FAILED"); return false; }
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;
}
} }
buf2 = (uint8_t *)heap_caps_malloc(buf_bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
// Try double buffering in PSRAM if there is room /* Create display */
buf2 = (lv_color_t*)heap_caps_malloc(draw_buf_bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); lv_disp = lv_display_create(LCD_WIDTH, LCD_HEIGHT);
if (!buf2) { lv_display_set_flush_cb(lv_disp, disp_flush_cb);
// If not enough memory, keep single buffer lv_display_set_buffers(lv_disp, buf1, buf2, buf_bytes,
buf2 = NULL; 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 /* ── Touch ─────────────────────────────────────────────────── */
lv_disp_draw_buf_init(&draw_buf, buf, buf2, (uint32_t)draw_buf_pixels); if (bbct.init(TOUCH_I2C_SDA, TOUCH_I2C_SCL, TOUCH_RST, TOUCH_INT) != CT_SUCCESS) {
Serial.println("[DISP] Touch init FAILED");
// 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");
return false; return false;
} else {
Serial.println("Touch initialized");
} }
Serial.println("[DISP] Touch OK");
// Register LVGL input driver (touch) lv_touch = lv_indev_create();
static lv_indev_drv_t indev_drv; lv_indev_set_type(lv_touch, LV_INDEV_TYPE_POINTER);
lv_indev_drv_init(&indev_drv); lv_indev_set_read_cb(lv_touch, touch_read_cb);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = touchpad_read_callback;
lv_indev_drv_register(&indev_drv);
Serial.println("Display subsystem ready"); Serial.println("[DISP] Display subsystem ready (LVGL 9.5)");
return true; return true;
} }
/* ── update (call from loop) ──────────────────────────────────── */
void DisplayManager::update() { void DisplayManager::update() {
// Advance LVGL tick by 5 ms per iteration
lv_tick_inc(5);
// Let LVGL process timers
lv_timer_handler(); lv_timer_handler();
} }
/* ── brightness ───────────────────────────────────────────────── */
void DisplayManager::setBrightness(uint8_t brightness) { void DisplayManager::setBrightness(uint8_t brightness) {
if (gfx) { if (gfx) gfx->setBrightness(brightness);
gfx->setBrightness(brightness); uint8_t pct = (uint8_t)(((uint16_t)brightness * 100 + 127) / 255);
}
// Persist brightness as percentage (0-100) in NVS
uint8_t pct = (uint8_t)(((uint16_t)brightness * 100 + 127) / 255); // round
if (pct > 100) pct = 100; if (pct > 100) pct = 100;
prefs.putUChar("brightness_pct", pct); prefs.putUChar("brightness_pct", pct);
} }
/* ── touch coords ─────────────────────────────────────────────── */
bool DisplayManager::getTouchCoordinates(int16_t &x, int16_t &y) { bool DisplayManager::getTouchCoordinates(int16_t &x, int16_t &y) {
TOUCHINFO ti; TOUCHINFO ti;
if(bbct.getSamples(&ti) && ti.count > 0) { if (bbct.getSamples(&ti) && ti.count > 0) { x = ti.x[0]; y = ti.y[0]; return true; }
x = ti.x[0];
y = ti.y[0];
return true;
}
return false; return false;
} }
// LVGL display flush callback /* ── LVGL flush callback (v9) ─────────────────────────────────── */
void DisplayManager::disp_flush_callback(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) { void DisplayManager::disp_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) {
if (!instance || !instance->gfx) return; if (!instance || !instance->gfx) return;
uint32_t w = lv_area_get_width(area);
uint32_t w = (area->x2 - area->x1 + 1); uint32_t h = lv_area_get_height(area);
uint32_t h = (area->y2 - area->y1 + 1);
instance->gfx->startWrite(); instance->gfx->startWrite();
instance->gfx->writeAddrWindow(area->x1, area->y1, w, h); 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(); instance->gfx->endWrite();
lv_disp_flush_ready(disp); lv_display_flush_ready(disp);
} }
// LVGL touch read callback /* ── LVGL touch callback (v9) ─────────────────────────────────── */
void DisplayManager::touchpad_read_callback(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) { void DisplayManager::touch_read_cb(lv_indev_t *indev, lv_indev_data_t *data) {
if (!instance) return; if (!instance) return;
TOUCHINFO ti; TOUCHINFO ti;
if(instance->bbct.getSamples(&ti) && ti.count > 0) { if (instance->bbct.getSamples(&ti) && ti.count > 0) {
data->state = LV_INDEV_STATE_PRESSED; data->state = LV_INDEV_STATE_PRESSED;
data->point.x = ti.x[0]; data->point.x = ti.x[0];
data->point.y = ti.y[0]; data->point.y = ti.y[0];
} else { } else {
+1 -1
View File
@@ -56,7 +56,7 @@ upload_command = esptool --chip esp32s3 --port ${UPLOAD_PORT} --baud ${UPLOAD_SP
; Libraries ; Libraries
lib_deps = lib_deps =
moononournation/GFX Library for Arduino @ ^1.6.0 moononournation/GFX Library for Arduino @ ^1.6.0
lvgl/lvgl @ ^8.3.9 lvgl/lvgl @ ^9
adafruit/Adafruit NeoPixel adafruit/Adafruit NeoPixel
https://github.com/RobTillaart/TCA9555.git#0.4.3 https://github.com/RobTillaart/TCA9555.git#0.4.3
https://github.com/bitbank2/bb_captouch.git https://github.com/bitbank2/bb_captouch.git
+121 -69
View File
@@ -1,92 +1,144 @@
/**
* @file lv_conf.h
* Configuration file for LVGL v9.5.0 — Kode Dot FTP Explorer
*/
#ifndef LV_CONF_H #ifndef LV_CONF_H
#define 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 STDLIB WRAPPER SETTINGS
#define LV_COLOR_16_SWAP 0 *=========================*/
#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_SIZE (48U * 1024U)
#define LV_MEM_CUSTOM 0
#define LV_MEM_SIZE (48U * 1024U)
/* HAL settings */ /*====================
#define LV_DISP_DEF_REFR_PERIOD 30 HAL SETTINGS
#define LV_INDEV_DEF_READ_PERIOD 30 *====================*/
#define LV_DEF_REFR_PERIOD 33
#define LV_DPI_DEF 130
/* Feature usage */ /*=================
#define LV_USE_ANIMATION 1 * OPERATING SYSTEM
#define LV_USE_SHADOW 0 *=================*/
#define LV_USE_BLEND_MODES 0 #define LV_USE_OS LV_OS_NONE
#define LV_USE_OPA_SCALE 0
#define LV_USE_IMG_TRANSFORM 0
/* Widget usage */ /*========================
#define LV_USE_ARC 0 * RENDERING CONFIGURATION
#define LV_USE_ANIMIMG 0 *========================*/
#define LV_USE_BAR 1 #define LV_DRAW_BUF_STRIDE_ALIGN 4
#define LV_USE_BTN 0 #define LV_DRAW_BUF_ALIGN 4
#define LV_USE_BTNMATRIX 0 #define LV_DRAW_LAYER_SIMPLE_BUF_SIZE (24 * 1024)
#define LV_USE_CANVAS 0 #define LV_USE_DRAW_SW 1
#define LV_USE_CHECKBOX 0 #define LV_DRAW_SW_DRAW_UNIT_CNT 1
#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
/* Themes */ /*=======================
#define LV_USE_THEME_DEFAULT 1 * FEATURE CONFIGURATION
#define LV_USE_THEME_BASIC 0 *=======================*/
#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_14 1
#define LV_FONT_MONTSERRAT_16 1
#define LV_FONT_MONTSERRAT_18 1 #define LV_FONT_MONTSERRAT_18 1
#define LV_FONT_MONTSERRAT_20 1
#define LV_FONT_MONTSERRAT_22 1 #define LV_FONT_MONTSERRAT_22 1
#define LV_FONT_MONTSERRAT_24 1
#define LV_FONT_MONTSERRAT_26 1 #define LV_FONT_MONTSERRAT_26 1
#define LV_FONT_MONTSERRAT_28 1
#define LV_FONT_MONTSERRAT_30 1 #define LV_FONT_MONTSERRAT_30 1
#define LV_FONT_MONTSERRAT_32 0
#define LV_FONT_MONTSERRAT_34 0 #define LV_FONT_MONTSERRAT_34 0
#define LV_FONT_MONTSERRAT_36 0
#define LV_FONT_MONTSERRAT_38 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_46 0
#define LV_FONT_MONTSERRAT_48 0 #define LV_FONT_MONTSERRAT_48 0
/* Others */ #define LV_FONT_DEFAULT &lv_font_montserrat_14
#define LV_USE_PERF_MONITOR 0
#define LV_USE_MEM_MONITOR 0
#define LV_USE_REFR_DEBUG 0
/* Compiler settings */ #define LV_USE_FONT_PLACEHOLDER 1
#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
/* HAL settings */ /*==================
#define LV_TICK_CUSTOM 0 * TEXT SETTINGS
#define LV_DPI_DEF 130 *==================*/
#define LV_TXT_ENC LV_TXT_ENC_UTF8
#endif /*LV_CONF_H*/ /*==================
* V8 COMPATIBILITY
*==================*/
#define LV_USE_OBJ_PROPERTY 0
#endif /*LV_CONF_H*/
+5 -1
View File
@@ -161,7 +161,7 @@ void ftpTransferCallback(FtpTransferOperation ftpOperation, const char *name, ui
// Time display // Time display
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/** Update the UI time label once per second */ /** Update the UI time and date labels once per second */
static void updateTimeDisplay() { static void updateTimeDisplay() {
unsigned long now = millis(); unsigned long now = millis();
if (now - last_time_update < 1000) return; if (now - last_time_update < 1000) return;
@@ -172,6 +172,10 @@ static void updateTimeDisplay() {
char buf[16]; char buf[16];
strftime(buf, sizeof(buf), "%H:%M:%S", &timeinfo); strftime(buf, sizeof(buf), "%H:%M:%S", &timeinfo);
ui_set_time(buf); ui_set_time(buf);
char datebuf[32];
strftime(datebuf, sizeof(datebuf), "%a, %d %b %Y", &timeinfo);
ui_set_date(datebuf);
} }
} }
+147 -70
View File
@@ -1,20 +1,23 @@
#include "ui.h" #include "ui.h"
#include <Arduino.h> #include <Arduino.h>
/* ── colour palette ──────────────────────────────────────────────── */ /* ── colour palette (AMOLED-optimised) ───────────────────────────── */
#define CLR_BG 0x111111 #define CLR_BG 0x0A0A0F
#define CLR_PANEL_BG 0x1A1A1A #define CLR_PANEL_BG 0x141420
#define CLR_PANEL_BRD 0x333333 #define CLR_PANEL_BRD 0x2A2A3A
#define CLR_WHITE 0xFFFFFF #define CLR_WHITE 0xFFFFFF
#define CLR_GRAY 0x888888 #define CLR_GRAY 0x888888
#define CLR_CYAN 0x00E5FF #define CLR_CYAN 0x00BCD4
#define CLR_GREEN 0x4CAF50 #define CLR_GREEN 0x4CAF50
#define CLR_RED 0xF44336 #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_BAR_FILL 0x4CAF50
#define CLR_ERROR_BG 0x330000 #define CLR_ERROR_BG 0x2D0A0A
#define CLR_DIMMED 0x777777 #define CLR_DIMMED 0x666677
#define CLR_SEPARATOR 0x333333 #define CLR_SEPARATOR 0x1E1E2E
#define CLR_TITLE_ACCENT 0x00BCD4
/* ── screen dimensions ───────────────────────────────────────────── */ /* ── screen dimensions ───────────────────────────────────────────── */
#define SCR_W 410 #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_wifi;
static lv_obj_t *lbl_ip; static lv_obj_t *lbl_ip;
static lv_obj_t *panel_ftp; 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_status;
static lv_obj_t *lbl_ftp_url; static lv_obj_t *lbl_ftp_url;
static lv_obj_t *lbl_ftp_creds; 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 *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_time;
static lv_obj_t *lbl_date; static lv_obj_t *lbl_date;
static lv_obj_t *obj_error_bar;
static lv_obj_t *lbl_error; static lv_obj_t *lbl_error;
/* ── cached IP for FTP URL ───────────────────────────────────────── */ /* ── cached IP for FTP URL ───────────────────────────────────────── */
static char cached_ip[48] = "0.0.0.0"; 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, static lv_obj_t *make_label(lv_obj_t *parent, const lv_font_t *font,
uint32_t color, lv_align_t align, 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) const char *text)
{ {
lv_obj_t *lbl = lv_label_create(parent); 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() 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_color(scr, lv_color_hex(CLR_BG), 0);
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0); lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
/* ── title ───────────────────────────────────────────────────── */ /* ────────────────────────────────────────────────────────────── */
lbl_title = make_label(scr, &lv_font_montserrat_26, CLR_WHITE, /* HEADER (y = 0 … 60) */
LV_ALIGN_TOP_MID, 0, 20, "FTP Explorer"); /* ────────────────────────────────────────────────────────────── */
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 */ /* subtle separator line */
static lv_point_t line_pts[] = {{55, 0}, {355, 0}}; static lv_point_precise_t line_pts[] = {{40, 0}, {370, 0}};
line_sep = lv_line_create(scr); line_sep = lv_line_create(scr);
lv_line_set_points(line_sep, line_pts, 2); 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_color(line_sep, lv_color_hex(CLR_SEPARATOR), 0);
lv_obj_set_style_line_width(line_sep, 1, 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, /* WI-FI SECTION (y = 65 … 140) */
LV_ALIGN_TOP_LEFT, 24, 70, /* ────────────────────────────────────────────────────────────── */
lbl_wifi = make_label(scr, &lv_font_montserrat_16, CLR_GREEN,
LV_ALIGN_TOP_LEFT, 28, 68,
LV_SYMBOL_WIFI " Disconnected"); LV_SYMBOL_WIFI " Disconnected");
lbl_ip = make_label(scr, &lv_font_montserrat_30, CLR_CYAN, 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); panel_ftp = lv_obj_create(scr);
lv_obj_set_size(panel_ftp, SCR_W - 40, 120); lv_obj_set_size(panel_ftp, SCR_W - 40, 138);
lv_obj_align(panel_ftp, LV_ALIGN_TOP_MID, 0, 145); 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_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_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_color(panel_ftp, lv_color_hex(CLR_PANEL_BRD), 0);
lv_obj_set_style_border_width(panel_ftp, 1, 0); lv_obj_set_style_border_width(panel_ftp, 1, 0);
lv_obj_set_style_radius(panel_ftp, 10, 0); lv_obj_set_style_border_opa(panel_ftp, LV_OPA_80, 0);
lv_obj_set_style_pad_all(panel_ftp, 12, 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); 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 */ /* 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_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_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_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); lv_obj_align(lbl_ftp_title, LV_ALIGN_TOP_LEFT, 0, 0);
lbl_ftp_status = lv_label_create(panel_ftp); /* status dot (8x8 circle) */
lv_label_set_text(lbl_ftp_status, "Status: Idle"); dot_ftp_status = lv_obj_create(panel_ftp);
lv_obj_set_style_text_font(lbl_ftp_status, &lv_font_montserrat_18, 0); lv_obj_set_size(dot_ftp_status, 8, 8);
lv_obj_set_style_text_color(lbl_ftp_status, lv_color_hex(CLR_GREEN), 0); lv_obj_set_style_radius(dot_ftp_status, 4, 0);
lv_obj_align(lbl_ftp_status, LV_ALIGN_TOP_LEFT, 0, 28); 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); lbl_ftp_url = lv_label_create(panel_ftp);
lv_label_set_text(lbl_ftp_url, "ftp://0.0.0.0"); 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_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); lv_obj_align(lbl_ftp_url, LV_ALIGN_TOP_LEFT, 0, 54);
/* credentials */
lbl_ftp_creds = lv_label_create(panel_ftp); lbl_ftp_creds = lv_label_create(panel_ftp);
lv_label_set_text(lbl_ftp_creds, "User: kode Pass: kode"); 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_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, /* STORAGE SECTION (y = 300 … 370) */
LV_ALIGN_TOP_LEFT, 24, 280, "SD Card"); /* ────────────────────────────────────────────────────────────── */
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); bar_sd = lv_bar_create(scr);
lv_obj_set_size(bar_sd, SCR_W - 48, 14); lv_obj_set_size(bar_sd, SCR_W - 56, 14);
lv_obj_align(bar_sd, LV_ALIGN_TOP_LEFT, 24, 310); lv_obj_align(bar_sd, LV_ALIGN_TOP_LEFT, 28, 330);
lv_bar_set_range(bar_sd, 0, 100); lv_bar_set_range(bar_sd, 0, 100);
lv_bar_set_value(bar_sd, 0, LV_ANIM_OFF); 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_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_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_obj_set_style_bg_color(bar_sd, lv_color_hex(CLR_BAR_FILL),
LV_PART_INDICATOR); LV_PART_INDICATOR);
lv_obj_set_style_bg_opa(bar_sd, LV_OPA_COVER, 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, 7, LV_PART_INDICATOR);
lv_obj_set_style_radius(bar_sd, 4, LV_PART_INDICATOR);
lbl_sd_text = make_label(scr, &lv_font_montserrat_14, CLR_GRAY, /* size text left-aligned */
LV_ALIGN_TOP_LEFT, 24, 330, "0.0 / 0.0 GB"); 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 ─────────────────────────────────────────────── */ /* percentage right-aligned */
lbl_time = make_label(scr, &lv_font_montserrat_42, CLR_WHITE, lbl_sd_pct = make_label(scr, &lv_font_montserrat_14, CLR_GRAY,
LV_ALIGN_TOP_MID, 0, 400, "--:--:--"); 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, 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_label_set_text(lbl_error, "");
lv_obj_set_width(lbl_error, SCR_W - 48); lv_obj_set_width(lbl_error, SCR_W - 72);
lv_obj_set_style_text_font(lbl_error, &lv_font_montserrat_18, 0); 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_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_obj_set_style_text_align(lbl_error, LV_TEXT_ALIGN_CENTER, 0);
lv_label_set_long_mode(lbl_error, LV_LABEL_LONG_WRAP); lv_label_set_long_mode(lbl_error, LV_LABEL_LONG_WRAP);
lv_obj_align(lbl_error, LV_ALIGN_TOP_MID, 0, 360); lv_obj_align(lbl_error, LV_ALIGN_CENTER, 0, 0);
lv_obj_add_flag(lbl_error, LV_OBJ_FLAG_HIDDEN);
} }
/* ── ui_set_wifi ─────────────────────────────────────────────────── */
void ui_set_wifi(const char *ssid, const char *ip) void ui_set_wifi(const char *ssid, const char *ip)
{ {
if (ssid && ssid[0]) { 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); lv_label_set_text_fmt(lbl_ftp_url, "ftp://%s", cached_ip);
} }
/* ── ui_set_ftp ──────────────────────────────────────────────────── */
void ui_set_ftp(const char *status) 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) 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); 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) void ui_set_sd(uint64_t used_mb, uint64_t total_mb)
{ {
int pct = 0; 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); lv_bar_set_value(bar_sd, pct, LV_ANIM_OFF);
/* show in GB with one decimal */
float used_gb = used_mb / 1024.0f; float used_gb = used_mb / 1024.0f;
float total_gb = total_mb / 1024.0f; float total_gb = total_mb / 1024.0f;
lv_label_set_text_fmt(lbl_sd_text, "%.1f / %.1f GB", (double)used_gb, lv_label_set_text_fmt(lbl_sd_info, "%.1f / %.1f GB",
(double)total_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) { if (pct > 90) {
lv_obj_set_style_bg_color(bar_sd, lv_color_hex(CLR_RED), lv_obj_set_style_bg_color(bar_sd, lv_color_hex(CLR_RED),
LV_PART_INDICATOR); 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) void ui_set_error(const char *msg)
{ {
if (msg && msg[0]) { if (msg && msg[0]) {
lv_label_set_text(lbl_error, msg); 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 { } else {
lv_label_set_text(lbl_error, ""); 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);
} }
} }
+1
View File
@@ -6,5 +6,6 @@ void ui_init();
void ui_set_wifi(const char *ssid, const char *ip); void ui_set_wifi(const char *ssid, const char *ip);
void ui_set_ftp(const char *status); void ui_set_ftp(const char *status);
void ui_set_time(const char *time_str); 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_sd(uint64_t used_mb, uint64_t total_mb);
void ui_set_error(const char *msg); void ui_set_error(const char *msg);