Tuya Thermostat with ESPHome: my configuration and suggestions

Hello,
this is my first post here, and I hope it is in the correct place.
I’m creating this post to share the configuration that, after two weeks of tinkering, allowed me to have a functional Tuya Thermostat flashed with ESPHome.
I would also like to get some opinion from you on the validity of this configuration or, if there is something I can do better.

Longs story short:
I bought this unbranded Tuya thermostat from amazon 3 years ago. Lately I started being concerned about the data shared with Tuya cloud, so I set it up using local tuya and blocked internet access to it.
It was working fine until, two weeks ago, I received an email from tuya stating that my APIs were about to expire and suddenly the device stopped connecting to wifi. Being isolated from the web, I assume the device has a timer that disconnects it from the wifi if it has no internet access.
I then discovered tuya cloudcutter and flashed it with ESPHome.

Following the guide, I set it up using “tuya climate” component, which however didn’t work. At the beginning it seems to operate correctly but, after some times it starts slowing down until it doesn’t answer anymore to commands.
Looking at the logs, it seems that it is flooded with messages and it starts getting checksum error. Basically each update of each sensor, trigger a huge amount of configuration writes from the device to tuya MCU

Then I discovered that setting individual commands using standard ESPHome components made them way more reliable then using tuya climate component. So I set them individually, then added a generic climate components connected to them. Here is my configuration:

### DOCS:
# [08:20:26][C][tuya:041]: Tuya:
# [08:20:26][C][tuya:058]:   Datapoint 3: int value (value: 45)    <---- Current temperature
# [08:20:26][C][tuya:058]:   Datapoint 102: int value (value: 0)   <---- External sensor
# [08:20:26][C][tuya:056]:   Datapoint 1: switch (value: ON)       <---- On/Off
# [08:20:26][C][tuya:058]:   Datapoint 2: int value (value: 26)    <---- Target temperature
# [08:20:26][C][tuya:062]:   Datapoint 4: enum (value: 1)          <---- Mode {0 -> scheduled, 1-> manual}
# [08:20:26][C][tuya:056]:   Datapoint 5: switch (value: OFF)      <---- Eco
# [08:20:26][C][tuya:056]:   Datapoint 6: switch (value: OFF)      <---- Lock
# [08:20:26][C][tuya:056]:   Datapoint 104: switch (value: ON)     <---- Not documented
# [08:20:26][C][tuya:062]:   Datapoint 103: enum (value: 1)        <---- Not documented
# [08:20:26][C][tuya:074]:   Product: 'IAYz2WK1th0cMLmL1.0.0'
#

esphome:
  name: thermostat
  friendly_name: Thermostat

bk72xx:
  #board: wb3s
  board: generic-bk7231t-qfn32-tuya

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: !secret api_encription

ota:
  - platform: esphome
    password: !secret ota_pwd

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: !secret esp_thermostat_hotspot_ssid
    password: !secret esp_thermostat_hotspot_pwd

captive_portal:

text_sensor:
  - platform: libretiny
    version: 
      name: LibreTiny Version

uart:
  rx_pin: RX1
  tx_pin: TX1
  baud_rate: 9600

time:
  - platform: homeassistant
    id: ha_time

tuya:
  # The MCU uses this for displaying the time in the display as well as for
  # scheduled programmes one may have configured in their thermostat.
  time_id: ha_time
  ignore_mcu_update_on_datapoints:
    - 5
    - 101
    - 102
    - 103
    - 104

# this is the tuya climate component that I wasn't able to operate correctly. 
# It slightly improved filtering some datapoints with 
# ignore_mcu_update_on_datapoints
#climate:
#  - platform: tuya
#    name: "Thermostat"
#    switch_datapoint: 1
#    target_temperature_datapoint: 2
#    current_temperature_datapoint: 3
#    #preset:
#    #  eco:
#    #    datapoint: 5
#    #    temperature: 18
#    temperature_multiplier: 0.5
#    visual:
#      min_temperature: 5 °C
#      max_temperature: 35 °C
#      temperature_step: 0.5 °C


sensor:
  - platform: "tuya"
    name: "Temperature"
    id: "termostat_temperature"
    sensor_datapoint: 3
    unit_of_measurement: "°C"
    device_class: "temperature"
    accuracy_decimals: 1
    filters:
      - multiply: 0.5

  # The external temperature sensor, if wired
  #- platform: "tuya"
  #  name: "Temperature (external)"
  #  sensor_datapoint: 102
  #  unit_of_measurement: "°C"
  #  device_class: "temperature"
  #  accuracy_decimals: 1
  #  filters:
  #    - multiply: 0.5
  #  disabled_by_default: true

number:
  - platform: "tuya"
    name: "Target Temperature"
    id: "thermostat_target_temperature"
    number_datapoint: 2
    min_value: 5.00
    max_value: 35.00
    multiply: 2
    step: 1
    on_value:
      then:
        - lambda: |-
            // update temperature only if state differs. This should avoid unlimited event call
            if (id(thermostat_control).target_temperature != id(thermostat_target_temperature).state)
            {
              auto call = id(thermostat_control).make_call();
              call.set_target_temperature(id(thermostat_target_temperature).state);
              call.perform();
            }

switch:
  - platform: "tuya"
    name: "Lock"
    icon: "mdi:lock"
    switch_datapoint: 6
    disabled_by_default: true
  
  - platform: "tuya"
    name: "Enabled"
    id: "thermostat_enabled"
    switch_datapoint: 1
    on_turn_off:
      if:
        condition:
          lambda: |-
            return id(thermostat_control).mode != CLIMATE_MODE_OFF;
        then:
          - lambda: |-
              auto call = id(thermostat_control).make_call();
              call.set_mode("OFF");
              call.perform();
    on_turn_on: 
      if:
        condition:
          lambda: |-
            return id(thermostat_control).mode == CLIMATE_MODE_OFF;
        then:
          - lambda: |-
              auto call = id(thermostat_control).make_call();
              call.set_mode("HEAT");
              call.perform();
        

select:
  - platform: "tuya"
    name: "Scheduled programming"
    icon: "mdi:calendar"
    enum_datapoint: 4
    options:
      0: Use scheduled programs
      1: Manual control
    disabled_by_default: true

button:
  - platform: restart
    name: "Restart"

climate:
  - platform: thermostat
    name: "Thermostat control"
    id: "thermostat_control"
    min_heating_off_time: 1s
    min_heating_run_time: 1s
    min_idle_time: 1s
    sensor: termostat_temperature
    heat_action: 
      then:
        - logger.log: "heat action"
    idle_action: 
        - logger.log: "idle action"
    off_mode:
      if:
        condition:
          - switch.is_on: thermostat_enabled
        then:
          - lambda: |-
              id(thermostat_enabled).turn_off();
              id(thermostat_enabled).publish_state(false);
    heat_mode:
      if:
        condition:
          - switch.is_off: thermostat_enabled
        then:
          - lambda: |-
              id(thermostat_enabled).turn_on();
              id(thermostat_enabled).publish_state(true);
    visual:
      min_temperature: 5 °C
      max_temperature: 35 °C
      temperature_step: 0.5 °C
    target_temperature_change_action:
      then:
        - lambda: |-
            // update temperature only if state differs. This should avoid unlimited event call
            if (id(thermostat_control).target_temperature != id(thermostat_target_temperature).state)
            {
              auto call = id(thermostat_target_temperature).make_call();
              call.set_value(id(thermostat_control).target_temperature);
              call.perform();
            }

I hope it helps other people trying to use the same device. If you have any suggestion on how to improve it, please, feel free to comment here

1 Like