Work in progress! esphome thermostat

Unfortunately some people actually plop that whole elephant on their plate and then choke on it. Internet guides arent always right/updated, cheap chinese components dont always work right or have instructions and you can make mistakes.

There’s no penalties or fines for compiling and uploading code for one component at a time. Im not kidding here, it can take 3-4x longer to troubleshoot a medium/large size project than it takes to do it right the first time.

1 Like

Thanks for referring to my post and that it was helpful to you. if you want to tinker around to learn, individual components may be fine. But if you want something more complete and ready-made I used this card

https://it.aliexpress.com/item/1005004279039725.html?spm=a2g0o.order_list.order_list_main.47.73943696SY4rY9&gatewayAdapt=glo2ita

I got this two-channel one because my thermostat manages a pellet insert in which the logic when switched on closes the first relay for ignition while the second relay manages the power modulation based on the desired temperature. with that card all I needed was a Dallas DS18B20 temperature sensor and a Nextion touchscreen panel. writing the firmware and managing the panel took some time because on the primary RX TX PINS the panel did not respond, therefore since I needed to connect it to the serial 2 PIN 16 and 17, and since on these PINs who manufactured the board had connected the two relays I had to make a hardware change that allowed me to get my thermostat working well. By virtue of this, if you have a simple boiler, just purchase a module with a single relay

2 Likes

Thanks for the tip with the ready made board, its definitely something to think about, I’m still in two minds at the moment, I already have all the components to create a relay module from a previous load of projects and this post on a Raspberry pi forum goes a long way to helping me create a module, that hopefully one day will soldered onto a custom pcb for this project.

I hope you don’t mind I liked your design for the UI so much I adapted it using the rendering engine in EspHome, as my display doesn’t have a dedicated chip for rendering like the Nextion.

here is the updated yaml:

spi:
  clk_pin: 18
  mosi_pin: 23
  miso_pin: 19

time:
  - platform: homeassistant
    id: my_time

image:
  - file: mdi:power
    id: power
    resize: 28x28
  - file: mdi:fire
    id: fire
    resize: 28x28
  - file: mdi:chevron-up
    id: chevron_up
    resize: 70x70
  - file: mdi:chevron-down
    id: chevron_down
    resize: 70x70
  - file: mdi:home-assistant
    id: home_assistant
    resize: 90x90

font:
  - file: "fonts/Roboto-Regular.ttf"
    id: my_font
    size: 25
  - file: "fonts/Roboto-Regular.ttf"
    id: big_font
    size: 40
  - file: "fonts/Roboto-Regular.ttf"
    id: little_font
    size: 20
  - file: "fonts/Roboto-Bold.ttf"
    id: bold_font
    size: 25

color:
  - id: grey
    hex: D1D0CE
  - id: white
    hex: FFFFFF
  - id: dark_grey
    hex: A0A0A0
  - id: HA_blue
    hex: 03a9f4

display:
  - platform: ili9xxx
    model: ili9341
    dc_pin: 5
    cs_pin: 33
    reset_pin: 16
    dimensions:
      height: 240
      width: 320
    transform:
      swap_xy: true 
      mirror_y: true
      mirror_x: true
    lambda: |-
      it.fill(id(white));
      it.strftime(5, 5, id(bold_font), Color::BLACK, "%H:%M", id(my_time).now());
      it.printf(5, 35, id(bold_font), Color::BLACK, "Temperature:");
      it.printf(110, 63, id(little_font), Color::BLACK, "%.1f°C", id(temp1).state);
      it.printf(225, 70, id(big_font), Color::BLACK, "%.1f", id(my_climate).target_temperature);
      it.image(235, 5, id(chevron_up), Color::BLACK);
      it.image(235,110, id(chevron_down), Color::BLACK);
      it.filled_rectangle(205, 180, 110, 55, id(grey));
      it.filled_rectangle(90, 180, 110, 55, id(grey));
      it.image(250, 182, id(fire), id(dark_grey));
      it.print(240, 207, id(little_font), id(dark_grey), "Heat");
      it.image(135, 182, id(power), id(dark_grey));
      it.print(133, 207, id(little_font), id(dark_grey), "Off");
      it.image(5, 80, id(home_assistant), id(HA_blue));
      

touchscreen:
  platform: xpt2046
  calibration_x_min: 323
  calibration_x_max: 3769
  calibration_y_min: 438
  calibration_y_max: 3769
  transform:
    swap_xy: true
  id: my_touchscreen
  cs_pin: 32
  #interrupt_pin: 21
  update_interval: 50ms
  threshold: 400
  on_touch:
    then:
      - light.control: 
          id: back_light
          brightness: 100%
      - delay: 20s
      - light.control: 
           id: back_light
           brightness: 35%



binary_sensor:
  - platform: touchscreen
    id: temp_down
    x_min: 235
    x_max: 295
    y_min: 110
    y_max: 160
    on_press:
      then:
        - climate.control:
            id: my_climate
            target_temperature: !lambda return id(my_climate).target_temperature - 0.5;

  - platform: touchscreen
    id: temp_up
    x_min: 235
    x_max: 295
    y_min: 20
    y_max: 70
    on_press:
      then:
        - climate.control:
            id: my_climate
            target_temperature: !lambda return id(my_climate).target_temperature + 0.5;

  - platform: touchscreen
    id: heating_on
    x_min: 210
    x_max: 315
    y_min: 180
    y_max: 235
    on_press:
      then:
        if:
          condition:
            light.is_on: back_light
          then:
            - climate.control:
                id: my_climate
                mode: HEAT

  - platform: touchscreen
    id: heating_off
    x_min: 90
    x_max: 195
    y_min: 180
    y_max: 235
    on_press:
      then:
        if:
          condition:
            light.is_on: back_light
          then:
            - climate.control:
                id: my_climate
                mode: "OFF"
dallas:
  pin: 15

sensor:
  - platform: dallas
    address: 0x5d03139779b91928
    id: temp1
    name: "Temperature sensor"

switch:
  - platform: gpio
    pin: 4
    id: led_one

# Define a PWM output on the ESP32
output:
  - platform: ledc
    pin: 17
    id: gpio_17_backlight_pwm

# Define a monochromatic, dimmable light for the backlight
light:
  - platform: monochromatic
    output: gpio_17_backlight_pwm
    name: "Display Backlight"
    id: back_light
    restore_mode: RESTORE_DEFAULT_ON

