Home Assistant Werte auf einem ePaper Display

Hier könnt Ihr eure Smart Home Projekte Vorstellen.
Antworten
Benutzeravatar
azrael783
Beiträge: 25
Registriert: Mi 27. Okt 2021, 08:57
Kontaktdaten:

Home Assistant Werte auf einem ePaper Display

Beitrag von azrael783 »

Einleitung
Irgendwann auf meiner Smart-Home Erkundungsreise bin ich auf einen Beitrag gestoßen, der zeigte wie man die in Home-Assistant gesammelten Werte, auf einem ePaper Display darstellen kann. Da ich gerne Dinge ausprobiere und ich unsere Smart-Home Zentrale etwas präsenter machen wollte, habe ich mir ein ePaper Display gekauft. Hier möchte ich euch zeigen, was ihr braucht (Hardware) und wie ihr die Werte aus HA auf das Display bekommt.

Hardware
Vorweg muss ich noch erwähnen, dass ich esphome nutze um das Display und den dazu notwendigen Mikrocontroller zu betrieben. Daher solltet ihr, falls ihr noch keine Hardware habt, darauf achten, dass die Hardware mit esphome kompatibel ist. Ich verwende folgende Teile:
  • ESP32 Mikrocontroller
  • 2,9 Zoll ePaper Display von Waveshare
  • Micro-USB Netzteil

Software

Auf der esphome Oberfläche habe ich dann eine neue Konfiguration angelegt. Nachdem der Wizard beendet wurde, kann man die "yaml" Datei entsprechend anpassen. Hier mal meine komplette Datei:

Code: Alles auswählen

esphome:
  name: esp32-display
  platform: ESP32
  board: esp32dev

wifi:
  ssid: !secret ssid
  password: !secret wlan_pwd
  # fast_connect: true
  # Optional manual IP
  manual_ip:
    static_ip: 192.168.0.141
    gateway: 192.168.0.1
    subnet: 255.255.255.0


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

captive_portal:

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:

spi:
  clk_pin: GPIO13
  mosi_pin: GPIO14

