Created
March 24, 2026 07:38
-
-
Save jnettels/cdd9c25d87110b02f4d58a89e1dd0637 to your computer and use it in GitHub Desktop.
Weatherman Dashboard
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # WEATHERMAN DASHBOARD | |
| # For Home Assistant and ESPHome | |
| # Designed by Madelena Mak 2022 - https://mmak.es | |
| # https://github.com/Madelena/esphome-weatherman-dashboard | |
| # Other good reference: | |
| # https://github.com/Nerdiyde/ESPHomeSnippets/tree/main/Snippets/eInk_frame_insert_ribba_5inchX7inch | |
| # Components: | |
| # Waveshare 7.5 Inch E-Paper Display Hat Module V2 | |
| # https://www.amazon.de/dp/B075R4QY3L | |
| # Supports esphome model "7.50inV2p" for partial refresh, | |
| # but that still needs full refresh of the whole screen | |
| # to prevent burn-in. I do not know the required time | |
| # interval, currently it is set to 600 seconds. | |
| # The busy pin on the 7.5v2 screens needs to be inverted | |
| # https://github.com/esphome/issues/issues/4739#issuecomment-1659939636 | |
| # Waveshare Universal e-Paper Driver Board with WiFi SoC ESP32 | |
| # https://www.amazon.de/dp/B07M5CNP3B | |
| # Set the physical "mode switch" on the board to "A" | |
| # https://www.waveshare.com/wiki/E-Paper_ESP32_Driver_Board | |
| esphome: | |
| name: esphome-web-0bdd98 | |
| friendly_name: weatherman | |
| min_version: 2026.1.3 | |
| on_boot: | |
| priority: 200.0 | |
| then: | |
| - component.update: eink_display | |
| - wait_until: | |
| condition: | |
| lambda: 'return id(data_updated) == true;' | |
| # Wait a bit longer so all the items are received | |
| - delay: 5s | |
| - logger.log: "Initial sensor data received: Refreshing display..." | |
| - lambda: 'id(initial_data_received) = true;' | |
| - script.execute: update_screen | |
| esp32: | |
| board: esp32dev | |
| framework: | |
| type: esp-idf | |
| # Enable logging | |
| logger: | |
| # Enable Home Assistant API | |
| api: | |
| ota: | |
| - platform: esphome | |
| # Wifi information | |
| wifi: | |
| ssid: !secret wifi_ssid | |
| password: !secret wifi_password | |
| # Enable fallback hotspot (captive portal) in case wifi connection fails | |
| ap: | |
| ssid: !secret weatherman_ap_ssid | |
| password: !secret weatherman_ap_password | |
| captive_portal: | |
| button: | |
| - platform: shutdown | |
| name: "Weatherman - Shutdown" | |
| on_press: | |
| - script.execute: clear_screen | |
| - platform: restart | |
| name: "Weatherman - Restart" | |
| - platform: template | |
| name: "Weatherman - Refresh Screen" | |
| entity_category: config | |
| on_press: | |
| - script.execute: update_screen | |
| - platform: template | |
| name: "Weatherman - Clear Screen" | |
| entity_category: config | |
| on_press: | |
| - script.execute: clear_screen | |
| # Global variables for detecting if the display needs to be refreshed. (Thanks @paviro!) | |
| globals: | |
| - id: data_updated | |
| type: bool | |
| restore_value: no | |
| initial_value: 'false' | |
| - id: initial_data_received | |
| type: bool | |
| restore_value: no | |
| initial_value: 'false' | |
| - id: recorded_display_refresh | |
| type: int | |
| restore_value: yes | |
| initial_value: '0' | |
| - id: screen_needs_to_be_cleared | |
| type: bool | |
| restore_value: no | |
| initial_value: 'false' | |
| # Script for updating screen - Refresh display and publish refresh count and time. (Thanks @paviro!) | |
| script: | |
| - id: update_screen | |
| then: | |
| - lambda: 'id(data_updated) = false;' | |
| - component.update: eink_display | |
| - lambda: 'id(recorded_display_refresh) += 1;' | |
| - lambda: 'id(display_last_update).publish_state(id(homeassistant_time).now().timestamp);' | |
| - id: clear_screen | |
| then: | |
| - lambda: 'id(screen_needs_to_be_cleared) = true;' | |
| - component.update: eink_display | |
| - lambda: 'id(screen_needs_to_be_cleared) = false;' | |
| # Check whether the display needs to be refreshed every minute, | |
| time: | |
| - platform: homeassistant | |
| id: homeassistant_time | |
| on_time: | |
| - seconds: 0 | |
| minutes: /1 | |
| then: | |
| - script.execute: update_screen | |
| # Include custom fonts | |
| font: | |
| - file: 'fonts/GothamRnd-Book.ttf' | |
| id: font_small_book | |
| size: 30 | |
| - file: 'fonts/GothamRnd-Bold.ttf' | |
| id: font_large_bold | |
| size: 115 | |
| glyphs: [' ', '-', '°', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'C'] | |
| - file: 'fonts/GothamRnd-Bold.ttf' | |
| id: font_clock | |
| size: 90 | |
| glyphs: [' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', ':'] | |
| - file: 'fonts/GothamRnd-Bold.ttf' | |
| id: font_date | |
| size: 50 | |
| glyphs: [' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', ':'] | |
| - file: 'fonts/GothamRnd-Bold.ttf' | |
| id: font_medium_bold | |
| size: 40 | |
| # glyphs: [' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'M', 'I', 'N'] | |
| - file: 'fonts/GothamRnd-Bold.ttf' | |
| id: font_small_bold | |
| size: 35 | |
| # glyphs: [' ', '-', '°', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'C', 'M', 'I', 'N'] | |
| # Include Material Design Icons font | |
| # Thanks to https://community.home-assistant.io/t/display-materialdesign-icons-on-esphome-attached-to-screen/199790/16 | |
| - file: 'fonts/materialdesignicons-webfont.ttf' | |
| id: font_mdi_large | |
| size: 100 | |
| glyphs: &mdi-glyphs | |
| # Weather | |
| - "\U000F0590" # mdi-weather-cloudy | |
| - "\U000F0F2F" # mdi-weather-cloudy-alert | |
| - "\U000F0E6E" # mdi-weather-cloudy-arrow-right | |
| - "\U000F0591" # mdi-weather-fog | |
| - "\U000F0592" # mdi-weather-hail | |
| - "\U000F0F30" # mdi-weather-hazy | |
| - "\U000F0898" # mdi-weather-hurricane | |
| - "\U000F0593" # mdi-weather-lightning | |
| - "\U000F067E" # mdi-weather-lightning-rainy | |
| - "\U000F0594" # mdi-weather-clear-night | |
| - "\U000F0F31" # mdi-weather-night-partly-cloudy | |
| - "\U000F0595" # mdi-weather-partly-cloudy | |
| - "\U000F0F32" # mdi-weather-partly-lightning | |
| - "\U000F0F33" # mdi-weather-partly-rainy | |
| - "\U000F0F34" # mdi-weather-partly-snowy | |
| - "\U000F0F35" # mdi-weather-partly-snowy-rainy | |
| - "\U000F0596" # mdi-weather-pouring | |
| - "\U000F0597" # mdi-weather-rainy | |
| - "\U000F0598" # mdi-weather-snowy | |
| - "\U000F0F36" # mdi-weather-snowy-heavy | |
| - "\U000F067F" # mdi-weather-snowy-rainy | |
| - "\U000F0599" # mdi-weather-sunny | |
| - "\U000F0F37" # mdi-weather-sunny-alert | |
| - "\U000F14E4" # mdi-weather-sunny-off | |
| - "\U000F059A" # mdi-weather-sunset | |
| - "\U000F059B" # mdi-weather-sunset-down | |
| - "\U000F059C" # mdi-weather-sunset-up | |
| - "\U000F0F38" # mdi-weather-tornado | |
| - "\U000F059D" # mdi-weather-windy | |
| - "\U000F059E" # mdi-weather-windy-variant | |
| - "\U000F17FF" # mdi-sun-wireless-outline | |
| # Power / Energy | |
| - "\U000F0079" # mdi:battery | |
| - "\U000F0A72" # mdi:solar-power | |
| - "\U000F140B" # mdi:lightning-bolt | |
| - file: 'fonts/materialdesignicons-webfont.ttf' | |
| id: font_mdi_medium | |
| size: 35 | |
| glyphs: *mdi-glyphs | |
| - file: 'fonts/NotoEmoji-VariableFont_wght.ttf' | |
| id: font_noto_medium | |
| size: 60 | |
| glyphs: ' 🎂❤️⚔🎸😊😴👾🎉🎈🎄🕹🎮🪄🐄🐖🐎🐒🐲🦄👋🚮' | |
| # | |
| # !%()+=,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz | |
| sensor: | |
| # Create sensors for monitoring Weatherman remotely. | |
| - platform: template | |
| name: "Weatherman - Display Last Update" | |
| device_class: timestamp | |
| entity_category: "diagnostic" | |
| id: display_last_update | |
| - platform: template | |
| name: "Weatherman - Recorded Display Refresh" | |
| accuracy_decimals: 0 | |
| unit_of_measurement: "Refreshes" | |
| state_class: "total_increasing" | |
| entity_category: "diagnostic" | |
| lambda: 'return id(recorded_display_refresh);' | |
| - platform: wifi_signal | |
| name: "Weatherman - WiFi Signal Strength" | |
| id: wifisignal | |
| unit_of_measurement: "dBm" | |
| entity_category: "diagnostic" | |
| update_interval: 60s | |
| # Call Weather sensors from HA. | |
| - platform: homeassistant | |
| entity_id: weather.forecast_home | |
| attribute: temperature | |
| id: weather_temperature | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| - platform: homeassistant | |
| entity_id: sensor.weatherman_data | |
| attribute: weather_temperature_0 | |
| id: weather_temperature_0 | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| - platform: homeassistant | |
| entity_id: sensor.weatherman_data | |
| attribute: weather_temperature_1 | |
| id: weather_temperature_1 | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| - platform: homeassistant | |
| entity_id: sensor.weatherman_data | |
| attribute: weather_temperature_2 | |
| id: weather_temperature_2 | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| - platform: homeassistant | |
| entity_id: sensor.weatherman_data | |
| attribute: weather_temperature_3 | |
| id: weather_temperature_3 | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| - platform: homeassistant | |
| entity_id: sensor.weatherman_data | |
| attribute: weather_precipitation_daily | |
| id: weather_precipitation_daily | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| filters: | |
| - lambda: !lambda |- | |
| return isnan(x) ? 999 : x; | |
| - platform: homeassistant | |
| entity_id: sensor.weatherman_data | |
| attribute: weather_uv_index_daily | |
| id: weather_uv_index_daily | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| filters: | |
| - lambda: !lambda |- | |
| return isnan(x) ? 999 : x; | |
| - platform: homeassistant | |
| entity_id: sensor.stp8_0se_sunny_tripower_8_0_se_battery_soc_total | |
| id: stp_battery_soc | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| - platform: homeassistant | |
| entity_id: sensor.stp8_0se_sunny_tripower_8_0_se_pv_power | |
| id: stp_pv_power | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| - platform: homeassistant | |
| entity_id: sensor.sunny_tripower_verbrauch | |
| id: stp_consumption | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| text_sensor: | |
| - platform: homeassistant | |
| entity_id: weather.forecast_home | |
| id: weather_state | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| - platform: homeassistant | |
| entity_id: sensor.weatherman_data | |
| attribute: weather_condition_now | |
| id: weather_condition_now | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| - platform: homeassistant | |
| entity_id: sensor.weatherman_data | |
| attribute: weather_condition_0 | |
| id: weather_condition_0 | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| - platform: homeassistant | |
| entity_id: sensor.weatherman_data | |
| attribute: weather_timestamp_0 | |
| id: weather_timestamp_0 | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| - platform: homeassistant | |
| entity_id: sensor.weatherman_data | |
| attribute: weather_condition_1 | |
| id: weather_condition_1 | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| - platform: homeassistant | |
| entity_id: sensor.weatherman_data | |
| attribute: weather_timestamp_1 | |
| id: weather_timestamp_1 | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| - platform: homeassistant | |
| entity_id: sensor.weatherman_data | |
| attribute: weather_condition_2 | |
| id: weather_condition_2 | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| - platform: homeassistant | |
| entity_id: sensor.weatherman_data | |
| attribute: weather_timestamp_2 | |
| id: weather_timestamp_2 | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| - platform: homeassistant | |
| entity_id: sensor.weatherman_data | |
| attribute: weather_condition_3 | |
| id: weather_condition_3 | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| - platform: homeassistant | |
| entity_id: sensor.weatherman_data | |
| attribute: weather_timestamp_3 | |
| id: weather_timestamp_3 | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| - platform: homeassistant | |
| entity_id: input_text.weatherman_message | |
| id: weatherman_message | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| - script.execute: update_screen | |
| - platform: homeassistant | |
| entity_id: input_text.weatherman_emoji | |
| id: weatherman_emoji | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| - script.execute: update_screen | |
| - platform: homeassistant | |
| entity_id: sensor.anstehende_abholung | |
| id: anstehende_abholung | |
| on_value: | |
| then: | |
| - lambda: 'id(data_updated) = true;' | |
| - script.execute: update_screen | |
| # Define colors | |
| color: | |
| - id: color_bg | |
| red: 0% | |
| green: 0% | |
| blue: 0% | |
| white: 0% | |
| - id: color_text | |
| red: 0% | |
| green: 0% | |
| blue: 0% | |
| white: 100% | |
| # Pins for Waveshare ePaper ESP Board | |
| spi: | |
| clk_pin: GPIO13 | |
| mosi_pin: GPIO14 | |
| # Now render everything on the ePaper screen. | |
| display: | |
| - platform: waveshare_epaper | |
| id: eink_display | |
| cs_pin: GPIO15 | |
| dc_pin: GPIO27 | |
| busy_pin: | |
| number: GPIO25 | |
| inverted: True | |
| reset_pin: GPIO26 | |
| reset_duration: 2ms | |
| model: 7.50inV2p | |
| update_interval: never | |
| full_update_every: 30 | |
| rotation: 90° | |
| lambda: |- | |
| // Map weather states to MDI characters. | |
| std::map<std::string, std::string> weather_icon_map | |
| { | |
| {"cloudy", "\U000F0590"}, | |
| //{"cloudy-alert", "\U000F0F2F"}, | |
| //{"cloudy-arrow-right", "\U000F0E6E"}, | |
| {"fog", "\U000F0591"}, | |
| {"hail", "\U000F0592"}, | |
| //{"hazy", "\U000F0F30"}, | |
| //{"hurricane", "\U000F0898"}, | |
| {"lightning", "\U000F0593"}, | |
| {"lightning-rainy", "\U000F067E"}, | |
| {"clear-night", "\U000F0594"}, | |
| {"night-partly-cloudy", "\U000F0F31"}, | |
| {"partlycloudy", "\U000F0595"}, | |
| //{"partly-lightning", "\U000F0F32"}, | |
| {"partly-rainy", "\U000F0F33"}, | |
| {"partly-snowy", "\U000F0F34"}, | |
| {"partly-snowy-rainy", "\U000F0F35"}, | |
| {"pouring", "\U000F0596"}, | |
| {"rainy", "\U000F0597"}, | |
| {"snowy", "\U000F0598"}, | |
| {"snowy-heavy", "\U000F0F36"}, | |
| {"snowy-rainy", "\U000F067F"}, | |
| {"sunny", "\U000F0599"}, | |
| //{"sunny-alert", "\U000F0F37"}, | |
| //{"sunny-off", "\U000F14E4"}, | |
| //{"sunset", "\U000F059A"}, | |
| //{"sunset-down", "\U000F059B"}, | |
| //{"sunset-up", "\U000F059C"}, | |
| //{"tornado", "\U000F0F38"}, | |
| {"windy", "\U000F059D"}, | |
| {"windy-variant", "\U000F059E"}, | |
| }; | |
| // Show loading screen before data is received. | |
| if (id(initial_data_received) == false) { | |
| it.printf(240, 390, id(font_small_bold), color_text, TextAlign::TOP_CENTER, "WARTE AUF DATEN..."); | |
| } | |
| else if (id(screen_needs_to_be_cleared) == true) { | |
| it.fill(color_bg); | |
| } | |
| else { | |
| // Clock | |
| char str_time[17]; | |
| char str_date[17]; | |
| int yi = 30; | |
| time_t currTime = id(homeassistant_time).now().timestamp; | |
| strftime(str_time, sizeof(str_time), "%H:%M", localtime(&currTime)); | |
| strftime(str_date, sizeof(str_date), "%d.%m.%Y", localtime(&currTime)); | |
| it.printf(240, yi, id(font_clock), color_text, TextAlign::TOP_CENTER, "%s", str_time); | |
| it.printf(240, yi+95, id(font_date), color_text, TextAlign::TOP_CENTER, "%s", str_date); | |
| it.filled_rectangle(60, yi+155, 360, 3); | |
| yi += 170; | |
| // Weather Section | |
| //it.printf(240, 84, id(font_title), color_text, TextAlign::TOP_CENTER, "WETTER"); | |
| it.printf(100, yi+0, id(font_mdi_large), color_text, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_condition_now).state.c_str()].c_str()); | |
| it.printf(300, yi+0, id(font_large_bold), color_text, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_temperature).state); | |
| // Precipitation and UV-Index daily forecast | |
| yi += 110; | |
| it.printf(92, yi, id(font_mdi_medium), color_text, TextAlign::TOP_LEFT, "%s", "\U000F0596"); | |
| it.printf(135, yi+4, id(font_small_book), color_text, TextAlign::TOP_LEFT, "%.1f mm", id(weather_precipitation_daily).state); | |
| it.printf(282, yi, id(font_mdi_medium), color_text, TextAlign::TOP_LEFT, "%s", "\U000F17FF"); | |
| it.printf(325, yi+4, id(font_small_book), color_text, TextAlign::TOP_LEFT, "%2.1f", id(weather_uv_index_daily).state); | |
| it.filled_rectangle(60, yi+45, 360, 3); | |
| yi += 65; | |
| it.printf(60, yi, id(font_small_book), color_text, TextAlign::TOP_CENTER, "%s", id(weather_timestamp_0).state.c_str()); | |
| it.printf(60, yi+28, id(font_mdi_medium), color_text, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_condition_0).state.c_str()].c_str()); | |
| it.printf(60, yi+72, id(font_small_bold), color_text, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_temperature_0).state); | |
| it.printf(180, yi, id(font_small_book), color_text, TextAlign::TOP_CENTER, "%s", id(weather_timestamp_1).state.c_str()); | |
| it.printf(180, yi+28, id(font_mdi_medium), color_text, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_condition_1).state.c_str()].c_str()); | |
| it.printf(180, yi+72, id(font_small_bold), color_text, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_temperature_1).state); | |
| it.printf(300, yi, id(font_small_book), color_text, TextAlign::TOP_CENTER, "%s", id(weather_timestamp_2).state.c_str()); | |
| it.printf(300, yi+28, id(font_mdi_medium), color_text, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_condition_2).state.c_str()].c_str()); | |
| it.printf(300, yi+72, id(font_small_bold), color_text, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_temperature_2).state); | |
| it.printf(420, yi, id(font_small_book), color_text, TextAlign::TOP_CENTER, "%s", id(weather_timestamp_3).state.c_str()); | |
| it.printf(420, yi+28, id(font_mdi_medium), color_text, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_condition_3).state.c_str()].c_str()); | |
| it.printf(420, yi+72, id(font_small_bold), color_text, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_temperature_3).state); | |
| it.filled_rectangle(60, yi+118, 360, 3); | |
| yi += 138; | |
| // PV Statistics | |
| // Battery | |
| it.printf(15, yi, id(font_mdi_medium), color_text, TextAlign::LEFT, "\U000F0079"); | |
| it.printf(50, yi+4, id(font_small_book), color_text, TextAlign::LEFT, | |
| "%3.0f%%", id(stp_battery_soc).state); | |
| // Solar power | |
| it.printf(155, yi, id(font_mdi_medium), color_text, TextAlign::LEFT, "\U000F0A72"); | |
| it.printf(190, yi+4, id(font_small_book), color_text, TextAlign::LEFT, | |
| "%1.1f kW", id(stp_pv_power).state/1000); | |
| // Consumption | |
| it.printf(320, yi, id(font_mdi_medium), color_text, TextAlign::LEFT, "\U000F140B"); | |
| it.printf(355, yi+4, id(font_small_book), color_text, TextAlign::LEFT, | |
| "%1.1f kW", id(stp_consumption).state/1000); | |
| // Custom message from Home Assistant | |
| it.printf(240, 600, id(font_medium_bold), color_text, TextAlign::TOP_CENTER, "%s", id(weatherman_message).state.c_str()); | |
| it.printf(240, 650, id(font_medium_bold), color_text, TextAlign::TOP_CENTER, "%s", id(anstehende_abholung).state.c_str()); | |
| // Custom emoji message from Home Assistant | |
| it.printf(240, 700, id(font_noto_medium), color_text, TextAlign::TOP_CENTER, "%s", id(weatherman_emoji).state.c_str()); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment