reTerminal E1004 im Alltagstest: Ein 13,3″-Farb-E-Paper-Bilderrahmen, der 100 % lokal läuft

Seeed hat mir für diesen Beitrag ein reTerminal E1004 zur Verfügung gestellt – vielen Dank an Mason und das Team. Meine Einschätzung bleibt davon unberührt: Ich zeige euch ehrlich, was gut funktioniert, welche Herstellerangaben kritisch zu betrachten sind und in welche Falle ich selbst tagelang getappt bin, bevor ich die Lösung fand.

Was ist das reTerminal E1004 ĂĽberhaupt?

Das reTerminal E1004 ist das größte Modell der reTerminal-E-Serie von Seeed. Diese Produktfamilie umfasst ESP32-S3-basierte E-Paper-Terminals, die von 7,3 Zoll (monochrom) bis zu diesem 13,3-Zoll-Vollfarb-Panel reichen. Im Gegensatz zu den meisten smarten Bilderrahmen kommt das E1004 ganz ohne WLAN-Kamera, App-Zwang oder Cloud-Abo aus. Stattdessen nutzt es wahlweise die No-Code-Dashboard-Plattform „SenseCraft Seeedash“ oder – und darum geht es in diesem Beitrag – bietet volle ESPHome-Unterstützung.

Technische Daten (laut Hersteller)

Bevor ich auf meine eigenen Erfahrungen eingehe, hier die offiziellen Spezifikationen:

  • Display: 13,3″ Vollfarb-E-Paper, E Ink® Spectra™ 6 Technologie, 1600 Ă— 1200 Pixel Auflösung (6 native Farben)
  • Prozessor: ESP32-S3 mit 8 MB PSRAM
  • Speicher: 32 MB Flash (laut Produktseite), inkl. microSD-Kartenslot (16 GB Karte vorinstalliert, FAT32 bis 32 GB unterstĂĽtzt)
  • Akku: 5.000 mAh, bis zu 6 Monate Laufzeit im Standard-Energiesparmodus (ein Refresh pro Tag)
  • Konnektivität: WLAN 2,4 GHz, Bluetooth 5.0
  • Bedienung: Touch-Buttons fĂĽr manuellen Bildwechsel oder Refresh
  • Software ab Werk: SenseCraft Seeedash (No-Code-Dashboard und Bild-Upload)
  • ESPHome-Kompatibilität: Ab Version 2026.7.0 (der E1004-Displaytreiber wurde erst kĂĽrzlich integriert)
  • Preis: 242,99 € (UVP laut Produktseite)

Wichtige Anmerkung zum Speicher: Auf der Produktseite werden 32 MB Flash angegeben. Bei jedem Boot-Vorgang meines Testgeräts gab der Bootloader jedoch unmissverständlich SPI Flash Size: 4MB aus. Dieser Wert wird direkt vom Chip ausgelesen und ist keine Schätzung. Ob dies an meiner speziellen Produktionscharge liegt oder ob sich die Angabe auf eine andere Variante bezieht, kann ich nicht sicher sagen. Für den Betrieb mit eigener Firmware macht dies jedoch einen spürbaren Unterschied. Daher rechne ich im Folgenden mit den tatsächlich gemessenen 4 MB.

Der eigentliche Clou: 100 % lokal mit ESPHome

Das Besondere am reTerminal E1004 ist die Möglichkeit, die Werksfirmware komplett durch ein eigenes ESPHome-Setup zu ersetzen. Damit entfällt jegliche Cloud-Abhängigkeit: Kein Bild-Upload über fremde Server, keine Registrierung und kein Abo. Das Gerät lädt seine Inhalte entweder direkt über eine URL eurer Wahl (in meinem Fall mein eigener Webspace) oder die Bilder werden fest in die Firmware eingebettet. Beides läuft vollständig innerhalb eures eigenen lokalen Netzwerks.

