Projekt-Vorstellung: Smartes Info-Display (GeekMagic Pro) mit ESPHome & LVGL

Dieser Code verwandelt das GeekMagic Pro (ESP32-S3) in eine interaktive Informationszentrale für Home Assistant. Dank der LVGL-Library bietet das Display flüssige Animationen und ein modernes User-Interface, das weit über einfache Textanzeigen hinausgeht.

Design & Assets

Ein Fokus bei diesem Projekt lag auf einer sauberen Typografie und einer intuitiven Bildsprache:

  • Multilayer-Typografie: * Roboto: Wird in verschiedenen Größen (16 bis 48 Pixel) für Zeit, Datum und Systemwerte genutzt, um eine klare Informationshierarchie zu schaffen.
    • Material Design Icons (MDI): Integriert als Font, um skalierbare Symbole für Sensoren (Thermometer, Wifi, RAM) und die blinkende Mülltonne zu nutzen.
    • Meteocons: Ein spezieller Wetter-Font, der für die großen Wetter-Symbole genutzt wird.
  • Custom Branding: * Die Startseite (Clock-Page) enthält ein zentral platziertes OM-Logo, das als RGB565-Image direkt im Flash hinterlegt ist. Das sorgt für einen professionellen “Out-of-the-Box”-Look direkt nach dem Booten.

Kern-Funktionen

  • Seiten-Management: Dynamische Liste von Pages (Clock, Weather, Trash, System), die flüssig ineinander übergehen.
  • Individuelle Müll-Warnung: Das Müll-Icon auf Seite 3 wird über Home Assistant getriggert. Ist der Abholtermin morgen, startet eine Blink-Animation (LV_OPA_0 bis LV_OPA_COVER), die nur aktiv ist, wenn die Seite auch wirklich angezeigt wird.
  • Intelligentes Wetter-Sync: Ein dediziertes Sync-Icon pulsiert diskret oben rechts, während Home Assistant die Wetterdaten zeilenweise überträgt, und verschwindet nach Abschluss automatisch.

Touch & Interaktion

  • Navigation: Einfacher Touch schaltet die Seiten mit einem Links-Schiebe-Effekt weiter. Am Ende der Liste erfolgt ein “Rückspul-Effekt” nach rechts.
  • System-Monitor: Ein langer Druck (800ms+) öffnet eine technische Übersicht mit Live-Balken für die RAM-Auslastung und Geräte-Temperaturen.
  • Automation-Lock: Ein extra-langer Druck (2.5s) toggelt das Auto-Blättern. Ein visueller “Bildschirm-Flash” (Overlay) bestätigt die Änderung (Grün für An, Rot für Aus).

Web-Konfiguration (No-Code Anpassung)

Alle wichtigen Parameter sind als number oder switch im ESPHome Web-Server oder direkt in Home Assistant verfügbar:

  • Helligkeitswerte für Tag und Nacht (Screensaver).
  • Intervalle für den Seitenwechsel und Timeouts.
  • Dynamische Begrenzung der maximalen Seitenanzahl über den Web-Slider.
substitutions:
  devicename: geekmagic-esp32
  friendly_devicename: "GeekMagic ESP32"
  device_id: geekmagic_esp32

 
esphome:
  name: ${devicename}
  friendly_name: "${friendly_devicename}"
  on_boot:
    priority: -100
    then:
      - lambda: |-
          // 1. Alle verfügbaren Seiten in die Liste laden
          id(app_pages).clear();
          id(app_pages).push_back(id(page_clock).obj);
          id(app_pages).push_back(id(page_weather).obj);
          id(app_pages).push_back(id(page_3).obj);
          
          // 2. Das Maximum des Sliders direkt setzen
          float neue_anzahl = (float)id(app_pages).size();
          id(cfg_max_main_pages_web).traits.set_max_value(neue_anzahl);
          
          // Sicherheit: Nutzer-Limit korrigieren, falls es höher als vorhanden ist
          if (id(cfg_max_main_pages) > (int)neue_anzahl) {
             id(cfg_max_main_pages) = (int)neue_anzahl;
          }
          
          id(cfg_max_main_pages_web).publish_state(id(cfg_max_main_pages));

          ESP_LOGI("main", "Slider-Maximum dynamisch auf %.0f gesetzt.", neue_anzahl);
      - script.execute: update_time_display
      - script.execute: start_icon_rotation
      - script.execute: muell_blink_logic
  libraries:
    - "Wire"
    - "SPI"

esp32:
  board: esp32dev
  flash_size: 16MB
  framework:
    type: arduino
  
logger:
  level: DEBUG
  logs:
    # Das schaltet den gesamten LVGL-Kanal stumm, egal ob Layout, Flex oder Zeichnen
    lvgl: ERROR

api:
  encryption:
    key: !secret esphome_encryption_key
  
  services:
    - service: set_weather_icon_by_name
      variables:
        condition: string
        red: int
        green: int
        blue: int
      then:
        - lambda: |-
            // 1. Animation starten
            id(start_icon_rotation).execute(); 

            // 2. Bestehende Logik
            const char* icon_char = "B"; 
            if (condition == "sunny") icon_char = "B";
            else if (condition == "clear-night") icon_char = "I";
            else if (condition == "partlycloudy") icon_char = "H";
            else if (condition == "cloudy") icon_char = "N";
            else if (condition == "fog") icon_char = "M";
            else if (condition == "rainy") icon_char = "R";
            else if (condition == "pouring") icon_char = "X";
            else if (condition == "snowy") icon_char = "W";
            else if (condition == "lightning-rainy") icon_char = "S";
            else if (condition == "windy") icon_char = "F";
            else if (condition == "snowy-rainy") icon_char = "U";
            
            lv_obj_set_style_text_font(id(lv_weather_icon), id(weather_icons_48), LV_PART_MAIN);
            lv_label_set_text(id(lv_weather_icon), icon_char);
            lv_obj_set_style_text_color(id(lv_weather_icon), lv_color_make(red, green, blue), LV_PART_MAIN);
            lv_obj_add_flag(id(lv_weather_waiting), LV_OBJ_FLAG_HIDDEN);

    - service: set_weather_line
      variables:
        message: string
        line: int
      then:
        - lambda: |-
            if (line == 4) {
              lv_label_set_text(id(lv_weather_line_4), message.c_str());
              lv_obj_add_flag(id(lv_weather_waiting), LV_OBJ_FLAG_HIDDEN);
              // HIER: Animation stoppen und Icon verstecken
              id(stop_icon_rotation).execute(); // <--- HIER STOPPEN
            } else if (line == 1) {
              lv_label_set_text(id(lv_temp_text), message.c_str());
              lv_obj_add_flag(id(lv_weather_waiting), LV_OBJ_FLAG_HIDDEN);
            } else if (line == 3) {
              lv_label_set_text(id(lv_regen_text), message.c_str());
            }

    - service: set_page3_data
      variables:
        line2: string
        red: int
        green: int
        blue: int
        is_tomorrow: bool 
      then:
        - lambda: |-
            id(muell_is_tomorrow) = is_tomorrow;
            lv_label_set_text(id(muell_datum), line2.c_str());
            
            lv_color_t farbe = lv_color_make(red, green, blue);
            lv_obj_set_style_text_color(id(muell_icon), farbe, LV_PART_MAIN);
            lv_obj_set_style_text_color(id(muell_datum), farbe, LV_PART_MAIN);
