Smarthome display for window status and weather

Issue
One may leave the house and let one or more window(s) open.
If you own a house you may imagine that this can lead to a medium disaster, depending on weather or whatever.

I introduced some automations which send a Pushover message when you leave the homezone and a window is open but this maybe reaches you when you are some dozen meters away from home (or 2 kilometres with the car). Not really perfect… :thinking:

Thanksgiving
I don’t want to adorn myself with borrowed plumes. Most of the basic work I got from this post:
AZ Touch ESP example

It gives me a great start for my project and I have to say thank you.

Objective
Create something which gives you a hint right before you leave the house. Something with a small display would be nice, preferably glowing in the dark. :star2:

Prerequisites

  • Every window (or door) to show need a sensor.
  • If you want to use the weather display, well, you need some weather service and sensors.
  • some soldering experience
  • basic understanding and experience with ESP32 and/or other microcontrollers
  • basic understanding and experience with ESPHome

Solution
A small display right next to the front door, so everyone who leaves the house can easily see the window status.

Hardware

  • AZ-Delivery “AZ-Touch MOD 2.8 inch”
  • AZ-Delivery “ESP32 NodeMCU DevKit C”
  • BH1750 Light sensor (also available at AZ-Delivery but can come from wherever)
  • Since I had to power the device with AC, I used a rectifier diode and an electrolytic capacitor. If you have DC then it’s not necessary, the display has a wide voltage operation range.

ESPHome code

esphome:
  name: display-01

esp32:
  board: nodemcu-32s
  framework:
    type: arduino

# Enable logging
logger:
  level: INFO
  logs:
    component: ERROR

# Enable Home Assistant API
api:
  password: ""

  # play to buzzer from HA
  services:
    - service: play_tune
      variables:
        tune_str: string
      then:
        - rtttl.play:
            rtttl: !lambda 'return tune_str;'

ota:
  password: ""

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  manual_ip:
    static_ip: 192.168.nnn.nnn
    gateway: 192.168.nnn.nnn
    subnet: 255.255.255.0

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Display 01 Fallback Hotspot"
    password: "whateveryoulike"

captive_portal:

time:
  - platform: homeassistant
    id: ha_time

i2c:
  sda: GPIO33
  scl: GPIO32

sensor:
  - platform: bh1750
    id: display_01_illuminance
    name: "Display 01: Illuminance"
    address: 0x23
    update_interval: 2s
    accuracy_decimals: 1
    on_value_range:
      - below: 2.0
        then:
          - light.turn_on:
              id: backlight
              brightness: 30%
      - above: 2.0
        below: 20.0
        then:
          - light.turn_on:
              id: backlight
              brightness: 60%
      - above: 20.0
        then:
          - light.turn_on:
              id: backlight
              brightness: 100%

  - platform: homeassistant
    id: weather_temperature_outside
    entity_id: sensor.location_outside_temperature_current

  - platform: homeassistant
    id: weather_3h_temperature
    entity_id: sensor.weather_3h_temperature

  - platform: homeassistant
    id: weather_6h_temperature
    entity_id: sensor.weather_6h_temperature

binary_sensor:
  - platform: status
    id: display_01_status
    name: "Display 01: Status"

  - platform: homeassistant
    id: windows_bathroom_ground
    entity_id: binary_sensor.location_bathroom_ground_windows

  - platform: homeassistant
    id: windows_kitchen
    entity_id: binary_sensor.location_kitchen_windows

  - platform: homeassistant
    id: windows_living_room
    entity_id: binary_sensor.location_living_room_windows

  - platform: homeassistant
    id: windows_bedroom
    entity_id: binary_sensor.location_bedroom_windows

  - platform: homeassistant
    id: windows_dining_room
    entity_id: binary_sensor.location_dining_room_windows

  - platform: homeassistant
    id: windows_bathroom_upstairs
    entity_id: binary_sensor.location_bathroom_upstairs_windows

  - platform: homeassistant
    id: windows_room_upstairs_north
    entity_id: binary_sensor.location_room_upstairs_north_windows

  - platform: homeassistant
    id: windows_room_upstairs_south
    entity_id: binary_sensor.location_room_upstairs_south_windows

  - platform: template
    id: windows_all
    lambda: |-
      return
        id(windows_bathroom_ground).state ||
        id(windows_kitchen).state ||
        id(windows_living_room).state ||
        id(windows_bedroom).state ||
        id(windows_dining_room).state ||
        id(windows_bathroom_upstairs).state ||
        id(windows_room_upstairs_north).state ||
        id(windows_room_upstairs_south).state;