Für mich als Verfechter des „lokal, sonst nix“-Prinzips – insbesondere bei Kameras – ist das genau der Ansatz, den ich mir für einen smarten Bilderrahmen wünsche. Der Kompromiss im Vergleich zu SenseCraft Seeedash: Ihr müsst selbst Hand anlegen und eine YAML-Konfiguration anstelle einer No-Code-Oberfläche nutzen. Dafür erhaltet ihr jedoch die volle Kontrolle, inklusive Home-Assistant-Anbindung, eigener Sensorik und allen weiteren Funktionen, die ESPHome bietet.

Meine Lösung: Bild-Rotation, Wetter-Dashboard und Deep Sleep

Mein finales Setup umfasst folgende Funktionen:

  • Bild-Rotation: Das Display wechselt alle 45 Minuten automatisch zwischen zwei Motiven (in meinem Test ein KI-generiertes Bild und eine Stadtkarte). Die Bilder werden abwechselnd via online_image direkt von meinem Webserver geladen.
  • Deep Sleep: Zwischen den Aktualisierungen schläft das Gerät komplett. Es wacht nur kurz auf, lädt das nächste Bild, zeigt es an und geht sofort wieder in den Ruhemodus.
  • On-Demand-Dashboard: Ein Tastendruck weckt das Gerät und zeigt fĂĽr 60 Sekunden eine Kachel-Ăśbersicht an. Diese beinhaltet Temperatur und Luftfeuchtigkeit (ĂĽber den internen Sensor), den Akkustand sowie eine 3-Tage-Wetter- und Solarertrags-Prognose aus Home Assistant. Danach kehrt der Rahmen automatisch zur Bildrotation zurĂĽck.

Das gesamte Setup läuft über eine einzige ESPHome-YAML-Datei – ganz ohne SenseCraft oder Cloud-Anbindung.

Der Stolperstein: Wenn online_image einfach hängen bleibt

Hier muss ich ehrlich zugeben: Ich hätte fast aufgegeben.

Der naheliegendste Weg, ein Bild von einer URL anzuzeigen, ist die online_image-Komponente von ESPHome. Sie lädt das Bild zur Laufzeit per HTTP und zeichnet es direkt auf das Display. Das klingt simpel, war es aber in der Praxis nicht.

Sobald ich ein Farbfoto oder ein Bild in voller Panel-Auflösung anzeigen wollte, trat eines von zwei Problemen auf: Entweder stürzte das Gerät mit einem Speicherfehler an wechselnden, scheinbar unzusammenhängenden Stellen im Code ab (ein klassisches Indiz für Speicherkorruption). Oder es fror komplett und ohne Fehlermeldung ein – teilweise für über 20 Minuten, ohne dass etwas passierte. Kleine, in Graustufen gehaltene Bilder funktionierten hingegen zuverlässig.

Zunächst vermutete ich die Bildgröße als Ursache, dann das Farbformat und schließlich die Akkuspannung. Auf Anraten von Seeed testete ich systematisch den Betrieb via USB und reinem Akku, doch das Fehlerbild blieb identisch.

Nach einem ausführlichen GitHub-Issue und mit der Unterstützung eines ESPHome-Maintainers fand sich die Ursache: Der direkte Zeichenaufruf (lambda: it.image(...)), den ich ursprünglich genutzt hatte, rendert das komplette Bild in einem einzigen, blockierenden Vorgang. Bei fast zwei Millionen Pixeln in Farbe reicht das aus, um interne Sicherheitsmechanismen (wie den Task-Watchdog) auszulösen oder den Treiber in einen unerwarteten Zustand zu versetzen.

Die Lösung: Das Bild darf nicht mehr direkt per Lambda gezeichnet werden. Stattdessen muss es über LVGL (eine in ESPHome integrierte Grafikbibliothek) gerendert werden. LVGL zeichnet gepuffert in kleinen Segmenten anstatt in einem Rutsch und wartet zusätzlich, bis das Display wirklich bereit ist (update_when_display_idle: true). Genau diese Synchronisation hatte zuvor gefehlt. Seit dieser Umstellung läuft mein Setup selbst mit vollformatigen Farbfotos absolut zuverlässig.