ota:
  platform: esphome
  password: !secret ota_password

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  on_connect:
    then:
      - logger.log:
          format: "WLAN IP: %s"
          args: ['WiFi.localIP().toString().c_str()']
  ap:
    ssid: "${friendly_devicename} AP"
    password: !secret fallback_password

web_server:
  port: 80

time:
  - platform: sntp
    id: ${device_id}_time
    servers:
      - 0.de.pool.ntp.org
    timezone: Europe/Berlin

sensor:
  - platform: uptime
    name: "${friendly_devicename} Uptime"
    id: sensor_uptime

  - platform: wifi_signal
    name: "${friendly_devicename} WiFi Signal"
    id: sensor_wifi_signal
    update_interval: 60s
  
  - platform: template
    name: "${friendly_devicename} Free RAM"
    id: sensor_free_ram
    lambda: 'return heap_caps_get_free_size(MALLOC_CAP_INTERNAL);'
    update_interval: 60s
    unit_of_measurement: "B"
  
  - platform: internal_temperature
    id: esp_temp
    name: "ESP32 Chip Temperature"
    update_interval: 60s  

text_sensor:
  - platform: wifi_info
    ip_address:
      name: "${friendly_devicename} IP"
      id: wifi_ip


button:
  - platform: restart
    name: "${friendly_devicename} Neustart"

# --- KONFIGURATION ÜBER WEBSERVER ---
number:
  # 1. System-Timeout (Sekunden)
  - platform: template
    name: "${friendly_devicename} System-Timeout"
    id: cfg_system_timeout_seconds_web
    unit_of_measurement: "s"  # <--- Einheit hinzugefügt
    icon: "mdi:timer-outline"
    min_value: 5
    max_value: 300
    step: 5
    mode: box
    optimistic: true
    restore_value: true
    set_action:
      - lambda: |-
          id(cfg_system_timeout_seconds) = (int)x;
          ESP_LOGI("Config", "System-Timeout: %d Sekunden", id(cfg_system_timeout_seconds));

  # 2. Seitenwechsel-Intervall (Sekunden)
  - platform: template
    name: "${friendly_devicename} Seitenwechsel-Intervall"
    id: cfg_page_switch_seconds_web
    unit_of_measurement: "s"  # <--- Einheit hinzugefügt
    icon: "mdi:autorenew"
    min_value: 5
    max_value: 120
    step: 5
    mode: box
    optimistic: true
    restore_value: true
    set_action:
      - lambda: |-
          id(cfg_page_switch_seconds) = (int)x;
          ESP_LOGI("Config", "Seitenwechsel: %d Sekunden", id(cfg_page_switch_seconds));
  
  ###### Interval für die Animation ########
  - platform: template
    name: "${friendly_devicename} Animations-Dauer"
    id: cfg_anim_speed_web
    unit_of_measurement: "ms"
    icon: "mdi:speedometer"
    min_value: 100
    max_value: 2000
    step: 50
    mode: box
    optimistic: true
    restore_value: true
    set_action:
      - lambda: |-
          id(cfg_anim_speed) = (int)x;
          ESP_LOGI("Config", "Animations-Dauer: %d ms", id(cfg_anim_speed));

  # 3. ScreenSaver-Timeout (Sekunden)
  - platform: template
    name: "${friendly_devicename} ScreenSaver-Timeout"
    id: cfg_screen_saver_timeout_web
    unit_of_measurement: "s"  # <--- Einheit hinzugefügt
    icon: "mdi:shield-timer"
    min_value: 10
    max_value: 1800
    step: 10
    mode: box
    optimistic: true
    restore_value: true
    set_action:
      - lambda: |-
          id(cfg_screen_saver_timeout) = (int)x;
          ESP_LOGI("Config", "ScreenSaver-Timeout: %d Sekunden", id(cfg_screen_saver_timeout));

  # 4. Normale Helligkeit (%)
  - platform: template
    name: "${friendly_devicename} Helligkeit Normal"
    id: cfg_default_brightness_web
    unit_of_measurement: "%"  # <--- Einheit hinzugefügt
    icon: "mdi:brightness-6"
    min_value: 10
    max_value: 100
    step: 5
    mode: box
    optimistic: true
    restore_value: true
    set_action:
      - lambda: |-
          id(default_brightness) = (int)x;
          ESP_LOGI("Config", "Helligkeit Normal: %d%%", id(default_brightness));

  # 5. ScreenSaver-Helligkeit (%)
  - platform: template
    name: "${friendly_devicename} Helligkeit ScreenSaver"
    id: cfg_screen_saver_brightness_web
    unit_of_measurement: "%"  # <--- Einheit hinzugefügt
    icon: "mdi:brightness-4"
    min_value: 0
    max_value: 50
    step: 5
    mode: box
    optimistic: true
    restore_value: true
    set_action:
      - lambda: |-
          id(screen_saver_brightness) = (int)x;
          ESP_LOGI("Config", "Helligkeit ScreenSaver: %d%%", id(screen_saver_brightness));

  # 6. Maximale Seitenanzahl
  - platform: template
    name: "${friendly_devicename} Max. Seiten"
    id: cfg_max_main_pages_web
    unit_of_measurement: "Seiten"  # <--- Einheit hinzugefügt
    icon: "mdi:layers-outline"
    min_value: 1
    max_value: 10
    step: 1
    mode: box
    optimistic: true
    restore_value: true
    set_action:
      - lambda: |-
          id(cfg_max_main_pages) = (int)x;
          if (id(page_rotator) >= id(cfg_max_main_pages)) {
            id(page_rotator) = 0;
          }
      - script.execute: load_page_by_index

switch:
  # 1. ScreenSaver aktivieren/deaktivieren
  - platform: template
    name: "${friendly_devicename} ScreenSaver aktiv"
    id: cfg_enable_screen_saver_web
    optimistic: true
    restore_mode: RESTORE_DEFAULT_ON 
    turn_on_action:
      - globals.set:
          id: cfg_enable_screen_saver
          value: 'true'
      - logger.log: "ScreenSaver: aktiviert"
    turn_off_action:
      - globals.set:
          id: cfg_enable_screen_saver
          value: 'false'
      - logger.log: "ScreenSaver: deaktiviert"

  # 2. Automatisches Blättern
  - platform: template
    name: "${friendly_devicename} Auto-Blättern"
    id: cfg_enable_auto_page_switch_web
    icon: "mdi:autorenew"
    # Diese Zeile liest den Status für HA aus der Variable:
    lambda: 'return id(cfg_enable_auto_page_switch);'
    optimistic: true
    restore_mode: RESTORE_DEFAULT_ON 
    turn_on_action:
      - globals.set:
          id: cfg_enable_auto_page_switch
          value: 'true'
    turn_off_action:
      - globals.set:
          id: cfg_enable_auto_page_switch
          value: 'false'
      - logger.log: "Auto-Blättern: deaktiviert"

  # 3. System-Timeout
  - platform: template
    name: "${friendly_devicename} System-Timeout"
    id: cfg_enable_system_timeout_web
    optimistic: true
    restore_mode: RESTORE_DEFAULT_ON 
    turn_on_action:
      - globals.set:
          id: cfg_enable_system_timeout
          value: 'true'
      - logger.log: "System-Timeout: aktiviert"
    turn_off_action:
      - globals.set:
          id: cfg_enable_system_timeout
          value: 'false'
      - logger.log: "System-Timeout: deaktiviert"