climate:
  - platform: thermostat
    name: "My Thermostat"
    id: my_climate
    sensor: temp1
    heat_deadband: 0.5
    heat_overrun: 0.5
    min_heating_off_time: 6s
    min_heating_run_time: 6s
    min_idle_time: 6s
    heat_action:
      - switch.turn_on: led_one
    idle_action:
      - switch.turn_off: led_one

and here is a pic of the end result for today

one thing I do need to work out is, how to change the colour of the buttons when their state has changed, I’m sure it will be with an If, Else statement, but that’s a job for tomorrow.

2 Likes

Eeww!! I like that display! Clean and crisp graphics plus its a good size and practical. I always see people using those tiny .96" displays and im over here thinking to myself, " did they actually think that theough? Thats F’ing tiny!"

Do you have a brand/model or a link for that display?

Apologies for the amazon link, but it was a generic ILI9341 TFT display 240x320 (2.8"), so still small but it was cheap and what I needed really.

Amazon is fine. I prefer to pay a little more for the peace of mind and F supporting China and aliexpress crooks.

No worries. Just was curious. Really looking forward to this

And, just where do you think that the Amazon seller gets the parts?

2 Likes

@ GreyLinux - I am seriously impressed. When you are finished, please share the stl files because I’ll bet others will want to copy your project.

What will the relays be switching?

1 Like

Many of the same sellers sell the same stuff at aliexpress, amazon and ebay as they are all marketplaces. :globe_with_meridians:

You still support China (or Chinese business) the same way but on top you that you spend extra money to make bezoz happy :muscle: … beside amazon stuff continues peeing in bottles :raised_hands:

1 Like

So, for my use case it will be a single relay, used to switch my heating boiler, to heating ‘ON’ and for my use the boiler doesn’t require any voltage or very low voltage to signal heating ‘ON’ also known as dry contacts. However, depending on the use case it could be potentially used to signal a cooling unit. Although I might design a 2 relay unit for people that require both.

So there has been a massive update to the project, I have the yaml where I think it works well and everything works also I spent all of yesterday soldering the entire circuit to a prototype board and surprisingly everything worked first time.

The updates to the yaml are, incorporating a settings( probably not the best description) page by tapping the cogs icon on the home page, adding an IF statement for changing the HEAT and OFF to a different colour when the other is pressed. Finally the brightness configuration was a bit clunky before and didn’t work as expected so with the help of someone on discord I have remedied this to work perfectly.

This is the updated yaml

## Wiring
## --------
## The SPI pins, MOSI, MISO, and SCK, are shared between the display
## and the touchscreen (and SD card reader). 
## I did not wire TOUCH_IRQ (add update_interval: 50ms to touchscreen instead)
## The SD card pins are not connected.
##
## LCD to ESP32 (30 pin or d1 mini) wiring:
## * SCK/CLK to 18
## * MISO/DO/SDO to 19
## * MOSI/DIN/SDI to 23
## * DISP_CS to 33
## * DISP_LED to 17
## * TOUCH_CS to 32
## * DISP_DC to 21
## * DISP_RESET to 16
## * VCC to 3V3
## * GND to GND
## 
## * Relay to 22
## * Dallas to 14



esphome:
  name: thermostat-one
  friendly_name: thermostat-one

esp32:
  board: esp32dev
  framework:
    type: arduino

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

# Enable Home Assistant API
api:
  encryption:
    key: "*****************************************"

ota:
  password: "************************************"

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Thermostat-One Fallback Hotspot"
    password: "******************"

captive_portal:

spi:
  clk_pin: 18
  mosi_pin: 23
  miso_pin: 19

time:
  - platform: homeassistant
    id: my_time

text_sensor:
  - platform: wifi_info
    ip_address:
      name: IP_Address
      id: IP_Address
  - platform: version
    name: ESPHome_Version
    id: ESPHome_Version
    hide_timestamp: true

image:
  - file: mdi:power
    id: power
    resize: 28x28
  - file: mdi:fire
    id: fire
    resize: 28x28
  - file: mdi:chevron-up
    id: chevron_up
    resize: 70x70
  - file: mdi:chevron-down
    id: chevron_down
    resize: 70x70
  - file: mdi:home-assistant
    id: home_assistant
    resize: 90x90
  - file: mdi:cogs
    id: cogs
    resize: 50x50
  - file: mdi:home
    id: home
    resize: 50x50

font:
  - file: "fonts/Roboto-Regular.ttf"
    id: big_font
    size: 40
  - file: "fonts/Roboto-Regular.ttf"
    id: little_font
    size: 20
  - file: "fonts/Roboto-Bold.ttf"
    id: bold_font
    size: 25

color:
  - id: grey
    hex: D1D0CE
  - id: white
    hex: FFFFFF
  - id: dark_grey
    hex: A0A0A0
  - id: HA_blue
    hex: 03a9f4

display:
  - platform: ili9xxx
    model: ili9341
    id: my_display
    pages:
      - id: homepage
        lambda: |-
          it.fill(id(white));
          it.strftime(5, 5, id(bold_font), Color::BLACK, "%H:%M", id(my_time).now());
          it.printf(5, 35, id(bold_font), Color::BLACK, "Temperature:");
          it.printf(110, 63, id(little_font), Color::BLACK, "%.1f°C", id(temp1).state);
          it.printf(225, 70, id(big_font), Color::BLACK, "%.1f", id(my_climate).target_temperature);
          it.image(235, 5, id(chevron_up), Color::BLACK);
          it.image(235,110, id(chevron_down), Color::BLACK);
          it.image(5, 80, id(home_assistant), id(HA_blue));

          it.image(20, 180, id(cogs), id(dark_grey));
      
          if (id(my_climate).mode != CLIMATE_MODE_HEAT) {
            it.filled_rectangle(90, 180, 110, 55, id(HA_blue));
            it.image(135, 182, id(power), id(white));
            it.print(133, 207, id(little_font), id(white), "Off");
            it.filled_rectangle(205, 180, 110, 55, id(grey));
            it.print(240, 207, id(little_font), id(dark_grey), "Heat");
            it.image(250, 182, id(fire), id(dark_grey));
          } else {
            it.filled_rectangle(90, 180, 110, 55, id(grey));
            it.image(135, 182, id(power), id(dark_grey));
            it.print(133, 207, id(little_font), id(dark_grey), "Off");
            it.filled_rectangle(205, 180, 110, 55, id(HA_blue));
            it.print(240, 207, id(little_font), id(white), "Heat");
            it.image(250, 182, id(fire), id(white));
            }
      - id: settings_page
        lambda: |-
          it.fill(id(white));
          it.image(10, 10, id(home), id(dark_grey));
          it.print(90, 10, id(bold_font), Color::BLACK, "Settings");
          it.printf(5, 60, id(little_font), Color::BLACK, "ESPHome Version: %s",id(ESPHome_Version).state.c_str());
          it.printf(5, 85, id(little_font), Color::BLACK, "IP Address: %s", id(IP_Address).state.c_str());
          it.printf(5, 110, id(little_font), Color::BLACK, "Uptime: %.f minutes",id(ESP_uptime).state);
          it.printf(5, 135, id(little_font), Color::BLACK, "Wifi Signal: %.0f dBm",id(ESP_wifi_signal).state);
          it.printf(5, 160, id(little_font), Color::BLACK, "HA Status: %s", id(HA_status).state ? "Connected" : "Disconnected");
          
    dc_pin: 21
    cs_pin: 33
    reset_pin: 16
    dimensions:
      height: 240
      width: 320
    transform:
      swap_xy: true 
      mirror_y: true
      mirror_x: true
         

      
      
      

touchscreen:
  platform: xpt2046
  calibration_x_min: 323
  calibration_x_max: 3769
  calibration_y_min: 438
  calibration_y_max: 3769
  transform:
    swap_xy: true
  id: my_touchscreen
  cs_pin: 32
  update_interval: 50ms
  threshold: 400
  on_touch:
    - binary_sensor.template.publish:
        id: touching
        state: ON
  on_release:
    - binary_sensor.template.publish:
        id: touching
        state: OFF

binary_sensor:
  - platform: template
    id: touching
    filters:
      delayed_off: 20s
    on_press:
      - light.control: 
          id: back_light
          brightness: 100%
    on_release:
      - light.control: 
          id: back_light
          brightness: 35%

  - platform: status
    name: "Thermostat One State"
    id: HA_status

  - platform: touchscreen
    id: temp_down
    x_min: 235
    x_max: 295
    y_min: 110
    y_max: 160
    page_id: homepage
    on_press:
      then:
        - climate.control:
            id: my_climate
            target_temperature: !lambda return id(my_climate).target_temperature - 0.5;

  - platform: touchscreen
    id: temp_up
    x_min: 235
    x_max: 295
    y_min: 20
    y_max: 70
    page_id: homepage
    on_press:
      then:
        - climate.control:
            id: my_climate
            target_temperature: !lambda return id(my_climate).target_temperature + 0.5;

  - platform: touchscreen
    id: heating_on
    x_min: 210
    x_max: 315
    y_min: 180
    y_max: 235
    page_id: homepage
    on_press:
      then:
        - climate.control:
            id: my_climate
            mode: HEAT

  - platform: touchscreen
    id: heating_off
    x_min: 90
    x_max: 195
    y_min: 180
    y_max: 235
    page_id: homepage
    on_press:
      then:
        - climate.control:
            id: my_climate
            mode: "OFF"

  - platform: touchscreen
    id: settings_menu
    x_min: 20
    x_max: 70
    y_min: 180
    y_max: 235
    page_id: homepage
    on_press:
        - display.page.show: settings_page
        - component.update: my_display

  - platform: touchscreen
    id: home_page_menu
    x_min: 10
    x_max: 60
    y_min: 10
    y_max: 60
    page_id: settings_page
    on_press:
        - display.page.show: homepage
        - component.update: my_display
  
dallas:
  pin: 14
  update_interval: 10s

sensor:
  - platform: dallas
    address: 0x5d03139779b91928
    id: temp1
    name: "Temperature sensor"

  - platform: uptime
    id: ESP_uptime
    filters:
      - lambda: return x / 60.0;
    unit_of_measurement: minutes

  - platform: wifi_signal
    id: ESP_wifi_signal
    update_interval: 60s

switch:
  - platform: gpio
    pin: 22
    id: led_one

# Define a PWM output on the ESP32
output:
  - platform: ledc
    pin: 17
    id: gpio_17_backlight_pwm

# Define a monochromatic, dimmable light for the backlight
light:
  - platform: monochromatic
    output: gpio_17_backlight_pwm
    name: "Display Backlight"
    id: back_light
    restore_mode: RESTORE_DEFAULT_ON

climate:
  - platform: thermostat
    name: "My Thermostat"
    id: my_climate
    sensor: temp1
    heat_deadband: 0.5
    heat_overrun: 0.5
    min_heating_off_time: 6s
    min_heating_run_time: 6s
    min_idle_time: 6s
    heat_action:
      - switch.turn_on: led_one
    idle_action:
      - switch.turn_off: led_one
    

and here are some photos of the soldered circuit, complete with working relay and the new UI changes.




admittedly not my finest soldering as my best iron broke so I had to use a very old iron with no heating controls


all powered from 240V mains.

later today I will be working on a circuit diagram that I will post of the complete circuit

4 Likes

Here is the Circuit schematic for the above prototype, its not the best layout, Ive not had much experience creating circuit diagrams. Hopefully its legible enough to gain enough of an insight, plus the pinout from the yaml above should also help.

Glad to see you using female headers and then plugging devices into it. It kind of makes me cringe when i see people solder everything to a prototype board. I learned that lesson quickly and its really convenient being able to pull things off if they need replaced.

Be very careful with separating the heat from the ESP and screen from the temperature sensor. I’m using a smaller ESP8266 and little OLED screen and still had to have a couple of goes to avoid self-heating. My case now looks like this:

The Dallas sensor is behind the “front bars” insulated from the D1 Mini which is in the wall cavity with some expanded polystyrene foam and a plastic “lid”. The vents on the bottom and top channel airflow for the D1 Mini and the screen away from the sensor. I also turned the wifi transmission power right down to avoid heating.

1 Like

I couldn’t agree more, the other components are cheap and easier to solder/de-solder when needing to be replaced but pin headers on displays or micro-controllers/SOC are a nightmare to de-solder. I will probably have a female pin header for the display on the final pcb also, not only for easy replacement but also, so that I can mount the pcb and the rear case to the electrical back box where my current thermostat is and then mount the display and front cover after.

nice, I like the case very sleek. Yeah I learnt this lesson the hard way, when creating an EspHome temp sensors years ago, I couldn’t work out why my sensor readings were so far off compared to a proprietary sensor I had for measurement.

Don’t apologize. Your schematic is millions of times better than Fritzing crap.

@GreyLinux thank you for taking the time to document your journey - you have helped me tremendously. I’ve been inspired to share my progress as well in case anyone would find it helpful for themselves.

I used the following esp32 device and display:
http://amazon.com/gp/product/B0B1M9S9V6
https://www.amazon.com/dp/B08D5ZD528


substitutions:
  node_id: "master_bedroom_thermostat"

globals:
  - id: temp_in_fahrenheit
    restore_value: 'no'
    type: float
  - id: climate_mode
    restore_value: 'no'
    type: std::string
  - id: active_climate_action
    restore_value: 'no'
    type: std::string

time:
  - platform: homeassistant
    id: my_time
    on_time: 
      - seconds: 0
        minutes: /1
        then:
          - component.update: tft_display
    

# i2c:
#   - id: bus_a
#     sda: 47 # minid1 - 21 # Blue
#     scl: 48 # minid1 - 22 # Green
#     scan: true


### >>>>>>>>>>>>> Text Sensors <<<<<<<<<<<<<<< ###
text_sensor:
    # Last Rain - date time
  - platform: homeassistant
    name: outdoor_last_rain_sensor
    id: outdoor_last_rain_sensor
    entity_id: sensor.my_weather_station_last_rain
    internal: true
    on_value:
      if:
        condition:
          - lambda: 'return id(tft_display).get_active_page() == id(weather_station_page);'
        then:
          - component.update: tft_display

### >>>>>>>>>>>>> Sensors <<<<<<<<<<<<<<< ###
sensor:
  - platform: uptime
    id: ${node_id}_uptime
    name: Uptime
  - platform: homeassistant
    name: temp_sensor_celsius
    id: temp_sensor_celsius
    entity_id: sensor.master_bedroom_mmwave_sensor_temp
    internal: true
    filters: 
      - lambda: 
          id(temp_in_fahrenheit) = x;
          return (x - 32) * 5/9;
    on_value: 
      then:
        - component.update: tft_display
  - platform: homeassistant
    name: humidity_sensor
    id: humidity_sensor
    entity_id: sensor.master_bedroom_mmwave_sensor_humidity
    internal: true
    on_value: 
      then:
        - component.update: tft_display
### >>>>>>>>>>>>> Weather Station Sensors <<<<<<<<<<<<<<< ###
    # Temp
  - platform: homeassistant
    name: outdoor_temp_sensor
    id: outdoor_temp_sensor
    entity_id: sensor.my_weather_station_temp
    internal: true
    on_value:
      if:
        condition:
          - lambda: 'return id(tft_display).get_active_page() == id(weather_station_page);'
        then:
          - component.update: tft_display
    # Humidity
  - platform: homeassistant
    name: outdoor_humidity_sensor
    id: outdoor_humidity_sensor
    entity_id: sensor.my_weather_station_humidity
    internal: true
    on_value:
      if:
        condition:
          - lambda: 'return id(tft_display).get_active_page() == id(weather_station_page);'
        then:
          - component.update: tft_display
    # Feels Like Temp
  - platform: homeassistant
    name: outdoor_feels_like_temp_sensor
    id: outdoor_feels_like_temp_sensor
    entity_id: sensor.my_weather_station_feels_like
    internal: true
    on_value: 
      then:
        - component.update: tft_display
    # Absolute Pressure
  - platform: homeassistant
    name: outdoor_absolute_pressure_sensor
    id: outdoor_absolute_pressure_sensor
    entity_id: sensor.my_weather_station_abs_pressure
    internal: true
    on_value:
      if:
        condition:
          - lambda: 'return id(tft_display).get_active_page() == id(weather_station_page);'
        then:
          - component.update: tft_display
    # Relative Pressure
  - platform: homeassistant
    name: outdoor_relative_pressure_sensor
    id: outdoor_relative_pressure_sensor
    entity_id: sensor.my_weather_station_rel_pressure
    internal: true
    on_value:
      if:
        condition:
          - lambda: 'return id(tft_display).get_active_page() == id(weather_station_page);'
        then:
          - component.update: tft_display
    # Dew Point
  - platform: homeassistant
    name: outdoor_dew_point_sensor
    id: outdoor_dew_point_sensor
    entity_id: sensor.my_weather_station_dew_point
    internal: true
    on_value:
      if:
        condition:
          - lambda: 'return id(tft_display).get_active_page() == id(weather_station_page);'
        then:
          - component.update: tft_display
    # Solar UV Index
  - platform: homeassistant
    name: outdoor_solar_uv_index_sensor
    id: outdoor_solar_uv_index_sensor
    entity_id: sensor.my_weather_station_uv_index
    internal: true
    on_value:
      if:
        condition:
          - lambda: 'return id(tft_display).get_active_page() == id(weather_station_page);'
        then:
          - component.update: tft_display
    # Solar Rad W/m2
  - platform: homeassistant
    name: outdoor_solar_rad_watts_sensor
    id: outdoor_solar_rad_watts_sensor
    entity_id: sensor.my_weather_station_solar_rad
    internal: true
    on_value:
      if:
        condition:
          - lambda: 'return id(tft_display).get_active_page() == id(weather_station_page);'
        then:
          - component.update: tft_display
    # Solar Rad Lux
  - platform: homeassistant
    name: outdoor_solar_rad_lux_sensor
    id: outdoor_solar_rad_lux_sensor
    entity_id: sensor.my_weather_station_solar_rad_lx
    internal: true
    on_value:
      if:
        condition:
          - lambda: 'return id(tft_display).get_active_page() == id(weather_station_page);'
        then:
          - component.update: tft_display
    # Wind Speed
  - platform: homeassistant
    name: outdoor_wind_speed_sensor
    id: outdoor_wind_speed_sensor
    entity_id: sensor.my_weather_station_wind_speed
    internal: true
    on_value:
      if:
        condition:
          - lambda: 'return id(tft_display).get_active_page() == id(weather_station_page);'
        then:
          - component.update: tft_display
    # Wind Direction
  - platform: homeassistant
    name: outdoor_wind_direction_sensor
    id: outdoor_wind_direction_sensor
    entity_id: sensor.my_weather_station_wind_dir
    internal: true
    on_value:
      if:
        condition:
          - lambda: 'return id(tft_display).get_active_page() == id(weather_station_page);'
        then:
          - component.update: tft_display
    # Wind Gust
  - platform: homeassistant
    name: outdoor_wind_gust_sensor
    id: outdoor_wind_gust_sensor
    entity_id: sensor.my_weather_station_wind_gust
    internal: true
    on_value:
      if:
        condition:
          - lambda: 'return id(tft_display).get_active_page() == id(weather_station_page);'
        then:
          - component.update: tft_display
    # Event Rain
  - platform: homeassistant
    name: outdoor_event_rain_sensor
    id: outdoor_event_rain_sensor
    entity_id: sensor.my_weather_station_event_rain
    internal: true
    on_value:
      if:
        condition:
          - lambda: 'return id(tft_display).get_active_page() == id(weather_station_page);'
        then:
          - component.update: tft_display
    # Hourly Rate Rain
  - platform: homeassistant
    name: outdoor_hourly_rain_rate_sensor
    id: outdoor_hourly_rain_rate_sensor
    entity_id: sensor.my_weather_station_hourly_rain_rate
    internal: true
    on_value:
      if:
        condition:
          - lambda: 'return id(tft_display).get_active_page() == id(weather_station_page);'
        then:
          - component.update: tft_display
    # Daily Rain
  - platform: homeassistant
    name: outdoor_daily_rain_sensor
    id: outdoor_daily_rain_sensor
    entity_id: sensor.my_weather_station_daily_rain
    internal: true
    on_value:
      if:
        condition:
          - lambda: 'return id(tft_display).get_active_page() == id(weather_station_page);'
        then:
          - component.update: tft_display
    # Weekly Rain
  - platform: homeassistant
    name: outdoor_weekly_rain_sensor
    id: outdoor_weekly_rain_sensor
    entity_id: sensor.my_weather_station_weekly_rain
    internal: true
    on_value:
      if:
        condition:
          - lambda: 'return id(tft_display).get_active_page() == id(weather_station_page);'
        then:
          - component.update: tft_display
    # Monthly Rain
  - platform: homeassistant
    name: outdoor_monthly_rain_sensor
    id: outdoor_monthly_rain_sensor
    entity_id: sensor.my_weather_station_monthly_rain
    internal: true
    on_value:
      if:
        condition:
          - lambda: 'return id(tft_display).get_active_page() == id(weather_station_page);'
        then:
          - component.update: tft_display
    # Yearly Rain
  - platform: homeassistant
    name: outdoor_yearly_rain_sensor
    id: outdoor_yearly_rain_sensor
    entity_id: sensor.my_weather_station_yearly_rain
    internal: true
    on_value:
      if:
        condition:
          - lambda: 'return id(tft_display).get_active_page() == id(weather_station_page);'
        then:
          - component.update: tft_display
    # Lifetime Rain
  - platform: homeassistant
    name: outdoor_lifetime_rain_sensor
    id: outdoor_lifetime_rain_sensor
    entity_id: sensor.my_weather_station_lifetime_rain
    internal: true
    on_value:
      if:
        condition:
          - lambda: 'return id(tft_display).get_active_page() == id(weather_station_page);'
        then:
          - component.update: tft_display
### >>>>>>>>>>>>> BME280 SENSOR <<<<<<<<<<<<<<< ###
  # - platform: bme280_i2c 
  #   temperature:
  #     id: ${node_id}_temp_sensor
  #     name: BME280 Temp
  #     accuracy_decimals: 1     
  #     oversampling: 16x
  #   pressure:
  #     id: ${node_id}_pressure
  #     name: BME280 Pressure
  #     oversampling: 2x
  #   humidity:
  #     id: ${node_id}_humidity
  #     name: BME280 Humidity
  #     accuracy_decimals: 1     
  #     oversampling: 8x
  #   address: 0x76
  #   update_interval: 30s



### >>>>>>>>>>>>> Switches <<<<<<<<<<<<<<< ###
switch:
  - platform: restart # Restart
    name: Restart
    id: ${node_id}_restart
    icon: "mdi:restart"
  - platform: shutdown # Shutdown
    name: Shutdown
    id: ${node_id}_shutdown
  - platform: safe_mode # Safe Mode
    name: Restart (Safe Mode)"
    id: ${node_id}_safe_mode
  - platform: template # HVAC Power - Source: https://community.home-assistant.io/t/switch-linked-to-homeassistant-switch/593354/3?u=bsell93
    id: hvac_power
    optimistic: true
    turn_on_action: 
      - homeassistant.service: 
          service: switch.turn_on
          data:
            entity_id: switch.master_bedroom_hvac_power
      - delay: 1s
      - homeassistant.service: 
          service: climate.set_hvac_mode
          data:
            entity_id: climate.master_bedroom_hvac_climate
          data_template:
            hvac_mode: '{{ mode }}'
          variables: 
            mode: |-
              return id(climate_mode);
      - delay: 1s
      - homeassistant.service: 
          service: climate.set_temperature
          data:
            entity_id: climate.master_bedroom_hvac_climate
          data_template:
            temperature: '{{ target_temp }}'
          variables:
            target_temp: |-
              if (id(${node_id}_climate).mode == CLIMATE_MODE_COOL)
              {
                return round(id(${node_id}_climate).target_temperature_high * (9.0/5.0) + 32.0) - 1;
              }
              return round(id(${node_id}_climate).target_temperature_low * (9.0/5.0) + 32.0) + 1;
    turn_off_action:
      - homeassistant.service: 
          service: switch.turn_off
          data:
            entity_id: switch.master_bedroom_hvac_power



### >>>>>>>>>>>>> Climate <<<<<<<<<<<<<<< ###
climate:
  - platform: thermostat
    name: "Thermostat"
    id: ${node_id}_climate
    sensor: temp_sensor_celsius
    humidity_sensor: humidity_sensor
    min_cooling_off_time: 1s # 300s
    min_cooling_run_time: 1s # 300s
    min_heating_off_time: 1s # 300s
    min_heating_run_time: 1s # 300s
    min_idle_time: 30s
    visual:
      temperature_step:
        target_temperature: 1
        current_temperature: 1
    cool_action:
      - lambda: |-
          id(climate_mode) = "cool";
          id(active_climate_action) = "cooling";
      - switch.turn_on: hvac_power
    heat_action:
      - lambda: |-
          id(climate_mode) = "heat";
          id(active_climate_action) = "heating";
      - switch.turn_on: hvac_power
    idle_action:
      - lambda: |-
          id(active_climate_action) = "idle";
      - switch.turn_off: hvac_power
    on_state: 
      then:
        if:
          condition:
            - lambda: |-
                return id(active_climate_action) != "idle";
          then:
            - homeassistant.service: 
                service: climate.set_temperature
                data:
                  entity_id: climate.master_bedroom_hvac_climate
                data_template:
                  hvac_mode: '{{ hvac_mode }}'
                  temperature: '{{ target_temp }}'
                variables:
                  hvac_mode: |-
                    auto mode = id(${node_id}_climate).mode;
                    if (mode == CLIMATE_MODE_COOL)
                    {
                      return "cool";
                    }
                    else if (mode == CLIMATE_MODE_HEAT)
                    {
                      return "heat";
                    }
                    return "off";
                  target_temp: |-
                    if (id(${node_id}_climate).mode == CLIMATE_MODE_COOL)
                    {
                      return round(id(${node_id}_climate).target_temperature_high * (9.0/5.0) + 32.0) - 1;
                    }
                    return round(id(${node_id}_climate).target_temperature_low * (9.0/5.0) + 32.0) + 1;
    default_preset: Home
    on_boot_restore_from: memory
    preset:
      - name: Home
        mode: cool
        default_target_temperature_low: 73 °F
        default_target_temperature_high: 73 °F
      - name: Sleep
        mode: cool
        default_target_temperature_low: 70 °F
        default_target_temperature_high: 70 °F
      - name: Away
        mode: cool
        default_target_temperature_low: 76 °F
        default_target_temperature_high: 76 °F



### >>>>>>>>>>>>> Image <<<<<<<<<<<<<<< ###
image:
  - file: mdi:close
    id: close_icon
    resize: 28x28
  - file: mdi:power
    id: power_icon
    resize: 28x28
  - file: mdi:fire
    id: heat_icon
    resize: 28x28
  - file: mdi:snowflake
    id: cool_icon
    resize: 28x28
  - file: mdi:minus
    id: minus_icon
    resize: 40x40
  - file: mdi:plus
    id: plus_icon
    resize: 40x40
### >>>>>>>>>>>>> Font <<<<<<<<<<<<<<< ###
font:
  # gfonts://family[@weight]
  - file: "gfonts://Roboto"
    id: roboto_48
    size: 48
  - file: "gfonts://Roboto"
    id: roboto_24
    size: 24
  - file: "gfonts://Roboto"
    id: roboto_16
    size: 16
  - file: "gfonts://Roboto"
    id: roboto_10
    size: 10
### >>>>>>>>>>>>> Color <<<<<<<<<<<<<<< ###
color:
  - id: heat_red
    hex: ec780f
  - id: cool_blue
    hex: 096dff
  - id: off_green
    hex: 08f26d
  - id: grey
    hex: D1D0CE
  - id: dark_grey
    hex: A0A0A0
### >>>>>>>>>>>>> SPI <<<<<<<<<<<<<<< ###
spi:
  clk_pin: GPIO18 # minid1 - 18; esp32s3 - 11 # Brown
  mosi_pin: GPIO23 # minid1 - 23; esp32s3 - 13 # Blue
  miso_pin: GPIO19 # minid1 - 19; esp32s3 - 12 # Yellow

### >>>>>>>>>>>>> Display <<<<<<<<<<<<<<< ###
display:
  - platform: ili9xxx
    model: ILI9341
    id: tft_display
    dc_pin: GPIO21 # minid1 - 21; esp32s3 - 14 # Purple
    cs_pin: GPIO33 # minid1 - 33; esp32s3 - 10 # White
    reset_pin: GPIO26 # minid1 - 26; esp32s3 - 9 # Orange
    dimensions: 
      height: 240
      width: 320
    update_interval: never
    # auto_clear_enabled: false
    data_rate: 40MHz
    transform:
      swap_xy: true
      mirror_y: true
      mirror_x: true
    pages:
      ### >>>>>>>>>>>>> Home Page <<<<<<<<<<<<<<< ###
      - id: home_page
        lambda: |-
          // alignment lines
          // it.line(it.get_width()/2, 0, it.get_width()/2, it.get_height());
          // it.line(0, it.get_height()/2, it.get_width(), it.get_height()/2);
          // it.line(it.get_width()* 1/4, 0, it.get_width()* 1/4, it.get_height());
          // it.line(it.get_width()* 3/4, 0, it.get_width()* 3/4, it.get_height());
          // it.line(it.get_width()* 1/3, 0, it.get_width()* 1/3, it.get_height());
          // it.line(it.get_width()* 2/3, 0, it.get_width()* 2/3, it.get_height());

          // Show time
          it.strftime(5, 5, id(roboto_16), "%I:%M %p", id(my_time).now());
          // Show outdoor feels like temp and humidity
          it.printf(it.get_width() - 5, 5, id(roboto_16), TextAlign::TOP_RIGHT, "%.0f°", id(outdoor_feels_like_temp_sensor).state);
          // Set Current Humidity
          it.printf(it.get_width()/2, (it.get_height()/2) - 50, id(roboto_16), TextAlign::CENTER, "%.0f%%", id(humidity_sensor).state);
          // Set Current Temp
          it.printf(it.get_width()/2, it.get_height()/2, id(roboto_48), TextAlign::CENTER, "%.0f°", id(temp_in_fahrenheit));
          // Set Target Temp
          auto current_mode = id(${node_id}_climate).mode;
          auto target_temp = id(${node_id}_climate).target_temperature * (9.0/5.0) + 32.0; // Convert to Fahrenheit
          auto current_mode_color = id(off_green);
          auto active_mode_icon = id(power_icon); // mode icon
          auto is_on = false;
          if (current_mode == CLIMATE_MODE_HEAT)
          {
            target_temp = id(${node_id}_climate).target_temperature_low * (9.0/5.0) + 32.0;
            current_mode_color = id(heat_red);
            active_mode_icon = id(heat_icon);
            is_on = true;
          }
          else if (current_mode == CLIMATE_MODE_COOL)
          {
            target_temp = id(${node_id}_climate).target_temperature_high * (9.0/5.0) + 32.0;
            current_mode_color = id(cool_blue);
            active_mode_icon = id(cool_icon);
            is_on = true;
          }
          if (is_on)
          {
            // Plus/minus icons
            it.image((it.get_width()* 1/3) - 20, it.get_height()/2 - 20, id(minus_icon));
            it.image((it.get_width()* 2/3) - 20, it.get_height()/2 - 20, id(plus_icon));
            // Target Temp
            it.printf(it.get_width()/2, (it.get_height()/2) + 50, id(roboto_24), current_mode_color, TextAlign::CENTER, "%.0f°", target_temp);
            // Draw rectangle around target temp to indicate button
            it.rectangle((it.get_width()/2) - 25, (it.get_height()/2) + 32.5, 50, 37.5, current_mode_color);
          }
          // Mode icon
          it.image(5, it.get_height() - 33, active_mode_icon);
      ### >>>>>>>>>>>>> Mode Page <<<<<<<<<<<<<<< ###
      - id: mode_page
        lambda: |-
          // Show time
          it.strftime(5, 5, id(roboto_16), "%I:%M %p", id(my_time).now());
          // Show outdoor feels like temp and humidity
          it.printf(it.get_width() - 5, 5, id(roboto_16), TextAlign::TOP_RIGHT, "%.0f°", id(outdoor_feels_like_temp_sensor).state);
          auto current_mode = id(${node_id}_climate).mode;
          auto heat_mode_color = id(dark_grey);
          auto cool_mode_color = id(dark_grey);
          auto power_mode_color = id(dark_grey);
          if (current_mode == CLIMATE_MODE_HEAT)
          {
            heat_mode_color = id(heat_red);
          }
          else if (current_mode == CLIMATE_MODE_COOL)
          {
            cool_mode_color = id(cool_blue);
          }
          else
          {
            power_mode_color = id(off_green);
          }
          // Render Cool mode button
          it.rectangle((it.get_width() * 1/4) - 30, (it.get_height()/2) - 25, 60, 55, cool_mode_color);
          it.image((it.get_width() * 1/4) - 14, (it.get_height()/2) - 22, id(cool_icon), cool_mode_color);
          it.print((it.get_width() * 1/4) - 25, it.get_height()/2, id(roboto_24), cool_mode_color, "Cool");
          // Render Power mode button
          it.rectangle((it.get_width()/2) - 30, (it.get_height()/2) - 25, 60, 55, power_mode_color);
          it.image((it.get_width()/2) - 14, (it.get_height()/2) - 22, id(power_icon), power_mode_color);
          it.print((it.get_width()/2) - 17, it.get_height()/2, id(roboto_24), power_mode_color, "Off");
          // Render Heat mode button
          it.rectangle((it.get_width() * 3/4) - 30, (it.get_height()/2) - 25, 60, 55, heat_mode_color);
          it.image((it.get_width() * 3/4) - 14, (it.get_height()/2) - 22, id(heat_icon), heat_mode_color);
          it.print((it.get_width() * 3/4) - 25, it.get_height()/2, id(roboto_24), heat_mode_color, "Heat");
      ### >>>>>>>>>>>>> Weather Station Page <<<<<<<<<<<<<<< ###
      - id: weather_station_page
        lambda: |-
          // Show time
          it.strftime(5, 5, id(roboto_16), "%I:%M %p", id(my_time).now());
          // Show X - close button top right
          it.image(it.get_width() - 5, 5, id(close_icon), ImageAlign::TOP_RIGHT);
          // Temp
          it.printf(5, it.get_height() * 1/10, id(roboto_10), "Temperature: %.0f°", id(outdoor_temp_sensor).state);
          // Humidity
          it.printf(5, it.get_height() * 2/10, id(roboto_10), "Humidity: %.0f%%", id(outdoor_humidity_sensor).state);
          // Feels Like Temp
          it.printf(5, it.get_height() * 3/10, id(roboto_10), "Feels Like: %.0f°", id(outdoor_feels_like_temp_sensor).state);
          // Absolute Pressure
          it.printf(5, it.get_height() * 4/10, id(roboto_10), "Abs Pressure: %.0finHg", id(outdoor_absolute_pressure_sensor).state);
          // Relative Pressure
          it.printf(5, it.get_height() * 5/10, id(roboto_10), "Rel Pressure: %.0finHg", id(outdoor_relative_pressure_sensor).state);
          // Dew Point
          it.printf(5, it.get_height() * 6/10, id(roboto_10), "Dew Point: %.0f°", id(outdoor_dew_point_sensor).state);
          // Solar UV Index
          it.printf(5, it.get_height() * 7/10, id(roboto_10), "Solar UV Index: %.0f", id(outdoor_solar_uv_index_sensor).state);
          // Solar Rad W/m2 & Lux
          it.printf(5, it.get_height() * 8/10, id(roboto_10), "Solar Rad: %.0fW/m2 (%.0f lux)", id(outdoor_solar_rad_watts_sensor).state, id(outdoor_solar_rad_lux_sensor).state);
          // Wind Speed & Direction
          it.printf(5, it.get_height() * 9/10, id(roboto_10), "Wind: %.0fmph (%.0f°)", id(outdoor_wind_speed_sensor).state, id(outdoor_wind_direction_sensor).state);
          // Wind Gust
          it.printf(it.get_width()/2, it.get_height() * 9/10, id(roboto_10), "Wind Gust: %.0fmph", id(outdoor_wind_gust_sensor).state);
          // Event Rain
          it.printf(it.get_width()/2, it.get_height() * 1/10, id(roboto_10), "Event Rain: %.0fin", id(outdoor_event_rain_sensor).state);
          // Hourly Rain Rate
          it.printf(it.get_width()/2, it.get_height() * 2/10, id(roboto_10), "Hourly Rain Rate: %.0fin", id(outdoor_hourly_rain_rate_sensor).state);
          // Last Rain - date time
          auto last_rain = id(outdoor_last_rain_sensor).state;
          it.printf(it.get_width()/2, it.get_height() * 3/10, id(roboto_10), "Last Rain: %s %s", last_rain.substr(0,10).c_str(), last_rain.substr(11, 5).c_str());
          // Daily Rain
          it.printf(it.get_width()/2, it.get_height() * 4/10, id(roboto_10), "Daily Rain: %.0fin", id(outdoor_daily_rain_sensor).state);
          // Weekly Rain
          it.printf(it.get_width()/2, it.get_height() * 5/10, id(roboto_10), "Weekly Rain: %.0fin", id(outdoor_weekly_rain_sensor).state);
          // Monthly Rain
          it.printf(it.get_width()/2, it.get_height() * 6/10, id(roboto_10), "Monthly Rain: %.0fin", id(outdoor_monthly_rain_sensor).state);
          // Yearly Rain
          it.printf(it.get_width()/2, it.get_height() * 7/10, id(roboto_10), "Yearly Rain: %.0fin", id(outdoor_yearly_rain_sensor).state);
          // Lifetime Rain
          it.printf(it.get_width()/2, it.get_height() * 8/10, id(roboto_10), "Lifetime Rain: %.0fin", id(outdoor_lifetime_rain_sensor).state);



### >>>>>>>>>>>>> Touch Screen <<<<<<<<<<<<<<< ###
touchscreen:
  platform: xpt2046
  id: my_touchscreen
  cs_pin: GPIO32 # White
  update_interval: 50ms
  threshold: 400
  transform:
      swap_xy: true
  calibration:
    x_min: 203
    x_max: 3839
    y_min: 340
    y_max: 3849
  on_touch:
    - binary_sensor.template.publish:
        id: touching
        state: ON
    - lambda: |-
          ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d",
              touch.x,
              touch.y,
              touch.x_raw,
              touch.y_raw
              );
  on_release:
    - binary_sensor.template.publish:
        id: touching
        state: OFF



