CYD kein Touch , Display ok

Ich bringe auf einem CYD ESP32-2432S028R das Touch nicht zum Laufen Hat vielleicht jemand Erfahrung und ein bisschen Coding für mich. Das Display funktioniert.

Hi, schaue mal hier, vielleicht hilft das weiter.

Danke! Habe ich auch schon gesehen. Ist aber ein mächtiges, aber auch spezielles Coding. Wenn da ein Fehler kommt, such ich mir einen Wolf. Deshalb suche ich ja so ein “bisschen” Coding , ein Button oder so, um mich langsam vorzutasten. CGPT habe ich schon zur Verzweifelung getrieben -ohne Ergebnis.

Zeig doch mal was du schon hast!?

Bisher habe ich das Board dazu gebracht Farbe und Schrift anzuzeigen. Jeglicher Versuch, das Touch zu aktivieren hat entweder das Display wieder zerschossen oder gab nur die Koordinaten 0,0 zurück.

logger:

\# HSPI

spi:

clk_pin: GPIO14

mosi_pin: GPIO13

miso_pin: GPIO12

id: hspi

\# Backlight direkt HIGH

output:

\- platform: gpio

pin: GPIO21

id: backlight

switch:

\- platform: output

output: backlight

restore_mode: ALWAYS_ON

name: "Backlight Test"

font:

\- file: "gfonts://Roboto"

id: font_big

size: 26

display:

\- platform: ili9xxx

model: ili9342

id: tft_display

spi_id: hspi

cs_pin: GPIO15

dc_pin: GPIO2

reset_pin: GPIO4

rotation: 0

invert_colors: false

update_interval: 1s

color_palette: 8BIT

lambda: |-

it.fill(Color(0, 255, 0)); // Test grün

it.print(120, 160, id(font_big), TextAlign::CENTER, "ESP32 TFT OK");

EDIT by Jay: Code bitte immer über die Codefunktion „</>“ im Editor einfügen.

Code bitte immer in Code-Tags ( im Editor </>) setzen, sonst kann man das nicht wirklich lesen!?

esphome:
  name: cyd-demo-portrait
  friendly_name: CYD Demo Portrait (USB unten)

  on_boot:
    priority: 600
    then:
      - light.turn_on:
          id: backlight
          brightness: 0.80

esp32:
  board: esp32dev
  framework:
    type: arduino
  flash_size: 4MB

logger:

api:

ota:
  - platform: esphome

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:
    ssid: "cyd-demo-fallback"
    password: "12345678"

captive_portal:

# -----------------------------
# BACKLIGHT
# -----------------------------
output:
  - platform: ledc
    pin: GPIO21
    id: backlight_pwm

light:
  - platform: monochromatic
    id: backlight
    output: backlight_pwm
    restore_mode: ALWAYS_ON
    default_transition_length: 0s

# -----------------------------
# TIME
# -----------------------------
time:
  - platform: sntp
    id: esptime
    timezone: Europe/Berlin

# -----------------------------
# TOUCH STATE
# -----------------------------
globals:
  - id: last_touch_x
    type: int
    initial_value: "0"
  - id: last_touch_y
    type: int
    initial_value: "0"
  - id: touched
    type: bool
    initial_value: "false"

# -----------------------------
# SPI
# -----------------------------
spi:
  - id: tft
    clk_pin: GPIO14
    mosi_pin: GPIO13
    miso_pin: GPIO12

  - id: touch_spi
    clk_pin: GPIO25
    mosi_pin: GPIO32
    miso_pin: GPIO39

# -----------------------------
# TOUCHSCREEN
# -----------------------------
touchscreen:
  platform: xpt2046
  spi_id: touch_spi
  cs_pin: GPIO33
  interrupt_pin: GPIO36
  update_interval: 35ms
  threshold: 400

  calibration:
    x_min: 280
    x_max: 3860
    y_min: 340
    y_max: 3860

  transform:
    swap_xy: false
    mirror_x: true
    mirror_y: false

  on_touch:
    - lambda: |-
        if (touches.empty()) return;
        id(last_touch_x) = touches[0].x;
        id(last_touch_y) = touches[0].y;
        id(touched) = true;

  on_release:
    - lambda: |-
        id(touched) = false;

# -----------------------------
# FONT
# -----------------------------
font:
  - file: "fonts/RobotoSlab-Medium.ttf"
    id: font_small
    size: 18

# -----------------------------
# DISPLAY
# -----------------------------
display:
  - platform: ili9xxx
    spi_id: tft
    model: ili9341
    cs_pin: GPIO15
    dc_pin: GPIO2
    invert_colors: false
    update_interval: 200ms
    auto_clear_enabled: true
    color_palette: 8BIT

    dimensions:
      width: 320
      height: 240

    rotation: 90

    lambda: |-
      it.fill(Color(0, 0, 0));

      it.printf(8, 8, id(font_small), Color(255,255,255),
                "CYD Demo (USB unten)");

      it.strftime(8, 32, id(font_small), Color(0,128,255),
                  "%H:%M:%S", id(esptime).now());

      it.printf(8, 60, id(font_small), Color(255,255,255),
                "Touch: %d, %d", id(last_touch_x), id(last_touch_y));

      if (id(touched)) {
        int w = it.get_width();
        int h = it.get_height();

        int x = id(last_touch_x);
        int y = id(last_touch_y);

        if (x < 0) x = 0;
        if (y < 0) y = 0;
        if (x >= w) x = w - 1;
        if (y >= h) y = h - 1;

        it.filled_circle(x, y, 6, Color(0,255,0));
      }