display:
  - platform: waveshare_epaper
    id: epaper
    cs_pin: GPIO15
    dc_pin: GPIO27
    busy_pin: GPIO25
    reset_pin: GPIO26
    model: 2.90in
    rotation: 90°
    full_update_every: 180
    lambda: |-
	  // Print date and time
      it.strftime(295, 0, id(comic15), TextAlign::TOP_RIGHT, "%H:%M", id(esptime).now());
      it.strftime(140, 0, id(comic15), TextAlign::TOP_LEFT, "%d-%b-%y", id(esptime).now());
      it.strftime(30, 0, id(comic15), TextAlign::TOP_LEFT, "%A", id(esptime).now());
      // Print line underneath date and time
      it.line(0, 20, 296, 20);
      // Print weather info
      it.printf(20, 22, id(comic15), "Wetterinfo:");
      it.printf(5, 38, id(comic15), "Heute:");
      // Print weather symbol
      if (id(current_condition).has_state()) {
        std::map<std::string, std::string> weather_icon_map
          {
            {"clear-night", "\U000F0594"},
            {"cloudy", "\U000F0590"},
            {"exceptional", "\U000F069B"},
            {"fog", "\U000F0591"},
            {"hail", "\U000F0592"},
            {"lightning", "\U000F0593"},
            {"lightning-rainy", "\U000F067E"},
            {"partlycloudy", "\U000F0595"},
            {"pouring", "\U000F0596"},
            {"rainy", "\U000F0597"},
            {"snwoy", "\U000F0598"},
            {"snowy-rainy", "\U000F067F"},
            {"sunny", "\U000F0599"},
            {"windy", "\U000F059D"},
            {"windy-variant", "\U000F059E"},
          };
            it.printf(20, 60, id(icon_font30), TextAlign::TOP_LEFT , weather_icon_map[id(current_condition).state.c_str()].c_str());
      }
      // Print weather forecast
      it.printf(65, 38, id(comic15), "Morgen:");
      if (id(forecast_condition).has_state()) {
        std::map<std::string, std::string> weather_icon_map2
          {
            {"clear-night", "\U000F0594"},
            {"cloudy", "\U000F0590"},
            {"exceptional", "\U000F069B"},
            {"fog", "\U000F0591"},
            {"hail", "\U000F0592"},
            {"lightning", "\U000F0593"},
            {"lightning-rainy", "\U000F067E"},
            {"partlycloudy", "\U000F0595"},
            {"pouring", "\U000F0596"},
            {"rainy", "\U000F0597"},
            {"snwoy", "\U000F0598"},
            {"snowy-rainy", "\U000F067F"},
            {"sunny", "\U000F0599"},
            {"windy", "\U000F059D"},
            {"windy-variant", "\U000F059E"},
          }; 
            it.printf(80, 60, id(icon_font30), TextAlign::TOP_LEFT, weather_icon_map2[id(forecast_condition).state.c_str()].c_str());
      }
      // Print moon phase
      it.printf(135, 22, id(comic15), TextAlign::TOP_LEFT, "Mond-");
      it.printf(135, 39, id(comic15), TextAlign::TOP_LEFT, "phase:");
      
      if (id(moon).has_state()) {
        std::map<std::string, std::string> moon_icon_map
        {
          {"new_moon", "\U000F0F64"},
          {"full_moon", "\U000F0F62"},
          {"waxing_crescent", "\U000F0F67"},
          {"first_quarter", "\U000F0F61"},
          {"waxing_gibbous", "\U000F0F68"},
          {"waning_gibbous", "\U000F0F66"},
          {"lightning-rainy", "\U000F067E"},
          {"last_quarter", "\U000F0F63"},
          {"waning_crescent", "\U000F0F65"},
        };
          it.printf(155, 80, id(icon_font30), TextAlign::CENTER, moon_icon_map[id(moon).state.c_str()].c_str());
      }
      
      // Print line behind moon phase
      it.line(185, 20, 185, 128);
      
      // Print sunrise and sun sunset times
      it.printf(195, 25, id(icon_font25), TextAlign::TOP_LEFT, "\U000F059C");
      if (id(next_sunrise).has_state()) {
        it.printf(225, 22, id(comic15), TextAlign::TOP_LEFT, "%s", id(next_sunrise).state.c_str());
      }
      it.printf(195, 53, id(icon_font25), TextAlign::TOP_LEFT, "\U000F059B");
      if (id(next_sunset).has_state()) {
        it.printf(225, 55, id(comic15), TextAlign::TOP_LEFT, "%s", id(next_sunset).state.c_str());
      }
      
      
      // Print outside temperature (from homeassistant sensor)
      if (id(outside_temperature).has_state()) {
        it.printf(40, 100, id(comic15), TextAlign::CENTER, "%.1f°", id(outside_temperature).state);
      }
      it.printf(10, 100, id(icon_font15), TextAlign::CENTER, "\U000F0599"); 
      
      // Print inside temperature (from homeassistant sensor)
      if (id(inside_temperature).has_state()) {
        it.printf(40, 120, id(comic15), TextAlign::CENTER, "%.1f°", id(inside_temperature).state);
      }
      it.printf(10, 120, id(icon_font15), TextAlign::CENTER, "\U000F04B9"); 
      
      // Print forecast temperature (from homeassistant sensor)
      if (id(forecast_temp).has_state()) {
        it.printf(102, 100, id(comic15), TextAlign::CENTER, "%.1f°", id(forecast_temp).state);
      }
      it.printf(75, 100, id(icon_font15), TextAlign::CENTER, "\U000F0737");      

      // Print forecast_low temperature (from homeassistant sensor)
      if (id(forecast_temp_low).has_state()) {
        it.printf(102, 120, id(comic15), TextAlign::CENTER, "%.1f°", id(forecast_temp_low).state);
      }  
      it.printf(75, 120, id(icon_font15), TextAlign::CENTER, "\U000F072E");  
      
      // Print line behind weather info
      it.line(130, 20, 130, 128);
      
      // Print inside temperature (from homeassistant sensor)
      // if (id(inside_temperature).has_state()) {
      //   it.printf(127, 80, id(font3), TextAlign::TOP_RIGHT , "%.1f°", id(inside_temperature).state);
      // }