globals:
  # --- STATUS VARIABLEN ---
  - id: page_rotator
    type: int
    initial_value: '0'

  - id: system_page_timeout_counter
    type: int
    initial_value: '0'

  - id: page_switch_counter
    type: int
    initial_value: '0'

  - id: touch_inactivity_seconds
    type: int
    initial_value: '0'

  - id: screen_saver_active
    type: bool
    restore_value: true
    initial_value: 'false'

  - id: on_system_info
    type: bool
    initial_value: 'false'

  - id: last_page_index
    type: int
    initial_value: '0'
  
  - id: app_pages
    type: std::vector<lv_obj_t*>
    restore_value: false
  
  - id: muell_is_tomorrow
    type: bool
    initial_value: 'false'

  # --- KONFIGURATIONS-WERTE (JETZT übers Web einstellbar) ---
  - id: cfg_system_timeout_seconds
    type: int
    restore_value: true  # ÄNDERN: restore_value statt Substitution
    initial_value: '30'  # Standardwert setzen

  - id: cfg_page_switch_seconds
    type: int
    restore_value: true  # ÄNDERN
    initial_value: '20'  # Standardwert

  - id: cfg_screen_saver_timeout
    type: int
    restore_value: true  # ÄNDERN
    initial_value: '300' # Standardwert

  - id: cfg_enable_system_timeout
    type: bool
    restore_value: false  # ÄNDERN
    initial_value: 'true' # Standardwert

  - id: cfg_enable_auto_page_switch
    type: bool
    restore_value: true  # ÄNDERN
    initial_value: 'true' # Standardwert

  - id: cfg_enable_screen_saver
    type: bool
    restore_value: false  # ÄNDERN
    initial_value: 'true' # Standardwert

  - id: cfg_max_main_pages
    type: int
    restore_value: true  # ÄNDERN
    initial_value: '3'   # Standardwert

  - id: cfg_anim_speed
    type: int
    restore_value: true
    initial_value: '400'

  # --- NEUE GLOBALS FÜR HELLIGKEIT ---
  - id: default_brightness
    type: int
    restore_value: true
    initial_value: '70'  # Standardwert 70%

  - id: screen_saver_brightness
    type: int
    restore_value: true
    initial_value: '20'  # Standardwert 20%
  
  # Interner Berechnungswert (für deinen Code)
  - id: max_page_index
    type: int
    initial_value: '1'  # Wird automatisch berechnet

 
  

esp32_touch:
  setup_mode: False # WICHTIG: Zeigt die Rohwerte im Log

binary_sensor:
  - platform: esp32_touch
    pin: GPIO32
    threshold: 1350
    id: gp32
    on_press:
      then:
        - lambda: |-
            id(touch_inactivity_seconds) = 0;
            if (id(screen_saver_active)) {
              id(screen_saver_active) = false;
              auto call = id(${device_id}_backlight).make_call();
              call.set_brightness(id(default_brightness) / 100.0);
              call.perform();
              id(touch_inactivity_seconds) = -1; 
            }

    on_multi_click:
      # --- EINFACHER KLICK (mit Aufweck-Schutz) ---
      - timing:
          - ON for 50ms to 600ms
          - OFF for 400ms to 1000s 
        then:
          - lambda: |-
              // Wenn der Wert -1 ist, war es ein Aufweck-Klick -> Nichts tun außer Timer reset
              if (id(touch_inactivity_seconds) < 0) {
                id(touch_inactivity_seconds) = 0;
                return; 
              }
              
              // Normales Blättern
              id(on_system_info) = false;
              int max_page_index = id(cfg_max_main_pages) - 1;  
              if (max_page_index < 0) max_page_index = 0;
              id(page_rotator) = (id(page_rotator) + 1) % (max_page_index + 1);
          - script.execute: load_page_by_index

      # # --- DOPPELKLICK ---
      # - timing:
      #     - ON for 50ms to 350ms
      #     - OFF for 50ms to 400ms
      #     - ON for 50ms to 350ms
      #   then:
      #     - light.toggle: ${device_id}_backlight
      #     - logger.log: "Doppelklick erkannt"

      # --- 2. LANGER KLICK (800ms - 2s): System Info ---
      - timing:
          - ON for 800ms to 2000ms
        then:
          - lambda: |-
              id(on_system_info) = !id(on_system_info);
              if (id(on_system_info)) {
                id(refresh_system_info).execute();
                lv_scr_load_anim(id(page_system_info).obj, LV_SCR_LOAD_ANIM_OVER_TOP, 300, 0, false);
              } else {
                id(load_page_by_index).execute();
              }

      # --- EXTRA LANGER KLICK (Pause ein/aus) ---
      - timing:
          - ON for 2500ms to 10000ms
        then:
          - lambda: |-
              // Variable toggeln
              id(cfg_enable_auto_page_switch) = !id(cfg_enable_auto_page_switch);
              id(cfg_enable_auto_page_switch_web).publish_state(id(cfg_enable_auto_page_switch));
              if (!id(cfg_enable_auto_page_switch)) {
                // AUTO-BLÄTTERN AUS -> ROT
                lv_obj_set_style_bg_color(id(lv_flash_overlay), lv_color_make(255, 0, 0), LV_PART_MAIN);
              } else {
                // AUTO-BLÄTTERN AN -> GRÜN
                lv_obj_set_style_bg_color(id(lv_flash_overlay), lv_color_make(0, 255, 0), LV_PART_MAIN);
              }
          - script.execute: screen_explosion

      
text:
  - platform: template
    name: "Weather Text 1"
    id: ${device_id}_weather_text_1
    mode: text
    optimistic: true
    on_value:
      then:
        - lambda: lv_label_set_text(id(lv_temp_text), x.c_str());

  - platform: template
    name: "Weather Text 2"
    id: ${device_id}_weather_text_2
    mode: text
    optimistic: true
    on_value:
      then:
        - logger.log:
            format: "Zeile 2: %s"
            args: ['x.c_str()']

  - platform: template
    name: "Weather Text 3"
    id: ${device_id}_weather_text_3
    mode: text
    optimistic: true
    on_value:
      then:
        - lambda: lv_label_set_text(id(lv_regen_text), x.c_str());

  - platform: template
    name: "Weather Text 4"
    id: ${device_id}_weather_text_4
    mode: text
    optimistic: true
    on_value:
      then:
        - lambda: lv_label_set_text(id(lv_weather_line_4), x.c_str());

