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') }}"
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