font:
  - file: 'fonts/comicbd.ttf'
    id: comic15
    size: 15
    glyphs:
      ['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
       '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
       'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
       'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
       'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
       'u', 'v', 'w', 'x', 'y', 'z','ü', 'ä', 'ö', '/','º','µ','³']
  - file: 'fonts/comic.ttf'
    id: comic20
    size: 20
    glyphs:
      ['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
       '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
       'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
       'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
       'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
       'u', 'v', 'w', 'x', 'y', 'z','ü', 'ä', 'ö', '/','º','µ','³']  

  - file: 'fonts/materialdesignicons-webfont.ttf'
    id: icon_font30
    size: 30
    glyphs:
      - "\U000F0590" # weather-cloudy
      - "\U000F0F2F" # weather-cloudy-alert
      - "\U000F0E6E" # weather-cloudy-arrow-right
      - "\U000F0591" # weather-fog
      - "\U000F0592" # weather-hail
      - "\U000F0F30" # weather-hazy
      - "\U000F0898" # weather-hurricane
      - "\U000F0593" # weather-lightning
      - "\U000F067E" # weather-lightning-rainy
      - "\U000F0594" # weather-night
      - "\U000F0F31" # weather-night-partly-cloudy
      - "\U000F0595" # weather-partly-cloudy
      - "\U000F0F32" # weather-partly-lightning
      - "\U000F0F33" # weather-partly-rainy
      - "\U000F0F34" # weather-partly-snowy
      - "\U000F0F35" # weather-partly-snowy-rainy
      - "\U000F0596" # weather-pouring
      - "\U000F0597" # weather-rainy
      - "\U000F0598" # weather-snowy
      - "\U000F0F36" # weather-snowy-heavy
      - "\U000F067F" # weather-snowy-rainy
      - "\U000F0599" # weather-sunny
      - "\U000F0F37" # weather-sunny-alert
      - "\U000F14E4" # weather-sunny-off
      - "\U000F059A" # weather-sunset
      - "\U000F059B" # weather-sunset-down
      - "\U000F059C" # weather-sunset-up
      - "\U000F0F38" # weather-tornado
      - "\U000F059D" # weather-windy
      - "\U000F059E" # weather-windy-variant
      - "\U000F069B" # emoticon-dead-outline for weather exeptional
      - "\U000F0F61" # moon-first-quarter
      - "\U000F0F62" # moon-full
      - "\U000F0F63" # moon-last-quarter
      - "\U000F0F64" # moon-new
      - "\U000F0F65" # moon-waning-crescent
      - "\U000F0F66" # moon-waning-gibbous
      - "\U000F0F67" # moon-waxing-cresent
      - "\U000F0F68" # moon-waxing-gibbous

  - file: 'fonts/materialdesignicons-webfont.ttf'
    id: icon_font20
    size: 20
    glyphs:
      - "\U000F0F61" # moon-first-quarter
      - "\U000F0F62" # moon-full
      - "\U000F0F63" # moon-last-quarter
      - "\U000F0F64" # moon-new
      - "\U000F0F65" # moon-waning-crescent
      - "\U000F0F66" # moon-waning-gibbous
      - "\U000F0F67" # moon-waxing-cresent
      - "\U000F0F68" # moon-waxing-gibbous
      - "\U000F059B" # weather-sunset-down
      - "\U000F059C" # weather-sunset-up
  
  - file: 'fonts/materialdesignicons-webfont.ttf'
    id: icon_font25
    size: 25
    glyphs:
      - "\U000F059B" # weather-sunset-down
      - "\U000F059C" # weather-sunset-up

  - file: 'fonts/materialdesignicons-webfont.ttf'
    id: icon_font15
    size: 15
    glyphs:
      - "\U000F0737" # arrow-up-bold
      - "\U000F072E" # arrow-down-bold
      - "\U000F02DC" # home
      - "\U000F04B9" # sofa
      - "\U000F0599" # sun

time:
  - platform: homeassistant
    id: esptime
    timezone: Europe/Berlin

sensor:
  - platform: wifi_signal
    name: "Wemos Display WiFi Signal"
    update_interval: 900s
  - platform: uptime
    name: "Wemos Display Uptime"
  - platform: homeassistant
    id: outside_temperature
    entity_id: sensor.aussentemperatur
    internal: true
  - platform: homeassistant
    id: inside_temperature
    entity_id: sensor.multisensor_wohnzimmer_2
    internal: true
  - platform: homeassistant
    id: forecast_temp
    entity_id: sensor.openweathermap_forecast_temperature
    internal: true
  - platform: homeassistant
    id: forecast_temp_low
    entity_id: sensor.openweathermap_forecast_temperature_low
    internal: true