script:
  - id: ${device_id}_update_weather_text
    mode: queued
    parameters:
      message: string
      line: int
    then:
      - lambda: |-
          if (line == 1) {
            lv_obj_add_flag(id(lv_weather_waiting), LV_OBJ_FLAG_HIDDEN);
            lv_label_set_text(id(lv_temp_text), message.c_str());
          } else if (line == 3) {
            lv_label_set_text(id(lv_regen_text), message.c_str());
          } else if (line == 4) {
            lv_label_set_text(id(lv_weather_line_4), message.c_str());
          }
  
  - id: update_time_display
    then:
      - lambda: |-
          auto now = id(${device_id}_time).now();
          if (now.is_valid()) {
            char time_str[6];
            snprintf(time_str, sizeof(time_str), "%02d:%02d", now.hour, now.minute);
            lv_label_set_text(id(lv_time), time_str);
            const char* weekdays[] = {"Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"};
            lv_label_set_text(id(lv_weekday), weekdays[now.day_of_week - 1]);
            char date_str[12];
            snprintf(date_str, sizeof(date_str), "%02d.%02d.%04d", now.day_of_month, now.month, now.year);
            lv_label_set_text(id(lv_date), date_str);
          }
  # Dieses Skript lädt die Seite basierend auf dem aktuellen Index
  - id: load_page_by_index
    then:
      - lambda: |-
          int n = id(page_rotator);
          int o = id(last_page_index);
          int total_vorhanden = (int)id(app_pages).size();
          int nutzer_limit = (int)id(cfg_max_main_pages);

          if (total_vorhanden == 0 || n == o) return;

          // 1. Limit-Prüfung
          if (n >= nutzer_limit || n >= total_vorhanden) {
            n = 0;
            id(page_rotator) = 0;
          }

          if (n == o) return;

          // 2. Richtungs-Logik für den "Rückspul-Effekt"
          lv_scr_load_anim_t anim;

          if (n > o) {
            // Normales Vorwärtsblättern (0 -> 1, 1 -> 2)
            anim = LV_SCR_LOAD_ANIM_MOVE_LEFT;
          } else {
            // Rückwärtsblättern ODER Sprung von Ende auf Anfang (2 -> 0)
            // Dies erzeugt den optischen Effekt, dass die Seiten nach rechts weggeschoben werden
            anim = LV_SCR_LOAD_ANIM_MOVE_RIGHT;
          }

          // 3. Animation ausführen
          lv_scr_load_anim(id(app_pages)[n], anim, id(cfg_anim_speed), 0, false);
          
          id(last_page_index) = n;
          ESP_LOGD("display", "Wechsel %d -> %d | Animation: %s", o, n, 
                  (anim == LV_SCR_LOAD_ANIM_MOVE_LEFT ? "VOR (Links)" : "ZURÜCK/RESET (Rechts)"));
    
  - id: refresh_system_info
    mode: restart
    then:
      - lambda: |-
          char buf[32];
          
          // 1. UPTIME
          int s = (int)id(sensor_uptime).state;
          sprintf(buf, "%dh %dm", s / 3600, (s % 3600) / 60);
          lv_label_set_text(id(lv_system_uptime), buf);

          // 2. RAM & BAR
          float free_heap = id(sensor_free_ram).state;
          float used_percent = 100.0 - (free_heap / 300000.0 * 100.0);
          if (used_percent < 0) used_percent = 0;
          if (used_percent > 100) used_percent = 100;

          lv_bar_set_value(id(lv_ram_bar), (int)used_percent, LV_ANIM_ON);
          
          // Farbe der Bar setzen (Grün -> Gelb -> Rot)
          if (used_percent > 90) {
            lv_obj_set_style_bg_color(id(lv_ram_bar), lv_color_make(255, 0, 0), LV_PART_INDICATOR);
          } else if (used_percent > 70) {
            lv_obj_set_style_bg_color(id(lv_ram_bar), lv_color_make(255, 255, 0), LV_PART_INDICATOR);
          } else {
            lv_obj_set_style_bg_color(id(lv_ram_bar), lv_color_make(0, 255, 0), LV_PART_INDICATOR);
          }

          sprintf(buf, "%.1f KB", free_heap / 1024.0);
          lv_label_set_text(id(lv_system_ram), buf);
        
          // 3. IP ADRESSE & TEMP
          if (id(wifi_ip).has_state()) {
            lv_label_set_text(id(lv_system_ip), id(wifi_ip).state.c_str());
          } else {
            lv_label_set_text(id(lv_system_ip), "keine IP");
          }
          if (!isnan(id(esp_temp).state)) {
            sprintf(buf, "%.1f °C", id(esp_temp).state);
            lv_label_set_text(id(lv_system_temp), buf);
          }
  
  - id: start_icon_rotation
    then:
      - lambda: |-
          lv_obj_clear_flag(id(lv_weather_update_icon), LV_OBJ_FLAG_HIDDEN);
          
          // Wir nutzen Pulsieren (Opacity) statt Rotation, um den Fehler zu vermeiden
          lv_anim_t a;
          lv_anim_init(&a);
          lv_anim_set_var(&a, id(lv_weather_update_icon));
          lv_anim_set_exec_cb(&a, [](void * var, int32_t v) {
              lv_obj_set_style_opa((lv_obj_t *)var, v, LV_PART_MAIN);
          });
          lv_anim_set_values(&a, 50, 255); // Von halb-transparent zu voll sichtbar
          lv_anim_set_time(&a, 600);
          lv_anim_set_playback_time(&a, 600); // Zurück-Animation
          lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
          lv_anim_start(&a);

  - id: stop_icon_rotation
    then:
      - lambda: |-
          lv_obj_add_flag(id(lv_weather_update_icon), LV_OBJ_FLAG_HIDDEN);
          lv_anim_del(id(lv_weather_update_icon), NULL);
          lv_obj_set_style_opa(id(lv_weather_update_icon), 255, LV_PART_MAIN);

  - id: screen_explosion
    then:
      - lambda: |-
          // Start-Deckkraft (180 ist ein guter Wert, man sieht den Inhalt noch leicht)
          lv_obj_set_style_bg_opa(id(lv_flash_overlay), 180, LV_PART_MAIN);
          
          lv_anim_t a;
          lv_anim_init(&a);
          lv_anim_set_var(&a, id(lv_flash_overlay));
          lv_anim_set_exec_cb(&a, [](void * var, int32_t v) {
              lv_obj_set_style_bg_opa((lv_obj_t *)var, v, LV_PART_MAIN);
          });
          lv_anim_set_values(&a, 180, 0); // Von 180 auf 0 (unsichtbar)
          lv_anim_set_time(&a, 500);      // Eine halbe Sekunde Effekt
          lv_anim_start(&a);
  
  - id: muell_blink_logic
    mode: single
    then:
      - while:
          condition: 
            lambda: |-
              return true;
          then:
            - lambda: |-
                static bool umschalter = true; 
                
                if (id(muell_is_tomorrow) && lv_scr_act() == id(page_3).obj) {
                    umschalter = !umschalter;
                    if (umschalter) {
                        lv_obj_set_style_text_opa(id(muell_icon), LV_OPA_COVER, LV_PART_MAIN);
                    } else {
                        lv_obj_set_style_text_opa(id(muell_icon), LV_OPA_0, LV_PART_MAIN);
                    }
                } else {
                    lv_obj_set_style_text_opa(id(muell_icon), LV_OPA_COVER, LV_PART_MAIN);
                    umschalter = true; 
                }
            - delay: 500ms

output:
  - platform: ledc
    pin: GPIO25
    inverted: true
    id: backlight_pwm

