Use ESPHome with e-ink Displays to blend in with your home decor!

Weatherman Dashboard for ESPHome

I have multiple dashboards around my apartment and they display different information depending on the context. Since this is placed near the front door, it contains concise information for what’s outside with a homey style to match the rest of the wall.

This is part of the ecosystem for my Home Assistant configuration.

Hardware

Software

  • ESPHome
  • Home Assistant running on a Raspberry Pi as a remote server

Installation

  1. No soldering is required since the e-Paper driver board was integrated into the ESP32 board. All I needed to do was to connect the e-Paper screen to the driver board, and then connect the driver board to the USB socket on my light switch.
  2. Copy /fonts, /images, and weatherman.yaml to your /.config/esphome folder.
  3. Integrate the content of sensor.yaml to your Home Assistant template configuration YAML file.
  4. Install HA-GTFS-RT to your Home Assistant using HACS.
  5. Once booted, flash weatherman.yaml the ESP32 board using ESPHome.
  6. Enjoy!

Data Sources

References

Here are some other repos that I referenced from:

80 Likes

This looks amazing. Do you have more pictures or video of it changing info etc? I would love to build this into some shelves :slight_smile:

1 Like

Thank you! :smiley:

Here’s another angle of the display.

(I’ll wait for a different hour of the day to take another picture when the L train is not Not Good :laughing: )

2 Likes

I see an intercom screen, that says tap to speak… Do you have more details about what you’re doing there?

That is just the intercom provided by the building. I printed labels with the same fonts to match the other frames.

1 Like

Oh, so it’s just a frame around the intercom and it looks a lot like it’s another one of those screens.

On yet another look, I just realized it is only one e-ink screen. My initial thoughts were that there were 4 e-ink screens there since you have them all in matching frames. duh. It looks nice having that stuff in frames. Good job!

2 Likes

This is amazing! Very nicely done. I had planed to do something similar with a 10" touch display, but your solution is much less invasive - and would be better accepted by my wife. :slight_smile:

1 Like

Thank you very much! Matching interior decors is always a +1 for me. :smiley:

Please note that Waveshare Paper support on ESPHome maxes out at 7.8" displays. After that the resolution is too high unfortunately for ESP chips to handle. :frowning:

Does this screen allow for partial refresh? Every time it refreshes, it keeps blinking the whole display.

This is a fantastic project and great inspiration, thanks for sharing.

I have just finished building mine with some minor changes through trial and error. The main difference is that it relies on data from custom weather entity which combines information from Met Office and Aqara Temperature Sensor (temp, humidity, pressure) fitted inside 3D printed Stevenson Screen attached to my fence. Here is my finished build:

16 Likes

This looks beautiful! :sparkling_heart: I’m glad that you like the project and made it even better!

Looks like this will refresh every minute for the clock, correct? Let me know if the display stays ok after a few weeks. I suspect that my display has burn-ins because I was making it white on black instead of black on white like what you did.

1 Like

Thank you. So far, I am not experiencing any problems with the display, but that was my concern, so I decided to invert it.

There are two more things I am planning to add to it as conditionals, warnings about strong wind and frost, last one based on thermal comfort component (very useful for gardeners).

Overall It is really cool and very useful project.

1 Like

I build such display as well, very good WAF :ok_hand:. Unfortunately got a very grainy picture. After trying different modes i decided to buy an additional epaper + driver (from dutch store tinytronics.nl where it was even cheaper than aliexpress) to figure out that the esp32 driverboards from china had a weak 3v3 powersupply. Connected a different esp32 board to the epd driver board and now all is well :ok_hand: now i have to make a second display somewhere else :innocent:

1 Like

Decided to completely redesign it, latest iteration below:

I added temperature & pressure trend, air transparency (visibility), UV Index, wind strength & wind direction, moon phase with moon rise and moon set, two conditional warning icons (frost, strong wind just beside Celsius). Lastly, the display now shows Wi-Fi connection strength for the esp32 and battery level of Aqara sensor located outside.

11 Likes

I was so hyped by this project that I ordered an e-paper display right away :smiley:
After some french translation and changes of the original template, there is my finished build :

I added my plants water level, with a warning icon when the percentage is below 10%.
I also bought stuff online to build a DIY weather station, so I plan to add detailed weather data on a second page and use a zigbee button to switch between pages.

The two major issues that I encountered is that you can’t easily print multi-line text (I wanted to display news title from a custom RSS feed), and that in the display lambda function, you can’t iterate over a homeassistant sensor returning an array of values.

Overall it’s an awesome project, thank you for sharing it ! :star_struck:

9 Likes

Hello theggz! This looks amazing and I’m glad you like it!

