Image Description

I wanted to create a dashboard at a glance for me and the misses to look at for quick information with data from our home assistant instance.

Got the idea from someone’s similar setup on reddit, but it seems he sold hes idea to seeed studio and never shared any of the code or how it was created. Only what parts he used.

When the product was announced as well and was posted to r/homeassistant it was also deleted

So I decided to find out and create one myself and this is me sharing how I managed to build mine and what I learned.

Hardware

Seeed Studio hardware:

7.5" Monochrome eInk display

ePaper Breakout Board for Seeed Studio XIAO

Seeed Studio XIAO ESP32C3 (Pre-Soldered)

STL:

Seeed Screen Frame

Figuring out what is been used.

From a quick google search it looks like what is been used is ESPHOME. ESPHome is a system which allows you to turn common microcontrollers into smart home devices. It uses YAML configuration files and, based on the content of these file(s), it creates custom firmware which you can then install directly onto your device.

Installing ESPHOME is quite simple and can be done by following this guide.

Reading in the documentation I found the following:

The waveshare_epaper display platform allows you to use some E-Paper displays sold by Waveshare with ESPHome.

But, I didn’t have a waveshare display, but one from Seeed Studio.

Getting the screen to run with ESPHOME

So I connected the devices together as seen on the picture below and tried with the example code provided in the documentation page.

Image Description

# Example configuration entry
font:
  - file: 'fonts/Comic Sans MS.ttf'
    id: font1
    size: 8

spi:
  clk_pin: D0
  mosi_pin: D1

display:
  - platform: waveshare_epaper
    cs_pin: D2
    dc_pin: D3
    busy_pin: D4
    reset_pin: D5
    model: 2.90in
    full_update_every: 30
    lambda: |-
      it.print(0, 0, id(font1), "Hello World!");

No dice on first attempt, so i looked up the SPI pin layout to make sure that they where correct.

Looking at the schematic from seeed, the pins looks like this on the ESP32C3.

Image Description

And looking at Seeed only code samples for the display I created this mapping:

ePaper SPI Pins XIAO
RST D0
CS D1
DC D3
BUSY D2
SCK D8
MOSI D10
3V3 3V3
GND GND

So ended up with the following SPI settings:

font:
  - file: 'fonts/Comic Sans MS.ttf'
    id: font1
    size: 8

spi:
  clk_pin: GPIO8
  mosi_pin: GPIO10
  id: spi_stack
  
display:
  - platform: waveshare_epaper
    id: screen
    cs_pin: GPIO3
    dc_pin: GPIO5
    busy_pin: GPIO7
    reset_pin: GPIO2
    spi_id: spi_stack
    model: ??????
    full_update_every: 30
    lambda: |-
      it.print(0, 0, id(font1), "Hello World!");

Now we only need to find the model to use, the ESPHOME documentation shows support for the following models:

  • 7.30in-f - 7.3in 7-color display (black, white, red, yellow, blue, green, and orange)
  • 7.50in
  • 7.50in-bV2 - also supports v3, B/W rendering only
  • 7.50in-bV3 - display with the ‘(V3)’ sticker on the back, B/W rendering only
  • 7.50in-bV3-bwr - display with the ‘(V3)’ sticker on the back, BWR rendering enabled (uses double the amount of RAM for the display buffer as B/W rendering)
  • 7.50in-bc - display with version sticker ‘(C)’ on the back, B/W rendering only
  • 7.50inV2 - Can’t use with an ESP8266 as it runs out of RAM
  • 7.50inV2alt (alternative version to the above 7.50inV2)
  • 7.50inV2p - Support for partial refresh and fast refresh (Only suitable for 7.50inV2 models manufactured after September 2023)
  • 7.50in-hd-b - Can’t use with an ESP8266 as it runs out of RAM

I ended up trying all of this and the only one that actually displayed anything on the screen was 7.50inV2, but it was really really faint.

Image Description

I did ask for if anyone had experienced this issue on the Seeed discord, I was quickly contacted by a seeed employee that asked if I could send pictures of the breakout board. He quickly noticed that it was defect and prepared an support case where they sent me a new board. 5 days later I got the new board delivered. Swapped it out and put power to the ESP32C3 device and it showed me this:

Image Description

Now we are cooking!

The YAML so far:

font:
  - file: 'fonts/Comic Sans MS.ttf'
    id: font1
    size: 8
spi:
  clk_pin: GPIO8
  mosi_pin: GPIO10
  id: spi_stack
display:
  - platform: waveshare_epaper
    id: screen
    cs_pin: GPIO3
    dc_pin: GPIO5
    busy_pin: GPIO7
    reset_pin: GPIO2
    spi_id: spi_stack
    model: 7.50inv2
    update_interval: never
	reset_duration: 2ms
	rotation: 0
    lambda: |-
      it.print(400, 220, id(font1), "Hello!");

Now for the fancy stuff!

So how do we make it print fancy stuff and make it pretty. ESPHOME documentation was quite helpful here, especially the Display Core documentation and the Font Renderer.

Something that got linked to me from the user Ordo273 I have been talking to on reddit DM’s about this project could have helped me a lot when I was doing this project. But, he linked me a Display Simulator created by someone called mat932

Image Description

If I know about this before i was sitting their adjusting and flashing to the ESP32 microcontroller for each change to see the results, I could have saved myself some time and about 500 re-flashes.

The site gives you a quick way to test the display functions ( with some limitations ).

Fonts

The Fonts I ended up using in my setup was the following