light:
  - platform: monochromatic
    output: backlight_pwm
    name: "${friendly_devicename} Backlight"
    id: ${device_id}_backlight
    restore_mode: ALWAYS_ON

spi:
  clk_pin: GPIO18
  mosi_pin: GPIO23
  interface: hardware
  id: spihwd

image:
  - file: "images/om.png"
    id: om_logo
    resize: 50x50
    type: RGB565

  - file: "images/wetter_bg.jpg"
    id: weather_bg_img
    type: RGB565 # Schnellste Performance für ESP32
    dither: FLOYDSTEINBERG # Macht Farbverläufe schöner

font:
  - file: "gfonts://Roboto"
    id: ROBOTO_48
    size: 48
    glyphs: "0123456789:./"

  - file: "gfonts://Roboto"
    id: ROBOTO_24
    size: 24
    glyphs: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:./-°%, äöüÄÖÜß()!?+*=[]|_>"

  - file: "gfonts://Roboto"
    id: ROBOTO_18
    size: 18
    glyphs: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:./-°%, äöüÄÖÜß()!?+*=[]|_>"

  - file: "gfonts://Roboto"
    id: ROBOTO_16
    size: 16
    glyphs: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:./-°%, äöüÄÖÜß()!?+*=[]|_>"
      
  - file: "fonts/meteocons.ttf"
    id: weather_icons_48
    size: 65
    glyphs: "BIHFMRWSEUNX"
  - file: "fonts/meteocons.ttf"
    id: weather_icons_small
    size: 24
    glyphs: "BRS0123456789%E'"

  - file: "fonts/materialdesignicons-webfont.ttf"  # MDI Font herunterladen
    id: MDI_24
    size: 24
    glyphs:
      # --- Bekannte Funktions-Icons (als Referenz) ---
      - "\U000F0150" # clock
      - "\U000F0121" # wifi
      - "\U000F016A" # memory/chip
      # --- Verschiedene Thermometer-Varianten (MDI Codes) ---
      - "\U000F0531" # thermometer
      - "\U000F0530" # thermometer (alt)
      - "\U000F0535" # thermometer-high
      - "\U000F0E3E" # thermometer-lines
      - "\U000F03BF" # oil-temp
      - "\U000F0CF2" # thermometer-water
      - "\U000F10E2" # thermometer-alert
      - "\U000F10C2"
      - "\U000F0445" # mdi-sync (Das drehende Update-Icon)
      - "\U000F0A79" # MDI-delete (Mülltonne)
  
  - file: "fonts/materialdesignicons-webfont.ttf"  # MDI Font herunterladen
    id: MDI_72
    size: 72
    glyphs:
      - "\U000F0A79" # MDI-delete (Mülltonne)

display:
  - platform: ili9xxx
    id: lcd_display
    model: st7789v
    spi_id: spihwd
    data_rate: 40MHz
    dc_pin: GPIO2
    reset_pin: GPIO4
    spi_mode: MODE3
    dimensions:
      width: 240
      height: 240
    invert_colors: true
    auto_clear_enabled: false
    update_interval: never

interval:
  - interval: 10s
    then:
      - lambda: |-
          // ==========================================
          // 1. WETTER-ICON LOGIK & FARBE
          // ==========================================
          std::string check_text = lv_label_get_text(id(lv_temp_text));

          if (check_text.find("°") != std::string::npos) {
            // Icon einblenden
            lv_obj_clear_flag(id(lv_temp_icon), LV_OBJ_FLAG_HIDDEN);
            
            // Temperatur als Zahl extrahieren für die Farblogik
            float t = atof(check_text.c_str()); 

            if (t <= 0.0) {
              // Blau bei Frost
              lv_obj_set_style_text_color(id(lv_temp_icon), lv_color_make(0, 150, 255), LV_PART_MAIN);
            } else if (t > 0.0 && t <= 15.0) {
              // Hellblau/Türkis bei Kälte
              lv_obj_set_style_text_color(id(lv_temp_icon), lv_color_make(0, 255, 255), LV_PART_MAIN);
            } else if (t > 15.0 && t <= 25.0) {
              // Orange bei Wohlfühltemperatur
              lv_obj_set_style_text_color(id(lv_temp_icon), lv_color_make(255, 165, 0), LV_PART_MAIN);
            } else {
              // Rot bei Hitze
              lv_obj_set_style_text_color(id(lv_temp_icon), lv_color_make(255, 0, 0), LV_PART_MAIN);
            }
          } else {
            lv_obj_add_flag(id(lv_temp_icon), LV_OBJ_FLAG_HIDDEN);
          }

          // ==========================================
          // 2. SYSTEM-INFO AKTUALISIEREN (wenn aktiv)
          // ==========================================
          if (id(on_system_info)) {
            id(system_page_timeout_counter) += 10;
            
            // Das zentrale Skript erledigt die ganze Arbeit (RAM, IP, Uptime, Farben):
            id(refresh_system_info).execute(); 

          } else {
            id(system_page_timeout_counter) = 0;
          }

      # --- TIMEOUT & AUTOMATIONEN (YAML-Stil) ---
      - if:
          condition:
            lambda: |-
              return id(cfg_enable_system_timeout) && 
                     id(on_system_info) && 
                     id(system_page_timeout_counter) >= id(cfg_system_timeout_seconds);
          then:
            - globals.set: { id: system_page_timeout_counter, value: '0' }
            - globals.set: { id: on_system_info, value: 'false' }
            - lvgl.page.show: page_clock

      - if:
          condition:
            lambda: 'return id(cfg_enable_auto_page_switch) && !id(on_system_info);'
          then:
            - lambda: 'id(page_switch_counter) += 10;'
            - if:
                condition:
                  lambda: 'return id(page_switch_counter) >= id(cfg_page_switch_seconds);'
                then:
                  - globals.set: { id: page_switch_counter, value: '0' }
                  - lambda: |-
                      id(page_rotator)++;
                      if (id(page_rotator) >= id(cfg_max_main_pages)) {
                        id(page_rotator) = 0;
                      }
                  - script.execute: load_page_by_index

      - if:
          condition:
            lambda: 'return id(cfg_enable_screen_saver);'
          then:
            - lambda: 'id(touch_inactivity_seconds) += 10;'
            - if:
                condition:
                  lambda: |-
                    return (id(touch_inactivity_seconds) >= id(cfg_screen_saver_timeout)) 
                           && !id(screen_saver_active);
                then:
                  - globals.set: { id: screen_saver_active, value: 'true' }
                  - light.turn_on:
                      id: ${device_id}_backlight
                      brightness: !lambda "return id(screen_saver_brightness) / 100.0;"
                  - logger.log: "Bildschirmschoner aktiviert"
            

  - interval: 1s
    then:
      - script.execute: update_time_display