We found a major problem with e-ink screens. If you have the display using white on black instead of black on white, please reduce the refresh interval down to every few hours to preserve the screen quality.

Change the line

update_interval: 5min

to something like 3h since the information displayed on your screen does not need to be updated so frequently.

2 Likes

That also looks nice! would you mind sharing the .yaml (including HA templates?)

1 Like

Not a problem, the code might need some clean-up though, but it works. The only issue I have is that I cannot compile the yaml if I am using secrets. ESPHome returns error, just change whatever needs to be changed to suit your needs. Oh, you would need to edit “weatherman” config to swap entitity names with yours.

# WEATHERMAN DASHBOARD
# For Home Assistant and ESPHome
# Designed by Madelena Mak 2022 - https://mmak.es

# Cue "Blame it on the Weatherman" by B*Witched!
esphome:
  name: "weatherman"

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:

wifi:
  ssid: "CHANGEME"
  password: "CHANGEME"
  
  reboot_timeout: 300s
  
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Esphome-Web-901078"
    password: "WHATEVER"
   

# Include time
time:
  - platform: homeassistant
    id: esptime

#Include sun
sun:
  latitude: CHANGEME
  longitude: CHANGEME

# Include custom fonts
font:
  - file: 'fonts/GothamRnd-Book.ttf'
    id: font_small_book
    size: 18
  - file: 'fonts/GothamRnd-Bold.ttf'
    id: font_large_bold
    size: 108
    glyphs: [' ', '°', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'C',]
  - file: 'fonts/GothamRnd-Bold.ttf'
    id: font_time
    size: 90
    glyphs: [' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':']
  - file: 'fonts/GothamRnd-Book.ttf'
    id: font_weekday
    size: 30
  - file: 'fonts/GothamRnd-Book.ttf'
    id: font_day
    size: 65
    glyphs: [' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
  - file: 'fonts/GothamRnd-Bold.ttf'
    id: font_month
    size: 40
  - 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: 18
    # 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: 96
    glyphs: &mdi-weather-glyphs
      - "\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-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
      - "\U000F058E" # mdi-water-percent
      - "\U000F04C5" # mdi-spedometer
      - "\U000F0F29" # mdi-snowflake-alert
      - "\U000F15FA" # mdi-windsock
      - "\U000F19B3" # mdi-arrow-down-thin
      - "\U000F19B2" # mdi-arrow-up-thin
      - "\U000F19B4" # mdi-arrow-top-right-thin
      - "\U000F19B7" # mdi-arrow-bottom-right-thin
      - "\U000F00A5" # mdi-binoculars
      - "\U000F018C" # mdi-compass-outline
      - "\U000F05A9" # mdi-wifi
      - "\U000F0928" # mdi-wifi-strength-4
      - "\U000F0925" # mdi-wifi-strength-3 
      - "\U000F0922" # mdi-wifi-strength-2
      - "\U000F091F" # mdi-wifi-strength-1
      - "\U000F092B" # mdi-wifi-strength-alert-outline
      - "\U000F0079" # mdi-battery
      - "\U000F0082" # mdi-battery-90
      - "\U000F0081" # mdi-battery-80
      - "\U000F0080" # mdi-battery-70
      - "\U000F007F" # mdi-battery-60
      - "\U000F007E" # mdi-battery-50
      - "\U000F007D" # mdi-battery-40
      - "\U000F007C" # mdi-battery-30
      - "\U000F007B" # mdi-battery-20
      - "\U000F007A" # mdi-battery-10
      - "\U000F10CD" # mdi-battery-alert-variant-outline
      - "\U000F0E03" # mdi-thermometer-chevron-up
      - "\U000F0E02" # mdi-thermometer-chevron-down
      - "\U000F1A09" # mdi-triangle-small-down
      - "\U000F1A0A" # mdi-triangle-small-up
  - file: 'fonts/materialdesignicons-webfont.ttf'
    id: font_mdi_medlarge
    size: 60
    glyphs:
      - "\U000F058E" # mdi-water-percent
      - "\U000F04C5" # mdi-spedometer
      - "\U000F0F29" # mdi-snowflake-alert
      - "\U000F15FA" # mdi-windsock
      - "\U000F0F61" # mdi-moon-first-quarter
      - "\U000F0F62" # mdi-moon-full
      - "\U000F0F63" # mdi-moon-last-quarter
      - "\U000F0F64" # mdi-moon-new
      - "\U000F0F65" # mdi-moon-waning-crescent
      - "\U000F0F66" # mdi-moon-waning-gibbous
      - "\U000F0F67" # mdi-moon-waxing-crescent
      - "\U000F0F68" # mdi-moon-waxing-gibbous
  - file: 'fonts/materialdesignicons-webfont.ttf'
    id: font_mdi_medium
    size: 36
    glyphs: *mdi-weather-glyphs


# Include Custom Titles
image:
  - file: "images/weatherman-title-weather.png"
    id: title_weather
    type: BINARY


# Call Weather sensors from HA
sensor:
  # Temperature - My Weather station
  - platform: homeassistant
    entity_id: weather.my_weather_station
    attribute: temperature
    id: weather_temperature
    
  # Humidity - My Weather station
  - platform: homeassistant
    entity_id: weather.my_weather_station
    attribute: humidity
    id: weather_humidity

  # Air pressure - My Weather station
  - platform: homeassistant
    entity_id: weather.my_weather_station
    attribute: pressure
    id: weather_pressure

  # Wind Bearing - from Yr.no (built-in HA weather privider)
  - platform: homeassistant
    entity_id: weather.home
    attribute: wind_bearing
    id: wind_bearing

  # Battery % for Aqara sensor
  - platform: homeassistant
    entity_id: sensor.zigbeeweather_battery
    id: battery_percent
  # Battery Voltage for Aqara sensor
  - platform: homeassistant
    entity_id: sensor.zigbeeweather_battery
    attribute: voltage
    id: battery_voltage

  # Weatherman entities
  - platform: homeassistant
    entity_id: sensor.weatherman_data
    attribute: weather_temperature_0
    id: weather_temperature_0
  - platform: homeassistant
    entity_id: sensor.weatherman_data
    attribute: weather_temperature_1
    id: weather_temperature_1
  - platform: homeassistant
    entity_id: sensor.weatherman_data
    attribute: weather_temperature_2
    id: weather_temperature_2
  - platform: homeassistant
    entity_id: sensor.weatherman_data
    attribute: weather_temperature_3
    id: weather_temperature_3

  # Strong Wind
  - platform: homeassistant
    entity_id: sensor.wind_warning
    id: wind_warning
  # Wind Speed 
  - platform: homeassistant
    entity_id: weather.my_weather_station
    attribute: wind_speed
    id: wind_speed
  # UV Index
  - platform: homeassistant
    entity_id: sensor.dundonald_uv_index_3_hourly
    id: uv_index
  # WiFi Signal     
  - platform: wifi_signal
    name: "WiFi Signal Sensor"
    id: wifisignal
    update_interval: 20s


text_sensor:
  # Pressure Trend
  - platform: homeassistant
    entity_id: binary_sensor.atmospheric_pressure
    id: pressure_trend
  # Temp Trend
  - platform: homeassistant
    entity_id: binary_sensor.outside_temperature
    id: temp_trend
  # Weather State
  - platform: homeassistant
    entity_id: weather.my_weather_station
    id: weather_state
  # Frost Risk
  - platform: homeassistant
    entity_id: sensor.weather_frostrisk
    id: frost_risk
  # # Weatherman entities
  - platform: homeassistant
    entity_id: sensor.weatherman_data
    attribute: weather_condition_now
    id: weather_condition_now
  - platform: homeassistant
    entity_id: sensor.weatherman_data
    attribute: weather_condition_0
    id: weather_condition_0
  - platform: homeassistant
    entity_id: sensor.weatherman_data
    attribute: weather_timestamp_0
    id: weather_timestamp_0
  - platform: homeassistant
    entity_id: sensor.weatherman_data
    attribute: weather_condition_1
    id: weather_condition_1
  - platform: homeassistant
    entity_id: sensor.weatherman_data
    attribute: weather_timestamp_1
    id: weather_timestamp_1
  - platform: homeassistant
    entity_id: sensor.weatherman_data
    attribute: weather_condition_2
    id: weather_condition_2
  - platform: homeassistant
    entity_id: sensor.weatherman_data
    attribute: weather_timestamp_2
    id: weather_timestamp_2
  - platform: homeassistant
    entity_id: sensor.weatherman_data
    attribute: weather_condition_3
    id: weather_condition_3
  - platform: homeassistant
    entity_id: sensor.weatherman_data
    attribute: weather_timestamp_3
    id: weather_timestamp_3
  # Sunrise
  - platform: sun
    type: sunrise
    id: sun_sunrise
    format: "%H:%M"
  # Sunset
  - platform: sun
    type: sunset
    id: sun_sunset
    format: "%H:%M"
  # Visibility range from Met Office
  - platform: homeassistant
    entity_id: sensor.dundonald_visibility_distance_daily
    id: visibility_range
  # Moon phase
  - platform: homeassistant
    entity_id: sensor.moon
    id: moon_phase
  - platform: homeassistant
    entity_id: sensor.moon_rise
    id: moon_rise
  - platform: homeassistant
    entity_id: sensor.moon_set
    id: moon_set


# Define colors
# This design is white on black so this is necessary.
color:
  - id: color_black
    red: 0%
    green: 0%
    blue: 0%
    white: 50%
  - id: color_white
    red: 0%
    green: 0%
    blue: 0%
    white: 0%


# Pins for Waveshare ePaper ESP Board
spi:
  clk_pin: GPIO13
  mosi_pin: GPIO14


# Now render everything on the ePaper screen.
display:
  - platform: waveshare_epaper
    cs_pin: GPIO15
    dc_pin: GPIO27
    busy_pin: GPIO25
    reset_pin: GPIO26
    model: 7.50inV2
    update_interval: 60s
    rotation: 90°
    auto_clear_enabled: false
    lambda: |-
      // Map weather states to MDI characters.
      std::map<std::string, std::string> visibility_short
        {
          {"20-40", "30"},
          {"20-30", "25"},
          {"10-20", "15"},
        };
      std::map<std::string, std::string> moon_icon_map
        {
          {"first_quarter", "\U000F0F61"},
          {"full_moon", "\U000F0F62"},
          {"last_quarter", "\U000F0F63"},
          {"new_moon", "\U000F0F64"},
          {"waning_crescent", "\U000F0F65"},
          {"waning_gibbous", "\U000F0F66"},
          {"waning_crescent", "\U000F0F67"},
          {"waxing_gibbous", "\U000F0F68"},
          
        };
      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"},
          {"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"},
        };

      // Fill background in black.
      it.fill(color_white);

      // ----------------------------------------------------------------------------------- Print full weekday name
      it.strftime(340, 95, id(font_weekday), TextAlign::TOP_RIGHT, "%A", id(esptime).now());
      // ----------------------------------------------------------------------------------- Print time in HH:MM format 335
      it.strftime(340, 200, id(font_time), TextAlign::BASELINE_RIGHT, "%H:%M", id(esptime).now());
      // TEST it.printf(315, 200, id(font_time), TextAlign::BASELINE_RIGHT, "88:88");
      
      // ----------------------------------------------------------------------------------- Print day of the month
      it.strftime(440, 95, id(font_day), TextAlign::TOP_RIGHT, "%d", id(esptime).now());
      // ----------------------------------------------------------------------------------- Print abbreviated month name
      it.strftime(440, 200, id(font_month), TextAlign::BASELINE_RIGHT, "%b", id(esptime).now());
      // TEST it.printf(440, 200, id(font_month), TextAlign::BASELINE_RIGHT, "AAA");

      // ----------------------------------------------------------------------------------- WiFi
      if(id(wifisignal).has_state ()) {
        if (id(wifisignal).state >= -50) {
            // Excellent # mdi-wifi-strength-4 "\U000F0928" 255, 230
            it.printf(85, 487, id(font_mdi_medium), color_black, TextAlign::TOP_CENTER, "\U000F0928");
        } else if (id(wifisignal).state  >= -60) {
            //Good # mdi-wifi-strength-3 "\U000F0925"
            it.printf(85, 487, id(font_mdi_medium), color_black, TextAlign::TOP_CENTER, "\U000F0925");
        } else if (id(wifisignal).state  >= -67) {
            //Fair # mdi-wifi-strength-2 "\U000F0922"
            it.printf(85, 487, id(font_mdi_medium), color_black, TextAlign::TOP_CENTER, "\U000F0922");
        } else if (id(wifisignal).state  >= -70) {
            //Weak # mdi-wifi-strength-1 "\U000F091F"
            it.printf(85, 487, id(font_mdi_medium), color_black, TextAlign::TOP_CENTER, "\U000F091F");
        } else {
            //Unlikely working signal # mdi-wifi-strength-alert-outline "\U000F092B"
            it.printf(85, 487, id(font_mdi_medium), color_black, TextAlign::TOP_CENTER, "\U000F092B");
        }
      }
      // ----------------------------------------------------------------------------------- LOW BATTERY
        if(id(battery_voltage).has_state ()) {
            if(id(battery_voltage).state <= 2700) {
                // Replace battery, less than 10% 420, 558 / 87
                it.printf(85, 567, id(font_mdi_medium), color_black, TextAlign::BASELINE_CENTER, "\U000F10CD");
            } else if (id(battery_percent).state == 100) {
                // "\U000F0079" # mdi-battery
                it.printf(85, 567, id(font_mdi_medium), color_black, TextAlign::BASELINE_CENTER, "\U000F0079");
            } else if (id(battery_percent).state >= 90) {
                // "\U000F0082" # mdi-battery-90
                it.printf(85, 567, id(font_mdi_medium), color_black, TextAlign::BASELINE_CENTER, "\U000F0082");
            } else if (id(battery_percent).state >= 80) {
                // "\U000F0081" # mdi-battery-80
                it.printf(85, 567, id(font_mdi_medium), color_black, TextAlign::BASELINE_CENTER, "\U000F0081");
            } else if (id(battery_percent).state >= 70) {
                // "\U000F0080" # mdi-battery-70
                it.printf(85, 567, id(font_mdi_medium), color_black, TextAlign::BASELINE_CENTER, "\U000F0080");
            } else if (id(battery_percent).state >= 60) {
                // "\U000F007F" # mdi-battery-60
                it.printf(85, 567, id(font_mdi_medium), color_black, TextAlign::BASELINE_CENTER, "\U000F007F");
            } else if (id(battery_percent).state >= 50) {
                // "\U000F007E" # mdi-battery-50
                it.printf(85, 567, id(font_mdi_medium), color_black, TextAlign::BASELINE_CENTER, "\U000F007E");
            } else if (id(battery_percent).state >= 40) {
                // "\U000F007D" # mdi-battery-40
                it.printf(85, 567, id(font_mdi_medium), color_black, TextAlign::BASELINE_CENTER, "\U000F007D");
            } else if (id(battery_percent).state >= 30) {
                // "\U000F007C" # mdi-battery-30
                it.printf(85, 567, id(font_mdi_medium), color_black, TextAlign::BASELINE_CENTER, "\U000F007C");
            } else if (id(battery_percent).state >= 20) {
                // "\U000F007B" # mdi-battery-20
                it.printf(85, 567, id(font_mdi_medium), color_black, TextAlign::BASELINE_CENTER, "\U000F007B");
            } else if (id(battery_percent).state >= 10) {
                // "\U000F007A" # mdi-battery-10
                it.printf(85, 567, id(font_mdi_medium), color_black, TextAlign::BASELINE_CENTER, "\U000F007A");
            }
        }
      // ----------------------------------------------------------------------------------- Print sunrise
        if(id(sun_sunrise).has_state ()) {
            it.printf(100, 235, id(font_mdi_medium), color_black, TextAlign::CENTER_RIGHT, "\U000F059C");
            it.printf(105, 235, id(font_small_bold), color_black, TextAlign::CENTER_LEFT, "%s", id(sun_sunrise).state.c_str());
        }      
      // ----------------------------------------------------------------------------------- Print sunset
        if(id(sun_sunset).has_state ()) {
            it.printf(455, 235, id(font_mdi_medium), color_black, TextAlign::CENTER_RIGHT, "\U000F059B");
            it.printf(415, 235, id(font_small_bold), color_black, TextAlign::CENTER_RIGHT, "%s", id(sun_sunset).state.c_str());
        }
      // ----------------------------------------------------------------------------------- Print moonrise
        if(id(moon_rise).has_state ()) {
            it.printf(155, 260, id(font_mdi_medium), color_black, TextAlign::CENTER_RIGHT, "\U000F1A0A");
            it.printf(155, 260, id(font_small_bold), color_black, TextAlign::CENTER_LEFT, "%s", id(moon_rise).state.c_str());
        }      
      // ----------------------------------------------------------------------------------- Print moonset
        if(id(moon_set).has_state ()) {
            it.printf(400, 260, id(font_mdi_medium), color_black, TextAlign::CENTER_RIGHT, "\U000F1A09");
            it.printf(365, 260, id(font_small_bold), color_black, TextAlign::CENTER_RIGHT, "%s", id(moon_set).state.c_str());
        }
      // Weather Section ------------------------------------------------------------------- PLACE NAME
        // TEST it.image(20, 338, id(title_weather));
        it.print(265, 280, id(font_weekday), color_black, TextAlign::TOP_CENTER, "DUNDONALD");

      // ----------------------------------------------------------------------------------- WEATHER STATE ICON --------------- (110, 358)
        it.printf(245, 335, id(font_mdi_large), color_black, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_condition_now).state.c_str()].c_str());
      // TEST it.printf(245, 340, id(font_mdi_large), color_black, TextAlign::TOP_CENTER, "\U000F0F35");
      
      // ----------------------------------------------------------------------------------- MOON PHASE ICON ------------------
        if(id(moon_phase).has_state ()) {
            it.printf(265, 243, id(font_mdi_medlarge), color_black, TextAlign::CENTER, "%s", moon_icon_map[id(moon_phase).state.c_str()].c_str());
        }     
      // ----------------------------------------------------------------------------------- HUMIDITY
        if(id(weather_humidity).has_state ()) {
            it.printf(100, 352, id(font_mdi_medium), color_black, TextAlign::BASELINE_RIGHT, "\U000F058E");
            it.printf(145, 350, id(font_small_bold), color_black, TextAlign::BASELINE_LEFT, "%s", "%");
            it.printf(140, 350, id(font_weekday), color_black, TextAlign::BASELINE_RIGHT, "%2.0f", id(weather_humidity).state);
        }      
      // ----------------------------------------------------------------------------------- VISIBILITY
        if(id(visibility_range).has_state ()) {
            it.printf(100, 400, id(font_mdi_medium), color_black, TextAlign::BASELINE_RIGHT, "\U000F00A5");
            it.printf(140, 400, id(font_weekday), color_black, TextAlign::BASELINE_RIGHT, "%s", visibility_short[id(visibility_range).state.c_str()].c_str());
            it.printf(145, 400, id(font_small_bold), color_black, TextAlign::BASELINE_LEFT, "km");
        }
      // ----------------------------------------------------------------------------------- UV INDEX
        if(id(uv_index).has_state ()) {
            it.printf(100, 454, id(font_mdi_medium), color_black, TextAlign::BASELINE_RIGHT, "\U000F0F37");
            it.printf(140, 450, id(font_weekday), color_black, TextAlign::BASELINE_RIGHT, "%2.0f", id(uv_index).state);
            it.printf(145, 450, id(font_small_bold), color_black, TextAlign::BASELINE_LEFT, "UVI");
        }
      // ----------------------------------------------------------------------------------- PRESSURE
        if(id(weather_pressure).has_state ()) {
            // pressure trend
            if(id(pressure_trend).state == "on") {
                it.printf(325, 353, id(font_mdi_medium), color_black, TextAlign::BASELINE_CENTER, "\U000F19B4");
            } else if(id(pressure_trend).state == "off") {
                it.printf(325, 352, id(font_mdi_medium), color_black, TextAlign::BASELINE_CENTER, "\U000F19B7");
            }
            it.printf(410, 350, id(font_weekday), color_black, TextAlign::BASELINE_RIGHT, "%2.0f", id(weather_pressure).state);
            it.printf(415, 350, id(font_small_bold), color_black, TextAlign::BASELINE_LEFT, "hPa");
        }
        // TEST it.printf(410, 340, id(font_weekday), color_black, TextAlign::BASELINE_RIGHT, "1888");
      // ----------------------------------------------------------------------------------- WIND
        if(id(wind_speed).has_state ()) {
            it.printf(330, 400, id(font_mdi_medium), color_black, TextAlign::BASELINE_CENTER, "\U000F15FA");
            it.printf(410, 400, id(font_weekday), color_black, TextAlign::BASELINE_RIGHT, "%2.0f", id(wind_speed).state);
            it.printf(415, 400, id(font_small_bold), color_black, TextAlign::BASELINE_LEFT, "kmh");
            if(id(wind_warning).state > 20) {
                it.printf(415, 485, id(font_mdi_medium), color_black, TextAlign::TOP_LEFT, "\U000F15FA");
            }
        }
        // TEST it.printf(420, 390, id(font_weekday), color_black, TextAlign::BASELINE_RIGHT, "188");
      // ----------------------------------------------------------------------------------- WIND DIRECTION & BEARING
        if(id(wind_bearing).has_state ()) {
            it.printf(330, 454, id(font_mdi_medium), color_black, TextAlign::BASELINE_CENTER, "\U000F018C");
            it.printf(410, 450, id(font_weekday), color_black, TextAlign::BASELINE_RIGHT, "%2.0f", id(wind_bearing).state);
            if (id(wind_bearing).state > 337.5) {
                it.printf(415, 450, id(font_small_bold), color_black, TextAlign::BASELINE_LEFT, "N");
            } else if(id(wind_bearing). state > 292.5) {
                it.printf(415, 450, id(font_small_bold), color_black, TextAlign::BASELINE_LEFT, "NW");
            } else if(id(wind_bearing). state > 247.5) {
                it.printf(415, 450, id(font_small_bold), color_black, TextAlign::BASELINE_LEFT, "W");
            } else if(id(wind_bearing). state > 202.5) {
                it.printf(415, 450, id(font_small_bold), color_black, TextAlign::BASELINE_LEFT, "SW");
            } else if(id(wind_bearing). state > 157.5) {
                it.printf(415, 450, id(font_small_bold), color_black, TextAlign::BASELINE_LEFT, "S");
            } else if(id(wind_bearing). state > 112.5) {
                it.printf(415, 450, id(font_small_bold), color_black, TextAlign::BASELINE_LEFT, "SE");
            } else if(id(wind_bearing). state > 67.5) {
                it.printf(415, 450, id(font_small_bold), color_black, TextAlign::BASELINE_LEFT, "E");
            } else if(id(wind_bearing). state > 22.5) {
                it.printf(415, 450, id(font_small_bold), color_black, TextAlign::BASELINE_LEFT, "NE");
            } else if(id(wind_bearing). state >= 0) {
                it.printf(415, 450, id(font_small_bold), color_black, TextAlign::BASELINE_LEFT, "N");
            }
        }
      // ----------------------------------------------------------------------------------- Frost Warning
        if(id(frost_risk).has_state ()) {
            if(id(frost_risk).state != "no_risk") {
                it.printf(415, 565, id(font_mdi_medium), color_black, TextAlign::BASELINE_LEFT, "\U000F0F29");
            }
        }
      // ----------------------------------------------------------------------------------- TEMPERATURE ---------------------- (310, 458)
        if(id(weather_temperature).has_state ()) {
            it.printf(410, 480, id(font_large_bold), color_black, TextAlign::TOP_RIGHT, "%2.0f°C", id(weather_temperature).state);
            // TEST it.printf(410, 480, id(font_large_bold), color_black, TextAlign::TOP_RIGHT, "%s", "88°C");
        }
        if(id(temp_trend).has_state ()) {
            if(id(temp_trend).state == "on") {
                it.printf(123, 527, id(font_mdi_medium), color_black, TextAlign::CENTER, "\U000F0E03");
            } else if(id(temp_trend).state == "off") {
                it.printf(123, 527, id(font_mdi_medium), color_black, TextAlign::CENTER, "\U000F0E02");
            }
        }
      
        if(id(weather_temperature_0).has_state ()) {
            it.printf(90, 602, id(font_small_bold), color_black, TextAlign::TOP_CENTER, "%s", id(weather_timestamp_0).state.c_str());
            it.printf(90, 626, id(font_mdi_medium), color_black, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_condition_0).state.c_str()].c_str());
            it.printf(90, 674, id(font_small_bold), color_black, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_temperature_0).state);
        }
        
        if(id(weather_temperature_1).has_state ()) {
            it.printf(200, 602, id(font_small_bold), color_black, TextAlign::TOP_CENTER, "%s", id(weather_timestamp_1).state.c_str());
            it.printf(200, 626, id(font_mdi_medium), color_black, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_condition_1).state.c_str()].c_str());
            it.printf(200, 674, id(font_small_bold), color_black, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_temperature_1).state);
        }
        
        if(id(weather_temperature_2).has_state ()) {
            it.printf(315, 602, id(font_small_bold), color_black, TextAlign::TOP_CENTER, "%s", id(weather_timestamp_2).state.c_str());
            it.printf(315, 626, id(font_mdi_medium), color_black, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_condition_2).state.c_str()].c_str());
            it.printf(315, 674, id(font_small_bold), color_black, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_temperature_2).state);
        }
        
        if(id(weather_temperature_3).has_state ()) {
            it.printf(425, 602, id(font_small_bold), color_black, TextAlign::TOP_CENTER, "%s", id(weather_timestamp_3).state.c_str());
            it.printf(425, 626, id(font_mdi_medium), color_black, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_condition_3).state.c_str()].c_str());
            it.printf(425, 674, id(font_small_bold), color_black, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_temperature_3).state);
        }
      
captive_portal:


Template entities:

  - sensor:
      - name: "Moon Rise"
        icon: "mdi:triangle-small-up"
        state: >-
          {% set x = states('sensor.astroweather_moon_next_rising') %}
          
          {{ x[11:16] }}
  - sensor:
      - name: "Moon Set"
        icon: "mdi:triangle-small-down"
        state: >-
          {% set x = states('sensor.astroweather_moon_next_setting') %}
          
          {{ x[11:16] }}

The above comes from Astroweater integration available to install in HACS:

Wind warning is based on my custom weather entity, by changing [1] to other number you can chose how soon you will see the warning.

  - sensor:
      - name: "Wind Warning"
        icon: "mdi:weather-windy"
        unit_of_measurement: "km/h"
        state: "{{state_attr('weather.my_weather_station', 'forecast')[1]['wind_speed']}}"

UV index sensor comes with MET Office integration. I also need to change source for the wind speed . I just noticed Met Office provides separate sensors in its integration, but if I remember correctly these are disabled by default. You can easily be change the identity name in the code once activated.

Temp trend:

  - platform: trend
    sensors:
      outside_temperature:
        entity_id: sensor.zigbeeweather_temperature
        friendly_name: "Temperature Trend"
        max_samples: 60
        sample_duration: 10800