font:
  - file: "fonts/GoogleSans-Medium.ttf"
    id: font_date
    size: 58
  - file: "fonts/GoogleSans-Medium.ttf"
    id: font_35
    size: 35
  - file: "fonts/GoogleSans-Bold.ttf"
    id: font_time
    size: 105
  - file: "fonts/GoogleSans-Bold.ttf"
    id: font_value
    size: 50
  - file: "fonts/GoogleSans-Bold.ttf"
    id: font_value_small
    size: 40
  - file: "fonts/GoogleSans-Bold.ttf"
    id: font_stock_title
    size: 40
  - file: "fonts/GoogleSans-Bold.ttf"
    id: font_stock_price
    size: 60
  - file: "fonts/GoogleSans-Bold.ttf"
    id: font_stock_price_small
    size: 50
  - file: "fonts/GoogleSans-Medium.ttf"
    id: font_stock_highlow
    size: 30
  - file: "gfonts://Inter@900"
    id: big
    size: 120
  - file: "gfonts://Inter@900"
    id: medium
    size: 60
  - file: "gfonts://Inter@700"
    id: small
    size: 30
  - file: "gfonts://Inter@700"
    id: tiny
    size: 24
    extras:
      - file: "fonts/mdi.ttf"
        glyphs: [
		    "\U000F0590", # weather-cloudy
		    "\U000F0F2F", # weather-cloudy-alert
		    "\U000F0E6E", # weather-cloudy-arrow-right
		    "\U000F0591", # weather-fog
		    "\U000F0592", # weather-hail
		    "\U000F0F30", # weather-hazy
		    "\U000F0898", # weather-hurricane
		    "\U000F0593", # weather-lightning
		    "\U000F067E", # weather-lightning-rainy
		    "\U000F0594", # weather-night
		    "\U000F0F31", # weather-night-partly-cloudy
		    "\U000F0595", # weather-partly-cloudy
		    "\U000F0F32", # weather-partly-lightning
		    "\U000F0F33", # weather-partly-rainy
		    "\U000F0F34", # weather-partly-snowy
			"\U000F0F35", # weather-partly-snowy-rainy
			"\U000F0596", # weather-pouring
			"\U000F0597", # weather-rainy
			"\U000F0598", # weather-snowy
			"\U000F0F36", # weather-snowy-heavy
			"\U000F067F", # weather-snowy-rainy
			"\U000F0599", # weather-sunny
			"\U000F0F37", # weather-sunny-alert
			"\U000f14e4", # weather-sunny-off
			"\U000F059A", # weather-sunset
			"\U000F059B", # weather-sunset-down
			"\U000F059C", # weather-sunset-up
			"\U000F0F38", # weather-tornado
			"\U000F059D", # weather-windy
			"\U000F059E", # weather-windy-variant
			"\U000F181E", # mdi-door-sliding
			"\U000F1820", # mdi-door-sliding-open
			"\U000F171D", # mdi-fan-auto
			"\U000F081D", # mdi-fan-off
			"\U000F0210", # mdi-fan
			"\U000F0567", # mdi-video
			"\U000F0568", # mdi-video-off
			"\U000F044B", # mdi-record-rec
			"\U000F0004", # mdi-account
			"\U000F0012", # mdi-account-off
			"\U000F00EB", # mdi-cake-Variant
			"\U000F17FF", # mdi-sun-wireless-outline
			"\U000F011B", # mdi-cat
		]

  - file: "fonts/mdi.ttf"
	id: font_mdi_large
	size: 200
	glyphs: [
		"\U000F0590", # weather-cloudy
		"\U000F0F2F", # weather-cloudy-alert
		"\U000F0E6E", # weather-cloudy-arrow-right
		"\U000F0591", # weather-fog
		"\U000F0592", # weather-hail
		"\U000F0F30", # weather-hazy
		"\U000F0898", # weather-hurricane
		"\U000F0593", # weather-lightning
		"\U000F067E", # weather-lightning-rainy
		"\U000F0594", # weather-night
		"\U000F0F31", # weather-night-partly-cloudy
		"\U000F0595", # weather-partly-cloudy
		"\U000F0F32", # weather-partly-lightning
		"\U000F0F33", # weather-partly-rainy
		"\U000F0F34", # weather-partly-snowy
		"\U000F0F35", # weather-partly-snowy-rainy
		"\U000F0596", # weather-pouring
		"\U000F0597", # weather-rainy
		"\U000F0598", # weather-snowy
		"\U000F0F36", # weather-snowy-heavy
		"\U000F067F", # weather-snowy-rainy
		"\U000F0599", # weather-sunny
		"\U000F0F37", # weather-sunny-alert
		"\U000f14e4", # weather-sunny-off
		"\U000F059A", # weather-sunset
		"\U000F059B", # weather-sunset-down
		"\U000F059C", # weather-sunset-up
		"\U000F0F38", # weather-tornado
		"\U000F059D", # weather-windy
		"\U000F059E", # weather-windy-variant
		"\U000F181E", # mdi-door-sliding
		"\U000F1820", # mdi-door-sliding-open
		"\U000F171D", # mdi-fan-auto
		"\U000F081D", # mdi-fan-off
		"\U000F0210", # mdi-fan
		"\U000F0567", # mdi-video
		"\U000F0568", # mdi-video-off
		"\U000F044B", # mdi-record-rec
		"\U000F0004", # mdi-account
		"\U000F0012", # mdi-account-off
		"\U000F00EB", # mdi-cake-Variant
		"\U000F17FF", # mdi-sun-wireless-outline
		"\U000F011B", # mdi-cat
	]
  - file: "fonts/mdi.ttf"
    id: font_mdi_medium
	size: 40
	glyphs: [
		"\U000F0590", # weather-cloudy
		"\U000F0F2F", # weather-cloudy-alert
		"\U000F0E6E", # weather-cloudy-arrow-right
		"\U000F0591", # weather-fog
		"\U000F0592", # weather-hail
		"\U000F0F30", # weather-hazy
		"\U000F0898", # weather-hurricane
		"\U000F0593", # weather-lightning
		"\U000F067E", # weather-lightning-rainy
		"\U000F0594", # weather-night
		"\U000F0F31", # weather-night-partly-cloudy
		"\U000F0595", # weather-partly-cloudy
		"\U000F0F32", # weather-partly-lightning
		"\U000F0F33", # weather-partly-rainy
		"\U000F0F34", # weather-partly-snowy
		"\U000F0F35", # weather-partly-snowy-rainy
		"\U000F0596", # weather-pouring
		"\U000F0597", # weather-rainy
		"\U000F0598", # weather-snowy
		"\U000F0F36", # weather-snowy-heavy
		"\U000F067F", # weather-snowy-rainy
		"\U000F0599", # weather-sunny
		"\U000F0F37", # weather-sunny-alert
		"\U000F14E4", # weather-sunny-off
		"\U000F059A", # weather-sunset
		"\U000F059B", # weather-sunset-down
		"\U000F059C", # weather-sunset-up
		"\U000F0F38", # weather-tornado
		"\U000F059D", # weather-windy
		"\U000F059E", # weather-windy-variant
		"\U000F181E", # mdi-door-sliding
		"\U000F1820", # mdi-door-sliding-open
		"\U000F171D", # mdi-fan-auto
		"\U000F081D", # mdi-fan-off
		"\U000F0210", # mdi-fan
		"\U000F0567", # mdi-video
		"\U000F0568", # mdi-video-off
		"\U000F044B", # mdi-record-rec
		"\U000F0004", # mdi-account
		"\U000F0012", # mdi-account-off
		"\U000F00EB", # mdi-cake-Variant
		"\U000F17FF", # mdi-sun-wireless-outline
		"\U000F011B", # mdi-cat
	]
  - file: "fonts/mdi.ttf"
	id: font_mdi_side_high
	size: 80
	glyphs: [
		"\U000F0590", # weather-cloudy
		"\U000F0F2F", # weather-cloudy-alert
		"\U000F0E6E", # weather-cloudy-arrow-right
		"\U000F0591", # weather-fog
		"\U000F0592", # weather-hail
		"\U000F0F30", # weather-hazy
		"\U000F0898", # weather-hurricane
		"\U000F0593", # weather-lightning
		"\U000F067E", # weather-lightning-rainy
		"\U000F0594", # weather-night
		"\U000F0F31", # weather-night-partly-cloudy
		"\U000F0595", # weather-partly-cloudy
		"\U000F0F32", # weather-partly-lightning
		"\U000F0F33", # weather-partly-rainy
		"\U000F0F34", # weather-partly-snowy
		"\U000F0F35", # weather-partly-snowy-rainy
		"\U000F0596", # weather-pouring
		"\U000F0597", # weather-rainy
		"\U000F0598", # weather-snowy
		"\U000F0F36", # weather-snowy-heavy
		"\U000F067F", # weather-snowy-rainy
		"\U000F0599", # weather-sunny
		"\U000F0F37", # weather-sunny-alert
		"\U000F14E4", # weather-sunny-off
		"\U000F059A", # weather-sunset
		"\U000F059B", # weather-sunset-down
		"\U000F059C", # weather-sunset-up
		"\U000F0F38", # weather-tornado
		"\U000F059D", # weather-windy
		"\U000F059E", # weather-windy-variant
		"\U000F181E", # mdi-door-sliding
		"\U000F1820", # mdi-door-sliding-open
		"\U000F171D", # mdi-fan-auto
		"\U000F081D", # mdi-fan-off
		"\U000F0210", # mdi-fan
		"\U000F0567", # mdi-video
		"\U000F0568", # mdi-video-off
		"\U000F044B", # mdi-record-rec
		"\U000F0004", # mdi-account
		"\U000F0012", # mdi-account-off
		"\U000F00EB", # mdi-cake-Variant
		"\U000F17FF", # mdi-sun-wireless-outline
		"\U000F011B", # mdi-cat
	]
  - file: 'fonts/mdi.ttf'
	id: mdi_wifi
	size: 25
	glyphs: [
		'󰞃', # mdi-wifi-strength-alert-outline
		'󰢿', # mdi-wifi-strength-1
		'󰢼', # mdi-wifi-strength-2
		'󰢽', # mdi-wifi-strength-3
		'󰢾' # mdi-wifi-strength-4
	]