Das sollte eigentlich so gehen (bei mir läuft es jedenfalls so !). :waving_hand:

Und dasselbe in „Ouer!“

CYD Demo – Landscape USB rechts
esphome:
  name: cyd-demo-landscape-usb-right
  friendly_name: CYD Demo (Landscape USB rechts)

  on_boot:
    priority: 600
    then:
      - light.turn_on:
          id: backlight
          brightness: 0.80

esp32:
  board: esp32dev
  framework:
    type: arduino
  flash_size: 4MB

logger:

api:

ota:
  - platform: esphome

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:
    ssid: "cyd-demo-fallback"
    password: "12345678"

captive_portal:

output:
  - platform: ledc
    pin: GPIO21
    id: backlight_pwm

light:
  - platform: monochromatic
    id: backlight
    output: backlight_pwm
    restore_mode: ALWAYS_ON
    default_transition_length: 0s

globals:
  - id: last_touch_x
    type: int
    initial_value: "0"
  - id: last_touch_y
    type: int
    initial_value: "0"
  - id: touched
    type: bool
    initial_value: "false"

spi:
  - id: tft
    clk_pin: GPIO14
    mosi_pin: GPIO13
    miso_pin: GPIO12

  - id: touch_spi
    clk_pin: GPIO25
    mosi_pin: GPIO32
    miso_pin: GPIO39

touchscreen:
  platform: xpt2046
  spi_id: touch_spi
  cs_pin: GPIO33
  interrupt_pin: GPIO36
  update_interval: 35ms
  threshold: 400

  calibration:
    x_min: 280
    x_max: 3860
    y_min: 340
    y_max: 3860

  transform:
    swap_xy: true
    mirror_x: false
    mirror_y: false

  on_touch:
    - lambda: |-
        if (touches.empty()) return;
        id(last_touch_x) = touches[0].x;
        id(last_touch_y) = touches[0].y;
        id(touched) = true;

  on_release:
    - lambda: |-
        id(touched) = false;

font:
  - file: "fonts/RobotoSlab-Medium.ttf"
    id: font_small
    size: 18

display:
  - platform: ili9xxx
    spi_id: tft
    model: ili9341
    cs_pin: GPIO15
    dc_pin: GPIO2
    invert_colors: false

    rotation: 90
    dimensions:
      width: 240
      height: 320

    update_interval: 200ms
    auto_clear_enabled: true
    color_palette: 8BIT

    lambda: |-
      const int W = it.get_width();
      const int H = it.get_height();

      it.fill(Color(0,0,0));

      it.printf(10, 10, id(font_small), Color(255,255,255),
                "CYD Demo (Landscape USB rechts)");
      it.printf(10, 34, id(font_small), Color(180,180,180),
                "W=%d H=%d", W, H);

      it.printf(10, 64, id(font_small), Color(255,255,255),
                "Touch: %d, %d", id(last_touch_x), id(last_touch_y));

      if (id(touched)) {
        int x = id(last_touch_x);
        int y = id(last_touch_y);

        if (x < 0) x = 0;
        if (y < 0) y = 0;
        if (x >= W) x = W - 1;
        if (y >= H) y = H - 1;

        it.filled_circle(x, y, 6, Color(0,255,0));
      }

Danke !! Du hast mich ein ganzes Stück nach vorn gebracht. Portrait funktioniert = gibt Koordinaten zurück. Landscape-Coding stellt weiter in Portrait dar. Der obere Teil hat keine Farbe und die Schrift auf der linken Seite flackert. Wenn Du da jetzt noch einen Tip hättest? Und wenn Du mir ein Beispiel spendiertest, wie ich einen Button einfüge . . .

Demo für ein CYD (ESP32 + ILI9341 + XPT2046) im Landscape-Modus (USB rechts).

Features:
 - Startseite mit Header (Uhr + WLAN-Balken) + Footer (leer)
 - Demo-Button auf der Startseite (Touch)
 - Setup-Seite per Tap auf Header (Helligkeit: ↑ / % / ↓)
 - Backlight-Helligkeit wird live gesetzt und dauerhaft gespeichert (restore_value: yes)
 - Setup-Seite wird NICHT über Neustart gespeichert (restore_value: no)
Kompletten Code anzeigen
esphome:
  name: cyd-demo-landscape-usb-right
  friendly_name: CYD Demo (Landscape USB rechts)
  comment: Demo für ein CYD (ESP32 + ILI9341 + XPT2046) im Landscape-Modus (USB rechts).

#    Features:
#      - Startseite mit Header (Uhr + WLAN-Balken) + Footer (leer)
#      - Demo-Button auf der Startseite (Touch)
#      - Setup-Seite per Tap auf Header (Helligkeit: ↑ / % / ↓)
#      - Backlight-Helligkeit wird live gesetzt und dauerhaft gespeichert (restore_value: yes)
#      - Setup-Seite wird NICHT über Neustart gespeichert (restore_value: no)

  # Beim Booten Backlight auf gespeicherte Helligkeit setzen
  on_boot:
    priority: 600
    then:
      - light.turn_on:
          id: backlight
          brightness: !lambda 'return id(display_brightness);'

esp32:
  board: esp32dev
  framework:
    type: arduino
  flash_size: 4MB

# Serielle Logs zur Fehlersuche
logger:

# API für Home Assistant / ESPHome API (optional, aber praktisch)
api:

