257 lines
7.2 KiB
C++
257 lines
7.2 KiB
C++
#include "DisplayDriverGFX.h"
|
|
#include "board_config.h"
|
|
#include <ESP_IOExpander_Library.h>
|
|
#include <Wire.h>
|
|
|
|
#ifndef BLACK
|
|
#define BLACK 0x0000
|
|
#endif
|
|
#ifndef WHITE
|
|
#define WHITE 0xFFFF
|
|
#endif
|
|
#ifndef RED
|
|
#define RED 0xF800
|
|
#endif
|
|
|
|
// CH422G logical pin numbers
|
|
#define EXIO_TP_RST IO_EXPANDER_PIN_NUM_1
|
|
#define EXIO_LCD_BL IO_EXPANDER_PIN_NUM_2
|
|
#define EXIO_LCD_RST IO_EXPANDER_PIN_NUM_3
|
|
#define EXIO_SD_CS IO_EXPANDER_PIN_NUM_4
|
|
#define EXIO_USB_SEL IO_EXPANDER_PIN_NUM_5
|
|
|
|
static ESP_IOExpander* expander = nullptr;
|
|
|
|
// ── IO Expander ──
|
|
|
|
void DisplayDriverGFX::expanderInit() {
|
|
Serial.println("[IO] IO expander init...");
|
|
expander = new ESP_IOExpander_CH422G(
|
|
I2C_MASTER_NUM,
|
|
ESP_IO_EXPANDER_I2C_CH422G_ADDRESS
|
|
);
|
|
expander->init();
|
|
expander->begin();
|
|
expander->multiPinMode(
|
|
EXIO_TP_RST | EXIO_LCD_BL | EXIO_LCD_RST | EXIO_SD_CS | EXIO_USB_SEL,
|
|
OUTPUT
|
|
);
|
|
// Deassert resets, backlight OFF for now
|
|
expander->multiDigitalWrite(
|
|
EXIO_TP_RST | EXIO_LCD_RST | EXIO_SD_CS,
|
|
0xFF
|
|
);
|
|
expander->digitalWrite(EXIO_LCD_BL, LOW);
|
|
Serial.println("[IO] CH422G initialized");
|
|
}
|
|
|
|
void DisplayDriverGFX::setBacklight(bool on) {
|
|
if (expander) {
|
|
expander->digitalWrite(EXIO_LCD_BL, on ? HIGH : LOW);
|
|
Serial.printf("[GFX] Backlight %s\n", on ? "ON" : "OFF");
|
|
}
|
|
}
|
|
|
|
// ── Touch ──
|
|
|
|
void DisplayDriverGFX::touchInit() {
|
|
Wire.begin(I2C_MASTER_SDA, I2C_MASTER_SCL);
|
|
Wire.beginTransmission(GT911_ADDR);
|
|
uint8_t err = Wire.endTransmission();
|
|
if (err == 0) {
|
|
Serial.println("[TOUCH] GT911 initialized");
|
|
} else {
|
|
Serial.printf("[TOUCH] GT911 not found (I2C err %d)\n", err);
|
|
}
|
|
}
|
|
|
|
TouchEvent DisplayDriverGFX::readTouch() {
|
|
TouchEvent ev = { false, 0, 0 };
|
|
Wire.beginTransmission(GT911_ADDR);
|
|
if (Wire.endTransmission() != 0) return ev;
|
|
|
|
// Read touch status register (0x814E)
|
|
Wire.beginTransmission(GT911_ADDR);
|
|
Wire.write(0x81);
|
|
Wire.write(0x4E);
|
|
Wire.endTransmission(false);
|
|
Wire.requestFrom((uint8_t)GT911_ADDR, (uint8_t)1);
|
|
if (!Wire.available()) return ev;
|
|
|
|
uint8_t status = Wire.read();
|
|
uint8_t touches = status & 0x0F;
|
|
|
|
if ((status & 0x80) && touches > 0 && touches <= 5) {
|
|
// Read first touch point (0x8150)
|
|
Wire.beginTransmission(GT911_ADDR);
|
|
Wire.write(0x81);
|
|
Wire.write(0x50);
|
|
Wire.endTransmission(false);
|
|
Wire.requestFrom((uint8_t)GT911_ADDR, (uint8_t)4);
|
|
if (Wire.available() >= 4) {
|
|
uint8_t xl = Wire.read();
|
|
uint8_t xh = Wire.read();
|
|
uint8_t yl = Wire.read();
|
|
uint8_t yh = Wire.read();
|
|
ev.pressed = true;
|
|
ev.x = (xh << 8) | xl;
|
|
ev.y = (yh << 8) | yl;
|
|
}
|
|
}
|
|
|
|
// Clear status
|
|
Wire.beginTransmission(GT911_ADDR);
|
|
Wire.write(0x81);
|
|
Wire.write(0x4E);
|
|
Wire.write(0x00);
|
|
Wire.endTransmission();
|
|
|
|
return ev;
|
|
}
|
|
|
|
int DisplayDriverGFX::dashboardTouch(int x, int y) {
|
|
// Unified 2x2 grid
|
|
int col = (x * 2) / DISPLAY_WIDTH; // 0 or 1
|
|
int row = (y * 2) / DISPLAY_HEIGHT; // 0 or 1
|
|
return row * 2 + col; // 0, 1, 2, or 3
|
|
}
|
|
|
|
HoldState DisplayDriverGFX::updateHold(unsigned long holdMs) {
|
|
TouchEvent ev = readTouch();
|
|
|
|
if (ev.pressed) {
|
|
if (!_lastTouched) {
|
|
_holdStart = millis();
|
|
_lastTouched = true;
|
|
}
|
|
unsigned long elapsed = millis() - _holdStart;
|
|
float progress = constrain((float)elapsed / holdMs, 0.0f, 1.0f);
|
|
|
|
if (elapsed >= holdMs) {
|
|
_lastTouched = false;
|
|
_holdStart = 0;
|
|
return {false, true, 1.0f};
|
|
}
|
|
return {true, false, progress};
|
|
} else {
|
|
_lastTouched = false;
|
|
_holdStart = 0;
|
|
return {false, false, 0.0f};
|
|
}
|
|
}
|
|
void DisplayDriverGFX::updateHint() {
|
|
// placeholder for idle hint animation
|
|
}
|
|
|
|
// ── Display ──
|
|
|
|
void DisplayDriverGFX::begin() {
|
|
// 1. Touch (I2C on GPIO 8/9)
|
|
touchInit();
|
|
delay(200);
|
|
|
|
// 2. IO expander
|
|
expanderInit();
|
|
|
|
// 3. RGB display
|
|
Serial.println("[GFX] GFX init...");
|
|
Arduino_ESP32RGBPanel* rgbPanel = new Arduino_ESP32RGBPanel(
|
|
LCD_DE, LCD_VSYNC, LCD_HSYNC, LCD_PCLK,
|
|
LCD_R0, LCD_R1, LCD_R2, LCD_R3, LCD_R4,
|
|
LCD_G0, LCD_G1, LCD_G2, LCD_G3, LCD_G4, LCD_G5,
|
|
LCD_B0, LCD_B1, LCD_B2, LCD_B3, LCD_B4,
|
|
0, // hsync_polarity
|
|
40, // hsync_front_porch
|
|
48, // hsync_pulse_width
|
|
88, // hsync_back_porch
|
|
0, // vsync_polarity
|
|
13, // vsync_front_porch
|
|
3, // vsync_pulse_width
|
|
32, // vsync_back_porch
|
|
1, // pclk_active_neg
|
|
16000000 // prefer_speed
|
|
);
|
|
|
|
_gfx = new Arduino_RGB_Display(
|
|
DISPLAY_WIDTH, DISPLAY_HEIGHT, rgbPanel,
|
|
DISPLAY_ROTATION, true
|
|
);
|
|
|
|
if (!_gfx->begin()) {
|
|
Serial.println("[GFX] *** Display init FAILED ***");
|
|
return;
|
|
}
|
|
|
|
Serial.printf("[GFX] Display OK: %dx%d\n", DISPLAY_WIDTH, DISPLAY_HEIGHT);
|
|
Serial.printf("[MEM] Free heap: %d | Free PSRAM: %d\n",
|
|
ESP.getFreeHeap(), ESP.getFreePsram());
|
|
|
|
// 4. Clear and backlight on
|
|
_gfx->fillScreen(BLACK);
|
|
setBacklight(true);
|
|
drawBoot();
|
|
}
|
|
|
|
int DisplayDriverGFX::width() { return _gfx ? _gfx->width() : DISPLAY_WIDTH; }
|
|
int DisplayDriverGFX::height() { return _gfx ? _gfx->height() : DISPLAY_HEIGHT; }
|
|
|
|
// ── Render (state machine driven) ──
|
|
|
|
void DisplayDriverGFX::render(const ScreenState& state) {
|
|
if (!_gfx) return;
|
|
switch (state.screen) {
|
|
case ScreenID::BOOT: drawBoot(); break;
|
|
case ScreenID::DASHBOARD: drawDashboard(state); break;
|
|
case ScreenID::ALERT: drawAlert(state); break;
|
|
case ScreenID::OFF: drawOff(); break;
|
|
default: drawBoot(); break;
|
|
}
|
|
}
|
|
|
|
void DisplayDriverGFX::drawBoot() {
|
|
if (!_gfx) return;
|
|
_gfx->fillScreen(BLACK);
|
|
_gfx->setTextColor(WHITE);
|
|
_gfx->setTextSize(4);
|
|
_gfx->setCursor(250, 200);
|
|
_gfx->println("Klubhaus Alert");
|
|
_gfx->setTextSize(2);
|
|
_gfx->setCursor(300, 260);
|
|
_gfx->println("Booting...");
|
|
}
|
|
|
|
void DisplayDriverGFX::drawDashboard(const ScreenState& state) {
|
|
if (!_gfx) return;
|
|
_gfx->fillScreen(BLACK);
|
|
_gfx->setTextColor(WHITE);
|
|
_gfx->setTextSize(3);
|
|
_gfx->setCursor(20, 20);
|
|
_gfx->println("KLUBHAUS DASHBOARD");
|
|
_gfx->setTextSize(2);
|
|
_gfx->setCursor(20, 80);
|
|
_gfx->printf("IP: %s", state.ipAddr.c_str());
|
|
_gfx->setCursor(20, 110);
|
|
_gfx->printf("RSSI: %d dBm", state.wifiRssi);
|
|
_gfx->setCursor(20, 140);
|
|
}
|
|
|
|
void DisplayDriverGFX::drawAlert(const ScreenState& state) {
|
|
if (!_gfx) return;
|
|
_gfx->fillScreen(RED);
|
|
_gfx->setTextColor(WHITE);
|
|
_gfx->setTextSize(5);
|
|
_gfx->setCursor(200, 180);
|
|
_gfx->println("DOORBELL!");
|
|
_gfx->setTextSize(3);
|
|
_gfx->setCursor(100, 280);
|
|
const char* body = state.alertBody.c_str();
|
|
_gfx->println(state.alertBody.length() ? body : "Someone is at the door");
|
|
}
|
|
|
|
void DisplayDriverGFX::drawOff() {
|
|
if (!_gfx) return;
|
|
_gfx->fillScreen(BLACK);
|
|
setBacklight(false);
|
|
}
|
|
|