As you can see I have a font called mdi.ttf, this font is the icon font set that is used inside home assistant and can be found here

The ttf file is placed under /homeassistant/esphome/fonts

Image Description

As a example:

“\U000F0004”, # mdi-account

the last digest according to the page is F0004 for the account icon. Image Description

That way you can use the icons in the strings like so in the lamda part in the display code:

it.printf(525,190,id(font_mdi_medium),TextAlign::CENTER_HORIZONTAL,"\U000F0004");

Sensor data

To display sensor data like is Mikael home you can do the following:

text_sensor:
  - platform: homeassistant
	id: person_mikael
	entity_id: person.duux

That way you can change the printf to the following:

std::map<std::string, std::string> person_map
{
	{"home", "\U000F0004"},
	{"not_home", "\U000F0012"},
};
it.printf(523,150, id(tiny), TextAlign::CENTER_HORIZONTAL, "Mikael");
it.printf(525,190,id(font_mdi_medium),TextAlign::CENTER_HORIZONTAL,"%s",person_map[id(person_mikael).state.c_str()].c_str());

What this code does is create a mapping of what to display based on value of the state from the sensor. This mapping is defined in the std::map array defined first. Then it will print out Mikael as test then the icon under the text.

person_map[id(person_mikael).state.c_str()].c_str()

The printf function expects a string (%s) and the code last in the function listed below will look in the person_map with the value from the sensor id called person_mikaeland convert that to a c_str

Now it will show the account icon or the account-off icon from the mdi font based on the state of that sensor.

You can also add if/else statement in the code like this:

if (id(person_mikael).state == "home") {
	it.printf(525,230,id(tiny),TextAlign::CENTER_HORIZONTAL, "HOME");
}
else {
	it.printf(525,230,id(tiny),TextAlign::CENTER_HORIZONTAL, "AWAY");
}

Another good example of this is if you want to create a WiFi Signal Strength indicator on the top right corner of the screen:

/* WiFi Signal Strength */
if(id(wifisignal).has_state()) {
	int x = 790;
	int y = 20;
	if (id(wifisignal).state >= -50) {
		//Excellent
		it.print(x, y, id(mdi_wifi), TextAlign::BASELINE_CENTER, "󰢾");
		ESP_LOGI("WiFi", "Exellent");
	} else if (id(wifisignal).state >= -60) {
		//Good
		it.print(x, y, id(mdi_wifi), TextAlign::BASELINE_CENTER, "󰢽");
		ESP_LOGI("WiFi", "Good");
	} else if (id(wifisignal).state >= -75) {
		//Fair
		it.print(x, y, id(mdi_wifi), TextAlign::BASELINE_CENTER, "󰢼");
		ESP_LOGI("WiFi", "Fair");
	} else if (id(wifisignal).state >= -100) {
		//Weak
		it.print(x, y, id(mdi_wifi), TextAlign::BASELINE_CENTER, "󰢿");
		ESP_LOGI("WiFi", "Weak");
	} else {
		//Unlikely working signal
		it.print(x, y, id(mdi_wifi), TextAlign::BASELINE_CENTER, "󰞃");
		ESP_LOGI("WiFi", "Unlikely");
	}
}

And then add a sensor for it by adding the following code:

sensor:
  - platform: wifi_signal
	name: "WiFi Signal Sensor"
	id: wifisignal
	update_interval: 60s

Draw a box

Now lets draw a box around it to make it more fancy

it.rectangle(480,180,90,90);

This command will draw a box with the first to numbers representing the top left corner of the box and the last to been the increase of the pixel count for the right bottom corner of the box.

Image Description

Lets get more advanced

Now that’s cool, but lets get even more advanced as we want to be able to display time, date, weather, forecasts, temperature and even more sensors. And create pages instead of cluttering the screen.

Time and Date

To be able to display the time and date we need to provide a sensor for it.

First we define a time sensor that is connected to the home assistant time.