# OTA Updates (Upload über Netzwerk)
ota:
  - platform: esphome

# WLAN + Fallback AP (falls WLAN nicht erreichbar)
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:
    ssid: "cyd-demo-fallback"
    password: "12345678"

captive_portal:

# ============================================================================
# BACKLIGHT (Display-Hintergrundbeleuchtung)
# ============================================================================
# CYD Backlight hängt häufig an GPIO21. Wir erzeugen PWM über LEDC und steuern
# damit ein monochromes "light". Brightness in ESPHome ist 0.0 .. 1.0.
output:
  - platform: ledc
    pin: GPIO21
    id: backlight_pwm

light:
  - platform: monochromatic
    id: backlight
    output: backlight_pwm
    restore_mode: ALWAYS_ON
    default_transition_length: 0s

# ============================================================================
# TIME (für Uhrzeit im Header)
# ============================================================================
time:
  - platform: sntp
    id: esptime
    timezone: Europe/Berlin

# ============================================================================
# WIFI SIGNAL (für Balkenanzeige)
# ============================================================================
sensor:
  - platform: wifi_signal
    id: wifi_rssi
    update_interval: 30s

# ============================================================================
# GLOBALS (Zustände, die wir im Code nutzen)
# ============================================================================
globals:
  # Setup-Seite anzeigen? (NICHT über Neustart merken)
  - id: show_setup_page
    type: bool
    restore_value: no
    initial_value: "false"

  # Display-Helligkeit (über Neustart merken)
  - id: display_brightness
    type: float
    restore_value: yes
    initial_value: "0.80"

  # Demo-Button Status (über Neustart egal -> no)
  # Wird beim Tippen auf den Demo-Button umgeschaltet.
  - id: demo_button_on
    type: bool
    restore_value: no
    initial_value: "false"

# ============================================================================
# SPI BUSSE
# ============================================================================
# Display SPI
spi:
  - id: tft
    clk_pin: GPIO14
    mosi_pin: GPIO13
    miso_pin: GPIO12

  # Touch SPI (XPT2046 ist eigener SPI-Bus)
  - id: touch_spi
    clk_pin: GPIO25
    mosi_pin: GPIO32
    miso_pin: GPIO39

# ============================================================================
# TOUCHSCREEN (XPT2046)
# ============================================================================
# Wichtig: Die transform-Parameter + calibration Werte sind bei dir funktionierend.
# Touch liefert Koordinaten passend zu unserem Layout (Landscape).
touchscreen:
  platform: xpt2046
  spi_id: touch_spi
  cs_pin: GPIO33
  interrupt_pin: GPIO36
  update_interval: 35ms
  threshold: 400

  calibration:
    x_min: 280
    x_max: 3860
    y_min: 340
    y_max: 3860

  transform:
    swap_xy: true
    mirror_x: false
    mirror_y: false

  # --------------------------------------------------------------------------
  # Touch-Logik:
  # - Tap in Header: Setup-Seite an/aus
  # - Wenn Setup offen: ↑ / ↓ Bereiche regeln die Helligkeit
  # - Wenn Startseite: Demo-Button toggeln
  # --------------------------------------------------------------------------
  on_touch:
    - lambda: |-
        if (touches.empty()) return;

        const int tx = touches[0].x;
        const int ty = touches[0].y;

        // --- Layout Konstanten (müssen mit Display-Lambda zusammenpassen) ---
        const int bar_h = 24;
        const int header_y0 = 2;

        // Header (oben) Bereich: toggelt Setup
        if (ty >= header_y0 && ty <= (header_y0 + bar_h)) {
          id(show_setup_page) = !id(show_setup_page);
          return;
        }

        // ---------------- SETUP Seite Touch ----------------
        if (id(show_setup_page)) {

          // Links angeordneter Brightness-Block:
          //   ↑
          //  80%
          //   ↓
          const int left_x = 28;
          const int top_y  = header_y0 + bar_h + 22;

          // Touch-Hitbox (groß genug für Finger)
          const int hit_w = 90;
          const int hit_h = 40;

          // Schrittweite: 5%
          const float step = 0.05f;

          // Helper: clamp + sofort Backlight setzen
          auto apply_brightness = [&]() {
            float b = id(display_brightness);
            if (b < 0.0f) b = 0.0f;
            if (b > 1.0f) b = 1.0f;
            id(display_brightness) = b;

            auto call = id(backlight).turn_on();
            call.set_brightness(b);
            call.perform();
          };

          // UP (Pfeil oben)
          if (tx >= left_x &&
              tx <= (left_x + hit_w) &&
              ty >= top_y &&
              ty <= (top_y + hit_h)) {
            id(display_brightness) += step;
            apply_brightness();
            return;
          }

          // DOWN (Pfeil unten) - enger an Prozent
          const int down_y = top_y + 50;
          if (tx >= left_x &&
              tx <= (left_x + hit_w) &&
              ty >= down_y &&
              ty <= (down_y + hit_h)) {
            id(display_brightness) -= step;
            apply_brightness();
            return;
          }

          return; // Setup-Seite: sonst keine weiteren Aktionen
        }

        // ---------------- START Seite Touch ----------------
        // Demo-Button Bereich (muss zum Display-Lambda passen)
        const int btn_x = 16;
        const int btn_y = header_y0 + bar_h + 36;
        const int btn_w = 150;
        const int btn_h = 48;

        if (tx >= btn_x && tx <= (btn_x + btn_w) &&
            ty >= btn_y && ty <= (btn_y + btn_h)) {

          // Toggle Demo-Button Zustand
          id(demo_button_on) = !id(demo_button_on);

          // Optional: kleine visuelle Rückmeldung über Backlight "blinken"
          // (kurz heller, dann zurück)
          // Achtung: optional, kann entfernt werden.
          auto call = id(backlight).turn_on();
          call.set_brightness(id(display_brightness));
          call.perform();

          return;
        }

