#include "DisplayDriverTFT.h" void DisplayDriverTFT::begin() { // Backlight pinMode(PIN_LCD_BL, OUTPUT); digitalWrite(PIN_LCD_BL, LOW); _tft.init(); _tft.setRotation(DISPLAY_ROTATION); _tft.fillScreen(TFT_BLACK); Serial.printf("[GFX] Display OK: %dx%d\n", DISPLAY_WIDTH, DISPLAY_HEIGHT); ScreenState st; st.screen = ScreenID::BOOT; st.bootStage = BootStage::SPLASH; drawBoot(st); digitalWrite(PIN_LCD_BL, HIGH); Serial.println("[GFX] Backlight ON"); } void DisplayDriverTFT::setBacklight(bool on) { digitalWrite(PIN_LCD_BL, on ? HIGH : LOW); } // ── Rendering ─────────────────────────────────────────────── void DisplayDriverTFT::render(const ScreenState& st) { if(st.screen != _lastScreen || (st.screen == ScreenID::BOOT && st.bootStage != _lastBootStage)) { _needsRedraw = true; _lastScreen = st.screen; _lastBootStage = st.bootStage; } switch(st.screen) { case ScreenID::BOOT: if(_needsRedraw) { drawBoot(st); _needsRedraw = false; } break; case ScreenID::ALERT: drawAlert(st); break; case ScreenID::DASHBOARD: if(_needsRedraw) { drawDashboard(st); _needsRedraw = false; } break; case ScreenID::OFF: if(_needsRedraw) { _tft.fillScreen(TFT_BLACK); _needsRedraw = false; } break; } } void DisplayDriverTFT::drawBoot(const ScreenState& st) { BootStage stage = st.bootStage; _tft.fillScreen(TFT_BLACK); _tft.setTextColor(TFT_WHITE, TFT_BLACK); _tft.setTextSize(2); _tft.setCursor(10, 10); _tft.printf("KLUBHAUS v%s", FW_VERSION); _tft.setTextSize(1); _tft.setCursor(10, 40); _tft.print(BOARD_NAME); // Show boot stage status _tft.setCursor(10, 70); switch(stage) { case BootStage::SPLASH: _tft.print("Initializing..."); break; case BootStage::INIT_DISPLAY: _tft.print("Display OK"); break; case BootStage::INIT_NETWORK: _tft.print("Network init..."); break; case BootStage::CONNECTING_WIFI: _tft.print("Connecting WiFi..."); break; case BootStage::READY: _tft.print("All systems go!"); break; case BootStage::DONE: _tft.print("Ready!"); break; } } void DisplayDriverTFT::drawAlert(const ScreenState& st) { uint32_t elapsed = millis() - st.alertStartMs; uint8_t pulse = 180 + (uint8_t)(75.0f * sinf(elapsed / 300.0f)); uint16_t bg = _tft.color565(pulse, 0, 0); _tft.fillScreen(bg); _tft.setTextColor(TFT_WHITE, bg); _tft.setTextSize(3); _tft.setCursor(10, 20); _tft.print(st.alertTitle.length() > 0 ? st.alertTitle : "ALERT"); _tft.setTextSize(2); _tft.setCursor(10, 80); _tft.print(st.alertBody); _tft.setTextSize(1); _tft.setCursor(10, DISPLAY_HEIGHT - 20); _tft.print("Hold to silence..."); } void DisplayDriverTFT::drawDashboard(const ScreenState& st) { _tft.fillScreen(TFT_BLACK); _tft.setTextColor(TFT_WHITE, TFT_BLACK); _tft.setTextSize(1); _tft.setCursor(5, 5); _tft.printf("KLUBHAUS — %s", deviceStateStr(st.deviceState)); int y = 30; _tft.setCursor(5, y); y += 18; _tft.printf("WiFi: %s %ddBm", st.wifiSsid.c_str(), st.wifiRssi); _tft.setCursor(5, y); y += 18; _tft.printf("IP: %s", st.ipAddr.c_str()); _tft.setCursor(5, y); y += 18; _tft.printf("Up: %lus Heap: %d", st.uptimeMs / 1000, ESP.getFreeHeap()); _tft.setCursor(5, y); y += 18; _tft.printf("Last poll: %lus ago", st.lastPollMs > 0 ? (millis() - st.lastPollMs) / 1000 : 0); } // ── Touch ─────────────────────────────────────────────────── TouchEvent DisplayDriverTFT::readTouch() { TouchEvent evt; uint16_t tx, ty; uint8_t touched = _tft.getTouch(&tx, &ty, 100); // Detect transitions (press/release) if(touched && !_touchWasPressed) { // Press transition: finger just touched down evt.pressed = true; _touchDownX = tx; _touchDownY = ty; evt.downX = _touchDownX; evt.downY = _touchDownY; } else if(!touched && _touchWasPressed) { // Release transition: finger just lifted evt.released = true; evt.downX = _touchDownX; evt.downY = _touchDownY; } // Current position if still touched if(touched) { evt.x = tx; evt.y = ty; evt.downX = _touchDownX; evt.downY = _touchDownY; } // Track previous state for next call _touchWasPressed = touched; return evt; } int DisplayDriverTFT::dashboardTouch(int x, int y) { // 2x2 grid, accounting for 30px header if(y < 30) return -1; int col = (x * 2) / DISPLAY_WIDTH; // 0 or 1 int row = ((y - 30) * 2) / (DISPLAY_HEIGHT - 30); // 0 or 1 if(col < 0 || col > 1 || row < 0 || row > 1) return -1; return row * 2 + col; // 0, 1, 2, or 3 } HoldState DisplayDriverTFT::updateHold(unsigned long holdMs) { HoldState h; TouchEvent t = readTouch(); if(t.pressed) { if(!_holdActive) { _holdActive = true; _holdStartMs = millis(); h.started = true; } uint32_t held = millis() - _holdStartMs; h.active = true; h.progress = constrain((float)held / (float)holdMs, 0.0f, 1.0f); h.completed = (held >= holdMs); // Simple progress bar at bottom of screen int barW = (int)(DISPLAY_WIDTH * h.progress); _tft.fillRect(0, DISPLAY_HEIGHT - 8, barW, 8, TFT_WHITE); _tft.fillRect(barW, DISPLAY_HEIGHT - 8, DISPLAY_WIDTH - barW, 8, TFT_DARKGREY); } else { if(_holdActive) { // Clear the progress bar when released _tft.fillRect(0, DISPLAY_HEIGHT - 8, DISPLAY_WIDTH, 8, TFT_DARKGREY); } _holdActive = false; } return h; } void DisplayDriverTFT::updateHint(int x, int y, bool active) { float period = active ? 500.0f : 2000.0f; float t = fmodf(millis(), period) / period; uint8_t v = static_cast(30.0f + 30.0f * sinf(t * 2.0f * PI)); uint16_t col = _tft.color565(v, v, v); _tft.drawRect(x - 40, y - 20, 80, 40, col); }