Mein Tipp für euch: Falls ihr selbst mit online_image und einem E-Paper-Display arbeitet: Greift bei größeren oder farbigen Bildern von Anfang an auf LVGL zurück und verzichtet auf direkte Lambda-Zeichnungen. Das hätte mir locker zwei bis drei Abende Fehlersuche erspart.

Hier noch mein genutzter Code:

# =============================================================================
# reTerminal E1004 - Picture frame with on-demand sensor dashboard
#
# Default state: rotates between two images (Einstein / Cologne map) every
# 45 minutes via deep_sleep, using the LVGL-based online_image approach
# confirmed working in https://github.com/esphome/esphome/issues/18797.
#
# KEY1 (GPIO4): wakes the device (if asleep) and shows a sensor dashboard
# (local temperature/humidity, battery, HA weather) for 60 seconds, then
# automatically returns to the current rotation image and goes back to
# sleep.
#
# TODO before flashing: create the three HA template sensors from
# ha-forecast-templates.yaml first (sensor.frame_forecast_day0/1/2), or the
# forecast tiles below will just show "--".
# =============================================================================
substitutions:
  device_name: reterminal-e1004
  friendly_name: "reTerminal_E1004"

esphome:
  name: ${device_name}
  friendly_name: ${friendly_name}
  on_boot:
    priority: -100
    then:
      - output.turn_on: bsp_battery_enable
      - delay: 200ms
      - component.update: sht4x_sensor
      - component.update: battery_voltage
      - component.update: battery_level

esp32:
  board: seeed_xiao_esp32s3
  # ESPHome's default 5s task watchdog can be too short for a full-panel
  # LVGL image quantization pass. 60s gives generous headroom.
  watchdog_timeout: 60s
  framework:
    type: esp-idf

psram:
  mode: octal

logger:
  hardware_uart: UART0
  level: DEBUG

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  on_connect:
    # Alternates between the two images on each wake: downloads only the
    # one due to be shown this cycle, then flips the flag for next time.
    # show_cologne_next persists across deep_sleep via RTC memory
    # (restore_value: true), so the alternation survives sleep cycles.
    - lambda: |-
        if (id(show_cologne_next)) {
          id(image_cologne).update();
        } else {
          id(image_einstein).update();
        }
        id(show_cologne_next) = !id(show_cologne_next);

# Persists across deep_sleep (RTC memory) - tracks which image to show next.
globals:
  - id: show_cologne_next
    type: bool
    restore_value: true
    initial_value: "true"

# Home Assistant native API
api:
  encryption:
    key: !secret api_key

# Time synced from Home Assistant, used to show the date (DD.MM.) next to
# Heute/Morgen/Ăśbermorgen on the dashboard.
time:
  - platform: homeassistant
    id: ha_time
    timezone: "Europe/Berlin"

# OTA (Over-The-Air) firmware update over WiFi
ota:
  - platform: esphome
    password: !secret ota_password

http_request:

# =============================================================================
# Images: platform: online_image, rendered via the LVGL image widget on
# image_page (see lvgl: section below).
# =============================================================================
image:
  - platform: online_image
    url: "URL-ZU-DEINEM-BILD"
    id: image_einstein
    format: JPEG
    type: RGB565
    resize: 1200x1600
    on_download_finished:
      - lvgl.image.update:
          src: image_einstein
          id: lv_image

  - platform: online_image
    url: "URL-ZU-DEINEM-BILD"
    id: image_cologne
    format: JPEG
    type: RGB565
    resize: 1200x1600
    on_download_finished:
      - lvgl.image.update:
          src: image_cologne
          id: lv_image

spi:
  clk_pin: GPIO7
  mosi_pin: GPIO9
  miso_pin: GPIO8

i2c:
  sda: GPIO19
  scl: GPIO20

output:
  # Battery measurement enable
  - platform: gpio
    pin: GPIO21
    id: bsp_battery_enable