# ============================================================================
# FONTS
# ============================================================================
# font_small: normaler Text
# font_icons: Material Design Icons für Pfeile (chevron-up/down)
font:
  - file: "fonts/RobotoSlab-Medium.ttf"
    id: font_small
    size: 18

  - file: "fonts/materialdesignicons-webfont.ttf"
    id: font_icons
    size: 40
    glyphs:
      - "\U000F0143"  # chevron-up
      - "\U000F0140"  # chevron-down

# ============================================================================
# DISPLAY (ILI9341) - Landscape (USB rechts)
# ============================================================================
# WICHTIG:
# - rotation: 90
# - dimensions: width: 240, height: 320
# Das ergibt nach rotation effektiv W=320, H=240 im Lambda.
display:
  - platform: ili9xxx
    spi_id: tft
    model: ili9341
    cs_pin: GPIO15
    dc_pin: GPIO2
    invert_colors: false

    rotation: 90
    dimensions:
      width: 240
      height: 320

    update_interval: 200ms
    auto_clear_enabled: true
    color_palette: 8BIT

    # ------------------------------------------------------------------------
    # Rendering:
    # 1) Header-Balken (leicht grau) + Linie darunter (+2px)
    # 2) Footer-Balken (leicht grau) + Linie darüber (-2px)
    # 3) Header-Inhalt:
    #    - Startseite: Uhr links + WLAN rechts
    #    - Setup-Seite: "Setup" zentriert + WLAN rechts
    # 4) Content:
    #    - Setup-Seite: Brightness ↑ % ↓ (links)
    #    - Startseite: Demo-Button + Statusanzeige
    # ------------------------------------------------------------------------
    lambda: |-
      const int W = it.get_width();   // sollte 320 sein
      const int H = it.get_height();  // sollte 240 sein

      it.fill(Color(0,0,0));

      // --- Layout Konstanten ---
      const int bar_h = 24;
      const int header_y0 = 2;
      const int footer_y0 = H - bar_h - 2;

      // Farben
      Color bar_bg      = Color(40,40,40);   // Balken Hintergrund (leicht grau)
      Color divider_col = Color(70,70,70);   // Linienfarbe
      Color text_main   = Color(235,235,235);
      Color text_dim    = Color(160,160,160);
      Color ok_green    = Color(0,255,0);

      // ================= HEADER =================
      it.filled_rectangle(0, header_y0, W, bar_h, bar_bg);

      // Linie unter Header +2px
      it.line(0, header_y0 + bar_h + 2, W, header_y0 + bar_h + 2, divider_col);

      // ================= FOOTER =================
      it.filled_rectangle(0, footer_y0, W, bar_h, bar_bg);

      // Linie über Footer -2px
      it.line(0, footer_y0 - 2, W, footer_y0 - 2, divider_col);

      // ================= HEADER CONTENT =================
      if (id(show_setup_page)) {
        // Setup: Überschrift mittig
        it.printf(W/2, header_y0 + 8, id(font_small), text_main,
                  TextAlign::CENTER, "Setup");
      } else {
        // Startseite: Uhr links
        it.strftime(8, header_y0 - 2, id(font_small), text_main,
                    "%H:%M", id(esptime).now());
      }

      // WLAN Balken rechts (immer)
      float rssi = id(wifi_rssi).state;
      if (!isnan(rssi)) {
        int base_x = W - 30;
        int base_y = header_y0 + bar_h - 8;

        Color col;
        if (rssi > -60) col = ok_green;
        else if (rssi > -75) col = Color(255,165,0);
        else col = Color(255,0,0);

        if (rssi > -85) it.filled_rectangle(base_x,     base_y-4,  3, 4,  col);
        if (rssi > -75) it.filled_rectangle(base_x+5,   base_y-8,  3, 8,  col);
        if (rssi > -65) it.filled_rectangle(base_x+10,  base_y-12, 3, 12, col);
        if (rssi > -55) it.filled_rectangle(base_x+15,  base_y-16, 3, 16, col);
      }

      // ================= SETUP PAGE =================
      if (id(show_setup_page)) {

        // Brightness-UI links, kompakt:
        //   ↑
        //  80%
        //   ↓
        const int left_x = 28;
        const int top_y  = header_y0 + bar_h + 22;

        int pct = (int)(id(display_brightness) * 100.0f + 0.5f);
        if (pct < 0) pct = 0;
        if (pct > 100) pct = 100;

        // Pfeil hoch
        it.printf(left_x, top_y,
                  id(font_icons), ok_green,
                  TextAlign::LEFT, "\U000F0143");

        // Prozent direkt darunter
        it.printf(left_x + 2, top_y + 32,
                  id(font_small), Color(255,255,255),
                  TextAlign::LEFT, "%d%%", pct);

        // Pfeil runter (enger an Prozent)
        it.printf(left_x, top_y + 50,
                  id(font_icons), ok_green,
                  TextAlign::LEFT, "\U000F0140");

        return;
      }

      // ================= STARTSEITE =================
      // Content-Bereich zwischen Header und Footer
      const int content_top = header_y0 + bar_h + 10;

      // Titel
      it.printf(W/2, header_y0 + 8, id(font_small), text_main,
                 TextAlign::CENTER, "Startseite");

      // ---------- DEMO BUTTON ----------
      // Button Box
      const int btn_x = 16;
      const int btn_y = content_top + 26;
      const int btn_w = 150;
      const int btn_h = 48;

      // Farbe je nach Zustand
      Color btn_bg = id(demo_button_on) ? Color(0,120,255) : Color(60,60,60);
      Color btn_border = Color(110,110,110);

      it.filled_rectangle(btn_x, btn_y, btn_w, btn_h, btn_bg);
      it.rectangle(btn_x, btn_y, btn_w, btn_h, btn_border);

      // Button Text
      it.printf(btn_x + btn_w/2,
                btn_y + 23,
                id(font_small),
                Color(255,255,255),
                TextAlign::CENTER,
                "Button");

      // Status-Text rechts daneben 
      it.printf(btn_x + btn_w + 14,
                btn_y + 14,
                id(font_small),
                Color(235,235,235),
                TextAlign::LEFT,
                "State: %s", id(demo_button_on) ? "ON" : "OFF");

      // Zusatzinfo (RSSI)
      if (!isnan(rssi)) {
        it.printf(16, btn_y + btn_h + 16,
                  id(font_small), text_dim,
                  TextAlign::LEFT,
                  "WLAN: %.0f dBm", rssi);
      }