lvgl:
  log_level: INFO
  color_depth: 16
  bg_color: 0x000000
  displays:
  top_layer:
    widgets:
      - obj:
          id: lv_flash_overlay
          width: 100%
          height: 100%
          bg_color: 0xFF0000 
          bg_opa: 0          
          border_width: 0
          radius: 0
          
  pages:
    - id: page_clock
      widgets:
        - label:
            id: lv_time
            align: CENTER
            y: -80
            text_font: ROBOTO_48
            text_color: 0xFFFF00
        - label:
            id: lv_weekday
            y: -40
            align: CENTER
            text_font: ROBOTO_24
            text_color: 0x0099FF
        - label:
            id: lv_date
            y: -10
            align: CENTER
            text_font: ROBOTO_18
            text_color: 0xFFFFFF
        - image:
            src: om_logo
            align: CENTER
            y: 60
      ################     Wetter Seite ################
    - id: page_weather
      widgets:
        # - image:
        #     id: lv_weather_bg
        #     src: weather_bg_img
        #     align: CENTER
        #     width: 240
        #     height: 240
        
        - label:
            id: lv_weather_update_icon
            text: "\U000F0445" # mdi-sync
            text_font: MDI_24
            text_color: 0x00FF00
            align: TOP_RIGHT
            x: -10
            y: 10
            hidden: true # Startet unsichtbar
            bg_opa: 0

                 
        - label:
            x: 10
            y: 10
            text: "Wetter Info"
            text_font: ROBOTO_16
            text_color: 0xFF3333
            bg_opa: 0
        
        ############### Temperatur Anzeige ##################
        - obj:
            y: -10
            width: 240
            height: 40
            align: CENTER
            bg_opa: 0
            border_width: 0
            scrollbar_mode: "OFF"
            layout:
              type: flex
              flex_flow: ROW
              flex_align_main: CENTER
              flex_align_cross: CENTER
            widgets:
              - label:
                  id: lv_temp_icon
                  text: "'"
                  text_font: weather_icons_small
                  text_color: 0xFF9900
                  text_align: RIGHT
                  hidden: true
                  # --- DER TRICK ---
                  width: 35      # Feste Breite für das Icon-Feld
                  pad_right: 5   # Abstand zum Text innerhalb dieser Breite
              - label:
                  id: lv_temp_text
                  text: "Warte auf Daten..."
                  text_font: ROBOTO_24
                  text_color: 0xFFFFFF
                  width: SIZE_CONTENT


        ###############   WETTER ICON #########
        - label:
            id: lv_weather_icon
            y: -60
            text: "B"
            align: CENTER
            text_font: weather_icons_48
            text_color: 0xFFFF00
            


        ############### Regen Anzeige ##################
        - obj:
            y: 35
            width: 240
            height: 40
            align: CENTER
            bg_opa: 0
            border_width: 0
            scrollbar_mode: "OFF"
            layout:
              type: flex
              flex_flow: ROW
              flex_align_main: CENTER
              flex_align_cross: CENTER
            widgets:
              - label:
                  id: lv_regen_icon
                  text: "R"
                  text_font: weather_icons_small
                  text_align: RIGHT
                  text_color: 0x0099FF
                  # --- DER GLEICHE TRICK ---
                  width: 35      # Exakt gleiche Breite wie oben
                  pad_right: 5   # Exakt gleicher Abstand
              - label:
                  id: lv_regen_text
                  text: "0%"
                  text_font: ROBOTO_24
                  text_color: 0xFFFFFF
                  width: SIZE_CONTENT
        
        
        ############# Wetter Linie 4 (mit Scroll) ##########
        - label:
            id: lv_weather_line_4
            y: 80               # Etwas mehr Abstand nach unten
            width: 240
            height: 40
            align: CENTER
            bg_opa: 0
            border_width: 0           # Volle Displaybreite
            text: " " 
            text_font: ROBOTO_18
            text_color: 0xFFFFFF
            # --- SCROLL SETTINGS ---
            long_mode: SCROLL_CIRCULAR 
            text_align: CENTER   # Zentriert, solange der Text kurz ist
            pad_left: 5
            pad_right: 5
            bg_color: 0x000000


        - label:
            id: lv_weather_waiting
            y: 80
            width: 240
            height: 40
            align: CENTER
            bg_opa: 0
            border_width: 0
            text: "Warte auf Daten..."
            text_align: CENTER
            text_font: ROBOTO_16
            text_color: 0xFFFF00


## System Info Seite


    - id: page_system_info
      widgets:
        - label:
            x: 10
            y: 10
            text: "System Status"
            text_font: ROBOTO_16
            text_color: 0x00FF00

        # --- UPTIME ZEILE ---
        - obj:
            y: -40
            width: 220
            height: 40
            align: CENTER
            bg_opa: 0
            border_width: 0
            scrollbar_mode: "OFF"
            layout:
              type: flex
              flex_flow: ROW
              flex_align_main: START
              flex_align_cross: CENTER
            widgets:
              - label:
                  text: "\U000F0150"  # mdi-clock (U+F0150)
                  text_font: MDI_24
                  text_color: 0x0099FF
              - label:
                  text: " "
                  width: 10
              - label:
                  id: lv_system_uptime
                  text: "Lade..."
                  text_font: ROBOTO_18
                  text_color: 0xFFFFFF
                  text_align: RIGHT
                  width: 140

        # --- RAM ZEILE NEU (Icon + Text oben, Balken unten) ---
        - obj:
            y: 0
            width: 220
            height: 50
            align: CENTER
            bg_opa: 0
            border_width: 0
            scrollbar_mode: "OFF"
            layout:
              type: flex
              flex_flow: ROW_WRAP  # Erlaubt den Umbruch für den Balken
              flex_align_main: SPACE_BETWEEN # Icon links, Text rechts
              flex_align_cross: CENTER
            widgets:
              - label:
                  text: "\U000F016A" # mdi-chip
                  text_font: MDI_24
                  text_color: 0x00FF00
              - label:
                  id: lv_system_ram
                  text: "Lade..."
                  text_font: ROBOTO_18
                  text_color: 0xFFFFFF
                  text_align: RIGHT
                  width: 140 # Platzhalter für den Text
              - bar:
                  id: lv_ram_bar
                  width: 210 # Fast volle Breite
                  height: 8   # Etwas dünner für elegante Optik
                  min_value: 0
                  max_value: 100
                  bg_color: 0x333333 # Dunkelgrauer Hintergrund


        # --- IP ZEILE ---
        - obj:
            y: 40
            width: 220
            height: 40
            align: CENTER
            bg_opa: 0
            border_width: 0
            scrollbar_mode: "OFF"
            layout:
              type: flex
              flex_flow: ROW
              flex_align_main: START
              flex_align_cross: CENTER
            widgets:
              - label:
                  text: "\U000F0121"  # mdi-wifi (U+F0121)
                  text_font: MDI_24
                  text_color: 0xFFFF00
              - label:
                  text: " "
                  width: 10
              - label:
                  id: lv_system_ip
                  text: "Lade..."
                  text_font: ROBOTO_16
                  text_color: 0xFFFFFF
                  text_align: RIGHT
                  width: 140

    # --- TEMPERATUR ZEILE ---
        - obj:
            y: 80 # Position unter der IP-Zeile
            width: 220
            height: 40
            align: CENTER
            bg_opa: 0
            border_width: 0
            scrollbar_mode: "OFF"
            layout:
              type: flex
              flex_flow: ROW
              flex_align_main: SPACE_BETWEEN # Icon links, Text rechts
              flex_align_cross: CENTER
            widgets:
              - label:
                  text: "\U000F10C2"  # mdi-thermometer
                  text_font: MDI_24
                  text_color: 0xFF5500 # Schönes Orange-Rot
              - label:
                  id: lv_system_temp
                  text: "-- °C"
                  text_font: ROBOTO_18
                  text_color: 0xFFFFFF
                  text_align: RIGHT
                  width: 140

    # -------------------------------------------------------
    # SEITE 3: Müllabfuhr (Minimalistisches Icon Design)
    # -------------------------------------------------------
    - id: page_3
      widgets:
        - label:
            x: 10
            y: 10
            text: "Nächste Abfuhr"
            text_font: ROBOTO_16
            text_color: 0xAAAAAA
            bg_opa: 0

        - obj:
            width: 240
            height: 120
            align: CENTER
            bg_opa: 0
            border_width: 0
            layout:
              type: flex
              flex_flow: ROW
              flex_align_main: CENTER
              flex_align_cross: CENTER
            widgets:
              - label:
                  id: muell_icon
                  text: "\U000F0A79"
                  text_font: MDI_72
                  text_color: 0x555555 # Standardgrau
              - label:
                  id: muell_datum
                  text: "Warten..."
                  text_font: ROBOTO_24
                  text_color: 0xFFFFFF
                  pad_left: 15 # Abstand zwischen Icon und Datum