Pressure trend:

  - platform: trend
    sensors:
      atmospheric_pressure:
        entity_id: sensor.zigbeeweather_pressure
        friendly_name: "Pressure Trend"
        max_samples: 60
        sample_duration: 21600

Lastly, frost risk is based on your current temperature, frost point and absolute humidit, quite reliable. Easily available through Thermal Comfort custom integration (HACS):

If I missed something, let me know. Hope it works without any issues.

8 Likes

This is awesome, I actually picked up the hardware months ago and am just now getting round to doing the build.

I’m pretty new to this level of ESPHome, What I want to do is get calendars lists to display. essentially to do lists. But I’m not getting anything showing up yet.

Is this enough to just get the titles of two of the lists to display?

I get no errors in the logs, but im not even getting

WAITING FOR DATA

Should the screen flash or anything on boot?

globals:
  - id: data_updated
    type: bool
    restore_value: no
    initial_value: 'false'
  - id: initial_data_received
    type: bool
    restore_value: no
    initial_value: 'false'

# Include custom fonts
font:
  - file: 'fonts/GothamRnd-Book.ttf'
    id: font_small_book
    size: 18
  - file: 'fonts/GothamRnd-Bold.ttf'
    id: font_large_bold
    size: 108
    glyphs: [' ', '°', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'C']
  - file: 'fonts/GothamRnd-Bold.ttf'
    id: font_title
    size: 54
    glyphs: ['W', 'E', 'A', 'T', 'H', 'R', 'L', 'I', 'N', ' ']
  - file: 'fonts/GothamRnd-Bold.ttf'
    id: font_medium_bold
    size: 30
    # glyphs: [' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'M', 'I', 'N']
  - file: 'fonts/GothamRnd-Bold.ttf'
    id: font_small_bold
    size: 18
    # glyphs: ['°', '0', '1', +'2', '3', '4', '5', '6', '7', '8', '9', 'C', 'M', 'I', 'N']