text_sensor:
  - platform: homeassistant
    id: weather
    entity_id: weather.home_5_hourly

  - platform: homeassistant
    id: weather_3h_condition
    entity_id: sensor.weather_3h_condition

  - platform: homeassistant
    id: weather_6h_condition
    entity_id: sensor.weather_6h_condition

switch:
  - platform: restart
    id: display_01_restart
    name: "Display 01: Restart"

output:
  # backlight
  - platform: ledc
    id: backlight_out
    pin: GPIO15
    inverted: true

  # buzzer
  - platform: ledc
    id: rtttl_out
    pin: GPIO21

# component buzzer
rtttl:
  output: rtttl_out

# backlight
light:
  - platform: monochromatic
    id: backlight
    name: "Display 01: Backlight"
    output: backlight_out
    restore_mode: ALWAYS_ON

spi:
  clk_pin: GPIO18
  mosi_pin: GPIO23
  miso_pin: GPIO19

touchscreen:
  platform: xpt2046
  id: mtouch
  cs_pin: 14
  interrupt_pin: 27
  update_interval: 50ms
  report_interval: 1s
  swap_x_y: false
  threshold: 400
  calibration_x_min: 272
  calibration_x_max: 3807
  calibration_y_min: 3887
  calibration_y_max: 384
  on_touch:
    - display.page.show_next: screen
    - script.execute: screen_reset

script:
  - id: screen_reset
    mode: restart
    then:
      - delay: 10 s
      - display.page.show: page1

color:
  - id: color_background
    red: 100%
    green: 100%
    blue: 100%

  - id: color_head_text
    red: 0%
    green: 0%
    blue: 0%

  - id: color_lines
    red: 0%
    green: 0%
    blue: 60%

  - id: color_text
    red: 0%
    green: 0%
    blue: 0%

  - id: color_text_alert
    red_int: 255
    green_int: 163
    blue_int: 0

  - id: color_text_okay
    red: 0%
    green: 70%
    blue: 0%

  - id: color_coming_weather
    red: 0%
    green: 0%
    blue: 20%