Probier mal!

Habe es noch etwas erweitert, jetzt mit Button, Schalter und Anzeige von Temperaturen aus Home Assistant

Kompletten Code (erweitert) anzeigen
substitutions:
  # --------------------------------------------------------------------------
  # HIER STELLST DU ALLES EIN, WAS DU SPÄTER ÄNDERN WILLST, OHNE IM CODE ZU SUCHEN
  # --------------------------------------------------------------------------
  # Temperaturen aus Home Assistant (Entity IDs)
  temp_entity_1: "sensor.kuhlschrank_temperatur"
  temp_entity_2: "sensor.gefrierschrank_temperatur"

  # Texte, die auf dem Display vor den Temperaturen stehen
  temp_label_1: "T1"
  temp_label_2: "T2"

  # Button 1: Wird bei Touch ausgelöst (HA Action + Entity)
  btn1_label: "Heizung"
  btn1_action: "switch.toggle"
  btn1_entity: "switch.ns_panel_stube_relay_1"

  # Schalter (statt Button 2): Zeigt Zustand an und setzt ON/OFF
  sw_label: "Luefter"
  sw_entity: "switch.ns_panel_stube_relay_2"

esphome:
  name: cyd-demo-landscape-usb-right
  friendly_name: CYD Demo (Landscape USB rechts)
  comment: CYD (ESP32 + ILI9341 + XPT2046), Landscape (USB rechts)

  # Beim Start: Backlight auf den zuletzt gespeicherten Wert setzen
  on_boot:
    priority: 600
    then:
      - light.turn_on:
          id: backlight
          brightness: !lambda 'return id(display_brightness);'

esp32:
  board: esp32dev
  framework:
    type: arduino
  flash_size: 4MB

# Serielle Logs (hilfreich bei Fehlersuche)
logger:

# Verbindung zu Home Assistant (damit HA Entitäten gelesen und Actions gesendet werden können)
api:

# OTA Updates (Firmware per WLAN flashen)
ota:
  - platform: esphome

# WLAN + Notfall-Access-Point falls das normale WLAN nicht erreichbar ist
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:
    ssid: "cyd-demo-fallback"
    password: "12345678"

captive_portal:

# ============================================================================
# DISPLAY-HINTERGRUNDBELEUCHTUNG (Backlight)
# ============================================================================
# Viele CYD-Boards haben Backlight auf GPIO21. Wir steuern es per PWM (leiser/dimmer)
output:
  - platform: ledc
    pin: GPIO21
    id: backlight_pwm

# Aus PWM wird ein "light" (Helligkeit 0.0 .. 1.0)
light:
  - platform: monochromatic
    id: backlight
    output: backlight_pwm
    restore_mode: ALWAYS_ON
    default_transition_length: 0s

# ============================================================================
# UHRZEIT (für Anzeige oben im Header)
# ============================================================================
# Holt Zeit über Internet (SNTP) – reicht für eine einfache Uhranzeige
time:
  - platform: sntp
    id: esptime
    timezone: Europe/Berlin

# ============================================================================
# SENSOREN
# ============================================================================
sensor:
  # WLAN Signalstärke (für dBm Anzeige/Signalbalken)
  - platform: wifi_signal
    id: wifi_rssi
    update_interval: 30s

  # Temperatur 1 aus Home Assistant
  - platform: homeassistant
    id: temp_1
    entity_id: ${temp_entity_1}
    internal: true

  # Temperatur 2 aus Home Assistant
  - platform: homeassistant
    id: temp_2
    entity_id: ${temp_entity_2}
    internal: true

# ============================================================================
# INTERNE VARIABLEN (globals)
# ============================================================================
globals:
  # Wenn true: Setup-Seite sichtbar (Helligkeit einstellen)
  - id: show_setup_page
    type: bool
    restore_value: no
    initial_value: "false"

  # Displayhelligkeit (wird gespeichert und nach Neustart wiederhergestellt)
  - id: display_brightness
    type: float
    restore_value: yes
    initial_value: "0.80"

  # "Pressed"-Effekt: Button 1 kurz blau beim Tippen
  - id: btn1_pressed
    type: bool
    restore_value: no
    initial_value: "false"

  # "Pressed"-Effekt: Schalter kurz blau beim Tippen
  - id: sw_pressed
    type: bool
    restore_value: no
    initial_value: "false"

# ============================================================================
# HOME ASSISTANT: Zustände lesen + Touch-Trigger
# ============================================================================
binary_sensor:
  # -------------------------------------------------------
  # btn1_touch ist nur ein "Trigger": wir setzen den per Touch kurz ON/OFF.
  # Wenn er ON wird, wird eine HA-Action ausgeführt.
  # -------------------------------------------------------
  - platform: template
    id: btn1_touch
    internal: true
    on_press:
      - homeassistant.action:
          action: ${btn1_action}
          data:
            entity_id: ${btn1_entity}

  # -------------------------------------------------------
  # Schalterzustand direkt aus Home Assistant lesen (ON/OFF)
  # Damit können wir auf dem Display "ON/OFF" anzeigen.
  # -------------------------------------------------------
  - platform: homeassistant
    id: sw_state
    entity_id: ${sw_entity}
    internal: true

# ============================================================================
# SCRIPTS (kleine Abläufe, die wir mehrfach brauchen)
# ============================================================================
script:
  # Button 1: Pressed-Farbe nach kurzer Zeit zurücksetzen
  - id: btn1_release_delay
    mode: restart
    then:
      - delay: 200ms
      - lambda: |-
          id(btn1_pressed) = false;

  # Schalter: Pressed-Farbe nach kurzer Zeit zurücksetzen
  - id: sw_release_delay
    mode: restart
    then:
      - delay: 200ms
      - lambda: |-
          id(sw_pressed) = false;

  # Schalter in Home Assistant EIN schalten
  - id: sw_turn_on
    mode: restart
    then:
      - homeassistant.action:
          action: switch.turn_on
          data:
            entity_id: ${sw_entity}

  # Schalter in Home Assistant AUS schalten
  - id: sw_turn_off
    mode: restart
    then:
      - homeassistant.action:
          action: switch.turn_off
          data:
            entity_id: ${sw_entity}

# ============================================================================
# SPI BUSSE
# ============================================================================
# 1) Display SPI (ILI9341)
# 2) Touch SPI (XPT2046) ist oft ein eigener Bus
spi:
  - id: tft
    clk_pin: GPIO14
    mosi_pin: GPIO13
    miso_pin: GPIO12

  - id: touch_spi
    clk_pin: GPIO25
    mosi_pin: GPIO32
    miso_pin: GPIO39

# ============================================================================
# TOUCHSCREEN (XPT2046)
# ============================================================================
touchscreen:
  platform: xpt2046
  spi_id: touch_spi
  cs_pin: GPIO33
  interrupt_pin: GPIO36
  update_interval: 35ms
  threshold: 400

  # Kalibrierung: sorgt dafür, dass Touch-Koordinaten stimmen
  calibration:
    x_min: 280
    x_max: 3860
    y_min: 340
    y_max: 3860

  # Orientierung: passt zu "Landscape, USB rechts"
  transform:
    swap_xy: true
    mirror_x: false
    mirror_y: false

  # --------------------------------------------------------------------------
  # Touch-Logik:
  # - Tippen im Header (oben): Setup-Seite an/aus
  # - Setup-Seite: Pfeile hoch/runter ändern die Display-Helligkeit
  # - Startseite:
  #     * Button 1: schaltet eine HA-Entity (toggle)
  #     * Schalter: zeigt Zustand (ON/OFF) und schaltet gezielt ON/OFF
  # --------------------------------------------------------------------------
  on_touch:
    - lambda: |-
        if (touches.empty()) return;

        const int tx = touches[0].x;
        const int ty = touches[0].y;

        const int bar_h = 24;
        const int header_y0 = 2;

        // 1) Header: Setup-Seite an/aus
        if (ty >= header_y0 && ty <= (header_y0 + bar_h)) {
          id(show_setup_page) = !id(show_setup_page);
          return;
        }

        // 2) Setup-Seite: Helligkeit hoch/runter
        if (id(show_setup_page)) {
          const int left_x = 28;
          const int top_y  = header_y0 + bar_h + 22;

          const int hit_w = 90;
          const int hit_h = 40;
          const float step = 0.05f;  // 5%

          auto apply_brightness = [&]() {
            float b = id(display_brightness);
            if (b < 0.0f) b = 0.0f;
            if (b > 1.0f) b = 1.0f;
            id(display_brightness) = b;

            auto call = id(backlight).turn_on();
            call.set_brightness(b);
            call.perform();
          };

          // Pfeil hoch
          if (tx >= left_x &&
              tx <= (left_x + hit_w) &&
              ty >= top_y &&
              ty <= (top_y + hit_h)) {
            id(display_brightness) += step;
            apply_brightness();
            return;
          }

          // Pfeil runter
          const int down_y = top_y + 50;
          if (tx >= left_x &&
              tx <= (left_x + hit_w) &&
              ty >= down_y &&
              ty <= (down_y + hit_h)) {
            id(display_brightness) -= step;
            apply_brightness();
            return;
          }

          return;
        }

        // 3) Startseite: Layout-Koordinaten (müssen zum Display-Lambda passen)
        const int content_top = header_y0 + bar_h + 10;

        const int btn_x = 16;
        const int btn_w = 150;
        const int btn_h = 48;

        const int btn1_y = content_top + 26;

        // Schalter-Reihe
        const int sw_y = btn1_y + btn_h + 12;
        const int sw_w = 150;
        const int sw_h = 48;

        // Button 1: HA toggle
        if (tx >= btn_x && tx <= (btn_x + btn_w) &&
            ty >= btn1_y && ty <= (btn1_y + btn_h)) {
          ESP_LOGI("touch", "BTN1 hit x=%d y=%d", tx, ty);

          id(btn1_pressed) = true;
          id(btn1_touch).publish_state(true);
          id(btn1_touch).publish_state(false);
          id(btn1_release_delay).execute();
          return;
        }

        // Schalter: Zustand prüfen und gezielt ON/OFF setzen
        if (tx >= btn_x && tx <= (btn_x + sw_w) &&
            ty >= sw_y && ty <= (sw_y + sw_h)) {
          ESP_LOGI("touch", "SW hit x=%d y=%d", tx, ty);

          id(sw_pressed) = true;
          id(sw_release_delay).execute();

          if (id(sw_state).has_state() && id(sw_state).state) {
            id(sw_turn_off).execute();
          } else {
            id(sw_turn_on).execute();
          }
          return;
        }