### >>>>>>>>>>>>> Binary Sensors <<<<<<<<<<<<<<< ###
binary_sensor:
  ### >>>>>>>>>>>>> Home Page Touch Areas <<<<<<<<<<<<<<< ###
  - platform: touchscreen
    id: temp_down
    x_min: 70
    x_max: 130
    y_min: 100
    y_max: 150
    page_id: home_page
    on_press:
      then:
        - climate.control:
            id: ${node_id}_climate
            target_temperature_low: !lambda 
              auto current_mode = id(${node_id}_climate).mode;
              if (current_mode == CLIMATE_MODE_HEAT)
              {
                auto target_temp_in_f = id(${node_id}_climate).target_temperature_low * (9.0/5.0) + 32.0;
                return ((target_temp_in_f - 1) - 32) * 5/9;
              }
              auto lowest_temp = 50;
              return ((lowest_temp) - 32) * 5/9; // Convert back to celsius
            target_temperature_high: !lambda 
              auto current_mode = id(${node_id}_climate).mode;
              if (current_mode == CLIMATE_MODE_COOL)
              {
                auto target_temp_in_f = id(${node_id}_climate).target_temperature_high * (9.0/5.0) + 32.0;
                return ((target_temp_in_f - 1) - 32) * 5/9;
              }
              auto highest_temp = 86;
              return ((highest_temp) - 32) * 5/9; // Convert back to celsius
        - component.update: tft_display
  - platform: touchscreen
    id: temp_up
    x_min: 205
    x_max: 260
    y_min: 100
    y_max: 150
    page_id: home_page
    on_press:
      then:
        - climate.control:
            id: ${node_id}_climate
            target_temperature_low: !lambda
              auto current_mode = id(${node_id}_climate).mode;
              if (current_mode == CLIMATE_MODE_HEAT)
              {
                auto target_temp_in_f = id(${node_id}_climate).target_temperature_low * (9.0/5.0) + 32.0;
                return ((target_temp_in_f + 1) - 32) * 5/9;
              }
              auto lowest_temp = 50;
              return ((lowest_temp) - 32) * 5/9; // Convert back to celsius
            target_temperature_high: !lambda 
              auto current_mode = id(${node_id}_climate).mode;
              if (current_mode == CLIMATE_MODE_COOL)
              {
                auto target_temp_in_f = id(${node_id}_climate).target_temperature_high * (9.0/5.0) + 32.0;
                return ((target_temp_in_f + 1) - 32) * 5/9;
              }
              auto highest_temp = 86;
              return ((highest_temp) - 32) * 5/9; // Convert back to celsius
        - component.update: tft_display
  - platform: touchscreen
    id: mode_select
    x_min: 0
    x_max: 55
    y_min: 205
    y_max: 240
    page_id: home_page
    on_press:
      then:
        - display.page.show: mode_page
        - component.update: tft_display
  - platform: touchscreen
    id: open_weather_button
    x_min: 270
    x_max: 320
    y_min: 0
    y_max: 50
    page_id: home_page
    on_release:
      then:
        - display.page.show: weather_station_page
        - component.update: tft_display
  ### >>>>>>>>>>>>> Mode Page Touch Areas <<<<<<<<<<<<<<< ###
  - platform: touchscreen
    id: set_cool_mode
    x_min: 50
    x_max: 115
    y_min: 90
    y_max: 150
    page_id: mode_page
    on_press:
      then:
        - climate.control:
            id: ${node_id}_climate
            mode: COOL
        - display.page.show: home_page
        - component.update: tft_display
  - platform: touchscreen
    id: set_off_mode
    x_min: 135
    x_max: 200
    y_min: 90
    y_max: 150
    page_id: mode_page
    on_press:
      then:
        - climate.control:
            id: ${node_id}_climate
            mode: "OFF"
        - display.page.show: home_page
        - component.update: tft_display
  - platform: touchscreen
    id: set_heat_mode
    x_min: 220
    x_max: 285
    y_min: 90
    y_max: 150
    page_id: mode_page
    on_press:
      then:
        - climate.control:
            id: ${node_id}_climate
            mode: "HEAT"
        - display.page.show: home_page
        - component.update: tft_display
  ### >>>>>>>>>>>>> Weather Page Touch Areas <<<<<<<<<<<<<<< ###
  - platform: touchscreen
    id: close_button
    x_min: 270
    x_max: 320
    y_min: 0
    y_max: 50
    page_id: weather_station_page
    on_release:
      then:
        - display.page.show: home_page
        - component.update: tft_display
  ### >>>>>>>>>>>>> Backlight Binary Sensor <<<<<<<<<<<<<<< ###
  - platform: template
    id: touching
    filters:
      delayed_off: 20s
    on_press:
      - light.control: 
          id: back_light
          brightness: 100%
    on_release:
      - light.control: 
          id: back_light
          brightness: 35%