text_sensor:
  - platform: homeassistant
    id: next_sunset
    entity_id: sensor.esphome_next_sunset
    internal: true
  - platform: homeassistant
    id: next_sunrise
    entity_id: sensor.esphome_next_sunrise
    internal: true
  - platform: homeassistant
    id: moon
    entity_id: sensor.moon
    internal: true
  - platform: homeassistant
    id: forecast_condition
    entity_id: sensor.openweathermap_forecast_condition
    internal: true
  - platform: homeassistant
    id: current_condition
    entity_id: sensor.openweathermap_condition
    internal: true
  - platform: wifi_info
    ip_address:
      name: "Wemos Dislpay IP"
  - platform: version
    name: Wemos Display ESPHome Version
    hide_timestamp: True


switch:
  - platform: restart
    name: "Wemos Display Restart"
  - platform: shutdown
    name: "Wemos Display Shutdown"

Code Erläuterungen
Boah, ganz schön viel Code, der einen erst einaml ein wenig erschlagen kann. Daher jetzt mal der Reihe nach: Als erstes wird euch wahrscheinlich auffallen, dass ich `!secrets` benutze. Das mache ich, damit ich nicht jedes mal das Passwort und die SSID für das Wlan eintragen muss. Solltet ihr die Secrets noch nicht nutzen, könnt ihr diese über "Secrets" oben rechts in der Oberfläche erstellen

Ich vergebe allen Geräten eine feste IP. Das macht das Hinzufügen zu Home-Assistant leichter und blockiert keine Adresen im DHCP Bereich. Ihr müsst die Adresse natürlich entsprechend anpassen, oder komplett löschen, wenn ihr DHCP nutzen wollt.

Nun folgt auch schon die Einrichtung des Displays, dieser verwendet den SPI Bus und somit muss dieser Konfiguriert werden. Die Pins müsst ihr natürlich entsprechend des verwendeten Mikrocontrollers anpassen. Je nachdem welches Display ihr verwendet, müsst ihr das Modell und evtl. auch die Ausrichtung anpassen.

Damit ihr etwas auf dem Display darstellen könnt, braucht ihr eine, oder auch mehrere Schriftarten. Die Schriften habe ich im Verzeichnis `fonts` im `/config` Ordner von esphome abgespeichert. Ich verwende eine Schriftart in verschiedenen Größen für den Text und für die Symbole verwende ich die `materialdesignicons-webfont.ttf`, ebenfalls in verschiedenen Größen. Unter `glyphs:` werden die zu darstellenden Zeichen / Symbole angegeben. Um die Codes für die Icons zu finden, könnt ihr hier nachsehen. Damit esphome etwas mit dem Code für ein Icon anfangen kann, müsst ihr vor den Code noch ein `\U000` schreiben.

Unter `lambda: |-` folgen dann die Anweisungen, damit etwas auf dem Bildschirm "geschrieben" wird. Um Werte von Home-Assistant auf dem Display darstellen zu können, müsst ihr die gewünschten Werte dem Mikrocontroller bekannt machen. Ich stelle hauptsächlich Wetterinformationen auf dem Display dar und habe die entsprechenden Sensoren unter `sensor:` und `text_sensor:` von HA übernommen. Damit der Mikrocontroller die Werte nicht wieder zurück an HA sendet, solltet ihr den Wert `internal` auf `true` setzen.

Damit ich die Uhrzeit des Sonnenauf- und -untergangs auf dem Display darstellen konnte, habe ich in Home-Assistent Template-Sensoren angelegt:

Code: Alles auswählen

# Beispiel sensor.yaml
  - platform: template
    sensors:
      esphome_next_sunrise:
        friendly_name: "Esphome Next Sunrise"
        value_template: "{{ as_timestamp(states.sun.sun.attributes.next_dawn) | timestamp_custom ('%H:%M') }}"
      esphome_next_sunset:
        friendly_name: "Esphome Next Sunset"
        value_template: "{{ as_timestamp(states.sun.sun.attributes.next_setting) | timestamp_custom ('%H:%M') }}"
Ihr findet das Projekt auch auf meinem Blog (Link in meinem Profil). Dort verweise ich auch auf mein GitHub Repo, wo ihr neben dem kompletten Code auch die Dateien für die Schriftarten findet.
Der nächste Schritt wäre jetzt, ein Gehäuse für das Display zu bauen. Das Ergbnis werde ich dann wieder auf meinem Blog veröffentlichen ;)
Wer Rechtschreibfehler findet, darf sie gerne behalten :D
Antworten