Update esphome to 2023.12.x

same for me
"

============================================================

Edit substitutions for your naming, devices, and passwords here.

By default, this code assumes you have a climate thermostat entity and a separate temperature sensor.

If you want to use the temperature sensor built into your thermostat instead, scroll down to sensor: and use the commented code.

thermostat_entity: Your climate entity that this device will control.

temperature_entity: Your temperature sensor that measures the current temperature in the room. Only used for display purposes.

============================================================

An experiment by Guy Sie

- GitHub

- Twitter

Based on example ESPHome code by Jonny Bergdahl

- GitHub

- Twitter

- YouTube

============================================================

ESPHome YAML start

substitutions:
name: termostatotouch
friendly_name: “termostatotouch”
project_name: “Guy Sie.CYD Thermostat Controller”
project_version: “0.1.0”
thermostat_entity: climate.termostato
temperature_entity: sensor.minima_temperatura

esphome:
name: ${name}
friendly_name: ${friendly_name}
project:
name: ${project_name}
version: ${project_version}
esp32:
board: esp32dev
framework:
type: arduino

Enable logging

logger:
logs:
component: ERROR

Enable Home Assistant API

api:
encryption:
key: !secret api_key
#“8TQgHaZqzLkm6hG5XNWzaA0VpM9AmtBbMKdZqMmrX04=”

ota:
password: “fd04a0f34cf29179c929ed016adfa21b”

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

Enable fallback hotspot (captive portal) in case wifi connection fails

ap:
ssid: “Termostatotouch Fallback Hotspot”
password: “xuVpbuUlTLK3”

captive_portal:

============================================================

ESPHome Display related setup

Create a font to use, add and remove glyphs as needed.

font:

  • file:
    type: gfonts
    family: Roboto
    weight: 400
    id: roboto40
    size: 40
    glyphs: " .,°0123456789COfNAna"
  • file:
    type: gfonts
    family: Roboto
    weight: 400
    id: roboto14
    size: 14
    glyphs: " .,0123456789NAna"
  • file:
    type: gfonts
    family: Roboto
    weight: 400
    id: roboto10
    size: 10
    glyphs: “.CTNAurentagoci”
  • file:
    type: gfonts
    family: Material+Symbols+Outlined
    weight: 600
    id: icon_font_34
    size: 34
    glyphs: [
    “\U0000f16a”, # mdi-fire
    “\U0000e8ac”, # mdi-power
    “\U0000e145”, # mdi-plus
    “\U0000e15b”, # mdi-minus
    ]

Create color scheme

color:

  • id: white
    hex: ffffff
  • id: ha_blue
    hex: 18bcf2
  • id: ha_yellow
    hex: ffd502
  • id: light_orange
    hex: edb597
  • id: mid_orange
    hex: df7d4a
  • id: dark_orange
    hex: bf5922
  • id: light_gray
    hex: e1e1e1
  • id: mid_gray
    hex: c0c0c0
  • id: dark_gray
    hex: 9e9e9e
  • id: blackish
    hex: “212121”

Define the graph

TO DO: Weird behavior of target temperature line. Seems to not display until changed? Figure this out.

graph:

  • id: tempgraph
    duration: 1h
    width: 260
    height: 100
    x_grid: 10min
    y_grid: 2.5
    max_value: 25.0
    min_value: 15.0
    traces:

Display current temperature as a solid blue line

  - sensor: current_temperature
    line_type: SOLID
    line_thickness: 2
    color: ha_blue

Display target temperature as a dashed yellow line

  - sensor: target_temperature
    line_type: DASHED
    line_thickness: 2
    color: ha_yellow

============================================================

Home Assistant related setup

light:

Set up display backlight

  • platform: monochromatic
    output: backlight_pwm
    name: Display Backlight
    id: backlight
    restore_mode: ALWAYS_ON

Set up LED on the back and turn it off by default

  • platform: rgb
    name: RGB
    id: led
    red: led_red
    green: led_green
    blue: led_blue
    restore_mode: ALWAYS_OFF

Set up sensors for thermostat state, current temperature, and target temperature

text_sensor:

  • platform: homeassistant
    id: thermostat
    entity_id: ${thermostat_entity}
    internal: true