### >>>>>>>>>>>>> Backlight <<<<<<<<<<<<<<< ###
# Define a PWM output on the ESP32
output:
  - platform: ledc
    pin: 25
    id: backlight_output
# Define a monochromatic, dimmable light for the backlight
light:
  - platform: monochromatic
    output: backlight_output
    name: "Display Backlight"
    id: back_light
    restore_mode: RESTORE_DEFAULT_ON





5 Likes

Update:

I have made this config reusable and also added in the HLK LD2450 sensor and BH1750 Illuminance sensor. I also have in the works a separate temp/humidity/pressure sensor using BME280 and esp-01s - I found putting the BME280 in a case with the display and all the other sensors was making the temp reading wildly inaccurate as well as having the flexibility to place a temp sensor in a desired location I think is a plus.

Here is some updated config code

The following us a usage of the template:

# Enable Home Assistant API
api:
  encryption:
    key: "<key>"

ota:
  password: "<pwd>"

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "<ssid>"
    password: "<ap-pwd>"
    
substitutions:
  node_id: some_thermostat
  thermostat_name: some-thermostat
  thermostat_friendly_name: Some Thermostat
  temp_sensor_entity_id: sensor.some_temp_sensor
  humidity_sensor_entity_id: sensor.some_humdity_sensor
  hvac_power_entity_id: switch.some_hvac_power
  hvac_climate_entity_id: climate.some_hvac_climate

<<: !include .thermostat.common.yaml

The following is the template that allows scaling multiple devices without copy paste (was going to copy paste it all here, but there were too many characters):

I also designed a case here:

1 Like