time:
  - platform: homeassistant
	id: homeassistant_time

Then we can use the strftime function instead of print to display the time in the format we want. In my case it will look like this:

// Date and Time
it.strftime(190, 10, id(small), TextAlign::CENTER_HORIZONTAL, "%A %b %d, %Y", id(homeassistant_time).now());
it.strftime(190, 30, id(big), TextAlign::CENTER_HORIZONTAL, "%H:%M", id(homeassistant_time).now());

Image Description

Weather

To get the weather in as a sensor I needed to add some manual sensors for some reasons. Might be due to my home assistant setup and what I use as a weather service. To add this as manual settings you need to add the following YAML in to the configuration.yaml in home assistant. Its located under /homeassistant directory.

Depending on your already configuration, but since I don’t have any other template setup I added this:

template: !include forecast.yaml

the forecast.yaml file has the following data in it to create the sensors I need:

- trigger:
    - platform: event #update at startup#
      event_type: homeassistant_started
    - platform: time_pattern
      hours: /1 #then update every hour#
  action:
    - service: weather.get_forecasts #update hourly forecasts#
      target:
        entity_id: weather.forecast_home #replace with your own weather integration
      data:
        type: hourly
      response_variable: hourly
    - service: weather.get_forecasts #update daily forecasts#
      target:
        entity_id: weather.forecast_home
      data:
        type: daily
      response_variable: daily
  sensor:
    #Sensors for current weather data#
    - name: "WeatherCurrent"
      state: "{{ states('weather.forecast_home') }}"
    - name: "TempCurrent"
      unit_of_measurement: "°C"
      state: >
        {{ state_attr('weather.forecast_home', 'temperature') }}
    #Sensors for forecast weather data#
    - name: Weather Forecast H1
      unique_id: weather_forecast_h1
      state: "{{ hourly['weather.forecast_home'].forecast[0].condition }}" #'hourly' pulls hourly forecasts, [0] = next hour's forecast
    - name: Temperature Forecast H1
      unique_id: temperature_forecast_h1
      state: "{{ hourly['weather.forecast_home'].forecast[0].temperature }}"
      unit_of_measurement: °C
    - name: Weather Forecast H2
      unique_id: weather_forecast_h2
      state: "{{ hourly['weather.forecast_home'].forecast[1].condition }}" # [1] = weather in 2 hours
    - name: Temperature Forecast H2
      unique_id: temperature_forecast_h2
      state: "{{ hourly['weather.forecast_home'].forecast[1].temperature }}"
      unit_of_measurement: °C
    - name: Weather Forecast H3
      unique_id: weather_forecast_h3
      state: "{{ hourly['weather.forecast_home'].forecast[2].condition }}"
    - name: Temperature Forecast H3
      unique_id: temperature_forecast_h3
      state: "{{ hourly['weather.forecast_home'].forecast[2].temperature }}"
      unit_of_measurement: °C
    - name: Weather Forecast D1
      unique_id: weather_forecast_d1
      state: "{{ daily['weather.forecast_home'].forecast[0].condition }}" #'daily' pulls daily forecasts, [0] = tomorrow's forecast
    - name: Temperature Forecast D1
      unique_id: temperature_forecast_d1
      state: "{{ daily['weather.forecast_home'].forecast[0].temperature }}"
      unit_of_measurement: °C
    - name: Weather Forecast D2
      unique_id: weather_forecast_d2
      state: "{{ daily['weather.forecast_home'].forecast[1].condition }}" # [1] = forecast day after tomorrow
    - name: Temperature Forecast D2
      unique_id: temperature_forecast_d2
      state: "{{ daily['weather.forecast_home'].forecast[1].temperature }}"
      unit_of_measurement: °C
    - name: Weather Forecast D3
      unique_id: weather_forecast_d3
      state: "{{ daily['weather.forecast_home'].forecast[2].condition }}"
    - name: Temperature Forecast D3
      unique_id: temperature_forecast_d3
      state: "{{ daily['weather.forecast_home'].forecast[2].temperature }}"
      unit_of_measurement: °C
- trigger:
    - platform: event #update at startup#
      event_type: homeassistant_started
    - platform: time_pattern
      #  at: "00:01:00"
      hours: /1
  action:
    - service: weather.get_forecasts
      target:
        entity_id: weather.forecast_home
      data:
        type: hourly
      response_variable: hourly
  sensor:
    #Sensors for forecast weather data for each hour of the day#
    - name: Temperature Forecast 01
      unique_id: temperature_forecast_01
      state: "{{ hourly['weather.forecast_home'].forecast[0].temperature }}"
      unit_of_measurement: °C
    - name: Temperature Forecast 02
      unique_id: temperature_forecast_02
      state: "{{ hourly['weather.forecast_home'].forecast[1].temperature }}"
      unit_of_measurement: °C
    - name: Temperature Forecast 03
      unique_id: temperature_forecast_03
      state: "{{ hourly['weather.forecast_home'].forecast[2].temperature }}"
      unit_of_measurement: °C
    - name: Temperature Forecast 04
      unique_id: temperature_forecast_04
      state: "{{ hourly['weather.forecast_home'].forecast[3].temperature }}"
      unit_of_measurement: °C
    - name: Temperature Forecast 05
      unique_id: temperature_forecast_05
      state: "{{ hourly['weather.forecast_home'].forecast[4].temperature }}"
      unit_of_measurement: °C
    - name: Temperature Forecast 06
      unique_id: temperature_forecast_06
      state: "{{ hourly['weather.forecast_home'].forecast[5].temperature }}"
      unit_of_measurement: °C
    - name: Temperature Forecast 07
      unique_id: temperature_forecast_07
      state: "{{ hourly['weather.forecast_home'].forecast[6].temperature }}"
      unit_of_measurement: °C
    - name: Temperature Forecast 08
      unique_id: temperature_forecast_08
      state: "{{ hourly['weather.forecast_home'].forecast[7].temperature }}"
      unit_of_measurement: °C
    - name: Temperature Forecast 09
      unique_id: temperature_forecast_09
      state: "{{ hourly['weather.forecast_home'].forecast[8].temperature }}"
      unit_of_measurement: °C
    - name: Temperature Forecast 10
      unique_id: temperature_forecast_10
      state: "{{ hourly['weather.forecast_home'].forecast[9].temperature }}"
      unit_of_measurement: °C
    - name: Temperature Forecast 11
      unique_id: temperature_forecast_11
      state: "{{ hourly['weather.forecast_home'].forecast[10].temperature }}"
      unit_of_measurement: °C
    - name: Temperature Forecast 12
      unique_id: temperature_forecast_12
      state: "{{ hourly['weather.forecast_home'].forecast[11].temperature }}"
      unit_of_measurement: °C
    - name: Temperature Forecast 13
      unique_id: temperature_forecast_13
      state: "{{ hourly['weather.forecast_home'].forecast[12].temperature }}"
      unit_of_measurement: °C
    - name: Temperature Forecast 14
      unique_id: temperature_forecast_14
      state: "{{ hourly['weather.forecast_home'].forecast[13].temperature }}"
      unit_of_measurement: °C
    - name: Temperature Forecast 15
      unique_id: temperature_forecast_15
      state: "{{ hourly['weather.forecast_home'].forecast[14].temperature }}"
      unit_of_measurement: °C
    - name: Temperature Forecast 16
      unique_id: temperature_forecast_16
      state: "{{ hourly['weather.forecast_home'].forecast[15].temperature }}"
      unit_of_measurement: °C
    - name: Temperature Forecast 17
      unique_id: temperature_forecast_17
      state: "{{ hourly['weather.forecast_home'].forecast[16].temperature }}"
      unit_of_measurement: °C
    - name: Temperature Forecast 18
      unique_id: temperature_forecast_18
      state: "{{ hourly['weather.forecast_home'].forecast[17].temperature }}"
      unit_of_measurement: °C
    - name: Temperature Forecast 19
      unique_id: temperature_forecast_19
      state: "{{ hourly['weather.forecast_home'].forecast[18].temperature }}"
      unit_of_measurement: °C
    - name: Temperature Forecast 20
      unique_id: temperature_forecast_20
      state: "{{ hourly['weather.forecast_home'].forecast[19].temperature }}"
      unit_of_measurement: °C
    - name: Temperature Forecast 21
      unique_id: temperature_forecast_21
      state: "{{ hourly['weather.forecast_home'].forecast[20].temperature }}"
      unit_of_measurement: °C
    - name: Temperature Forecast 22
      unique_id: temperature_forecast_22
      state: "{{ hourly['weather.forecast_home'].forecast[21].temperature }}"
      unit_of_measurement: °C
    - name: Temperature Forecast 23
      unique_id: temperature_forecast_23
      state: "{{ hourly['weather.forecast_home'].forecast[22].temperature }}"
      unit_of_measurement: °C

