Skip to content

Instantly share code, notes, and snippets.

@jnettels
Created March 24, 2026 07:38
Show Gist options
  • Select an option

  • Save jnettels/cdd9c25d87110b02f4d58a89e1dd0637 to your computer and use it in GitHub Desktop.

Select an option

Save jnettels/cdd9c25d87110b02f4d58a89e1dd0637 to your computer and use it in GitHub Desktop.
Weatherman Dashboard
# 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