Customizing native/tuya "climate" functionality

I have some Tuya-based thermostats, modded with esp chip and installed ESPHome.

Problem

The Tuya Climate component exposes data points, but in Home Assistant it only provides 2 modes: heat (on) and off. No custom operation modes or presets.

But the thermostat provides them and I have them as switches & selects in ESPHome:

What others do

for example wthermostatbeca software installed onto the ESP can automatically create some modes or presets that are available on the device.

My current solution in ESPHome

I made the climate:tuya as internal (hidden), a template thermostat climate with presets and a lot of automations that would turn on/off the switches, pick correct options from the internal input selects, etc.

Question

The only thing missing is creating custom icons that would be visible in the UI.

This seems impossible currently. But somehow WthermostatBeca software (using MQTT integration) is capable of doing it.

To get the Auto icon (Thermostat Climate Controller — ESPHome), i the thermostat would also need a cooling action, which creates a cool icon and dual gauges… But this thermostat is only for heating.

i am aware of lovelace simple-thermostat and all plugins, i just would like to provide a native way from esphome to home-assistant to provide a mode like Auto or Eco that would work natively like with other thermostats.

Hi @veli
I have a similar thermostat.
Can you share your esp code?

Wojtek

The back-and forth syncing sometimes caused an infinite loop so i reverted to basic sad heat/no_heat options. Basically configure the tuya to work as it should, then make a dummy clone that has all the fun stuff, and sync it with tuya actions, and sync it back so its actions go to tuya, but wont loop back infinitely :joy:.


climate:

# the original tuya  
  - platform: tuya
    disabled_by_default: true  
    id: ${device_name}_tuya
#    internal: true
    entity_category: diagnostic
    name: Tuya
    icon: mdi:thermostat-auto
    switch_datapoint: 1
    target_temperature_datapoint: 2
    current_temperature_datapoint: 3
    temperature_multiplier: 0.5
    active_state_datapoint: 104
    active_state_heating_value: ON
    eco_datapoint: 5
    eco_temperature: 15
    visual:
      min_temperature: ${default_target_temperature_low}
      max_temperature: ${default_target_temperature_high}
      temperature_step: 
        target_temperature: 1
        current_temperature: 0.5

# https://esphome.io/components/climate/thermostat        
  - platform: thermostat 
    id: ${device_name}
    name: None
    icon: mdi:thermostat-auto
    sensor: ${device_name}_current_temperature
    min_heating_off_time: ${min_heating_off_time}
    min_heating_run_time: ${min_heating_off_time}
    min_idle_time: ${min_idle_time}
    heat_action:
      - logger.log: id(${device_name}).action      
#      - switch.turn_on: hvac_action
    idle_action:
      - logger.log: id(${device_name}).action      
 #     - switch.turn_off: hvac_action
    heat_mode: 
      - logger.log: id(${device_name}).mode      
      - switch.turn_on: ${device_name}_power
    off_mode: 
      - logger.log: id(${device_name}).mode      
      - switch.turn_off: ${device_name}_power
#    min_cooling_off_time: 300s    
#    min_cooling_run_time: 300s
#    cool_mode: 
#      - logger.log: id(${device_name}).preset
#    auto_mode: 
#      - switch.turn_on: power
#    cool_action:
#      - logger.log: id(${device_name}).preset            
    default_preset: none
    preset:
      - name: none # auto, no eco
        default_target_temperature_low: ${default_target_temperature_none}        
#        default_target_temperature_high: ${default_target_temperature_high}        
      - name: eco # auto, eco
        default_target_temperature_low: ${default_target_temperature_eco}
#        default_target_temperature_high: ${default_target_temperature_high}
        mode: HEAT
      - name: away # manual, eco
        default_target_temperature_low: ${default_target_temperature_away}
#        default_target_temperature_high: ${default_target_temperature_high}
        mode: HEAT        
      - name: home # manual, no eco
        default_target_temperature_low: ${default_target_temperature_home}
 #       default_target_temperature_high: ${default_target_temperature_high}
    target_temperature_change_action: 
      - script.execute: tuya_set_target_temperature      
    preset_change:
      - script.stop: hvac_preset_change
      - script.execute: hvac_preset_change
    visual:
      min_temperature: ${default_target_temperature_low}
      max_temperature: ${default_target_temperature_high}
      temperature_step: ${temperature_step}