# Check whether the display needs to be refreshed every minute,
# based on whether new data is received or motion is detected. (Thanks @paviro!)
time:
  - platform: homeassistant
    id: homeassistant_time
    on_time:
      - seconds: 0
        minutes: /1
        then:
          - if:
              condition:
                lambda: 'return id(data_updated) == true;'
              then:
                - lambda: 'id(initial_data_received) = true;'
                - if:
                    condition:
                      binary_sensor.is_on: motion_detected
                    then:
                      - logger.log: "Sensor data updated and activity in home detected: Refreshing display..."
                - component.update: eink_display
                - lambda: 'id(data_updated) = false;'
              else:
                      - logger.log: "Sensor data updated but no activity in home - skipping display refresh."



# Check if motion is detected in the bathroom.
binary_sensor:
  - platform: homeassistant
    entity_id: binary_sensor.bathroom_motion_sensor
    id: motion_detected    

# Call calender sensors from HA.
sensor:
  - platform: homeassistant
    entity_id: calendar.home_assistant_tasks
    id: home_assistant_tasks
    on_value:
      then:
         - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: calendar.alexa_to_do_list
    id: alexa_to_do_list
    on_value:
      then:
         - lambda: 'id(data_updated) = true;'
    
  - platform: homeassistant
    entity_id: calendar.alexa_shopping_list
    id: alexa_shopping_list
    on_value:
      then:
         - lambda: 'id(data_updated) = true;'

