Introduction
I found a blog post about a Weatherman Dashboard by Madelena Mak which made me want to have one. I haven't used ESPHome on Home Assistant yet so that was also a reason why I wanted to build one to work with ESPHome.
Looking into the E-Ink display mentioned on the Weatherman Dashboard is from WaveShare and the one used was the 7.5" Black/White version. When I did some more searching into it I also found the 7.5" Red/Black/White which give the additional red colour. The downside of this version is that the refresh time increases from 5s to 16s. I thought this is acceptable for a weather display as it won't require refreshing that often.
2023-12-28: I got informed that the YAML script below only had BLACK for colour, which when I checked was correct. I forgot to update the blog entry with the latest code. I have just updated with the current code I am running.
2024-04-10: Since Home-Assistant 2024.04 the forecast atttributes are not part of the weather entity. The YAML section has been updated to set the forecast attributes into 2 entities, one for hourly and one for daily forecast.
Material used
For this project I am using the the following 4 items:
- 1x WaveShare 7.5" R&B&W E-Ink display
- 1x WaveShare Universal e-paper ESP32 Driver Board (Bluetooth/Wifi)
- 1x Adafruit Perma-Proto Full-sized Breadboard PCB
- 1x IKEA HOVSTA 13x18 cm photo frame (birch effect)
The HOVSTA also comes in medium brown and dark brown. Instead of the HOVSTA frame you could also use the RIBBA frame which is available in white and black.
I went for the HOVSTA photo frame because it has a box frame insert, with which you can have your photo to the front, or deeper to the back. By using the box frame insert behind the matting (a border that surrounds the photo), there is space to house the ESP32 development board.
There is additional room for extra hardware like a LiPo battery if required. I won't discuss this here since it depends on the use case and which charger board or battery is preferred. So I leave that to your own choice.
Building the frame
Since I am using the box frame insert behind the matting I will need to fix the E-Ink display to the matting to make sure it doesn't move. I used normal clear tape to attach it to the matting.
As can be seen the display is larger than the opening in the matting, so part of the display can't be used. This isn't a problem for me, since there is enough space free to present the information I want.
Then place the box frame insert back in, making sure the cable from the display isn't caught between the frame and the insert.
I then push 2 of the pins down to retain the display and box frame insert.
Next step is to connect the ribbon provided with the ESP32 Driver Board. I do this because I want to mount the electronics on the backplate, to leave the display alone, to avoid any risk of damage. By using the ribbon there is more room to take the backplate off when required.
Mount ESP32 to backplate
I use a breadboard PCB to mount the ESP32 Driver Board, so I then can fix the broadboard PCB to the backplate. By using the breadboard PCB I can lower the profile, to make it fit within the frame, and I also have space left for any additional electronics like LiPo charger board. You can of course mount the ESP32 Driver Board directly on the backplate.
To minimise the space as much as possible I am going to shorten the pins of the ESP32 Driver Board before soldering it onto the breadboard PCB. Cutting them just long enough that they fit the holes but don't stick out I can make the soldering as flush as possible.
I then use double-sided tape to stick the breadboard PCB onto the backplate.
Add configuration to Home Assistant
First we need to add the required entities to Home Assistant, which then can be used by ESPHome to display on the E-Ink display.
Add motion detection entity
To reduce the refresh on the display only when somebody is present in the livingroom I added the following entity to monitor both sensors (standard motion sensor and 60GHz occupancy sensor).
This entity has to be created as part of the template
configuration.
# Update screen only when occupancy is detected.
- binary_sensor:
- name: Weatherframe Motion Detected
unique_id: "dfa78de7-d761-425f-9731-86f1af332eac"
device_class: "occupancy"
delay_off: 00:01:00
state: >-
{%- if states('binary_sensor.motion_livingroom_occupancy') == 'on' or states('binary_sensor.presence_livingroom_presence') == 'on' %}
on
{%- else -%}
off
{%- endif -%}
This will need to be altered to monitor the devices you would require for your situation. If you don't care about this and have it update when actual weather data changes you can ommit this step.
Add weather data entities
The following section of the template
is to populate the fields used in the ESPHome configuration. Edit these to the relevant entities within Home Assistant to populate the required data.
It is made of 3 sections:
- Current temperature + condition
- Hourly temperature + condition (4 hours)
- Daily temperature and condition (4 days, today and next 3 days)
# Bundle up all the data to send over to Weatherframe (ESPHome device).
# For the timestamps use: %H = 24hrs / %I = 12hrs
# To add AM/PM add the following: {{ as_timestamp(state_attr('weather.openweathermap_hourly', 'forecast')[x].datetime) | timestamp_custom('%p') }}
- trigger:
- platform: time_pattern
minutes: "/30"
action:
- service: weather.get_forecasts
data:
type: hourly
target:
entity_id: weather.openweathermap_hourly
response_variable: hourly
sensor:
- name: Weather Forecast Hourly
unique_id: weather_forecast_hourly
state: "{{ now().isoformat() }}"
attributes:
forecast: "{{ hourly['weather.openweathermap_hourly'].forecast }}"
- trigger:
- platform: time_pattern
hours: "/1"
action:
- service: weather.get_forecasts
data:
type: daily
target:
entity_id: weather.openweathermap
response_variable: daily
sensor:
- name: Weather Forecast Daily
unique_id: weather_forecast_daily
state: "{{ now().isoformat() }}"
attributes:
forecast: "{{ daily['weather.openweathermap'].forecast }}"
- trigger:
platform: time_pattern
minutes: "/15"
sensor:
- name: Weatherframe Data
state: "OK"
attributes:
# Current temperature + condition
weather_now_temperature: >
{{ state_attr('weather.openweathermap', 'temperature') | round }}
weather_now_condition: >
{% set cond_now = states('weather.openweathermap') %}
{% if states('sun.sun') == 'below_horizon' %}
{% if cond_now == 'sunny' %} night {% elif cond_now == 'partlycloudy' %} night-partly-cloudy {% else %} {{ cond_now }} {% endif %}
{% else %}
{{ cond_now }}
{% endif %}
# Hourly temperature + condition + timestamp (4x)
weather_hourly_condition_0: >
{% set cond = state_attr('sensor.weather_forecast_hourly', 'forecast')[0].condition %}
{% set next_setting = as_timestamp(state_attr('sun.sun', 'next_setting')) %}
{% set next_rising = as_timestamp(state_attr('sun.sun', 'next_rising')) %}
{% set cond_time = as_timestamp(state_attr('sensor.weather_forecast_hourly', 'forecast')[0].datetime) %}
{% if ((cond_time > next_setting and cond_time < next_rising) or (cond_time < next_setting and cond_time < next_rising and next_rising < next_setting)) %}
{% if cond == 'sunny' %} night {% elif cond == 'partlycloudy' %} night-partly-cloudy {% else %} {{ cond }} {% endif %}
{% else %}
{{ cond }}
{% endif %}
weather_hourly_temperature_0: >
{{ state_attr('sensor.weather_forecast_hourly', 'forecast')[0].temperature | round }}
weather_hourly_timestamp_0: >
{{ as_timestamp(state_attr('sensor.weather_forecast_hourly', 'forecast')[0].datetime) | timestamp_custom('%H:00') }}
weather_hourly_condition_1: >
{% set cond = state_attr('sensor.weather_forecast_hourly', 'forecast')[1].condition %}
{% set next_setting = as_timestamp(state_attr('sun.sun', 'next_setting')) %}
{% set next_rising = as_timestamp(state_attr('sun.sun', 'next_rising')) %}
{% set cond_time = as_timestamp(state_attr('sensor.weather_forecast_hourly', 'forecast')[1].datetime) %}
{% if ((cond_time > next_setting and cond_time < next_rising) or (cond_time < next_setting and cond_time < next_rising and next_rising < next_setting)) %}
{% if cond == 'sunny' %} night {% elif cond == 'partlycloudy' %} night-partly-cloudy {% else %} {{ cond }} {% endif %}
{% else %}
{{ cond }}
{% endif %}
weather_hourly_temperature_1: >
{{ state_attr('sensor.weather_forecast_hourly', 'forecast')[1].temperature | round }}
weather_hourly_timestamp_1: >
{{ as_timestamp(state_attr('sensor.weather_forecast_hourly', 'forecast')[1].datetime) | timestamp_custom('%H:00') }}
weather_hourly_condition_2: >
{% set cond = state_attr('sensor.weather_forecast_hourly', 'forecast')[2].condition %}
{% set next_setting = as_timestamp(state_attr('sun.sun', 'next_setting')) %}
{% set next_rising = as_timestamp(state_attr('sun.sun', 'next_rising')) %}
{% set cond_time = as_timestamp(state_attr('sensor.weather_forecast_hourly', 'forecast')[2].datetime) %}
{% if ((cond_time > next_setting and cond_time < next_rising) or (cond_time < next_setting and cond_time < next_rising and next_rising < next_setting)) %}
{% if cond == 'sunny' %} night {% elif cond == 'partlycloudy' %} night-partly-cloudy {% else %} {{ cond }} {% endif %}
{% else %}
{{ cond }}
{% endif %}
weather_hourly_temperature_2: >
{{ state_attr('sensor.weather_forecast_hourly', 'forecast')[2].temperature | round }}
weather_hourly_timestamp_2: >
{{ as_timestamp(state_attr('sensor.weather_forecast_hourly', 'forecast')[2].datetime) | timestamp_custom('%H:00') }}
weather_hourly_condition_3: >
{% set cond = state_attr('sensor.weather_forecast_hourly', 'forecast')[3].condition %}
{% set next_setting = as_timestamp(state_attr('sun.sun', 'next_setting')) %}
{% set next_rising = as_timestamp(state_attr('sun.sun', 'next_rising')) %}
{% set cond_time = as_timestamp(state_attr('sensor.weather_forecast_hourly', 'forecast')[3].datetime) %}
{% if ((cond_time > next_setting and cond_time < next_rising) or (cond_time < next_setting and cond_time < next_rising and next_rising < next_setting)) %}
{% if cond == 'sunny' %} night {% elif cond == 'partlycloudy' %} night-partly-cloudy {% else %} {{ cond }} {% endif %}
{% else %}
{{ cond }}
{% endif %}
weather_hourly_temperature_3: >
{{ state_attr('sensor.weather_forecast_hourly', 'forecast')[3].temperature | round }}
weather_hourly_timestamp_3: >
{{ as_timestamp(state_attr('sensor.weather_forecast_hourly', 'forecast')[3].datetime) | timestamp_custom('%H:00') }}
# Daily temperature + condition + day of week (3x)
weather_daily_condition_0: >
{{ state_attr('sensor.weather_forecast_daily', 'forecast')[0].condition }}
weather_daily_temperature_0: >
{{ state_attr('sensor.weather_forecast_daily', 'forecast')[0].temperature | round }}
weather_daily_templow_0: >
{{ state_attr('sensor.weather_forecast_daily', 'forecast')[0].templow | round }}
weather_daily_timestamp_0: >
{{ as_timestamp(state_attr('sensor.weather_forecast_daily', 'forecast')[0].datetime) | timestamp_custom('%a') }}
weather_daily_condition_1: >
{{ state_attr('sensor.weather_forecast_daily', 'forecast')[1].condition }}
weather_daily_temperature_1: >
{{ state_attr('sensor.weather_forecast_daily', 'forecast')[1].temperature | round }}
weather_daily_templow_1: >
{{ state_attr('sensor.weather_forecast_daily', 'forecast')[1].templow | round }}
weather_daily_timestamp_1: >
{{ as_timestamp(state_attr('sensor.weather_forecast_daily', 'forecast')[1].datetime) | timestamp_custom('%a') }}
weather_daily_condition_2: >
{{ state_attr('sensor.weather_forecast_daily', 'forecast')[2].condition }}
weather_daily_temperature_2: >
{{ state_attr('sensor.weather_forecast_daily', 'forecast')[2].temperature | round }}
weather_daily_templow_2: >
{{ state_attr('sensor.weather_forecast_daily', 'forecast')[2].templow | round }}
weather_daily_timestamp_2: >
{{ as_timestamp(state_attr('sensor.weather_forecast_daily', 'forecast')[2].datetime) | timestamp_custom('%a') }}
weather_daily_condition_3: >
{{ state_attr('sensor.weather_forecast_daily', 'forecast')[3].condition }}
weather_daily_temperature_3: >
{{ state_attr('sensor.weather_forecast_daily', 'forecast')[3].temperature | round }}
weather_daily_templow_3: >
{{ state_attr('sensor.weather_forecast_daily', 'forecast')[3].templow | round }}
weather_daily_timestamp_3: >
{{ as_timestamp(state_attr('sensor.weather_forecast_daily', 'forecast')[3].datetime) | timestamp_custom('%a') }}
In the code above I use the entity weather.openweathermap_hourly
for the current and hourly forecast and the entity weather.openweathermap
for the daily forecast.
Once these have been added to Home Assistant it requires a restart to make them visible and populated. If everything has gone as expected you should now have a single entity called sensor.weatherframe_data
like the example below:
This entity will be used by EMSHome to create the actual display. Making sure this is showing as expected will make the next step easier.
Program ESP32 with ESPHome
I won't go into detail of how to program the ESP32 via ESPHome since there are already lots of tutorials about it, and it can also be found on the ESPHome website how to do it via Home Assistant.
Once you have the ESP32 Driver Board connected to ESPHome it is time to configure it with the required code.
Go into ESPHome and edit the device just added:
In the editor add the following section as part of the ESPHome configuration:
# ESPHome v2022.11.2 doesn't have the support for 7.5inv3 yet, so using external driver
external_components:
- source:
type: git
url: https://github.com/X4LD1M0/esphome
ref: waveshare-color
components: [ waveshare_epaper ]
refresh: 0s
# Global variables for detecting if the display needs to be refreshed.
globals:
- id: data_updated
type: bool
restore_value: no
initial_value: 'false'
- id: initial_data_received
type: bool
restore_value: no
initial_value: 'false'
# Check whether the display needs to be refreshed every minute,
# based on whether new data is received or motion is detected.
time:
- platform: homeassistant
id: homeassistant_time
on_time:
- seconds: 0
minutes: /15 # How often to check, defaults to every 15 min
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."
else:
- logger.log: "No sensors updated - skipping display refresh."
# Include custom fonts
font:
- file: 'fonts/GothamRnd-Book.ttf'
id: font_small_book
size: 18
- file: 'fonts/GothamRnd-Book.ttf'
id: font_medium_book
size: 30
glyphs: ['M', 'T', 'W', 'F', 'S', 'o', 'n', 'u', 'e', 'd', 'h', 'r', 'i', 'a', 't',':','0','1','2','3','4','5','6','7','8','9']
- 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']
- file: 'fonts/GothamRnd-Bold.ttf'
id: font_medium_bold
size: 30
glyphs: ['-', ' ', '°', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'C', 'E', 'H', 'K', 'L', 'N', 'O', 'R', 'T', 'U', 'Y']
- file: 'fonts/GothamRnd-Bold.ttf'
id: font_small_bold
size: 18
glyphs: [':', '-', ' ', '°', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'C']
# 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-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
- file: 'fonts/materialdesignicons-webfont.ttf'
id: font_mdi_medium
size: 36
glyphs: *mdi-weather-glyphs
- file: 'fonts/materialdesignicons-webfont.ttf'
id: font_mdi_status
size: 18
glyphs: &mdi-status-glyphs
- "\U000F092F" # mdi:wifi-strength-0
- "\U000F091F" # mdi:wifi-strength-1
- "\U000F0922" # mdi:wifi-strength-2
- "\U000F0925" # mdi:wifi-strength-3
- "\U000F0928" # mdi:wifi-strength-4
- "\U000F092E" # mdi:wifi-strength-off
- "\U000F02DC" # mdi-home
- "\U000F087B" # mdi-home-alert
- "\U000F12A3" # mdi-battery-high
- "\U000F12A2" # mdi-battery-medium
- "\U000F12A1" # mdi-battery-low
- "\U000F125E" # mdi-battery-off-outline
- "\U000F0954" # mdi-clock
- "\U000F1865" # mdi-clock-remove
binary_sensor:
# Check if ESPHome is connected
- platform: status
id: connection_status
on_state:
then:
- lambda: 'id(data_updated) = true;'
# Check if motion is detected in the living room.
- platform: homeassistant
entity_id: binary_sensor.weatherframe_motion_detected
id: motion_detected
on_state:
then:
- lambda: 'id(data_updated) = true;'
# - platform: ble_presence
# ibeacon_uuid: 'a6e00976-7c13-461d-8fd3-b909ecdd88f7'
# name: "ESP32 LRM Pixel-6"
# Call Weather sensors from HA.
sensor:
# - platform: custom
# lambda: |-
# auto BQ27441 = new My_BQ27441();
# App.register_component(BQ27441);
# return {BQ27441};
# sensors:
# name: "battery level"
# force_update: true
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_now_temperature
id: weather_now_temperature
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_hourly_temperature_0
id: weather_hourly_temperature_0
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_hourly_temperature_1
id: weather_hourly_temperature_1
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_hourly_temperature_2
id: weather_hourly_temperature_2
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_hourly_temperature_3
id: weather_hourly_temperature_3
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_daily_temperature_0
id: weather_daily_temperature_0
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_daily_templow_0
id: weather_daily_templow_0
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_daily_temperature_1
id: weather_daily_temperature_1
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_daily_templow_1
id: weather_daily_templow_1
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_daily_temperature_2
id: weather_daily_temperature_2
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_daily_templow_2
id: weather_daily_templow_2
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_daily_temperature_3
id: weather_daily_temperature_3
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_daily_templow_3
id: weather_daily_templow_3
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: wifi_signal
name: "Weather-Frame-01 WiFi Signal"
id: wifisignal
update_interval: 60s
text_sensor:
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_now_condition
id: weather_now_condition
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_hourly_condition_0
id: weather_hourly_condition_0
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_hourly_timestamp_0
id: weather_hourly_timestamp_0
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_hourly_condition_1
id: weather_hourly_condition_1
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_hourly_timestamp_1
id: weather_hourly_timestamp_1
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_hourly_condition_2
id: weather_hourly_condition_2
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_hourly_timestamp_2
id: weather_hourly_timestamp_2
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_hourly_condition_3
id: weather_hourly_condition_3
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_hourly_timestamp_3
id: weather_hourly_timestamp_3
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_daily_condition_0
id: weather_daily_condition_0
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_daily_timestamp_0
id: weather_daily_timestamp_0
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_daily_condition_1
id: weather_daily_condition_1
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_daily_timestamp_1
id: weather_daily_timestamp_1
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_daily_condition_2
id: weather_daily_condition_2
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_daily_timestamp_2
id: weather_daily_timestamp_2
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_daily_condition_3
id: weather_daily_condition_3
on_value:
then:
- lambda: 'id(data_updated) = true;'
- platform: homeassistant
entity_id: sensor.weatherframe_data
attribute: weather_daily_timestamp_3
id: weather_daily_timestamp_3
on_value:
then:
- lambda: 'id(data_updated) = true;'
# Define colors
# This design is white on black so this is necessary.
color:
- id: COLOR_RED
red: 100%
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
id: eink_display
cs_pin: GPIO15
dc_pin: GPIO27
busy_pin:
number: GPIO25
inverted: true
reset_pin: GPIO26
reset_duration: 20ms
model: 7.50in-bv2
# model: 7.50inv2b
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"},
{"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"},
};
// Fill background.
it.fill(COLOR_ON);
// Show loading screen before data is received.
if (id(initial_data_received) == false) {
it.printf(255, 390, id(font_small_bold), COLOR_OFF, TextAlign::TOP_CENTER, "WAITING FOR DATA...");
} else {
// Weather Current + Hourly
if(wifi_wificomponent->is_connected()) {
if (id(wifisignal).state >= -30) {
it.print(40, 60, id(font_mdi_status), COLOR_OFF, "\U000F0928");
} else if (id(wifisignal).state < -30 && id(wifisignal).state >= -67) {
it.print(40, 60, id(font_mdi_status), COLOR_OFF, "\U000F0925");
} else if (id(wifisignal).state < -67 && id(wifisignal).state >= -70) {
it.print(40, 60, id(font_mdi_status), COLOR_OFF, "\U000F0922");
} else if (id(wifisignal).state < -70 && id(wifisignal).state >= -80) {
it.print(40, 60, id(font_mdi_status), COLOR_OFF, "\U000F091F");
} else {
it.print(40, 60, id(font_mdi_status), COLOR_OFF, "\U000F092F");
}
} else {
it.print(40, 60, id(font_mdi_status), COLOR_RED, "\U000F092E");
}
if(id(connection_status).state) {
it.print(40, 80, id(font_mdi_status), COLOR_OFF, "\U000F02DC");
} else {
it.print(40, 80, id(font_mdi_status), COLOR_RED, "\U000F087B");
}
if(id(homeassistant_time).now().is_valid()) {
it.print(450, 80, id(font_mdi_status), COLOR_OFF, "\U000F0954");
} else {
it.print(450, 80, id(font_mdi_status), COLOR_RED, "\U000F1865");
}
it.printf(255, 74, id(font_title), COLOR_OFF, TextAlign::TOP_CENTER, "WEATHER");
it.printf(255, 148, id(font_medium_bold), COLOR_RED, TextAlign::TOP_CENTER, "CURRENT");
it.printf(115, 198, id(font_mdi_large), COLOR_OFF, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_now_condition).state.c_str()].c_str());
if (id(weather_now_temperature).state <= 0) {
it.printf(331, 204, id(font_large_bold), COLOR_OFF, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_now_temperature).state);
it.printf(328, 201, id(font_large_bold), COLOR_ON, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_now_temperature).state);
it.printf(325, 198, id(font_large_bold), COLOR_RED, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_now_temperature).state);
} else {
it.printf(325, 198, id(font_large_bold), COLOR_OFF, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_now_temperature).state);
}
it.printf(255, 318, id(font_medium_bold), COLOR_RED, TextAlign::TOP_CENTER, "HOURLY");
it.printf(120, 372, id(font_small_bold), COLOR_OFF, TextAlign::TOP_CENTER, "%s", id(weather_hourly_timestamp_0).state.c_str());
it.printf(120, 396, id(font_mdi_medium), COLOR_OFF, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_hourly_condition_0).state.c_str()].c_str());
it.printf(120, 440, id(font_small_bold), COLOR_OFF, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_hourly_temperature_0).state);
it.printf(210, 372, id(font_small_bold), COLOR_OFF, TextAlign::TOP_CENTER, "%s", id(weather_hourly_timestamp_1).state.c_str());
it.printf(210, 396, id(font_mdi_medium), COLOR_OFF, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_hourly_condition_1).state.c_str()].c_str());
it.printf(210, 440, id(font_small_bold), COLOR_OFF, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_hourly_temperature_1).state);
it.printf(300, 372, id(font_small_bold), COLOR_OFF, TextAlign::TOP_CENTER, "%s", id(weather_hourly_timestamp_2).state.c_str());
it.printf(300, 396, id(font_mdi_medium), COLOR_OFF, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_hourly_condition_2).state.c_str()].c_str());
it.printf(300, 440, id(font_small_bold), COLOR_OFF, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_hourly_temperature_2).state);
it.printf(390, 372, id(font_small_bold), COLOR_OFF, TextAlign::TOP_CENTER, "%s", id(weather_hourly_timestamp_3).state.c_str());
it.printf(390, 396, id(font_mdi_medium), COLOR_OFF, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_hourly_condition_3).state.c_str()].c_str());
it.printf(390, 440, id(font_small_bold), COLOR_OFF, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_hourly_temperature_3).state);
//it.printf(135, 372, id(font_medium_book), COLOR_OFF, TextAlign::TOP_CENTER, "%s", id(weather_hourly_timestamp_0).state.c_str());
//it.printf(135, 402, id(font_mdi_large), COLOR_OFF, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_hourly_condition_0).state.c_str()].c_str());
//it.printf(135, 498, id(font_medium_bold), COLOR_OFF, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_hourly_temperature_0).state);
//it.printf(255, 372, id(font_medium_book), COLOR_OFF, TextAlign::TOP_CENTER, "%s", id(weather_hourly_timestamp_1).state.c_str());
//it.printf(255, 402, id(font_mdi_large), COLOR_OFF, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_hourly_condition_1).state.c_str()].c_str());
//it.printf(255, 378, id(font_medium_bold), COLOR_OFF, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_hourly_temperature_1).state);
//it.printf(375, 372, id(font_medium_book), COLOR_OFF, TextAlign::TOP_CENTER, "%s", id(weather_hourly_timestamp_2).state.c_str());
//it.printf(375, 402, id(font_mdi_large), COLOR_OFF, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_hourly_condition_2).state.c_str()].c_str());
//it.printf(375, 498, id(font_medium_bold), COLOR_OFF, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_hourly_temperature_2).state);
// Weather Forecast
it.printf(255, 504, id(font_medium_bold), COLOR_RED, TextAlign::TOP_CENTER, "OUTLOOK");
if (id(homeassistant_time).now().hour < 12) {
it.printf(135, 552, id(font_medium_book), COLOR_OFF, TextAlign::TOP_CENTER, "%s", id(weather_daily_timestamp_0).state.c_str());
it.printf(135, 582, id(font_mdi_large), COLOR_OFF, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_daily_condition_0).state.c_str()].c_str());
it.printf(135, 678, id(font_medium_bold), COLOR_OFF, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_daily_temperature_0).state);
it.printf(255, 552, id(font_medium_book), COLOR_OFF, TextAlign::TOP_CENTER, "%s", id(weather_daily_timestamp_1).state.c_str());
it.printf(255, 582, id(font_mdi_large), COLOR_OFF, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_daily_condition_1).state.c_str()].c_str());
it.printf(255, 678, id(font_medium_bold), COLOR_OFF, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_daily_temperature_1).state);
it.printf(375, 552, id(font_medium_book), COLOR_OFF, TextAlign::TOP_CENTER, "%s", id(weather_daily_timestamp_2).state.c_str());
it.printf(375, 582, id(font_mdi_large), COLOR_OFF, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_daily_condition_2).state.c_str()].c_str());
it.printf(375, 678, id(font_medium_bold), COLOR_OFF, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_daily_temperature_2).state);
} else {
it.printf(135, 552, id(font_medium_book), COLOR_OFF, TextAlign::TOP_CENTER, "%s", id(weather_daily_timestamp_1).state.c_str());
it.printf(135, 582, id(font_mdi_large), COLOR_OFF, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_daily_condition_1).state.c_str()].c_str());
it.printf(135, 678, id(font_medium_bold), COLOR_OFF, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_daily_temperature_1).state);
it.printf(255, 552, id(font_medium_book), COLOR_OFF, TextAlign::TOP_CENTER, "%s", id(weather_daily_timestamp_2).state.c_str());
it.printf(255, 582, id(font_mdi_large), COLOR_OFF, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_daily_condition_2).state.c_str()].c_str());
it.printf(255, 678, id(font_medium_bold), COLOR_OFF, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_daily_temperature_2).state);
it.printf(375, 552, id(font_medium_book), COLOR_OFF, TextAlign::TOP_CENTER, "%s", id(weather_daily_timestamp_3).state.c_str());
it.printf(375, 582, id(font_mdi_large), COLOR_OFF, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_daily_condition_3).state.c_str()].c_str());
it.printf(375, 678, id(font_medium_bold), COLOR_OFF, TextAlign::TOP_CENTER, "%2.0f°C", id(weather_daily_temperature_3).state);
}
it.printf(40, 720, id(font_small_book), COLOR_OFF, TextAlign::TOP_LEFT, "%4d-%02d-%02d %02d:%02d", id(homeassistant_time).now().year, id(homeassistant_time).now().month, id(homeassistant_time).now().day_of_month, id(homeassistant_time).now().hour, id(homeassistant_time).now().minute);
}