sensor:

If you wish to use the current temperature from your thermostat, replace the following code block with the commmented code.

  • platform: homeassistant
    id: current_temperature
    entity_id: ${temperature_entity}
    internal: true

- platform: homeassistant

id: current_temperature

entity_id: ${thermostat_entity}

attribute: current_temperature

internal: true

  • platform: homeassistant
    id: target_temperature
    entity_id: ${thermostat_entity}
    attribute: temperature
    internal: true

Set up sensor for LDR (ambient light meter) in V

  • platform: adc
    pin: GPIO34
    id: board_ldr
    name: “board_ldr”
    update_interval: 1000ms
    entity_category: “diagnostic”
    accuracy_decimals: 3
    internal: true
    filters:
    • sliding_window_moving_average:
      window_size: 10
      send_every: 10

Reports the ambient light in %

TO DO: Could use this to auto adjust brightness of backlight?

Values are based on simple desk testing:

Full dark with backlight turned off: 1.039V

Full dark with backlight turned on: 0.403V

Full light: 0.075V

  • platform: copy
    source_id: board_ldr
    name: “Ambient Light”
    filters:
    • lambda: return min(max((100 - (((x - 0.075) / (1.039 - 0.075)) * 100)), 0.0), 100.0);
      unit_of_measurement: “%”
      accuracy_decimals: 0

Reports the WiFi signal strength/RSSI in dB

  • platform: wifi_signal
    name: “WiFi Signal dB”
    id: wifi_signal_db
    update_interval: 60s
    entity_category: “diagnostic”

Reports the WiFi signal strength in %

  • platform: copy
    source_id: wifi_signal_db
    name: “WiFi Signal Percent”
    filters:
    • lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
      unit_of_measurement: “Signal %”
      entity_category: “diagnostic”

Setup binary sensors for the four areas for touch

binary_sensor:

Minus button to lower target temperature, only active in heating mode

  • platform: touchscreen
    name: “Minus button”
    id: minusbutton
    x_min: 20
    x_max: 80
    y_min: 20
    y_max: 60
    on_press:
    • if:
      condition:
      text_sensor.state:
      id: thermostat
      state: ‘heat’
      then:
      - component.update: cyd
      - homeassistant.service:
      service: climate.set_temperature
      data:
      entity_id: ${thermostat_entity}
      temperature: !lambda |-
      auto target = id(target_temperature).state;
      target -= 0.5;
      return target;
      - logger.log: “Lowering target temperature”
      on_release:
    • component.update: cyd

Plus button to raise target temperature, only active in heating mode

  • platform: touchscreen
    name: “Plus button”
    id: plusbutton
    x_min: 240
    x_max: 300
    y_min: 20
    y_max: 60
    on_press:
    • if:
      condition:
      text_sensor.state:
      id: thermostat
      state: ‘heat’
      then:
      - component.update: cyd
      - homeassistant.service:
      service: climate.set_temperature
      data:
      entity_id: ${thermostat_entity}
      temperature: !lambda |-
      auto target = id(target_temperature).state;
      target += 0.5;
      return target;
      - logger.log: “Increasing target temperature”
      on_release:
    • component.update: cyd

Heat button to turn on heating mode

  • platform: touchscreen
    name: “Heat button”
    id: heatbutton
    x_min: 20
    x_max: 80
    y_min: 80
    y_max: 120
    on_press:
    • if:
      condition:
      text_sensor.state:
      id: thermostat
      state: ‘off’
      then:
      - component.update: cyd
      - logger.log: “Turn on heating mode”
      - homeassistant.service:
      service: climate.turn_on
      data:
      entity_id: ${thermostat_entity}
      on_release:
    • component.update: cyd

Off button to turn off heating mode

  • platform: touchscreen
    name: “Off button”
    id: offbutton
    x_min: 240
    x_max: 300
    y_min: 80
    y_max: 120
    on_press:
    • if:
      condition:
      text_sensor.state:
      id: thermostat
      state: ‘heat’
      then:
      - component.update: cyd
      - logger.log: “Turn off heating mode”
      - homeassistant.service:
      service: climate.turn_off
      data:
      entity_id: ${thermostat_entity}
      on_release:
    • component.update: cyd