Now you should have the needed weather data neatly created as sensors in home assistant. The sensors will be current temps and weather condition and 24 hours forecast of this data as well.

We also need to create a automation that calculates the highest temp and the lowest temp for the day.

alias: Store Forecast High and Low
description: Compute and store today's forecast high and low.
triggers:
  - at: "00:05:00"
    trigger: time
    enabled: true
  - trigger: time_pattern
    hours: /1
actions:
  - variables:
      f2: "{{ states('sensor.temperature_forecast_01') | float(0) }}"
      f3: "{{ states('sensor.temperature_forecast_02') | float(0) }}"
      f4: "{{ states('sensor.temperature_forecast_03') | float(0) }}"
      f5: "{{ states('sensor.temperature_forecast_04') | float(0) }}"
      f6: "{{ states('sensor.temperature_forecast_05') | float(0) }}"
      f7: "{{ states('sensor.temperature_forecast_06') | float(0) }}"
      f8: "{{ states('sensor.temperature_forecast_07') | float(0) }}"
      f9: "{{ states('sensor.temperature_forecast_08') | float(0) }}"
      f10: "{{ states('sensor.temperature_forecast_09') | float(0) }}"
      f11: "{{ states('sensor.temperature_forecast_10') | float(0) }}"
      f12: "{{ states('sensor.temperature_forecast_11') | float(0) }}"
      f13: "{{ states('sensor.temperature_forecast_12') | float(0) }}"
      f14: "{{ states('sensor.temperature_forecast_13') | float(0) }}"
      f15: "{{ states('sensor.temperature_forecast_14') | float(0) }}"
      f16: "{{ states('sensor.temperature_forecast_15') | float(0) }}"
      f17: "{{ states('sensor.temperature_forecast_16') | float(0) }}"
      f18: "{{ states('sensor.temperature_forecast_17') | float(0) }}"
      f19: "{{ states('sensor.temperature_forecast_18') | float(0) }}"
      f20: "{{ states('sensor.temperature_forecast_19') | float(0) }}"
      f21: "{{ states('sensor.temperature_forecast_20') | float(0) }}"
      f22: "{{ states('sensor.temperature_forecast_21') | float(0) }}"
      f23: "{{ states('sensor.temperature_forecast_22') | float(0) }}"
      f24: "{{ states('sensor.temperature_forecast_23') | float(0) }}"
      f25: "{{ states('sensor.temperature_forecast_h1') | float(0) }}"
      f26: "{{ states('sensor.temperature_forecast_h2') | float(0) }}"
      f27: "{{ states('sensor.temperature_forecast_h3') | float(0) }}"
      forecast_values: >-
        {{ [f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16,
        f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27] }}
      forecast_high: "{{ forecast_values | max }}"
      forecast_low: "{{ forecast_values | min }}"
  - data:
      entity_id: input_number.forecast_high
      value: "{{ forecast_high }}"
    action: input_number.set_value
  - data:
      entity_id: input_number.forecast_low
      value: "{{ forecast_low }}"
    action: input_number.set_value
mode: single

Now back to the YAML configuration in ESPHOME to add the sensors:

sensor:
  - platform: homeassistant
    id: temp_now
    entity_id: sensor.tempcurrent
  - platform: homeassistant
    id: temp_h1
    entity_id: sensor.temperature_forecast_h1
  - platform: homeassistant
    id: temp_h2
    entity_id: sensor.temperature_forecast_h2
  - platform: homeassistant
    id: temp_h3
    entity_id: sensor.temperature_forecast_h3
  - platform: homeassistant
    id: temp_d1
    entity_id: sensor.temperature_forecast_d1
  - platform: homeassistant
    id: temp_d2
    entity_id: sensor.temperature_forecast_d2
  - platform: homeassistant
    id: temp_d3
    entity_id: sensor.temperature_forecast_d3
  - platform: wifi_signal
    name: "WiFi Signal Sensor"
    id: wifisignal
    update_interval: 60s
  - platform: homeassistant
    id: highest_temp
    entity_id: input_number.forecast_high
  - platform: homeassistant
    id: lowest_temp
    entity_id: input_number.forecast_low