font:
  - file: "font/Ubuntu-Regular.ttf"
    id: font_rooms
    size: 18
    glyphs:
      ['&', '@', '!', '?', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
       '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
       'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
       'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
       'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
       'u', 'v', 'w', 'x', 'y', 'z', 'å', 'ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü', '/', '€', '’', 'ß']
 
  - file: "font/Ubuntu-Regular.ttf"
    id: font_header
    size: 24

  - file: "font/Ubuntu-Regular.ttf"
    id: font_header_small
    size: 18

  - file: "font/Ubuntu-Bold.ttf"
    id: font_alert
    size: 24

  - file: "font/Ubuntu-Regular.ttf"
    id: font_temperature
    size: 80
    glyphs:
      ['-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
 
  - file: "font/Ubuntu-Regular.ttf"
    id: font_temperature_unit
    size: 36
    glyphs:
      ['°', 'C']
 
  - file: "font/Ubuntu-Regular.ttf"
    id: font_coming_weather
    size: 24
    glyphs:
      [' ', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'C', 'u', 's', 'i', 'c', 'h', 't', ':', '°']
 
image:
  - file: "img/alert.png"
    id: image_alert
    type: RGBA

  - file: "img/cloud.png"
    id: image_cloud
    type: RGBA

  - file: "img/cloud-small.png"
    id: image_cloud_small
    type: RGBA

  - file: "img/cloud-partly.png"
    id: image_cloud_partly
    type: RGBA

  - file: "img/cloud-partly-small.png"
    id: image_cloud_partly_small
    type: RGBA

  - file: "img/dot-green.png"
    id: image_dot_green
    resize: 14x14
    type: RGB24

  - file: "img/dot-orange.png"
    id: image_dot_orange
    resize: 14x14
    type: RGB24

  - file: "img/okay.png"
    id: image_okay
    type: RGBA

  - file: "img/sun.png"
    id: image_sun
    type: RGBA

  - file: "img/sun-small.png"
    id: image_sun_small
    type: RGBA

  - file: "img/night.png"
    id: image_night
    type: RGBA

  - file: "img/night-small.png"
    id: image_night_small
    type: RGBA

  - file: "img/attention.png"
    id: image_attention
    type: RGBA

  - file: "img/attention-small.png"
    id: image_attention_small
    type: RGBA

  - file: "img/fog.png"
    id: image_fog
    type: RGBA

  - file: "img/fog-small.png"
    id: image_fog_small
    type: RGBA

  - file: "img/hail.png"
    id: image_hail
    type: RGBA

  - file: "img/hail-small.png"
    id: image_hail_small
    type: RGBA

  - file: "img/lightning.png"
    id: image_lightning
    type: RGBA

  - file: "img/lightning-small.png"
    id: image_lightning_small
    type: RGBA

  - file: "img/rain.png"
    id: image_rain
    type: RGBA

  - file: "img/rain-small.png"
    id: image_rain_small
    type: RGBA

  - file: "img/snow.png"
    id: image_snow
    type: RGBA

  - file: "img/snow-small.png"
    id: image_snow_small
    type: RGBA

  - file: "img/wind.png"
    id: image_wind
    type: RGBA

  - file: "img/wind-small.png"
    id: image_wind_small
    type: RGBA

display:
  - platform: ili9xxx
    model: ili9341
    id: screen
    cs_pin: GPIO5
    dc_pin: GPIO4
    reset_pin: GPIO22
    rotation: 90°
    pages:
      - id: page1
        lambda: |-
          static bool blink = true;

          // background
          it.fill(id(color_background));

          // header
          it.filled_rectangle(5, 30, it.get_width() - 10, 2, id(color_lines));
          it.print(it.get_width() / 2, 0, id(font_header), id(color_head_text), TextAlign::TOP_CENTER, "Windows");
          it.strftime(it.get_width() - 7, 4, id(font_header_small), id(color_head_text), TextAlign::TOP_RIGHT, "%H:%M", id(ha_time).now());

          // bath room upstairs
          if(id(windows_bathroom_upstairs).state) {
            it.image(16, 45, id(image_dot_orange));
            it.print(40, 40, id(font_rooms), id(color_text_alert), TextAlign::TOP_LEFT, "Bath upstairs");
          } else {
            it.image(16, 45, id(image_dot_green));
            it.print(40, 40, id(font_rooms), id(color_text_okay), TextAlign::TOP_LEFT, "Bath upstairs");
          }

          // upstairs north
          if(id(windows_room_upstairs_north).state) {
            it.image(16, 70, id(image_dot_orange));
            it.print(40, 65, id(font_rooms), id(color_text_alert), TextAlign::TOP_LEFT, "Upstairs north");
          } else {
            it.image(16, 70, id(image_dot_green));
            it.print(40, 65, id(font_rooms), id(color_text_okay), TextAlign::TOP_LEFT, "Upstairs north");
          }

          // upstairs south
          if(id(windows_room_upstairs_south).state) {
            it.image(16, 95, id(image_dot_orange));
            it.print(40, 90, id(font_rooms), id(color_text_alert), TextAlign::TOP_LEFT, "Upstairs south");
          } else {
            it.image(16, 95, id(image_dot_green));
            it.print(40, 90, id(font_rooms), id(color_text_okay), TextAlign::TOP_LEFT, "Upstairs south");
          }

          it.line(16, 114, 150, 114, id(color_lines));

          // bathroom
          if(id(windows_bathroom_ground).state) {
            it.image(16, 120, id(image_dot_orange));
            it.print(40, 115, id(font_rooms), id(color_text_alert), TextAlign::TOP_LEFT, "Bathroom");
          } else {
            it.image(16, 120, id(image_dot_green));
            it.print(40, 115, id(font_rooms), id(color_text_okay), TextAlign::TOP_LEFT, "Bathroom");
          }

          // kitchen
          if(id(windows_kitchen).state) {
            it.image(16, 145, id(image_dot_orange));
            it.print(40, 140, id(font_rooms), id(color_text_alert), TextAlign::TOP_LEFT, "Kitchen");
          } else {
            it.image(16, 145, id(image_dot_green));
            it.print(40, 140, id(font_rooms), id(color_text_okay), TextAlign::TOP_LEFT, "Kitchen");
          }

          // living room
          if(id(windows_living_room).state) {
            it.image(16, 170, id(image_dot_orange));
            it.print(40, 165, id(font_rooms), id(color_text_alert), TextAlign::TOP_LEFT, "Living room");
          } else {
            it.image(16, 170, id(image_dot_green));
            it.print(40, 165, id(font_rooms), id(color_text_okay), TextAlign::TOP_LEFT, "Living room");
          }

          // bedroom
          if(id(windows_bedroom).state) {
            it.image(16, 195, id(image_dot_orange));
            it.print(40, 190, id(font_rooms), id(color_text_alert), TextAlign::TOP_LEFT, "Bedroom");
          } else {
            it.image(16, 195, id(image_dot_green));
            it.print(40, 190, id(font_rooms), id(color_text_okay), TextAlign::TOP_LEFT, "Bedroom");
          }

          // dining room
          if(id(windows_dining_room).state) {
            it.image(16, 220, id(image_dot_orange));
            it.print(40, 215, id(font_rooms), id(color_text_alert), TextAlign::TOP_LEFT, "Dining room");
          } else {
            it.image(16, 220, id(image_dot_green));
            it.print(40, 215, id(font_rooms), id(color_text_okay), TextAlign::TOP_LEFT, "Dining room");
          }

          // all windows
          if(id(windows_all).state) {
            if(blink) it.image(245, 40, id(image_alert), ImageAlign::TOP_CENTER);
            blink = !blink;

            it.print(245, 160, id(font_alert), id(color_text_alert), TextAlign::TOP_CENTER, "Windows");
            it.print(245, 190, id(font_alert), id(color_text_alert), TextAlign::TOP_CENTER, "open");
          } else {
            it.image(245, 40, id(image_okay), ImageAlign::TOP_CENTER);
            it.print(245, 160, id(font_alert), id(color_text_okay), TextAlign::TOP_CENTER, "Windows");
            it.print(245, 190, id(font_alert), id(color_text_okay), TextAlign::TOP_CENTER, "closed");
          }

      - id: page2
        lambda: |-
          // background
          it.fill(id(color_background));

          // header
          it.filled_rectangle(5, 30, it.get_width() - 10, 2, id(color_lines));
          it.print(it.get_width() / 2, 0, id(font_header), id(color_head_text), TextAlign::TOP_CENTER, "Weather");
          it.strftime(it.get_width() - 7, 4, id(font_header_small), id(color_head_text), TextAlign::TOP_RIGHT, "%H:%M", id(ha_time).now());
          //it.printf(5, 4, id(font_header_small), id(color_text), TextAlign::TOP_LEFT, "%s", id(weather).state.c_str());

          // temperature
          it.printf(80, 100, id(font_temperature), id(color_text), TextAlign::CENTER, "%.0f", id(weather_temperature_outside).state);
          it.print(135, 69, id(font_temperature_unit), id(color_text), TextAlign::TOP_LEFT, "°C");

          // current weather
          if(id(weather).state == "clear-night"){
            it.image(190, 40, id(image_night));
          } else if(id(weather).state == "cloudy"){
            it.image(190, 40, id(image_cloud));
          } else if(id(weather).state == "exceptional"){
            it.image(190, 40, id(image_attention));
          } else if(id(weather).state == "fog"){
            it.image(190, 40, id(image_fog));
          } else if(id(weather).state == "hail"){
            it.image(190, 40, id(image_hail));
          } else if(id(weather).state == "lightning"){
            it.image(190, 40, id(image_lightning));
          } else if(id(weather).state == "lightning-rainy"){
            it.image(190, 40, id(image_lightning));
            it.image(190, 40, id(image_rain));
          } else if(id(weather).state == "pouring"){
            it.image(190, 40, id(image_cloud));
            it.image(190, 40, id(image_rain));
            it.image(200, 36, id(image_rain));
          } else if(id(weather).state == "rainy"){
            it.image(190, 40, id(image_cloud));
            it.image(190, 40, id(image_rain));
          } else if(id(weather).state == "partlycloudy"){
            it.image(190, 40, id(image_sun));
            it.image(190, 40, id(image_cloud_partly));
          } else if(id(weather).state == "snowy"){
            it.image(190, 40, id(image_cloud));
            it.image(190, 40, id(image_snow));
          } else if(id(weather).state == "snowy-rainy"){
            it.image(190, 40, id(image_cloud));
            it.image(190, 40, id(image_rain));
            it.image(190, 40, id(image_snow));
          } else if (id(weather).state == "sunny"){
            it.image(190, 40, id(image_sun));
          } else if (id(weather).state == "windy"){
            it.image(190, 40, id(image_wind));
          } else if (id(weather).state == "windy-variant"){
            it.image(190, 40, id(image_cloud));
            it.image(190, 40, id(image_wind));
          }

          // coming weather 3h
          it.print(20, 180, id(font_coming_weather), id(color_coming_weather), TextAlign::TOP_LEFT, "Outlook 3h:");
          it.printf(220, 180, id(font_coming_weather), id(color_coming_weather), TextAlign::TOP_RIGHT, "%.0f °C", id(weather_3h_temperature).state);

          if(id(weather_3h_condition).state == "clear-night"){
            it.image(250, 180, id(image_night_small));
          } else if(id(weather_3h_condition).state == "cloudy"){
            it.image(250, 180, id(image_cloud_small));
          } else if(id(weather_3h_condition).state == "exceptional"){
            it.image(250, 180, id(image_attention_small));
          } else if(id(weather_3h_condition).state == "fog"){
            it.image(250, 180, id(image_fog_small));
          } else if(id(weather_3h_condition).state == "hail"){
            it.image(250, 180, id(image_hail_small));
          } else if(id(weather_3h_condition).state == "lightning"){
            it.image(250, 180, id(image_lightning_small));
          } else if(id(weather_3h_condition).state == "lightning-rainy"){
            it.image(250, 180, id(image_lightning_small));
            it.image(250, 180, id(image_rain_small));
          } else if(id(weather_3h_condition).state == "pouring"){
            it.image(250, 180, id(image_cloud_small));
            it.image(250, 180, id(image_rain_small));
            it.image(200, 178, id(image_rain_small));
          } else if(id(weather_3h_condition).state == "rainy"){
            it.image(250, 180, id(image_cloud_small));
            it.image(250, 180, id(image_rain_small));
          } else if(id(weather_3h_condition).state == "partlycloudy"){
            it.image(250, 180, id(image_sun_small));
            it.image(250, 180, id(image_cloud_partly_small));
          } else if(id(weather_3h_condition).state == "snowy"){
            it.image(250, 180, id(image_cloud_small));
            it.image(250, 180, id(image_snow_small));
          } else if(id(weather_3h_condition).state == "snowy-rainy"){
            it.image(250, 180, id(image_cloud_small));
            it.image(250, 180, id(image_rain_small));
            it.image(250, 180, id(image_snow_small));
          } else if (id(weather_3h_condition).state == "sunny"){
            it.image(250, 180, id(image_sun_small));
          } else if (id(weather_3h_condition).state == "windy"){
            it.image(250, 180, id(image_wind_small));
          } else if (id(weather_3h_condition).state == "windy-variant"){
            it.image(250, 180, id(image_cloud_small));
            it.image(250, 180, id(image_wind_small));
          }

          // coming weather 6h
          it.print(20, 210, id(font_coming_weather), id(color_coming_weather), TextAlign::TOP_LEFT, "Outlook 6h:");
          it.printf(220, 210, id(font_coming_weather), id(color_coming_weather), TextAlign::TOP_RIGHT, "%.0f °C", id(weather_6h_temperature).state);

          if(id(weather_6h_condition).state == "clear-night"){
            it.image(250, 210, id(image_night_small));
          } else if(id(weather_3h_condition).state == "cloudy"){
            it.image(250, 210, id(image_cloud_small));
          } else if(id(weather_3h_condition).state == "exceptional"){
            it.image(250, 210, id(image_attention_small));
          } else if(id(weather_3h_condition).state == "fog"){
            it.image(250, 210, id(image_fog_small));
          } else if(id(weather_3h_condition).state == "hail"){
            it.image(250, 210, id(image_hail_small));
          } else if(id(weather_3h_condition).state == "lightning"){
            it.image(250, 210, id(image_lightning_small));
          } else if(id(weather_3h_condition).state == "lightning-rainy"){
            it.image(250, 210, id(image_lightning_small));
            it.image(250, 210, id(image_rain_small));
          } else if(id(weather_3h_condition).state == "pouring"){
            it.image(250, 210, id(image_cloud_small));
            it.image(250, 210, id(image_rain_small));
            it.image(200, 208, id(image_rain_small));
          } else if(id(weather_3h_condition).state == "rainy"){
            it.image(250, 210, id(image_cloud_small));
            it.image(250, 210, id(image_rain_small));
          } else if(id(weather_3h_condition).state == "partlycloudy"){
            it.image(250, 210, id(image_sun_small));
            it.image(250, 210, id(image_cloud_partly_small));
          } else if(id(weather_3h_condition).state == "snowy"){
            it.image(250, 210, id(image_cloud_small));
            it.image(250, 210, id(image_snow_small));
          } else if(id(weather_3h_condition).state == "snowy-rainy"){
            it.image(250, 210, id(image_cloud_small));
            it.image(250, 210, id(image_rain_small));
            it.image(250, 210, id(image_snow_small));
          } else if (id(weather_3h_condition).state == "sunny"){
            it.image(250, 210, id(image_sun_small));
          } else if (id(weather_3h_condition).state == "windy"){
            it.image(250, 210, id(image_wind_small));
          } else if (id(weather_3h_condition).state == "windy-variant"){
            it.image(250, 210, id(image_cloud_small));
            it.image(250, 210, id(image_wind_small));
          }

Additional resources
I created some additional pictures to use in this project.
You can get from here, it’s the file ‘img.zip’.

Some explanations / hints / remarks

  • I’m from Germany… you maybe find some german words instead of english… sorry, adapt it to your needs.

  • You need to adapt anyway all the names, IP settings, Wifi settings and so on.

  • The service to make some noise or tunes with the builtin buzzer is integrated, but basically I don’t use it. (But it’s fun, try feeding the service “*_play_tune” in HA with “star_wars:d=16,o=5,b=100:4e,4e,4e,8c,p,g,4e,8c,p,g,4e,4p,4b,4b,4b,8c6,p,g,4d#,8c,p,g,4e,8p”. )

  • The background light of this display can be controlled using GPIO15. In the beginning of the project I didn’t want to control the brightness. But it became clear quite fast: When you walk near the display in the night, it’s much too bright. Then I thought about using the sun integration to dim it at night, but then the display was dimmed the whole night and when someone switched on the light in floor it was way too dark. So finally: The display need to react on the environment illuminance. So I included a BH1750 sensor and used GPIO32 and GPIO33 as I²C bus. Depending on the brightness the backlight is controlled in 3 steps. I guess it can be smoother but it’s good enough for me.

  • The touchscreen needs calibration, you can follow the documentation in ESPHome for the XPT2046 platform. In my setup it’s not very important since I do not define any touch areas. Just tap anywhere on the screen and the display changes. It also switches back to the first page after some seconds.

  • Finding the right font was a bit of a challenge since most of the fonts doesn’t look good on such small displays. I made some experiments with bitmap fonts but that was not really succesfull. In the end I used Ubuntu-Regular and Ubuntu-Bold from Google Fonts. That gives a smooth look, even with small font sizes.

  • The ESP32 I used is nearly full with this application, maybe there is more possible with more memory.

  • The weather thing is a goodie and not the main functionality. But it’s nice. To make it work I created some helper sensors in Home Assistant, just to use it in the display:

template:
  - sensor:
      - unique_id: "weather_3h_temperature"
        name: "Weather forecast 3h: Temperature"
        unit_of_measurement: "°C"
        state: "{{ state_attr('weather.home_5_hourly', 'forecast')[4].temperature | float | round(1, default=12) }}"

      - unique_id: "weather_3h_condition"
        name: "Weather forecast 3h: Conditions"
        icon: mdi:weather-partly-cloudy
        state: "{{ state_attr('weather.home_5_hourly', 'forecast')[4].condition | string | default('') }}"

      - unique_id: "weather_6h_temperature"
        name: "Weather forecast 6h: Temperature"
        unit_of_measurement: "°C"
        state: "{{ state_attr('weather.home_5_hourly', 'forecast')[7].temperature | float | round(1, default=12) }}"

      - unique_id: "weather_6h_condition"
        name: "Weather forecast 6h: Conditions"
        icon: mdi:weather-partly-cloudy
        state: "{{ state_attr('weather.home_5_hourly', 'forecast')[7].condition | string | default('') }}"
  • It runs now since several months without major problems. One thing is: Sometimes (in rare cases) the touch screen is not reacting anymore, I don’t know the reason. The functionality itself keeps running, just no reaction on touch events. The solution is to power off and on again.

  • For a short moment I had the idea to make this thing anyhow powered by battery… but it’s not really feasible with this hardware and setup, I guess.

2 Likes

Gute Arbeit! Gefällt mir!
Good work, I like it.
Greetings from Germany

Have been using some of the code above (thanks for sharing) however the latest ESPHome update appears to disagree with my touchscreen… let me explain.

Here’s what I am seeing…
Touchscreen_Issues

And when I hover my mouse over the lines with red, here’s what I see.

And when I click the link… Here’s what it tells me.

So my question… is it valid or not valid, or have I done something wrong that I cannot see?

Thanks in advance :blush:

Oh… you’re right, good point… in the latest version of ESPHome some options for XPT2046 are marked as deprecated.

Simply removing the options ‘report_interval’ and ‘swap_x_y’ worked for me.
I was able to compile everything with this code:

touchscreen:
  platform: xpt2046
  id: mtouch
  cs_pin: 14
  interrupt_pin: 27
  update_interval: 50ms
  threshold: 400
  calibration_x_min: 272
  calibration_x_max: 3807
  calibration_y_min: 3887
  calibration_y_max: 384
  on_touch:
    - display.page.show_next: screen
    - script.execute: screen_reset

In my case a swap of x and/or y axes was not necessary so removing ‘swap_x_y’ does nothing. If you need to swap axes you have to deal with the ‘transform’ option, see here https://esphome.io/components/touchscreen/index.html.