============================================================

Load images and icons

Not used anymore.

MDI images now loaded as google material design icon font glyphs

Buttons are now made out of shaded filled rectangles

============================================================

Hardware related setup

Setup SPI for the display. The ESP32-2432S028R uses separate SPI buses for display and touch

spi:

  • id: tft
    clk_pin: GPIO14
    mosi_pin: GPIO13
    miso_pin: GPIO12
  • id: touch
    clk_pin: GPIO25
    mosi_pin: GPIO32
    miso_pin: GPIO39

Setup board power switches

switch:

  • platform: restart
    name: “$friendly_name restart”
  • platform: shutdown
    name: “$friendly_name shutdown”
  • platform: safe_mode
    name: “$friendly_name restart (Safe Mode)”

Setup a pin to control the backlight

output:

  • platform: ledc
    pin: GPIO21
    id: backlight_pwm

Setup channels for the red/green/blue of the LED on the back

  • platform: ledc
    pin: GPIO4
    id: led_red
    inverted: true
  • platform: ledc
    pin: GPIO16
    id: led_green
    inverted: true
  • platform: ledc
    pin: GPIO17
    id: led_blue
    inverted: true

Set up sound

TO DO: If speaker added to JST, we can use it to provide audio feedback on button presses.

#i2s_audio:

i2s_lrclk_pin: GPIO25

i2s_bclk_pin: GPIO26

#media_player:

- platform: i2s_audio

name: ESPHome I2S Media Player

dac_type: internal

mode: stereo

Setup the ili9xxx platform

Display is used as 240x320 by default so we rotate it to 90°

We print date and time wth the strftime() function, see the ESPHome documentation to

format date and atime to your locale.

display:

  • platform: ili9xxx
    model: ili9341
    spi_id: tft
    cs_pin: GPIO15
    dc_pin: GPIO2
    rotation: 90
    id: cyd

Draw interface for touchscreen button control, display of current and target temperature, and graph of current temperature

Only show target temperature and buttons if thermostat is in heating mode

Buttons change shading to look raised or sunken based on heating mode or binary sensor