text_sensor:
  - platform: homeassistant
    id: weather_now
    entity_id: sensor.weathercurrent
  - platform: homeassistant
    id: weather_h1
    entity_id: sensor.weather_forecast_h1
  - platform: homeassistant
    id: weather_h2
    entity_id: sensor.weather_forecast_h2
  - platform: homeassistant
    id: weather_h3
    entity_id: sensor.weather_forecast_h3
  - platform: homeassistant
    id: weather_d1
    entity_id: sensor.weather_forecast_d1
  - platform: homeassistant
    id: weather_d2
    entity_id: sensor.weather_forecast_d2
  - platform: homeassistant
    id: weather_d3
    entity_id: sensor.weather_forecast_d3
  - platform: homeassistant
    id: person_lisa
    entity_id: person.lisa
  - platform: homeassistant
    id:  person_mikael
    entity_id: person.duux

If we add the following lambda now we can draw the time, date, current temp and weather conditions and display a neat icon representing the weather.

// 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"},
            };
          // Date and Time
          it.strftime(190, 10, id(small), TextAlign::CENTER_HORIZONTAL, "%A %b %d, %Y", id(homeassistant_time).now());
          it.strftime(190, 30, id(big), TextAlign::CENTER_HORIZONTAL, "%H:%M", id(homeassistant_time).now());
          // Current Weather
          it.printf(190, 170, id(tiny), TextAlign::CENTER_HORIZONTAL, "Currently: %s", id(weather_now).state.c_str());
          it.printf(190, 200, id(tiny), TextAlign::CENTER_HORIZONTAL, "High: %.1f°c  Low: %.1f°c", id(highest_temp).state, id(lowest_temp).state);
          it.printf(190, 220, id(font_mdi_large), TextAlign::CENTER_HORIZONTAL, "%s", weather_icon_map[id(weather_now).state.c_str()].c_str());
          it.printf(190, 400, id(medium), TextAlign::CENTER_HORIZONTAL, "%.1f°c", id(temp_now).state);
it.line(400, 0, 400, 480); // add a nice line in the middel

Image Description

All that just for the weather data! probably a better way to do it but it works.

Forecast

If we remove the person sensors from the drawing at this time we can replace it with the hourly and daily forecasts. Don’t worry, we will add the other stuff back in once we get to the pages section.

Under the code that creates the line in the middle of the screen separating the left and the right side of the screen add the following code:

// START OF RIGHT SIDE
// Hourly Forecast
it.printf(600,110, id(tiny), TextAlign::CENTER_HORIZONTAL, "Hourly");
it.printf(520,150,id(font_mdi_medium),TextAlign::CENTER_HORIZONTAL,"%s",weather_icon_map[id(weather_h1).state.c_str()].c_str());
it.printf(600,150,id(font_mdi_medium),TextAlign::CENTER_HORIZONTAL,"%s",weather_icon_map[id(weather_h2).state.c_str()].c_str());
it.printf(680,150,id(font_mdi_medium),TextAlign::CENTER_HORIZONTAL,"%s",weather_icon_map[id(weather_h1).state.c_str()].c_str());
it.printf(520,190,id(tiny),TextAlign::CENTER_HORIZONTAL, "%.0f°c", id(temp_h1).state);
it.printf(600,190,id(tiny),TextAlign::CENTER_HORIZONTAL, "%.0f°c", id(temp_h2).state);
it.printf(680,190,id(tiny),TextAlign::CENTER_HORIZONTAL, "%.0f°c", id(temp_h3).state);
// Daily Forecast
it.printf(600,230,id(tiny),TextAlign::CENTER_HORIZONTAL,"Daily");
it.printf(520,270,id(font_mdi_medium),TextAlign::CENTER_HORIZONTAL,"%s",weather_icon_map[id(weather_d1).state.c_str()].c_str());
it.printf(600,270,id(font_mdi_medium),TextAlign::CENTER_HORIZONTAL,"%s",weather_icon_map[id(weather_d2).state.c_str()].c_str());
it.printf(680,270,id(font_mdi_medium),TextAlign::CENTER_HORIZONTAL,"%s",weather_icon_map[id(weather_d1).state.c_str()].c_str());
it.printf(520,310,id(tiny),TextAlign::CENTER_HORIZONTAL, "%.0f°c", id(temp_d1).state);
it.printf(600,310,id(tiny),TextAlign::CENTER_HORIZONTAL, "%.0f°c", id(temp_d2).state);
it.printf(680,310,id(tiny),TextAlign::CENTER_HORIZONTAL, "%.0f°c", id(temp_d3).state);
// Forecast Boxes Hourly
it.rectangle(480,140,80,80);
it.rectangle(480,140,160,80);
it.rectangle(480,140,240,80);
// Forecast Boxes Daily
it.rectangle(480,260,80,80);
it.rectangle(480,260,160,80);
it.rectangle(480,260,240,80);

Image Description

Pages

To not clutter the screen anymore and to try and keep it as minimalist as possible we can use what is called pages

It works like this:

display:
  - platform: ...
    # ...
    id: screen
    pages:
      - id: page1
        lambda: |-
          it.print(0, 10, id(my_font), "This is page 1!");
      - id: page2
        lambda: |-
          it.print(0, 10, id(my_font), "This is page 2!");

By adding pages we can have the content on the screen change and separated. We can add a timer and make it change what is displayed on the screen.

By adding this code to the YAML you can automate the page change:

interval:
  - interval: 15s
    then:
      - display.page.show_next: screen
      - component.update: screen

Here is a example of my code using it:

display:
  - platform: waveshare_epaper
    id: screen
    update_interval: never
    reset_duration: 2ms
    show_test_card: false
    model: 7.50inv2
    cs_pin: GPIO3
    dc_pin: GPIO5
    busy_pin: GPIO7
    reset_pin: GPIO2
    spi_id: spi_stack
    rotation: 0
    pages:
      - id: page1
        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"},
            };
          // Date and Time
          it.strftime(190, 10, id(small), TextAlign::CENTER_HORIZONTAL, "%A %b %d, %Y", id(homeassistant_time).now());
          it.strftime(190, 30, id(big), TextAlign::CENTER_HORIZONTAL, "%H:%M", id(homeassistant_time).now());
          // Current Weather
          it.printf(190, 170, id(tiny), TextAlign::CENTER_HORIZONTAL, "Currently: %s", id(weather_now).state.c_str());
          it.printf(190, 200, id(tiny), TextAlign::CENTER_HORIZONTAL, "High: %.1f°c  Low: %.1f°c", id(highest_temp).state, id(lowest_temp).state);
          it.printf(190, 220, id(font_mdi_large), TextAlign::CENTER_HORIZONTAL, "%s", weather_icon_map[id(weather_now).state.c_str()].c_str());
          it.printf(190, 400, id(medium), TextAlign::CENTER_HORIZONTAL, "%.1f°c", id(temp_now).state);
          /* WiFi Signal Strenght */
          if(id(wifisignal).has_state()) {
            int x = 790;
            int y = 20;
            if (id(wifisignal).state >= -50) {
                //Excellent
                it.print(x, y, id(mdi_wifi), TextAlign::BASELINE_CENTER, "󰢾");
                ESP_LOGI("WiFi", "Exellent");
            } else if (id(wifisignal).state  >= -60) {
                //Good
                it.print(x, y, id(mdi_wifi), TextAlign::BASELINE_CENTER, "󰢽");
                ESP_LOGI("WiFi", "Good");
            } else if (id(wifisignal).state  >= -75) {
                //Fair
                it.print(x, y, id(mdi_wifi), TextAlign::BASELINE_CENTER, "󰢼");
                ESP_LOGI("WiFi", "Fair");
            } else if (id(wifisignal).state  >= -100) {
                //Weak
                it.print(x, y, id(mdi_wifi), TextAlign::BASELINE_CENTER, "󰢿");
                ESP_LOGI("WiFi", "Weak");
            } else {
                //Unlikely working signal
                it.print(x, y, id(mdi_wifi), TextAlign::BASELINE_CENTER, "󰞃");
                ESP_LOGI("WiFi", "Unlikely");
            }
          }
          it.line(400, 0, 400, 480);
          // END OF LEFT SIDE
          // START OF RIGHT SIDE
          // Hourly Forecast
          it.printf(600,110, id(tiny), TextAlign::CENTER_HORIZONTAL, "Hourly");
          it.printf(520,150,id(font_mdi_medium),TextAlign::CENTER_HORIZONTAL,"%s",weather_icon_map[id(weather_h1).state.c_str()].c_str());
          it.printf(600,150,id(font_mdi_medium),TextAlign::CENTER_HORIZONTAL,"%s",weather_icon_map[id(weather_h2).state.c_str()].c_str());
          it.printf(680,150,id(font_mdi_medium),TextAlign::CENTER_HORIZONTAL,"%s",weather_icon_map[id(weather_h1).state.c_str()].c_str());
          it.printf(520,190,id(tiny),TextAlign::CENTER_HORIZONTAL, "%.0f°c", id(temp_h1).state);
          it.printf(600,190,id(tiny),TextAlign::CENTER_HORIZONTAL, "%.0f°c", id(temp_h2).state);
          it.printf(680,190,id(tiny),TextAlign::CENTER_HORIZONTAL, "%.0f°c", id(temp_h3).state);
          // Daily Forecast
          it.printf(600,230,id(tiny),TextAlign::CENTER_HORIZONTAL,"Daily");
          it.printf(520,270,id(font_mdi_medium),TextAlign::CENTER_HORIZONTAL,"%s",weather_icon_map[id(weather_d1).state.c_str()].c_str());
          it.printf(600,270,id(font_mdi_medium),TextAlign::CENTER_HORIZONTAL,"%s",weather_icon_map[id(weather_d2).state.c_str()].c_str());
          it.printf(680,270,id(font_mdi_medium),TextAlign::CENTER_HORIZONTAL,"%s",weather_icon_map[id(weather_d1).state.c_str()].c_str());
          it.printf(520,310,id(tiny),TextAlign::CENTER_HORIZONTAL, "%.0f°c", id(temp_d1).state);
          it.printf(600,310,id(tiny),TextAlign::CENTER_HORIZONTAL, "%.0f°c", id(temp_d2).state);
          it.printf(680,310,id(tiny),TextAlign::CENTER_HORIZONTAL, "%.0f°c", id(temp_d3).state);
          // Forecast Boxes Hourly
          it.rectangle(480,140,80,80);
          it.rectangle(480,140,160,80);
          it.rectangle(480,140,240,80);
          // Forecast Boxes Daily
          it.rectangle(480,260,80,80);
          it.rectangle(480,260,160,80);
          it.rectangle(480,260,240,80);
          // UV Index
          std::map<std::string, std::string> uv_string_map
            {
              {"extreme", "Extreme"},
              {"very_high", "Very High"},
              {"high", "High"},
              {"moderate", "Moderate"},
              {"low", "Low"},
            };
          if (id(uv_index).has_state()) {
            it.printf(600,400, id(tiny), TextAlign::CENTER, "\U000F17FF %.1f (%s)", id(uv_index).state, uv_string_map[id(uv_level).state.c_str()].c_str());
          }
      - id: page2
        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"},
            };
          // Date and Time
          it.strftime(190, 10, id(small), TextAlign::CENTER_HORIZONTAL, "%A %b %d, %Y", id(homeassistant_time).now());
          it.strftime(190, 30, id(big), TextAlign::CENTER_HORIZONTAL, "%H:%M", id(homeassistant_time).now());
          // Current Weather
          it.printf(190, 170, id(tiny), TextAlign::CENTER_HORIZONTAL, "Currently: %s", id(weather_now).state.c_str());
          it.printf(190, 200, id(tiny), TextAlign::CENTER_HORIZONTAL, "High: %.1f°c  Low: %.1f°c", id(highest_temp).state, id(lowest_temp).state);
          it.printf(190, 220, id(font_mdi_large), TextAlign::CENTER_HORIZONTAL, "%s", weather_icon_map[id(weather_now).state.c_str()].c_str());
          it.printf(190, 400, id(medium), TextAlign::CENTER_HORIZONTAL, "%.1f°c", id(temp_now).state);
          /* WiFi Signal Strenght */
          if(id(wifisignal).has_state()) {
            int x = 790;
            int y = 20;
            if (id(wifisignal).state >= -50) {
                //Excellent
                it.print(x, y, id(mdi_wifi), TextAlign::BASELINE_CENTER, "󰢾");
                ESP_LOGI("WiFi", "Exellent");
            } else if (id(wifisignal).state  >= -60) {
                //Good
                it.print(x, y, id(mdi_wifi), TextAlign::BASELINE_CENTER, "󰢽");
                ESP_LOGI("WiFi", "Good");
            } else if (id(wifisignal).state  >= -75) {
                //Fair
                it.print(x, y, id(mdi_wifi), TextAlign::BASELINE_CENTER, "󰢼");
                ESP_LOGI("WiFi", "Fair");
            } else if (id(wifisignal).state  >= -100) {
                //Weak
                it.print(x, y, id(mdi_wifi), TextAlign::BASELINE_CENTER, "󰢿");
                ESP_LOGI("WiFi", "Weak");
            } else {
                //Unlikely working signal
                it.print(x, y, id(mdi_wifi), TextAlign::BASELINE_CENTER, "󰞃");
                ESP_LOGI("WiFi", "Unlikely");
            }
          }
          it.line(400, 0, 400, 480);
          // END OF LEFT SIDE
          // START OF RIGHT SIDE
          // Persons
          // Map Person State
          std::map<std::string, std::string> person_map
            {
              {"home", "\U000F0004"},
              {"not_home", "\U000F0012"},
            };
          it.printf(523,150, id(tiny), TextAlign::CENTER_HORIZONTAL, "Mikael");
          it.printf(673,150, id(tiny), TextAlign::CENTER_HORIZONTAL, "Lisa");
          it.printf(525,190,id(font_mdi_medium),TextAlign::CENTER_HORIZONTAL,"%s",person_map[id(person_mikael).state.c_str()].c_str());
          it.printf(675,190,id(font_mdi_medium),TextAlign::CENTER_HORIZONTAL,"%s",person_map[id(person_lisa).state.c_str()].c_str());
          if (id(person_mikael).state == "home") {
            it.printf(525,230,id(tiny),TextAlign::CENTER_HORIZONTAL, "HOME");
          }
          else {
            it.printf(525,230,id(tiny),TextAlign::CENTER_HORIZONTAL, "AWAY");
          }
          if (id(person_lisa).state == "home") {
            it.printf(675,230,id(tiny),TextAlign::CENTER_HORIZONTAL, "HOME");
          }
          else {
            it.printf(675,230,id(tiny),TextAlign::CENTER_HORIZONTAL, "AWAY");
          }
           // Draw boxes
          it.rectangle(480,180,90,90);
          it.rectangle(630,180,90,90);

