Help with ESPHome + Eink screen to display weather

Hey guys. Thought it best to start my own thread instead of junking up the other one that I’ve been inspired from. I am new to all this and as such keep coming up against new challenges.

I’ve been inspired by this thread here Use ESPHome with e-ink Displays to blend in with your home decor!

So far I’ve got HA running on a VM within Ubuntu (Linux). I’ve set up Studio Code Server to edit my YAMLs via VS Code from my Windows machine. ESPHome was set up on my Windows machine via ESPHome Web to flash the basic WiFi config in order to then add ESPHome from the Add-on store and added my ESP32 device from there… This has taken a while to get to this point having initially set up on my Synology NAS and using docker but discovering not having the add-on store so going through a tonne of troubleshooting to get to where I am now (!)

Anyway, enough of the history and onto where I’m at now…
This is my framed waveshare eink display of which I have some fake data (aside from the date and current temperature which is being displayed in the temperature ‘High’ area currently)

Kind of got the gist of things having brought in the temperature attribute by way of:

sensor:
  - platform: homeassistant
    entity_id: weather.forecast_home
    attribute: temperature
    id: temperature

display:
   lambda: |-
     it.printf(348, 335, id(font_large), TextAlign::TOP_CENTER, "%.0f°C", id(temperature).state);

Full code is:

esphome:
  name: "frame"

  on_boot:
    priority: 200.0
    then:
      - wait_until:
            condition:
              lambda: 'return id(data_updated) == true;'
      # Wait a bit longer so all the items are received
      - component.update: eink_display
      - 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: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:

# 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'

script:
  - id: update_screen
    then:
      - lambda: 'id(data_updated) = false;'
      - component.update: eink_display
      - lambda: 'id(recorded_display_refresh) += 1;'
    

# Wifi information
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: nada
    password: nothinga

  manual_ip:
    # Set this to the IP of the ESP
    static_ip: 192.168.1.48
    # Set this to the IP address of the router. Often ends with .1
    gateway: 192.168.1.1
    # The subnet of the network. 255.255.255.0 works for most home networks.
    subnet: 255.255.255.0

time:
  - platform: homeassistant
    id: homeassistant_time
    timezone: Pacific/Auckland
    on_time:
      - seconds: 0
        minutes: /1
        then:
          - if:
              condition:
                lambda: 'return id(data_updated) == true;'
              then:
                - logger.log: "Sensor data updated and activity in home detected: Refreshing display..."
                - script.execute: update_screen
              else:
                - logger.log: "No sensors updated - skipping display refresh."

image:
  - file: "images/panda.png"
    id: panda
    type: BINARY

# Include custom fonts
font:
  - file: 'fonts/GothamRnd-Book.ttf'
    id: font_small_book
    size: 22
  - file: 'fonts/GothamRnd-Book.ttf'
    id: font_medium
    size: 55
    #glyphs: [' ', '-', '°', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'C']
  - file: 'fonts/GothamRnd-Bold.ttf'
    id: font_title
    size: 45
  - file: 'fonts/GothamRnd-Book.ttf'
    id: font_large
    size: 70
    # glyphs: [' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '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: 22
    # glyphs: [' ', '-', '°', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'C', 'M', 'I', 'N']

  - file:
      type: gfonts
      family: Caveat
      weight: 700
    id: caveat_bold
    size: 45

  # 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
  - file: 'fonts/materialdesignicons-webfont.ttf'
    id: font_mdi_medium
    size: 60
    glyphs: *mdi-weather-glyphs

sensor:
  - platform: homeassistant
    entity_id: weather.forecast_home
    attribute: temperature
    id: temperature
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'
  
# 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: |-
      // 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"},
          {"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"},
        };

      it.print(240, 80, id(font_medium_bold), TextAlign::TOP_CENTER, "Welcome");
      it.print(240, 120, id(font_medium_bold), TextAlign::TOP_CENTER, "Catherine and Guests");

      it.print(240, 180, id(font_medium), TextAlign::TOP_CENTER, "Hanmer Springs");
      it.strftime(240, 245, id(font_title), TextAlign::TOP_CENTER, "%A %b %d", id(homeassistant_time).now());
      if(id(temperature).has_state()) {
        it.printf(132, 335, id(font_large), TextAlign::TOP_CENTER, "4°C");
      }
      it.print(130, 410, id(font_small_book), TextAlign::TOP_CENTER, "Low");
      if(id(temperature).has_state()) {
        it.printf(348, 335, id(font_large), TextAlign::TOP_CENTER, "%.0f°C", id(temperature).state);
      }
      it.print(350, 410, id(font_small_book), TextAlign::TOP_CENTER, "High");

      it.print(75, 475, id(font_small_book), TextAlign::TOP_CENTER, "1pm");
      it.print(185, 475, id(font_small_book), TextAlign::TOP_CENTER, "2pm");
      it.print(295, 475, id(font_small_book), TextAlign::TOP_CENTER, "3pm");
      it.print(405, 475, id(font_small_book), TextAlign::TOP_CENTER, "4pm");

      it.printf(75, 505, id(font_mdi_medium), TextAlign::TOP_CENTER, "\U000F0595");
      it.printf(185, 505, id(font_mdi_medium), TextAlign::TOP_CENTER, "\U000F0598");
      it.printf(295, 505, id(font_mdi_medium), TextAlign::TOP_CENTER, "\U000F0599");
      it.printf(405, 505, id(font_mdi_medium), TextAlign::TOP_CENTER, "\U000F0597");

      it.print(75, 575, id(font_small_bold), TextAlign::TOP_CENTER, "9°C");
      it.print(185, 575, id(font_small_bold), TextAlign::TOP_CENTER, "13°C");
      it.print(295, 575, id(font_small_bold), TextAlign::TOP_CENTER, "15°C");
      it.print(405, 575, id(font_small_bold), TextAlign::TOP_CENTER, "14°C");

      it.filled_rectangle(0, 655, 480, 155);
      it.print(240, 680, id(caveat_bold),  COLOR_OFF, TextAlign::TOP_CENTER, "Cottage on Harrogate");

    