font:
  - file:
      type: gfonts
      family: Montserrat
      weight: 700
    id: font_dashboard_large
    size: 48
    bpp: 2
    glyphsets: [GF_Latin_Kernel, GF_Latin_Core]

  - file:
      type: gfonts
      family: Montserrat
      weight: 700
    id: font_dashboard_medium
    size: 32
    bpp: 2
    glyphsets: [GF_Latin_Kernel, GF_Latin_Core]

  - file:
      type: gfonts
      family: Montserrat
      weight: 600
    id: font_dashboard_small
    size: 24
    bpp: 2
    glyphsets: [GF_Latin_Kernel, GF_Latin_Core]

sensor:
  - platform: sht4x
    id: sht4x_sensor
    temperature:
      name: "Temperature"
      id: temp_sensor
    humidity:
      name: "Relative Humidity"
      id: hum_sensor
    update_interval: 60s

  - platform: adc
    pin: GPIO1
    name: "Battery Voltage"
    id: battery_voltage
    update_interval: 60s
    attenuation: 12db
    filters:
      - multiply: 2.0

  - platform: template
    name: "Battery Level"
    id: battery_level
    unit_of_measurement: "%"
    icon: "mdi:battery"
    device_class: battery
    state_class: measurement
    lambda: 'return id(battery_voltage).state;'
    update_interval: 60s
    filters:
      - calibrate_linear:
          - 4.15 -> 100.0
          - 3.96 -> 90.0
          - 3.91 -> 80.0
          - 3.85 -> 70.0
          - 3.80 -> 60.0
          - 3.75 -> 50.0
          - 3.68 -> 40.0
          - 3.58 -> 30.0
          - 3.49 -> 20.0
          - 3.41 -> 10.0
          - 3.30 -> 5.0
          - 3.27 -> 0.0
      - clamp:
          min_value: 0
          max_value: 100

  # Raw forecast values from the HA template sensors (see
  # ha-forecast-templates.yaml) - formatting/line breaks happen in the
  # show_dashboard script below.
  - platform: homeassistant
    id: fc0_templow
    entity_id: sensor.frame_forecast_day_0_templow
    internal: true
  - platform: homeassistant
    id: fc0_temphigh
    entity_id: sensor.frame_forecast_day_0_temphigh
    internal: true
  - platform: homeassistant
    id: fc0_precip
    entity_id: sensor.frame_forecast_day_0_precip
    internal: true
  - platform: homeassistant
    id: fc0_uv
    entity_id: sensor.frame_forecast_day_0_uv
    internal: true

  - platform: homeassistant
    id: fc1_templow
    entity_id: sensor.frame_forecast_day_1_templow
    internal: true
  - platform: homeassistant
    id: fc1_temphigh
    entity_id: sensor.frame_forecast_day_1_temphigh
    internal: true
  - platform: homeassistant
    id: fc1_precip
    entity_id: sensor.frame_forecast_day_1_precip
    internal: true
  - platform: homeassistant
    id: fc1_uv
    entity_id: sensor.frame_forecast_day_1_uv
    internal: true

  - platform: homeassistant
    id: fc2_templow
    entity_id: sensor.frame_forecast_day_2_templow
    internal: true
  - platform: homeassistant
    id: fc2_temphigh
    entity_id: sensor.frame_forecast_day_2_temphigh
    internal: true
  - platform: homeassistant
    id: fc2_precip
    entity_id: sensor.frame_forecast_day_2_precip
    internal: true
  - platform: homeassistant
    id: fc2_uv
    entity_id: sensor.frame_forecast_day_2_uv
    internal: true

  # Solcast PV forecast - different entity per day (existing entities,
  # no template sensor needed).
  - platform: homeassistant
    id: solar_day0
    entity_id: sensor.solcast_pv_forecast_prognose_verbleibende_leistung_heute
    internal: true
  - platform: homeassistant
    id: solar_day1
    entity_id: sensor.solcast_pv_forecast_prognose_morgen
    internal: true
  - platform: homeassistant
    id: solar_day2
    entity_id: sensor.solcast_pv_forecast_prognose_tag_3
    internal: true