Fragen dazu werden auch beantwortet.




3 „Gefällt mir“

Das sieht schon mal sehr gut aus! Kannst du eventuell auch noch die Bilddateien und die Schriften zur Verfügung stellen ich weiß nicht, was dann noch fehlt?
Danke schon mal.


Das Symbol habe ich benutzt. Die Schriften kann ich hier nicht hochladen, kannst du dir aber aus dem Netz ziehen. Du brauchst nur meteocons.ttf und materialdesignicons-webfont.ttf.

Wenn die nur so einfach zu finden wären …
Kannst Du uns noch sagen, wo du die heruntergeladen hast?

Ich habe jetzt mal

heruntergeladen und in config/esphome/font kopiert.
Das Bild om.png habe ich in config/esphome/images kopiert.
Damit konnte ich dann kompilieren.

Jetzt warte ich mal auf Hardware …

Hatte vom Handy aus geschrieben. Aber du hast Sie gefunden.

Jetzt fehlt leider noch die Datei “wetter_bg.jpg”. Oder ist da egel welche Bildgüße und Auflösung man nimmt?

Danke

Da die Verwendung der Datei auskommentiert ist (ab Zeile 995), habe ich das einfach auch mit der Definition gemacht;

1 „Gefällt mir“

Das habe ich übersehen. Da ich die Datei habe ist es nicht aufgefallen.

1 „Gefällt mir“

Kann mir bitte einer sagen, wie ich das flaschen kann?
Es ist fertig kompiliert bin Dateien ota und factory sind runtergeladen. Nur der GeekMagic wird nicht am USB Port gefunden. Benötige ich da noch ein Paar Treiber?
Danke schon mal für die Hilfe.

Soweit ich weiß, musst du eine Brücke im Hochlauf schließen, damit der ESP32 in oder mit Flashoption bootet.
Schaue in den Beschreibungen deines ESP32 nach, welcher Pin und ob er auf 3.3V oder 0V sein muss.

Es kann auch sein, Du brauchst noch einen seriellen Umsetzer auf 3,3 V, falls der USB-Port nicht oder mit dem falschen UART des ESC32 verbunden ist.
Hier mal ein Beispiel dafür mit einem ESP-01 (Der ist nicht im GeekMagic verbaut!):

Siehe auch: ESP8266 Pinout Reference: Which GPIO pins should you use?

1 „Gefällt mir“

@psyganja

Ich habe die gm32-n16 n4xx Pro Version. Dazu findet man mit Google leider nicht wirklich viel.

Welche Version hast Du denn ?

Und wie hast Du die Firmware da rauf bekommen ? Bei der 8266er Ultra Version geht das ja übers Webinteface. hier bei der ESP32er Version, geht es weder mit der kompilierten OTA noch mit der Factory Version. Mit der Methode von @miami ? In dem Fall wären auch weitere Infos hilfreich.

Mit der OTA bootet er zwar, aber dann in die originale FW. Mit der Factory Version macht der ESP gar nichts.

esphome:
  name: espminidisplay2
  friendly_name: ESP MiniDisplay 2

esp32:
  board: esp32dev
  framework:
    type: esp-idf

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password  

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Fallback Hotspot"
    password: "00000000000000"

captive_portal:


#enable HA API
api:

ota:
  - platform: esphome

#enable logger
logger:

Habe es statt esp-idf auch mit arduino versucht, aber selbes Problem.

Ich habe die selbe Version und habe den Chip per UART geflasht. Welche Infos brauchst du dafür?Das Forum hast du ja schon gefunden. Dort steht auch alles was du brauchst.

1 „Gefällt mir“

Na das Wichtigste steht da nicht, oder ich habe es trotz mehrmaligen Lesens des ganzen Thread nicht gefunden. ^^

Habe die Info dann hier gefunden:

Da steht unten die Belegung für den Adapter.

PINs on header (verified with multimeter, WROOM module pin number in parens):

    1 GND square
    2 TX (35)
    3 RX (34)
    4 3V3
    5 GPIO0 (25) - must be held low on reset
    6 RST (3)

To let the programmer automatically reset the board: Connect pad 1 (GND) to pad 5 (GPIO0).

Geflashed dann mit ESPHome Webinstaller:

Beim Verbinden mit dem PC muss man dabei den GPIO0 auf GND legen und dann auf Prepare for first use klicken. Danach kann man die Verbindung zu GND wieder lösen, also sobald man sieht das die FW geschrieben wird.

WiFi war etwas triggy, da musste ich mehrmals verbinden, da sonst immer der Fehler An error occurred. Improv Wi-Fi Serial not detected kam.

Danach wurde das Gerät in HA gefunden, ich konnte es hinzufügen. In ESPHome wurde es auch gefunden und auch dort konnte ich es wie gewohnt hinzufügen.

So, jetzt geht es dann ans Eingemachte.

Also hast du jetzt erfolgreich geflasht?

Ja, funktioniert jetzt.

Ich musste aber das Display anpassen auf platform: mipi_spi wie ich es auch bei der 8266er Version hatte statt platform: ili9xxx. Hatte sonst nur Schnee auf dem Display.

1 „Gefällt mir“

Hallo Zusammen,

ich hab jetzt auch probiert, das Display zu flashen.
Die Innitiale Installation hat so weit auch funktioniert,.
Wenn ich nun aber den Code aus dem ersten Post hoch laden möchte, dann bekomm ich ein FEhler biem Kompilieren.
Das übersteigt meine Fähigkeiten allerdings.
Kann mir hier einer aushelfen?

Compiling .pioenvs/kellerrobertminidisplay/lib2e2/lvgl/core/lv_obj_style.c.o
In file included from src/esphome/core/component.h:9,
                 from src/esphome/core/application.h:10,
                 from src/esphome/components/api/api_frame_helper.h:13,
                 from src/esphome/components/api/api_connection.h:5,
                 from src/esphome.h:3:
src/main.cpp: In lambda function:
src/main.cpp:467:39: error: 'WiFi' was not declared in this scope
  467 |       ESP_LOGD("main", "WLAN IP: %s", WiFi.localIP().toString().c_str());
      |                                       ^~~~