captive_portal:

But what I want to do is bring in the Forecast High and Low Temperature from my Accuweather integration. These are nested attributes which I can test in the template editor like so:

So I’d have assumed that this should be possible to be brought in a similar way that I have with current temperature. Yet for some reason this isnt possible using attribute: forecast[0].temperature like you can with just attribute: temperature

sensor:
  - platform: homeassistant
    entity_id: weather.Accuweather_Home
    attribute: forecast[0].temperature
    id: temperature

Seems strange to me but hey ho. So it seems I need to use templates instead but for some reason I just can’t figure this part out. If anyone can just throw me up some code to copy from to make this work, then I’m sure I’ll have an ‘aha’ moment in understanding this as I know its likely very simple to implement. I think I’m just fogged up from all the troubleshooting I’ve had to do to get to this point.

Much appreciated.

Hi, Did you tried the search function? :slight_smile:

hmm I don’t know if this sounds like a good way of doing it. It would mean importing a load of data onto the esphome that I’m not going to use to then extract it somehow (by way of ‘deserialization’ it seems).
I think the best route is to target the actual data I’m looking to obtain and I think this is to use templates but just not sure how to do that but I know I’m close.

You also can create template sensors in HA for the attributes you want and use those template sensors in ESPHome.

yep, this I understand. Its the implementation of it that I’m trying to get.

Edit: ok. So I’ve now created this sensor as a template in my sensor.yaml file which is imported into confi file and I can see it as an entity in HA. Great :slight_smile:

- platform: template
  sensors:
    temp_low:
      friendly_name: "Today Low Temperature"
      value_template: >-
        {{ state_attr('weather.Accuweather_Home','forecast')[0].templow }}

Almost there now. Just need to work out how to print out the entity’s state rather then the attribute of an entity’s state.

Ah sweet! I’ve finally got it. Of course just printing the state is as easy as printing an attribute’s state.

- platform: homeassistant
    entity_id: sensor.temp_low
    id: lowtemp
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

display:
  lambda: |-
    if(id(lowtemp).has_state()) {
        it.printf(132, 335, id(font_large), TextAlign::TOP_CENTER, "%.0f°C",id(lowtemp).state);
      }

Does anyone know how to adapt this code to display 4pm instead of 4 PM?

{{ as_timestamp(state_attr('weather.forecast_home_hourly', 'forecast')[4].datetime) | timestamp_custom('%I') | int }} {{ as_timestamp(state_attr('weather.forecast_home_hourly', 'forecast')[4].datetime) | timestamp_custom('%p') }}

Need to remove the whitespace and convert to lowercase to match the design as I dont think it looks great otherwise

Try this

{{ as_timestamp(state_attr('weather.forecast_home_hourly', 'forecast')[4].datetime) | timestamp_custom('%I') | int }}{{ as_timestamp(state_attr('weather.forecast_home_hourly', 'forecast')[4].datetime) | timestamp_custom('%p') | lower}}

You introduced the space yourself, in-between the brackets }} {{

1 Like

Well, I merely copied the code from someone else haha. Thank you. That did it. Though I think it only works to have no space when the letters are in lowercase (to be finnicky). Would this be easy to do? I’m guessing it needs to be converted to a string and then converted but not sure how to do this here.

9pm > 9 PM > 9PM

I am not following :wink:

The code I provided will output: 9pm (lowercase due to the | lower filter at the end)
What is it you need for your display?

If you want 9PM, just use:

{{ as_timestamp(state_attr('weather.forecast_home_hourly', 'forecast')[4].datetime) | timestamp_custom('%I') | int }}{{ as_timestamp(state_attr('weather.forecast_home_hourly', 'forecast')[4].datetime) | timestamp_custom('%p') }}
1 Like

oh sorry! Since you had only mentioned the whitespace, then I thought thats all you were correcting and so I had just deleted it from my own code that copied yours in. Thanks!

1 Like