# Define colors
# This design is white on black so this is necessary.
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: GPIO25
    reset_pin: GPIO26
    reset_duration: 2ms
    model: 7.50inV2
    update_interval: never
    rotation: 90°
    lambda: |-


      // Fill background.
      // it.fill(color_bg);

      // 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, "WAITING FOR DATA...");
      } else {

        // To Do List
        it.printf(240, 84, id(font_title), color_text, TextAlign::TOP_CENTER, "To Do");


        // Shopping List Section
        it.printf(240, 408, id(font_title), color_text, TextAlign::TOP_CENTER, "Shopping List");




      }


  

captive_portal:

1 Like

So been trying to tobbleshoot this and so far nothing.

ESP32 appears to be working.

Does the display flash when it gets powered?
Does ESPHome log writes to the display? (I get no info in the logs)

I also stripped everything out of the yaml

font:
  - file: 'fonts/GothamRnd-Book.ttf'
    id: font_small_book
    size: 18

  - file: "gfonts://Roboto"
    id: roboto
    size: 20

# 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: GPIO25
    reset_pin: GPIO26
    reset_duration: 2ms
    model: 7.50inV2
    update_interval: never
    rotation: 90°
    lambda: |-
      it.print(0, 0, id(roboto), "Hello World!");

Still nothing, can anyone see anything wrong here?

Thanks

Changed the update intival,

[13:09:32][D][esp32.preferences:113]: Saving 1 preferences to flash...
[13:09:32][D][esp32.preferences:142]: Saving 1 preferences to flash: 0 cached, 1 written, 0 failed

So is this working as far as the ESP32 is concerned?

EDIT;

OK, I think I got it working. Was the display select on the board itself :slight_smile: