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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,199 +1,139 @@
|
||||
#include <kodedot/display_manager.h>
|
||||
#include <Preferences.h>
|
||||
|
||||
// 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 {
|
||||
|
||||
Reference in New Issue
Block a user