lambda: |-
  it.fill(id(Color::BLACK));

  if (id(thermostat).state == "heat") {
    if (id(minusbutton).state) {
      it.filled_rectangle(20, 20, 60, 40, id(mid_gray));
      it.filled_rectangle(20, 58, 60, 2, id(light_gray));
      it.filled_rectangle(78, 20, 2, 40, id(light_gray));
      it.filled_rectangle(20, 20, 60, 2, id(dark_gray));
      it.filled_rectangle(20, 20, 2, 40, id(dark_gray));
      it.printf(50, 40, id(icon_font_34), blackish, TextAlign::CENTER, "\U0000e15b");
    }
    else {
      it.filled_rectangle(20, 20, 60, 40, id(mid_gray));
      it.filled_rectangle(20, 20, 60, 2, id(light_gray));
      it.filled_rectangle(20, 20, 2, 40, id(light_gray));
      it.filled_rectangle(20, 58, 60, 2, id(dark_gray));
      it.filled_rectangle(78, 20, 2, 40, id(dark_gray));
      it.printf(50, 40, id(icon_font_34), blackish, TextAlign::CENTER, "\U0000e15b");
    }

    if (id(plusbutton).state) {
      it.filled_rectangle(240, 20, 60, 40, id(mid_gray));
      it.filled_rectangle(240, 58, 60, 2, id(light_gray));
      it.filled_rectangle(298, 20, 2, 40, id(light_gray));
      it.filled_rectangle(240, 20, 60, 2, id(dark_gray));
      it.filled_rectangle(240, 20, 2, 40, id(dark_gray));
      it.printf(270, 40, id(icon_font_34), blackish, TextAlign::CENTER, "\U0000e145");
    }
    else {
      it.filled_rectangle(240, 20, 60, 40, id(mid_gray));
      it.filled_rectangle(240, 20, 60, 2, id(light_gray));
      it.filled_rectangle(240, 20, 2, 40, id(light_gray));
      it.filled_rectangle(240, 58, 60, 2, id(dark_gray));
      it.filled_rectangle(298, 20, 2, 40, id(dark_gray));
      it.printf(270, 40, id(icon_font_34), blackish, TextAlign::CENTER, "\U0000e145");
    }
  }

  if (id(thermostat).has_state()) {
    if (id(thermostat).state == "heat") {
      it.printf(160, 10, id(roboto10), TextAlign::TOP_CENTER, "Target");
      it.printf(160, 17, id(roboto40), TextAlign::TOP_CENTER, "%.1f °C", id(target_temperature).state);
    }
    else {
      if (id(thermostat).state == "off") {
        it.printf(160, 17, id(roboto40), TextAlign::TOP_CENTER, "Off");
      }
    }
  } 

  if (id(current_temperature).has_state()) {
    it.printf(160, 70, id(roboto10), TextAlign::TOP_CENTER, "Current");
    it.printf(160, 77, id(roboto40), TextAlign::TOP_CENTER, "%.1f °C", id(current_temperature).state);
    if (id(current_temperature).has_state()) {
      it.printf(160, 77, id(roboto40), TextAlign::TOP_CENTER, "%.1f °C", id(current_temperature).state);
    }
    if (id(thermostat).state == "heat") {
      it.filled_rectangle(20, 80, 60, 40, id(mid_orange));
      it.filled_rectangle(20, 118, 60, 2, id(light_orange));
      it.filled_rectangle(78, 80, 2, 40, id(light_orange));          
      it.filled_rectangle(20, 80, 60, 2, id(dark_orange));
      it.filled_rectangle(20, 80, 2, 40, id(dark_orange));
      it.printf(50, 100, id(icon_font_34), white, TextAlign::CENTER, "\U0000f16a");
   }
    else {
      it.filled_rectangle(20, 80, 60, 40, id(mid_gray));
      it.filled_rectangle(20, 80, 60, 2, id(light_gray));
      it.filled_rectangle(20, 80, 2, 40, id(light_gray));
      it.filled_rectangle(20, 118, 60, 2, id(dark_gray));
      it.filled_rectangle(78, 80, 2, 40, id(dark_gray));
      it.printf(50, 100, id(icon_font_34), blackish, TextAlign::CENTER, "\U0000f16a");
    }
    if (id(thermostat).state == "off") {
      it.filled_rectangle(240, 80, 60, 40, id(mid_gray));
      it.filled_rectangle(240, 118, 60, 2, id(light_gray));
      it.filled_rectangle(298, 80, 2, 40, id(light_gray));
      it.filled_rectangle(240, 80, 60, 2, id(dark_gray));
      it.filled_rectangle(240, 80, 2, 40, id(dark_gray));
      it.printf(270, 100, id(icon_font_34), blackish, TextAlign::CENTER, "\U0000e8ac");
    }
    else {
      it.filled_rectangle(240, 80, 60, 40, id(mid_gray));
      it.filled_rectangle(240, 80, 60, 2, id(light_gray));
      it.filled_rectangle(240, 80, 2, 40, id(light_gray));
      it.filled_rectangle(240, 118, 60, 2, id(dark_gray));
      it.filled_rectangle(298, 80, 2, 40, id(dark_gray));
      it.printf(270, 100, id(icon_font_34), blackish, TextAlign::CENTER, "\U0000e8ac");
    }
  }
  else {
    it.printf(160, 70, id(roboto10), TextAlign::TOP_CENTER, "Connecting...");
  }
  it.print(10, 133, id(roboto14), TextAlign::CENTER_LEFT, "25");
  it.print(10, 155, id(roboto14), TextAlign::CENTER_LEFT, "22.5");
  it.print(10, 180, id(roboto14), TextAlign::CENTER_LEFT, "20");
  it.print(10, 205, id(roboto14), TextAlign::CENTER_LEFT, "17.5");
  it.print(10, 227, id(roboto14), TextAlign::CENTER_LEFT, "15");
  it.graph(50, 130, id(tempgraph));

Set up the xpt2046 touch platform

touchscreen:
platform: xpt2046
id: touch
cs_pin: GPIO33
interrupt_pin: GPIO36
update_interval: 50ms
report_interval: 1s
threshold: 400
calibration_x_min: 3860
calibration_x_max: 280
calibration_y_min: 340
calibration_y_max: 3860
swap_x_y: false
"