binary_sensor:
  # KEY1 (GPIO4) - wakes the device (if asleep) and shows the sensor
  # dashboard for 60s, then returns to the current rotation image.
  - platform: gpio
    pin:
      number: GPIO4
      allow_other_uses: true
      mode: INPUT
      inverted: true
    id: button_2
    name: "KEY1 Dashboard Button"
    filters:
      - delayed_on: 20ms
      - delayed_off: 20ms
    on_press:
      then:
        - logger.log: "KEY1 (GPIO4) pressed - Dashboard"
        - script.execute: show_dashboard

script:
  - id: show_dashboard
    mode: restart
    then:
      # Keep the device awake for the full 60s dashboard display, even if
      # the normal deep_sleep run_duration would otherwise expire first.
      - deep_sleep.prevent: deep_sleep_1
      - lvgl.label.update:
          id: dashboard_temp_label
          text:
            format: "%.1f °C"
            args: ["id(temp_sensor).state"]
      - lvgl.label.update:
          id: dashboard_hum_label
          text:
            format: "%.0f %% rF"
            args: ["id(hum_sensor).state"]
      - lvgl.label.update:
          id: dashboard_battery_label
          text:
            format: "%.0f %%"
            args: ["id(battery_level).state"]
      - lvgl.label.update:
          id: dashboard_forecast0_label
          text: !lambda |-
            char buf[128];
            snprintf(buf, sizeof(buf), "Temp.: %.0f°/%.0f°\nNiederschlag: %.1f mm\nUV-Index: %.1f\nPV: %.1f kWh",
                     id(fc0_templow).state, id(fc0_temphigh).state,
                     id(fc0_precip).state, id(fc0_uv).state,
                     id(solar_day0).state);
            return std::string(buf);
      - lvgl.label.update:
          id: dashboard_forecast1_label
          text: !lambda |-
            char buf[128];
            snprintf(buf, sizeof(buf), "Temp.: %.0f°/%.0f°\nNiederschlag: %.1f mm\nUV-Index: %.1f\nPV: %.1f kWh",
                     id(fc1_templow).state, id(fc1_temphigh).state,
                     id(fc1_precip).state, id(fc1_uv).state,
                     id(solar_day1).state);
            return std::string(buf);
      - lvgl.label.update:
          id: dashboard_forecast2_label
          text: !lambda |-
            char buf[128];
            snprintf(buf, sizeof(buf), "Temp.: %.0f°/%.0f°\nNiederschlag: %.1f mm\nUV-Index: %.1f\nPV: %.1f kWh",
                     id(fc2_templow).state, id(fc2_temphigh).state,
                     id(fc2_precip).state, id(fc2_uv).state,
                     id(solar_day2).state);
            return std::string(buf);
      # Day titles with date (DD.MM.), computed from HA-synced time.
      - lvgl.label.update:
          id: dashboard_day0_title
          text: !lambda |-
            char buf[32];
            time_t t = id(ha_time).now().timestamp;
            struct tm tm_info;
            localtime_r(&t, &tm_info);
            char datebuf[8];
            strftime(datebuf, sizeof(datebuf), "%d.%m.", &tm_info);
            snprintf(buf, sizeof(buf), "Heute, %s", datebuf);
            return std::string(buf);
      - lvgl.label.update:
          id: dashboard_day1_title
          text: !lambda |-
            char buf[32];
            time_t t = id(ha_time).now().timestamp + 86400;
            struct tm tm_info;
            localtime_r(&t, &tm_info);
            char datebuf[8];
            strftime(datebuf, sizeof(datebuf), "%d.%m.", &tm_info);
            snprintf(buf, sizeof(buf), "Morgen, %s", datebuf);
            return std::string(buf);
      - lvgl.label.update:
          id: dashboard_day2_title
          text: !lambda |-
            char buf[32];
            time_t t = id(ha_time).now().timestamp + 2 * 86400;
            struct tm tm_info;
            localtime_r(&t, &tm_info);
            char datebuf[8];
            strftime(datebuf, sizeof(datebuf), "%d.%m.", &tm_info);
            snprintf(buf, sizeof(buf), "Ăśbermorgen, %s", datebuf);
            return std::string(buf);
      - lvgl.page.show: dashboard_page
      - delay: 60s
      - lvgl.page.show: image_page
      # Give LVGL/epaper_spi time to actually finish redrawing the image
      # before sleeping - otherwise deep_sleep.enter can cut the refresh
      # off mid-way and the display stays stuck showing the dashboard.
      - delay: 45s
      - deep_sleep.allow: deep_sleep_1
      - deep_sleep.enter: deep_sleep_1

display:
  - platform: epaper_spi
    model: seeed-reterminal-e1004

lvgl:
  update_when_display_idle: true
  buffer_size: 25%
  pages:
    - id: image_page
      bg_color: 0xFFFFFF
      bg_opa: COVER
      pad_all: 0
      widgets:
        - image:
            id: lv_image
            src: image_cologne

    - id: dashboard_page
      bg_color: 0xFFFFFF
      bg_opa: COVER
      pad_all: 40
      widgets:
        - label:
            text: "STATUS"
            text_font: font_dashboard_large
            text_color: 0x000000
            x: 0
            y: 0

        - obj:
            x: 0
            y: 100
            width: 1120
            height: 260
            bg_color: 0xFFFFFF
            bg_opa: COVER
            border_color: 0x000000
            border_width: 3
            radius: 10
            pad_all: 24
            widgets:
              - label:
                  text: "Temperatur / Feuchte"
                  text_font: font_dashboard_medium
                  text_color: 0x000000
              - label:
                  id: dashboard_temp_label
                  y: 60
                  text: "-- °C"
                  text_font: font_dashboard_large
                  text_color: 0x000000
              - label:
                  id: dashboard_hum_label
                  y: 140
                  text: "-- % rF"
                  text_font: font_dashboard_large
                  text_color: 0x000000

        - obj:
            x: 0
            y: 400
            width: 1120
            height: 200
            bg_color: 0xFFFFFF
            bg_opa: COVER
            border_color: 0x000000
            border_width: 3
            radius: 10
            pad_all: 24
            widgets:
              - label:
                  text: "Akku"
                  text_font: font_dashboard_medium
                  text_color: 0x000000
              - label:
                  id: dashboard_battery_label
                  y: 60
                  text: "-- %"
                  text_font: font_dashboard_large
                  text_color: 0x000000

        - obj:
            x: 0
            y: 640
            width: 1120
            height: 420
            bg_color: 0xFFFFFF
            bg_opa: COVER
            border_color: 0x000000
            border_width: 3
            radius: 10
            pad_all: 24
            widgets:
              - label:
                  text: "Wetter & Solar-Prognose"
                  text_font: font_dashboard_medium
                  text_color: 0x000000

              - obj:
                  x: 0
                  y: 60
                  width: 340
                  height: 320
                  bg_color: 0xFFFFFF
                  bg_opa: COVER
                  border_color: 0x000000
                  border_width: 2
                  radius: 8
                  pad_all: 14
                  widgets:
                    - label:
                        id: dashboard_day0_title
                        width: 310
                        text: "Heute"
                        text_font: font_dashboard_medium
                        text_color: 0x000000
                        long_mode: WRAP
                    - label:
                        id: dashboard_forecast0_label
                        y: 70
                        width: 310
                        height: 240
                        text: "--"
                        text_font: font_dashboard_small
                        text_color: 0x000000
                        long_mode: WRAP

              - obj:
                  x: 370
                  y: 60
                  width: 340
                  height: 320
                  bg_color: 0xFFFFFF
                  bg_opa: COVER
                  border_color: 0x000000
                  border_width: 2
                  radius: 8
                  pad_all: 14
                  widgets:
                    - label:
                        id: dashboard_day1_title
                        width: 310
                        text: "Morgen"
                        text_font: font_dashboard_medium
                        text_color: 0x000000
                        long_mode: WRAP
                    - label:
                        id: dashboard_forecast1_label
                        y: 70
                        width: 310
                        height: 240
                        text: "--"
                        text_font: font_dashboard_small
                        text_color: 0x000000
                        long_mode: WRAP

              - obj:
                  x: 740
                  y: 60
                  width: 340
                  height: 320
                  bg_color: 0xFFFFFF
                  bg_opa: COVER
                  border_color: 0x000000
                  border_width: 2
                  radius: 8
                  pad_all: 14
                  widgets:
                    - label:
                        id: dashboard_day2_title
                        width: 310
                        text: "Ăśbermorgen"
                        text_font: font_dashboard_medium
                        text_color: 0x000000
                        long_mode: WRAP
                    - label:
                        id: dashboard_forecast2_label
                        y: 70
                        width: 310
                        height: 240
                        text: "--"
                        text_font: font_dashboard_small
                        text_color: 0x000000
                        long_mode: WRAP

