Smart mirror with esp32 and TFT display

Hello everyone,
i want just share my current project in its first iteration.
I wanted to build a smart mirror which displays a few important information. It should be at the exterior door and replace our current (stupid) mirror so also my GF supports my ideas/expenses. :wink:

Material:

  • TTGO T4 V1.3 ILI9341 2,4 inch LCD Display with esp32 LINK
  • one sided mirror foil LINK
  • old picture frame


My Star Trek affection is here a bit visible. :upside_down_face:

What do you see from top:

  1. Temperature from my balkony sensor
  2. Temperature from my livingroom sensor
  3. Todays forecast from weather.home (HA default forecast)
  4. Forcast if it will rain the next 30min. See Used tutorials
  5. Check if there is a switch/light still on
  6. When the next garbage collection will take place. See Used tutorials

What i did:

  1. Wrote the code in espHome
  2. Implemented a Sensor in the HA config file
  3. Implemented a dimmable light in HA for dimming the TFT backlight
  4. Mounted the the TTGO T4 on some carton
  5. Glued the mirror foil on the picture frame glass
  6. Mounted everything together

Here my code from espHome:

esphome:
  name: smartmirror

esp32: # LILYGO TTGO T4 V1.3 ILI9341
  board: ttgo-t1
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:
  password: "2e416c3d54841a0eb0188d1b602679df"

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Smartmirror Fallback Hotspot"
    password: "8uM7SpkT1K8q"

captive_portal:

time:
  - platform: homeassistant
    id: homeassistant_time

spi: #intializing Display
  clk_pin: 18 # (Pin on display - SCK/T_CLK)
  mosi_pin: 23 # (Pin on display - SDI(MOSI)/T_DIN)
  miso_pin: 12 # (Pin on display - SDO(MISO)/T_DO)

display:
  - platform: ili9341 #intializing Display
    model: TFT 2.4 #Resolution 320x240
    rotation: 270
    cs_pin: 27 # (Pin on display - CS)
    dc_pin: 32 # (Pin on display - DC)
    led_pin: 4 # (Pin on display - LED)
    reset_pin: 14 # (Pin on display - RESET)
    
    lambda: |-
      //layout
      //top line
      it.filled_circle(25, 25, 25, yellow);
      it.filled_rectangle(27, 0, 185, 23, yellow);
      it.filled_circle(308, 11, 11, yellow);
      it.filled_rectangle(296, 0, 9, 23, yellow);
      //vertikal line I
      it.filled_rectangle(0, 29, 50, 22, yellow);
      it.line(51, 23, 51, 27, yellow);
      it.line(52, 23, 52, 25, yellow);
      it.draw_pixel_at(53, 24, yellow);
      it.line(53, 23, 56, 23, yellow);
      it.filled_rectangle(0, 54, 50, 48, blue);
      //mid line I
      it.filled_rectangle(0, 105, 50, 30, rosa);
      it.filled_circle(7, 136, 7, rosa);
      it.filled_rectangle(9, 131, 177, 13, rosa);
      it.filled_rectangle(186, 131, 112, 6, rosa);
      it.filled_rectangle(298, 131, 12, 13, rosa);
      it.filled_circle(312, 137, 6, rosa);
      it.line(50, 130, 50, 124, rosa);
      it.line(51, 130, 51, 127, rosa);
      it.line(52, 130, 52, 128, rosa);
      it.draw_pixel_at(53, 129, rosa);
      it.line(53, 130, 56, 130, rosa);
      //mid line II
      it.filled_rectangle(0, 158, 50, 28, grey);
      it.filled_circle(7, 156, 7, grey);
      it.filled_rectangle(9, 149, 177, 13, grey);
      it.filled_rectangle(186, 157, 112, 5, grey);
      it.filled_rectangle(298, 149, 12, 13, grey);
      it.filled_circle(312, 155, 6, grey);
      it.line(50, 162, 50, 168, grey);
      it.line(51, 162, 51, 166, grey);
      it.line(52, 162, 52, 164, grey);
      it.draw_pixel_at(53, 163, grey);
      it.line(53, 162, 56, 162, grey);
      //buttom line
      it.filled_rectangle(0, 189, 50, 21, yellow);
      it.filled_circle(25, 214, 25, yellow);
      it.filled_rectangle(30, 217, 275, 23, yellow);
      it.filled_circle(308, 228, 11, yellow);
      it.line(51, 216, 51, 212, yellow);
      it.line(52, 216, 52, 214, yellow);
      it.draw_pixel_at(53, 215, yellow);
      it.line(53, 216, 56, 216, yellow);
      
      //Data
      it.print(217, 0, id(OkudaBold), red, "Guten Tag!");
      it.print(7, 85, id(Okuda15), "Weather");
      it.printf(70, 25, id(Okuda), "Temp. Balkon: %.1f °C", id(temp_balkon).state);
      it.printf(70, 50, id(Okuda), "Temp. Wohnzimmer: %.1f °C", id(temp_wohnzimmer).state);
      it.printf(70, 75, id(Okuda), "Wetter heute: %s", id(wetter).state.c_str());
      it.printf(70, 100, id(Okuda), "%s in den nachsten 30min.", id(regen30min).state.c_str());
      
      it.strftime(193, 133, id(OkudaBold), yellow2, "%H:%M %d.%m.%y", id(homeassistant_time).now());
      it.printf(70, 161, id(Okuda), "Alles Licht aus: %s", id(lightcheck).state.c_str());
      it.printf(70, 186, id(Okuda), "Gelber Sack in %s", id(nachster_abfall).state.c_str());
      it.print(11, 188, id(Okuda15), "Status");


