207 lines
5.7 KiB
C++
207 lines
5.7 KiB
C++
#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;
|
|
if(_tft.getTouch(&tx, &ty)) {
|
|
evt.pressed = true;
|
|
evt.x = tx;
|
|
evt.y = ty;
|
|
}
|
|
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<uint8_t>(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);
|
|
}
|