Files
klubhaus-doorbell/boards/esp32-s3-lcd-43/DisplayDriverGFX.cpp

294 lines
7.2 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// boards/esp32-s3-lcd-43/DisplayDriverGFX.cpp
#include "DisplayDriverGFX.h"
#include "LovyanPins.h"
#include "board_config.h"
#include <Arduino.h>
// ── Globals ──
static LGFX* _gfx = nullptr;
// ── Forward declarations ──
static void initDisplay();
// ── Dimensions ──
static constexpr int DISP_W = 800;
static constexpr int DISP_H = 480;
// ── Display initialization ──
static void initDisplay() {
Serial.println("LovyanGFX init...");
// Note: LovyanGFX handles I2C internally (port 1 for touch, port 0 for CH422G)
// No need to call Wire.begin() or Wire1.begin()
_gfx = new LGFX();
_gfx->init();
_gfx->setRotation(0); // Landscape
_gfx->fillScreen(0x000000);
Serial.println("Display ready");
}
// ── Singleton ──
DisplayDriverGFX& DisplayDriverGFX::instance() {
static DisplayDriverGFX inst;
return inst;
}
// ── IDisplayDriver implementation ──
void DisplayDriverGFX::begin() {
initDisplay();
// Turn on backlight immediately
setBacklight(true);
}
void DisplayDriverGFX::setBacklight(bool on) {
if(_gfx) {
// LovyanGFX handles backlight via setBrightness
_gfx->setBrightness(on ? 255 : 0);
}
}
int DisplayDriverGFX::width() { return DISP_W; }
int DisplayDriverGFX::height() { return DISP_H; }
// ── Touch handling ──
TouchEvent DisplayDriverGFX::readTouch() {
TouchEvent evt;
if(!_gfx)
return evt;
int32_t x, y;
bool pressed = _gfx->getTouch(&x, &y);
// Only report NEW touches (debounce - ignore held touches)
evt.pressed = pressed && !_lastTouch.pressed;
if(pressed) {
evt.x = static_cast<int>(x);
evt.y = static_cast<int>(y);
_pressStartMs = millis();
}
_lastTouch.pressed = pressed;
return evt;
}
int DisplayDriverGFX::dashboardTouch(int x, int y) {
// Dashboard tiles: 2 rows × 4 columns
constexpr int cols = 4;
constexpr int rows = 2;
constexpr int tileW = DISP_W / cols;
constexpr int tileH = DISP_H / rows;
if(x < 0 || x >= DISP_W || y < 0 || y >= DISP_H) {
return -1;
}
int col = x / tileW;
int row = y / tileH;
return row * cols + col;
}
HoldState DisplayDriverGFX::updateHold(unsigned long holdMs) {
HoldState state;
if(!_lastTouch.pressed) {
_isHolding = false;
return state;
}
unsigned long elapsed = millis() - _pressStartMs;
if(!_isHolding) {
_isHolding = true;
state.started = true;
}
state.active = true;
state.progress = static_cast<float>(elapsed) / static_cast<float>(holdMs);
if(state.progress >= 1.0f) {
state.progress = 1.0f;
state.completed = true;
}
return state;
}
// ── Rendering ──
void DisplayDriverGFX::render(const ScreenState& state) {
if(!_gfx)
return;
// Check if we need full redraw
if(state.screen != _lastScreen || (state.screen == ScreenID::BOOT && state.bootStage != _lastBootStage)) {
_needsRedraw = true;
_lastScreen = state.screen;
_lastBootStage = state.bootStage;
}
switch(state.screen) {
case ScreenID::BOOT:
if(_needsRedraw) {
drawBoot(state);
_needsRedraw = false;
}
break;
case ScreenID::OFF:
if(_needsRedraw) {
_gfx->fillScreen(0x000000);
_needsRedraw = false;
}
break;
case ScreenID::ALERT:
// Only redraw on first entry or screen change
if(_needsRedraw) {
drawAlert(state);
_needsRedraw = false;
}
break;
case ScreenID::DASHBOARD:
if(_needsRedraw) {
drawDashboard(state);
_needsRedraw = false;
}
break;
}
}
void DisplayDriverGFX::drawBoot(const ScreenState& state) {
BootStage stage = state.bootStage;
_gfx->fillScreen(0x000000);
_gfx->setTextColor(0xFFFF);
_gfx->setTextSize(2);
_gfx->setCursor(10, 10);
_gfx->print("KLUBHAUS");
_gfx->setTextSize(1);
_gfx->setCursor(10, 50);
_gfx->print(BOARD_NAME);
// Show boot stage status
_gfx->setCursor(10, 80);
switch(stage) {
case BootStage::SPLASH:
_gfx->print("Initializing...");
break;
case BootStage::INIT_DISPLAY:
_gfx->print("Display OK");
break;
case BootStage::INIT_NETWORK:
_gfx->print("Network init...");
break;
case BootStage::CONNECTING_WIFI:
_gfx->print("Connecting WiFi...");
break;
case BootStage::READY:
_gfx->print("All systems go!");
break;
case BootStage::DONE:
_gfx->print("Ready!");
break;
}
}
void DisplayDriverGFX::drawAlert(const ScreenState& state) {
uint32_t elapsed = millis() - state.alertStartMs;
uint8_t pulse = static_cast<uint8_t>(180.0f + 75.0f * sinf(elapsed / 300.0f));
uint16_t bg = _gfx->color565(pulse, 0, 0);
_gfx->fillScreen(bg);
_gfx->setTextColor(0xFFFF, bg);
_gfx->setTextSize(3);
_gfx->setCursor(10, 20);
_gfx->print(state.alertTitle.length() > 0 ? state.alertTitle.c_str() : "ALERT");
_gfx->setTextSize(2);
_gfx->setCursor(10, 80);
_gfx->print(state.alertBody);
_gfx->setTextSize(1);
_gfx->setCursor(10, DISP_H - 20);
_gfx->print("Hold to silence...");
}
void DisplayDriverGFX::drawDashboard(const ScreenState& state) {
_gfx->fillScreen(0x001030); // Dark blue
// Header
_gfx->fillRect(0, 0, DISP_W, 30, 0x1A1A); // Dark gray
_gfx->setFont(&fonts::Font0); // Built-in minimal font
_gfx->setTextColor(0xFFFF);
_gfx->setTextSize(1);
_gfx->setCursor(5, 10);
_gfx->printf("KLUBHAUS");
// WiFi status
_gfx->setCursor(DISP_W - 100, 10);
_gfx->printf("WiFi:%s", state.wifiSsid.length() > 0 ? "ON" : "OFF");
// Tiles: 2 rows × 4 columns
constexpr int cols = 4;
constexpr int rows = 2;
constexpr int tileW = DISP_W / cols;
constexpr int tileH = (DISP_H - 30) / rows;
constexpr int margin = 8;
// Draw placeholder tiles (8 total for 2x4 grid)
const char* tileLabels[] = { "1", "2", "3", "4", "5", "6", "7", "8" };
for(int i = 0; i < 8; i++) {
int col = i % cols;
int row = i / cols;
int x = col * tileW + margin;
int y = 30 + row * tileH + margin;
int w = tileW - 2 * margin;
int h = tileH - 2 * margin;
// Tile background
_gfx->fillRoundRect(x, y, w, h, 8, 0x0220);
// Tile border
_gfx->drawRoundRect(x, y, w, h, 8, 0xFFFF);
// Tile number
_gfx->setTextColor(0xFFFF);
_gfx->setTextSize(2);
_gfx->setCursor(x + w / 2 - 10, y + h / 2 - 10);
_gfx->print(tileLabels[i]);
}
}
void DisplayDriverGFX::updateHint(int x, int y, bool active) {
if(!_gfx)
return;
static uint32_t lastTime = 0;
uint32_t now = millis();
if(now - lastTime < 100)
return;
lastTime = now;
// active=true: faster pulse (500ms), active=false: slower pulse (2000ms)
float period = active ? 500.0f : 2000.0f;
float t = fmodf(now, period) / period;
uint8_t v = static_cast<uint8_t>(30.0f + 30.0f * sinf(t * 2.0f * 3.14159f));
uint16_t col = _gfx->color565(v, v, v);
_gfx->drawCircle(x, y, 50, col);
}