# ============================================================================
# SCHRIFTEN
# ============================================================================
font:
  # Normale Schrift
  - file: "fonts/RobotoSlab-Medium.ttf"
    id: font_small
    size: 18

  # Icons (Pfeile hoch/runter)
  - file: "fonts/materialdesignicons-webfont.ttf"
    id: font_icons
    size: 40
    glyphs:
      - "\U000F0143"  # chevron-up
      - "\U000F0140"  # chevron-down

# ============================================================================
# DISPLAY (ILI9341) – Landscape (USB rechts)
# ============================================================================
display:
  - platform: ili9xxx
    spi_id: tft
    model: ili9341
    cs_pin: GPIO15
    dc_pin: GPIO2
    invert_colors: false

    # rotation 90 + dimensions 240x320 ergibt effektiv 320x240 im Lambda
    rotation: 90
    dimensions:
      width: 240
      height: 320

    update_interval: 200ms
    auto_clear_enabled: true
    color_palette: 8BIT

    # ------------------------------------------------------------------------
    # Anzeige:
    # - Oben: Header (Uhr + WLAN)
    # - Unten: Footer (nur Balken)
    # - Mitte:
    #   * Startseite: Button 1 + Schalter + Temperaturen
    #   * Setup: Helligkeit (hoch / Prozent / runter)
    # ------------------------------------------------------------------------
    lambda: |-
      const int W = it.get_width();
      const int H = it.get_height();

      it.fill(Color(0,0,0));

      const int bar_h = 24;
      const int header_y0 = 2;
      const int footer_y0 = H - bar_h - 2;

      Color bar_bg      = Color(40,40,40);
      Color divider_col = Color(70,70,70);
      Color text_main   = Color(235,235,235);
      Color text_dim    = Color(160,160,160);
      Color ok_green    = Color(0,255,0);

      // Header + Linie
      it.filled_rectangle(0, header_y0, W, bar_h, bar_bg);
      it.line(0, header_y0 + bar_h + 2, W, header_y0 + bar_h + 2, divider_col);

      // Footer + Linie
      it.filled_rectangle(0, footer_y0, W, bar_h, bar_bg);
      it.line(0, footer_y0 - 2, W, footer_y0 - 2, divider_col);

      // Header Inhalt: Uhr / Setup Text
      if (id(show_setup_page)) {
        it.printf(W/2, header_y0 + 8, id(font_small), text_main,
                  TextAlign::CENTER, "Setup");
      } else {
        it.strftime(8, header_y0 - 2, id(font_small), text_main,
                    "%H:%M", id(esptime).now());
        it.printf(W/2, header_y0 + 8, id(font_small), text_main,
                  TextAlign::CENTER, "Startseite");
      }

      // WLAN Balken rechts
      float rssi = id(wifi_rssi).state;
      if (!isnan(rssi)) {
        int base_x = W - 30;
        int base_y = header_y0 + bar_h - 8;

        Color col;
        if (rssi > -60) col = ok_green;
        else if (rssi > -75) col = Color(255,165,0);
        else col = Color(255,0,0);

        if (rssi > -85) it.filled_rectangle(base_x,     base_y-4,  3, 4,  col);
        if (rssi > -75) it.filled_rectangle(base_x+5,   base_y-8,  3, 8,  col);
        if (rssi > -65) it.filled_rectangle(base_x+10,  base_y-12, 3, 12, col);
        if (rssi > -55) it.filled_rectangle(base_x+15,  base_y-16, 3, 16, col);
      }

      // ===================== SETUP SEITE =====================
      if (id(show_setup_page)) {
        const int left_x = 28;
        const int top_y  = header_y0 + bar_h + 22;

        int pct = (int)(id(display_brightness) * 100.0f + 0.5f);
        if (pct < 0) pct = 0;
        if (pct > 100) pct = 100;

        it.printf(left_x, top_y, id(font_icons), ok_green,
                  TextAlign::LEFT, "\U000F0143");
        it.printf(left_x + 2, top_y + 32, id(font_small), Color(255,255,255),
                  TextAlign::LEFT, "%d%%", pct);
        it.printf(left_x, top_y + 50, id(font_icons), ok_green,
                  TextAlign::LEFT, "\U000F0140");
        return;
      }

      // ===================== STARTSEITE =====================
      const int content_top = header_y0 + bar_h + 10;

      const int btn_x = 16;
      const int btn_w = 150;
      const int btn_h = 48;
      const int btn1_y = content_top + 26;

      // Schalter-Reihe
      const int sw_y = btn1_y + btn_h + 12;
      const int sw_w = 150;
      const int sw_h = 48;

      // Farben
      Color border = Color(110,110,110);

      // Button 1: kurz blau beim Tippen
      Color btn1_bg = id(btn1_pressed) ? Color(0,120,255) : Color(60,60,60);
      it.filled_rectangle(btn_x, btn1_y, btn_w, btn_h, btn1_bg);
      it.rectangle(btn_x, btn1_y, btn_w, btn_h, border);
      it.printf(btn_x + btn_w/2, btn1_y + 23, id(font_small), Color(255,255,255),
                TextAlign::CENTER, "%s", "${btn1_label}");

      // Schalterzustand aus HA
      bool sw_known = id(sw_state).has_state();
      bool sw_on = sw_known ? id(sw_state).state : false;

      // Schalterfarbe:
      // - blau wenn gerade gedrückt
      // - grün wenn ON
      // - grau wenn OFF/unknown
      Color sw_bg;
      if (id(sw_pressed)) sw_bg = Color(0,120,255);
      else sw_bg = sw_on ? Color(0,160,80) : Color(60,60,60);

      it.filled_rectangle(btn_x, sw_y, sw_w, sw_h, sw_bg);
      it.rectangle(btn_x, sw_y, sw_w, sw_h, border);

      // Schalter-Text links
      it.printf(btn_x + 10, sw_y + 10, id(font_small), Color(255,255,255),
                TextAlign::LEFT, "%s", "${sw_label}");

      // Zustand rechts
      const char* st = sw_known ? (sw_on ? "ON" : "OFF") : "--";
      it.printf(btn_x + sw_w - 10, sw_y + 10, id(font_small), Color(255,255,255),
                TextAlign::RIGHT, "%s", st);

      // Temperaturen rechts daneben
      const int t_x  = btn_x + btn_w + 14;
      const int t_y1 = btn1_y + 8;
      const int t_y2 = sw_y + 8;

      float t1 = id(temp_1).state;
      float t2 = id(temp_2).state;

      if (!isnan(t1)) {
        it.printf(t_x, t_y1, id(font_small), text_main, TextAlign::LEFT,
                  "%s: %.1f°C", "${temp_label_1}", t1);
      } else {
        it.printf(t_x, t_y1, id(font_small), text_dim, TextAlign::LEFT,
                  "%s: --.-°C", "${temp_label_1}");
      }

      if (!isnan(t2)) {
        it.printf(t_x, t_y2, id(font_small), text_main, TextAlign::LEFT,
                  "%s: %.1f°C", "${temp_label_2}", t2);
      } else {
        it.printf(t_x, t_y2, id(font_small), text_dim, TextAlign::LEFT,
                  "%s: --.-°C", "${temp_label_2}");
      }

      // WLAN dBm unten
      if (!isnan(rssi)) {
        it.printf(16, sw_y + sw_h + 16,
                  id(font_small), text_dim,
                  TextAlign::LEFT,
                  "WLAN: %.0f dBm", rssi);
      }