As you can see I am drawing the WiFi Signal and all of the right side in both of the pages as i want that to be on all of my pages and i only change out what is on the left side of the screen. That way you get the feeling that only the left side is changing and the left side will only change between the weather forecast and the person sensors from home assistant.

Page1 Image Description

Page2 Image Description

Other page ideas

Birthdays

You can also show birthdays on the screen

Image Description

// START OF RIGHT SIDE
// Birthday functions
std::map<std::string, std::vector<std::string>> birthday_map {
  {"06.05", {"Mikael", "X"}},
  {"22.12", {"X"}}, // masked out the names here with X
  {"15.07", {"X"}},
  {"08.07", {"X"}},
  {"20.07", {"X"}},
  {"23.12", {"X"}},
  {"16.02", {"X"}},
  {"17.03", {"X"}},
  {"02.10", {"X"}},
  {"22.11", {"X"}},
  {"31.10", {"X"}},
  {"06.03", {"X"}},
  {"20.08", {"X"}},
  {"18.03", {"X"}},
};
// Get today's date as "DD.MM"
time_t now = id(homeassistant_time).now().timestamp;
struct tm* timeinfo = localtime(&now);
char today_str[6]; // "DD.MM" + null terminator
strftime(today_str, sizeof(today_str), "%d.%m", timeinfo);
std::string today = today_str;
it.printf(600,110, id(font_value), TextAlign::CENTER_HORIZONTAL, "Birthdays");
// Check if there are any birthdays today
auto iter = birthday_map.find(today);
if (iter != birthday_map.end()) {
  int y_offset = 270;
  it.printf(600, 180, id(font_mdi_side_high), TextAlign::CENTER_HORIZONTAL, "\U000F00EB");
  // Iterate through all names for today
  for (const std::string& name : iter->second) {
	  it.printf(600, y_offset, id(tiny), TextAlign::CENTER_HORIZONTAL, "%s", name.c_str());
	  y_offset += 40; // Increase vertical spacing for each name
  }
} else {
  it.printf(600, 270, id(tiny), TextAlign::CENTER, "Nobody Today");
}

Cat water and food sensors

As I have detection sensors on the food and water bawl for the cat, I can track his usage and display it on the screen.

Image Description

// START OF RIGHT SIDE
// CAT STUFF
it.printf(600, 70, id(meow_80), TextAlign::CENTER_HORIZONTAL, "t");
it.printf(600, 180, id(tiny), TextAlign::CENTER_HORIZONTAL, "Water: %.0f", id(cat_water_counter).state);
it.printf(600, 260, id(tiny), TextAlign::CENTER_HORIZONTAL, "Food: %.0f", id(cat_food_counter).state);
auto water_last = id(cat_water_last).state;
if (water_last.length() >= 16) {
  std::string time_str = water_last.substr(11, 5);  // Extract "HH:MM"
  it.printf(600, 205, id(tiny), TextAlign::CENTER_HORIZONTAL, "Last: %s", time_str.c_str());
} else {
  it.printf(600, 205, id(tiny), TextAlign::CENTER_HORIZONTAL, "Last: %s", water_last.c_str());
}
auto food_last = id(cat_food_last).state;
if (food_last.length() >= 16) {
  std::string time_str = food_last.substr(11, 5);
  it.printf(600, 285, id(tiny), TextAlign::CENTER_HORIZONTAL, "Last: %s", time_str.c_str());
} else {
  it.printf(600, 285, id(tiny), TextAlign::CENTER_HORIZONTAL, "Last: %s", food_last.c_str());
}

And its related sensors:

sensor:
  - platform: homeassistant
    id: cat_food_counter
    entity_id: counter.cat_food_counter
  - platform: homeassistant
    id: cat_water_counter
    entity_id: counter.cat_water_counter
text_sensor:
  - platform: homeassistant
    id: cat_food_last
    entity_id: input_datetime.last_cat_food_trigger
  - platform: homeassistant
    id: cat_water_last
    entity_id: input_datetime.last_cat_water_trigger

Trigger from Home Assistant

You can also create actions that you can trigger in automation’s by adding services to the YAML in ESPHOME:

# Enable Home Assistant API
api:
  encryption:
    key: "SUPERSECRETAPICODEYO!"
  services:
    - service: update_display
      then:
        - component.update: screen

That way you can create automation’s in home assistant:

alias: Update E-Paper Display on Weather Change
description: Trigger display update when the weather sensor changes.
triggers:
  - entity_id: sensor.weathercurrent
    trigger: state
actions:
  - action: esphome.epapir_dashboard_update_display
    data: {}
mode: single

Conclusion

This was a really fun project to dive in to and I learned a lot by tinkering with it. Hope I didn’t miss any critical information as I am writing this post project. If you have any questions, my contact info is on the left.