script:
  - id: hvac_preset_change
    mode: restart
    then: 
      - script.stop: tuya_set_target_temperature            
      - script.stop: hvac_set_target_temperature            
      - delay: 0.2s
      - script.execute: sync
      - delay: 0.2s
      - script.execute: tuya_set_target_temperature          

  - id: tuya_set_target_temperature
    then: 
    - script.stop: hvac_set_target_temperature
    - lambda: |- 
          auto set_target_temperature = id(${device_name}).target_temperature;
          auto get_target_temperature = id(${device_name}_tuya).target_temperature;
          if ( set_target_temperature != get_target_temperature ){
            auto climate = id(${device_name}_tuya).make_call();
            climate.set_target_temperature(set_target_temperature);
            climate.perform();
          }      
  - id: hvac_set_target_temperature
    mode: restart
    then:
    - script.stop: tuya_set_target_temperature
    - lambda: |- 
          auto set_target_temperature = id(${device_name}).target_temperature;
          auto get_target_temperature = id(${device_name}_target_temperature).state;
          if ( set_target_temperature != get_target_temperature ){
            auto climate = id(${device_name}).make_call();
            climate.set_target_temperature(get_target_temperature);
            climate.perform();
          }
  - id: sync
    mode: restart
    then:
      - script.stop: boot
      - lambda: |-                 
          auto hvac_preset = id(${device_name}).preset;
          auto set_preset_mode = id(${device_name}_preset_mode).make_call();
          
          auto climate = id(${device_name}).make_call();          

          auto preset = id(${device_name}_preset_mode).state;
          auto set_power = id(${device_name}_power).state;
          auto set_eco = id(${device_name}_eco).state;

          auto set_current_temperature = id(${device_name}_current_temperature).state;
          auto set_target_temperature = id(${device_name}_target_temperature).state;      
          auto set_away_temperature = id(${device_name}_away_temperature).state; 
          auto set_max_temperature = id(${device_name}_max_temperature).state; 
          auto set_min_temperature = id(${device_name}_min_temperature).state; 


          // if (eco) {
          //   climate.set_preset(CLIMATE_PRESET_ECO);
          // } else {
          //   // climate.set_preset(CLIMATE_MODE_OFF);
          // }

          if (CLIMATE_PRESET_ECO == hvac_preset){
            set_preset_mode.set_option("Auto");
            id(${device_name}_eco).turn_on();
            climate.set_target_temperature(set_min_temperature);
          }      
          else {
            id(${device_name}_eco).turn_off();

            if (CLIMATE_PRESET_HOME == hvac_preset){
              set_preset_mode.set_option("Manual");
              climate.set_target_temperature((set_max_temperature+set_min_temperature)/2);
            }                          
            if (CLIMATE_PRESET_NONE == hvac_preset){
              set_preset_mode.set_option("Auto");
              climate.set_target_temperature(set_current_temperature);
            }                                    
            if (CLIMATE_PRESET_AWAY == hvac_preset){
              id(${device_name}_eco).turn_on();
              set_preset_mode.set_option("Manual");
              climate.set_target_temperature(set_away_temperature);
            }                  
          }                  
          climate.perform();  
          set_preset_mode.perform();
      - delay: 3s
      - lambda: |-                 
          auto hvac_preset = id(${device_name}).preset;
          auto climate = id(${device_name}).make_call();          
          if (CLIMATE_PRESET_BOOST == hvac_preset){
            id(${device_name}_lock).turn_on();
          }                   
          else {
            id(${device_name}_lock).turn_off();            
          }
          climate.perform();         

  - id: boot
    mode: single
    then:
      - script.stop: sync
      - lambda: |- 
          auto preset = id(${device_name}_preset_mode).state;
          // auto set_action = id(${device_name}_tuya).action;
          auto set_power = id(${device_name}_power).state;
          auto set_eco = id(${device_name}_eco).state;

          auto set_target_temperature = id(${device_name}_target_temperature).state;      
          auto set_away_temperature = id(${device_name}_away_temperature).state; 
          auto set_max_temperature = id(${device_name}_max_temperature).state; 
          auto set_min_temperature = id(${device_name}_min_temperature).state; 

          auto climate = id(${device_name}).make_call();

          if (set_power) {
            climate.set_mode(CLIMATE_MODE_HEAT);
            if (set_eco) {
             climate.set_preset(CLIMATE_PRESET_ECO);
             climate.set_target_temperature(set_min_temperature);
            } else {
              if ("Manual" == preset){
                climate.set_preset(CLIMATE_PRESET_NONE);
                climate.set_target_temperature(set_target_temperature);                        
              }
              if ("Auto" == preset){
                climate.set_preset(CLIMATE_PRESET_AWAY);
                climate.set_target_temperature(set_away_temperature);
              }
            }
          } else {
            climate.set_mode(CLIMATE_MODE_OFF);
          }

          climate.perform();

Happy to hear back if you make something reliable with my old codebase, could update them over a few years :slight_smile:

Hi
I’m not a programmer but I managed to do something like this:

esphome:
  name: termostat-3
  friendly_name: termostat-3
esp8266:
#  board: esp01_1m
  board: esp12e
# Enable logging

logger:
  level: debug
  logs:
    component: error
  baud_rate: 0
# Enable Home Assistant API
api:
  encryption:
    key: "**********************"
ota:
  - platform: esphome
    password: "************************"
wifi:
  networks:
#    - ssid: **********
#      password: !secret wifi_password_1
    - ssid: ******
      password: !secret wifi_password_2
    - ssid: **********
      password: !secret wifi_password_3
    - ssid: **********
      password: !secret wifi_password_4
  manual_ip:
    static_ip: 192.168.1.82
    gateway: 192.168.1.1
    subnet: 255.255.255.0
    dns1: 192.168.1.1
    dns2: 8.8.8.8
#  power_save_mode: NONE
#  output_power: 17dB
# Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: !secret wifi_ap_ssid
    password: !secret wifi_ap_password
web_server:
  port: 80
  version: 3
  auth:
    username: !secret web_username
    password: !secret web_password
time:
  - id: time_provider
    platform: homeassistant
    on_time_sync:
      - logger.log: "Synchronized system clock"
captive_portal:
uart:
  rx_pin: GPIO3
  tx_pin: GPIO1
  baud_rate: 9600
  id: mcu_uart
tuya:
  time_id: time_provider
climate:
  - platform: tuya
    name: "Termostat"
    icon: mdi:heating-coil
    switch_datapoint: 1
    target_temperature_datapoint: 2
    current_temperature_datapoint: 3
    supports_heat: True
    supports_cool: False
    temperature_multiplier: 0.1
    active_state:
     datapoint: 5
     heating_value: 1
    visual:
     min_temperature: 10 °C
     max_temperature: 30 °C
     temperature_step: 0.1 °C
switch:
  - platform: tuya
    name: "ON/OFF"
    icon: mdi:power
    switch_datapoint: 1           
    restore_mode: ALWAYS_ON
    device_class: "switch"
  - platform: tuya
    name: "Sound"
    icon: mdi:volume-off
    switch_datapoint: 109           
    device_class: "switch"
  - platform: tuya
    name: "Out inverse"
    entity_category: config    
    icon: mdi:select-inverse
    switch_datapoint: 108           
    device_class: "switch"
  - platform: tuya
    name: "Anti frost"
    entity_category: config    
    icon: mdi:snowflake
    switch_datapoint: 103           
    device_class: "switch"
  - platform: tuya
    name: "Reset to default"
    entity_category: config
    icon: mdi:alert
    switch_datapoint: 104           
    device_class: "switch"
sensor:
  - platform: tuya
    name: "Temperatura"
    device_class: "temperature"
    sensor_datapoint: 3
    unit_of_measurement: "°C"
    accuracy_decimals: 1
    filters:
      - multiply: 0.1
  - platform: wifi_signal
    name: "WiFi Signal dB"
    id: wifi_signal_db
    update_interval: 60s
    entity_category: "diagnostic"
text_sensor:
  - platform: wifi_info
    ip_address:
      name: ESP IP Address
    ssid:
      name: ESP Connected SSID
    mac_address:
      name: ESP Mac Wifi Address
    scan_results:
      name: ESP Latest Scan Results
select:
  - platform: "tuya"
    name: "Program Mode"
    icon: mdi:view-list
    enum_datapoint: 4
    optimistic: true
    options:
      0: Manual
      1: Program
      2: Temporary program
      3: Leave home
  - platform: "tuya"
    name: "Backlight brightness"
    icon: mdi:lightbulb-on-30
    enum_datapoint: 106
    optimistic: false
    options:
      0: Dark
      1: Low
      2: Middle
      3: High        
  - platform: "tuya"
    name: "Sensor selection"
    icon: mdi:wrench
    entity_category: config
    optimistic: true
    enum_datapoint: 110
    options:
      0: Internal
      1: External
      2: Both
  - platform: "tuya"
    name: "Working day setting"
    icon: mdi:briefcase
    entity_category: config
    optimistic: true
    enum_datapoint: 107
    options:
      0: Not active
      1: 5+2 days
      2: 6+1 days
      3: 7 days
  - platform: "tuya"
    name: "Error"
    icon: mdi:alert-circle
    entity_category: diagnostic
    enum_datapoint: 11
    options:
      0: No errors
      1: Err 1-Ext. sensor error
      2: Err 2
      3: Err 3
number:
- platform: "tuya"
  name: "Precision"
  entity_category: config
  mode: box
  number_datapoint: 101
  min_value: 0.5
  max_value: 10.0
  step: 0.5
  multiply: 10
- platform: "tuya"
  name: "Korekta T"
  entity_category: config
  mode: box
  number_datapoint: 19
  min_value: -10
  max_value: 10
  step: 0.1
  multiply: 10
- platform: "tuya"
  name: "Max set. temperature"
  entity_category: config
  mode: box
  number_datapoint: 15
  min_value: 15
  max_value: 95
  step: 1
  multiply: 1
- platform: "tuya"
  name: "Max ext. sensor temp."
  entity_category: config
  mode: box
  number_datapoint: 102
  min_value: 35
  max_value: 60
  step: 1
  multiply: 1  

I don’t know how to make a weekly programmer. Each working day can be divided into 6 periods with a different temperature and days off - two periods. I have no idea how to do this in ESPHome.
To make it similar to WThermostatBeca.