1 „Gefällt mir“

Danke auch hierfür. Allerdings ist die Ausrichtung immer noch Portrait und deshalb die Darstellung etwas “durcheinander”.

Flash mal damit → ESPConnect und "erase entire bevor flashing " ankreuzen!

Probier bitte auch mal:

display:
  - platform: ili9xxx
    spi_id: tft
    model: ili9341
    cs_pin: GPIO15
    dc_pin: GPIO2
    invert_colors: false

    rotation: 180          # Landscape
    dimensions:
      width: 320          
      height: 240
    update_interval: 500ms
    auto_clear_enabled: true
    color_palette: 8BIT

die Chinesen ballern da machmal verschiedene Displayvarianten bunt gemischt in der selben Charge raus! :woman_shrugging:

1 „Gefällt mir“

Danke - aber das muss etwas warten. Ich werde berichten!

Moin! Jetzt sieht es so aus:

Buttons sind allerdings ohne Funktion.

Gruß

HCM

1 „Gefällt mir“

Du musst in Home Assistant unter ESPHome dem Gerät erlauben Änderungen im Home Assistant vornehmen zu dürfen!

1 „Gefällt mir“

Danke - auch das hat jetzt geklappt. (Ich darf wohl noch einiges lernen!) Ich werde mich jetzt mal an diesem Beispiel längs hangeln. Wie mach ich das Ganze im Portrait? (Dann lasse ich Dich auch erst mal in Ruhe!)

Hi, einfach die rotation von 180 auf 0 setzen, dann hast du Portrait-Ansicht.

Und den „Touch“ natürlich entsprechend anpassen! :wink:

90 war hier bei mir der richtige Wert. An dieser Stelle nochmal herzlichen Dank. Jetzt habe ich eine funktionierende Basis.

1 „Gefällt mir“

Logo, bei 0 zu 180 steht das ja nur auf dem „Kopf“! Wollten eh bloß testen ob du mit denkst! :innocent:

1 „Gefällt mir“

Tja - Prüfung wohl bestanden!, :smiling_face_with_sunglasses:Einen feinen Tag!

1 „Gefällt mir“