# =============================================================================
# [9] Deep sleep - refreshes the image automatically every 45 minutes, and
# can also be woken early at any time by pressing KEY1 (GPIO4) to show the
# dashboard on demand. run_duration is generous enough to cover WiFi
# connect, download, and the LVGL/epaper_spi redraw.
# =============================================================================

deep_sleep:
  id: deep_sleep_1
  run_duration: 120s
  sleep_duration: 45min
  esp32_ext1_wakeup:
    pins:
      - number: GPIO4
        allow_other_uses: true
        mode: INPUT_PULLUP
    mode: ANY_LOW

# secrets.yaml (create this file alongside your config):
# wifi_ssid: "Your WiFi SSID"
# wifi_password: "Your WiFi Password"
# api_key: "generate with: openssl rand -base64 32"
# ota_password: "choose-a-strong-password"

Fazit

Das reTerminal E1004 bietet genau das, was ich von einem smarten Bilderrahmen erwarte: ein echtes, rein lokales Setup ohne Cloud-Zwang, gepaart mit einem Panel, das bei Tageslicht deutlich besser aussieht als herkömmliche LCD-Rahmen.

Die ESPHome-Integration steckt allerdings noch in den Kinderschuhen (der Displaytreiber existiert erst seit wenigen Wochen). Das habe ich beim online_image-Problem deutlich gespürt – es handelt sich noch nicht um eine jahrelang gehärtete Software, sondern um eine aktive Baustelle. Wer jedoch bereit ist, sich in ESPHome einzuarbeiten und im Zweifelsfall auch mal ein GitHub-Issue zu eröffnen, wird mit einem Gerät belohnt, über das er die volle Kontrolle behält.

Was mir für ein abschließendes Urteil noch fehlt, sind Langzeit-Erfahrungswerte zur tatsächlichen Akkulaufzeit bei meinem spezifischen Nutzungsmuster. Eine 45-Minuten-Rotation ist deutlich ressourcenintensiver als der vom Hersteller beworbene „ein Refresh pro Tag“. Ein entsprechendes Update dazu werde ich bei Gelegenheit nachreichen.

Testprodukt "Vom Paketboten direkt in den Test"
Testprodukt „Vom Paketboten direkt in den Test“

Das genannte Produkt wurde mir vom Hersteller kostenlos zur Verfügung gestellt. Ich weise hierauf ausdrücklich hin, da ich Wert auf größtmögliche Transparenz lege.

Das kostenlose Bereitstellen eines Produktmusters hat keine Auswirkung auf meine Berichte hierzu, was du an möglicher geäußerter Kritik auch immer erkennen kannst.


Produktempfehlungen

5,00 EUR € Rabatt SONOFF SNZB-06P Zigbee 3.0 Menschlicher Präsenzsensor...
1,49 EUR € Rabatt SONOFF S60ZBTPF Zigbee Steckdose – Fernsteuerung...