src/esphome/core/log.h:96:100: note: in definition of macro 'esph_log_d'
   96 |   ::esphome::esp_log_printf_(ESPHOME_LOG_LEVEL_DEBUG, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__)
      |                                                                                                    ^~~~~~~~~~~
src/main.cpp:467:7: note: in expansion of macro 'ESP_LOGD'
  467 |       ESP_LOGD("main", "WLAN IP: %s", WiFi.localIP().toString().c_str());
      |       ^~~~~~~~

Danke und Grüße Robert

Ich meine in der verwendeten Bibliothek ESP_LOGD (die zieht sich ESPHome vermutlich automatisch) ist eine Funktion zum Schreiben ins Log nicht mehr vorhanden.
Daher habe ich das mal auskommentiert und konnte dann übersetzen.

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
#  on_connect:
#    then:
#      - logger.log:
#          format: "WLAN IP: %s"
#          args: ['WiFi.localIP().toString().c_str()']
  ap:
    ssid: "${friendly_devicename} AP"
    password: !secret fallback_password

Ich verstehe aber nicht, wie ich die Wetterinfos an das Teil gebe, dafür scheinen ja Weather Text 1 bis 4 da zu sein.
Weather Text 1 ist der Text für das aktuelle Wetter
Weather Text 3 ist der Text für die Regenwahrscheinlichkeit.
Aber wie beeinflusse ich das Icon für das aktuelle Wetter?

Ich kann ja mal meine Automation zum Wetter geben. Dann wird es vielleicht klarer.

alias: "GeekMagic: Wetter"
description: Sendet Icon-Name, Temperatur und Regenwahrscheinlichkeit an den ESP32
triggers:
  - trigger: state
    entity_id: sensor.openweathermap_condition
  - trigger: state
    entity_id: sensor.openweathermap_temperature
  - trigger: state
    entity_id: sensor.regenwahrscheinlichkeit_heute
  - trigger: homeassistant
    event: start
  - trigger: state
    entity_id: sensor.geekmagic_esp32_geekmagic_esp32_uptime
    not_from:
      - unavailable
      - unknown
actions:
  - action: esphome.geekmagic_esp32_set_weather_icon_by_name
    data:
      condition: "{{ states('sensor.openweathermap_condition') }}"
      red: 255
      green: 220
      blue: 0
  - action: esphome.geekmagic_esp32_set_weather_line
    data:
      line: 1
      message: "{{ states('sensor.openweathermap_temperature') | round(1) }}°C"
  - action: esphome.geekmagic_esp32_set_weather_line
    data:
      line: 3
      message: "{{ states('sensor.regenwahrscheinlichkeit_heute') | round(0) }}%"
  - action: esphome.geekmagic_esp32_set_weather_line
    data:
      line: 4
      message: >
        {% set forecast_list = state_attr('sensor.wettervorhersage', 'forecast')
        %} {% if forecast_list is not none and forecast_list | length > 1 %}
          {% set f = forecast_list[1] %}
          {% set date_obj = f.datetime | as_datetime %}
          {% set weekdays = {'Mon': 'Mo', 'Tue': 'Di', 'Wed': 'Mi', 'Thu': 'Do', 'Fri': 'Fr', 'Sat': 'Sa', 'Sun': 'So'} %}
          {% set wtag = weekdays[date_obj.strftime('%a')] if date_obj.strftime('%a') in weekdays else date_obj.strftime('%a') %}
          
          {% set conditions = {
            'clear-night': 'Klare Nacht',
            'cloudy': 'Bewölkt',
            'fog': 'Neblig',
            'hail': 'Hagel',
            'lightning': 'Gewitter',
            'lightning-rainy': 'Gewitter/Regen',
            'partlycloudy': 'Leicht bewölkt',
            'pouring': 'Starker Regen',
            'rainy': 'Regnerisch',
            'snowy': 'Schneefall',
            'snowy-rainy': 'Schneeregen',
            'sunny': 'Sonnig',
            'windy': 'Windig'
          } %}
          {% set cond = conditions[f.condition] if f.condition in conditions else f.condition %}
          
          Morgen ({{ wtag }}): {{ cond }} | {{ f.temperature | round(0) }}°/{{ f.templow | round(0) }}°C
        {% else %}
          Vorhersage nicht verfügbar
        {% endif %}
    enabled: true

Im ESPHome Code verbirgt sich die Logik hier.

  services:
    - service: set_weather_icon_by_name
      variables:
        condition: string
        red: int
        green: int
        blue: int
      then:
        - lambda: |-
            // 1. Animation starten
            id(start_icon_rotation).execute(); 

            // 2. Bestehende Logik
            const char* icon_char = "B"; 
            if (condition == "sunny") icon_char = "B";
            else if (condition == "clear-night") icon_char = "I";
            else if (condition == "partlycloudy") icon_char = "H";
            else if (condition == "cloudy") icon_char = "N";
            else if (condition == "fog") icon_char = "M";
            else if (condition == "rainy") icon_char = "R";
            else if (condition == "pouring") icon_char = "X";
            else if (condition == "snowy") icon_char = "W";
            else if (condition == "lightning-rainy") icon_char = "S";
            else if (condition == "windy") icon_char = "F";
            else if (condition == "snowy-rainy") icon_char = "U";
            
            lv_obj_set_style_text_font(id(lv_weather_icon), id(weather_icons_48), LV_PART_MAIN);
            lv_label_set_text(id(lv_weather_icon), icon_char);
            lv_obj_set_style_text_color(id(lv_weather_icon), lv_color_make(red, green, blue), LV_PART_MAIN);
            lv_obj_add_flag(id(lv_weather_waiting), LV_OBJ_FLAG_HIDDEN);

    - service: set_weather_line
      variables:
        message: string
        line: int
      then:
        - lambda: |-
            if (line == 4) {
              lv_label_set_text(id(lv_weather_line_4), message.c_str());
              lv_obj_add_flag(id(lv_weather_waiting), LV_OBJ_FLAG_HIDDEN);
              // HIER: Animation stoppen und Icon verstecken
              id(stop_icon_rotation).execute(); // <--- HIER STOPPEN
            } else if (line == 1) {
              lv_label_set_text(id(lv_temp_text), message.c_str());
              lv_obj_add_flag(id(lv_weather_waiting), LV_OBJ_FLAG_HIDDEN);
            } else if (line == 3) {
              lv_label_set_text(id(lv_regen_text), message.c_str());
            }


1 „Gefällt mir“

Vielen Dank! Jetzt läuft es (auch) bei mir (Ohne Müll-Termine, die brauche ich nicht).
Warum es läuft, kann ich allerdings nur erahnen :smiling_face:
Da habe ich ja noch einiges zu lernen…

Hallo ihr Lieben,

ich habe mich auch mal an diesem Projekt versucht. Zu Erst habe ich die Grund Datei über Das Software Update eingespielt. Danach war das Gerät in ESP Home auch online. nach ein paar versuchen mit Chat GPT mir ein Hallo Welt anzuzeigen ist die Verbindung jedoch abgebrochen. nun habe ich versucht das Gerät irgendwie über USB zu erreichen. Ist das bei meinem Board überhaupt möglich oder ist das Teil nun “schrott”?

Danke schonmal