font: 
  - file: "Okuda.ttf" #normal
    id: Okuda
    size: 25
  - file: "OkudaBold.ttf" #fett
    id: OkudaBold
    size: 25
  - file: "Okuda.ttf" #normal
    id: Okuda15
    size: 15

color:
  - id: red
    red: 100%
    green: 0%
    blue: 0%
  - id: rosa
    red_int: 204
    green_int: 154
    blue_int: 204
    white_int: 0
  - id: blue
    red_int: 150
    green_int: 160
    blue_int: 255
    white_int: 0
  - id: grey
    red_int: 180
    green_int: 178
    blue_int: 180
    white_int: 0
  - id: yellow
    red_int: 212
    green_int: 164
    blue_int: 114
    white_int: 0
  - id: yellow2
    red_int: 240
    green_int: 196
    blue_int: 19
    white_int: 0

# Define a PWM output on the ESP32
output:
  - platform: ledc
    pin: 4
    id: gpio_4_backlight_pwm

# Define a monochromatic, dimmable light for the backlight. Such light is later controllable via HA dashboard
light:
  - platform: monochromatic
    output: gpio_4_backlight_pwm
    name: "Display Backlight"
    id: back_light

sensor:
  - platform: homeassistant
    id: temp_balkon
    entity_id: sensor.temperature_5

  - platform: homeassistant
    id: temp_wohnzimmer
    entity_id: sensor.temperature_2
    
text_sensor:
  - platform: homeassistant
    id: wetter
    entity_id: weather.home
    filters:
    - substitute:
      - "sunny -> sonnig"
      - "partlycloudy -> teilweise wolkig"
      - "rainy -> Regen"
      - "windy -> windig"
      - "snowy -> Schnee"
      - "lightning -> Gewitter"
      - "cloudy -> wolkig"

  - platform: homeassistant
    id: lightcheck
    entity_id: light.alle_lichter
    filters:
    - substitute:
      - "on -> nein"
      - "off -> ja"

  - platform: homeassistant #https://www.youtube.com/watch?v=SoBjbW4zjWs
    id: nachster_abfall
    entity_id: sensor.mullabfuhr_wann
    filters:
    - substitute:
      - "1 -> morgen"
      - "2 -> ubermorgen"
      - "3 -> drei Tagen"
      - "4 -> vier Tagen"
      - "5 -> funf Tagen"
      - "6 -> sechs Tagen"
      - "7 -> naechste Woche"
      - "8 -> naechste Woche"
      - "9 -> naechste Woche"
      - "10 -> naechste Woche"
      - "11 -> naechste Woche"
      - "12 -> naechste Woche"
      - "13 -> naechste Woche"
      - "14 -> naechste Woche"
      
  - platform: homeassistant #https://www.ajfriesen.com/rain-warning-sensor-with-home-assistant/
    id: regen30min
    entity_id: sensor.rrrainin30min
    filters:
    - substitute:
      - "1 -> Es regnet"
      - "0 -> Kein Regen"

Config.yaml code:

- sensor: #Sensor zur detektion in wie vielen Tagen der nächste Eintrag im Müllabfuhr Kalender ist und wie dieser heist
  - unique_id: mullabfuhr_template   #https://www.youtube.com/watch?v=SoBjbW4zjWs
    name: mullabfuhr_wann
    state: "{{(((as_timestamp(state_attr('calendar.mullabfuhr', 'start_time')))-as_timestamp(now())) | int /60/1440) | round(0,'ceil')}}" 
    #state: "{{ state_attr('calendar.mullabfuhr','message')~'|'~(((as_timestamp(state_attr('calendar.mullabfuhr', 'start_time')))-as_timestamp(now())) | int /60/1440) | round(0,'ceil')}}" 
    icon: mdi:trash-can

sensor: # Sensor zur detektion ob es in 30min,60min oder 120min regnet. 
  - platform: rest  #https://www.ajfriesen.com/rain-warning-sensor-with-home-assistant/
    name: regenradar
    scan_interval: 300
    json_attributes:
      - raintext
      - rainin30min
      - rainin60min
      - rainin120min
    resource: https://morgenwirdes.de/api/v3/rain.php?lat=XX&long=YY
    value_template: "{{ value_json.raintext }}"
  - platform: template
    sensors:
      rrraintext:
        friendly_name: "Vorhersage"
        value_template: "{{ state_attr('sensor.regenradar', 'raintext') }}"
      rrrainin30min:
        friendly_name: "Regen in 30min"
        value_template: "{{ state_attr('sensor.regenradar', 'rainin30min') }}"
      rrrainin60min:
        friendly_name: "Regen in 60min"
        value_template: "{{ state_attr('sensor.regenradar', 'rainin60min') }}"
      rrrainin120min:
        friendly_name: "Regen in 120min"
        value_template: "{{ state_attr('sensor.regenradar', 'rainin120min') }}"

Used tutorials:

  • How to make events in the local HA calendar and present them in espHome LINK
  • How to implement an Rain Forcast for the next 30-120min LINK
  • a comparable project LINK

Future plans:

  • implement deepsleep with a wake up from a PIR
  • a way more sofisticated mirror
4 Likes

Any progress on this?

1 Like