AVATTO Tuya WiFi Smart Knob Thermostat Temperature Controller For Water Gas Boiler Electric Heating

Device AVATTO Tuya WiFi Smart Knob Thermostat Temperature Controller For Water Gas Boiler Electric Heating Works With Alexa Google Home

It contains CBU chip.

I flashed it with LibreTiny & esphome by connect RX,TX,GND to USB <> TTL UART convertor.

DataPoints ID with brief description / spec:

{
   "1":"Switch",
   "2":"Mode" enum ["auto","manual"],
   "3":"Set temperature" { "unit": "℃",  "min": 5,  "max": 45,  "scale": 0,  "step": 1},
   "17":"Set temperature _F",
   "23":"Temperature scale" enum [ "c", "f" ],
   "24":"Current temperature",
   "25":"Humidity display",
   "29":"Current temperature_F",
   "31":"Working day setting" enum [ "5_2", "6_1","7"],
   "32":"Working status",
   "39":"Factory data reset",
   "44":"Backlight brightness"  { "unit": "%", "min": 0, "max": 100, "scale": 0, "step": 1},
   "45":"Fault alarm",
   "71":"Week Program",
   "101":"Sensor Choose" enum ["in","out"],
   "102":"High temperature protection",
   "103":"Cryogenic protection",
   "104":"High temperature protection-℉",
   "105":"Low temperature protection-℉",
   "106":"Blind area temperature",
   "107":"Blind area temperature-F",
   "108":"temperature correction",
   "109":"temperature correction-F"
}

I do not use fahrenheit scale.

FULL YAML

esphome:
  name: thermostat
  friendly_name: thermostat
  on_boot:
    priority: -100
    then:
        # need to wait until TUYA init is DONE, but there is no automation for now, 
        # maybee after product DP? 
      - delay: 20s
        # This will confirm to MCU, that we will send Weather data
      - uart.write: [0x55, 0xaa, 0x00, 0x20, 0x00, 0x02, 0x01, 0x00, 0x22] 
      - lambda: |-
            std::vector<uint8_t> command = {0x55,0xAA,0x0,0x21,0x0,0x5B,0x1,0x6,0x77,0x2E,0x74,0x65,0x6D,0x70,0x0,0x4,0x0,0x0,0x0,0x0,0xC,0x77,0x2E,0x68,0x75,0x6D,0x69,0x64,0x69,0x74,0x79,0x2E,0x30,0x0,0x4,0x0,0x0,0x0};
            int hum_val = id(homeassistant_weather_humidity).state;
            command.insert(command.end(), hum_val);
            command.insert(command.end(), {0xB,0x77,0x2E,0x63,0x6F,0x6E,0x64,0x69,0x74,0x69,0x6F,0x6E,0x0,0x9,0x0,0x0,0x0,0x0,0x0,0x0,0x31,0x30,0x32,0x6,0x77,0x2E,0x70,0x6D,0x32,0x35,0x0,0x4,0x0,0x0,0x0,0x0,0x10,0x77,0x2E,0x63,0x6F,0x6E,0x64,0x69,0x74,0x69,0x6F,0x6E,0x4E,0x75,0x6D,0x2E,0x30,0x1,0x3});
            
            if (id(homeassistant_weather_state).state == "Sunny") {
              command.insert(command.end(), {0x31,0x32,0x30});
            } else if( id(homeassistant_weather_state).state == "Cloudy") {
              command.insert(command.end(), {0x31,0x30,0x31});
            } else if(id(homeassistant_weather_state).state == "Rainy" or  id(rain_meter_hour).state != 0) {
              command.insert(command.end(), {0x31,0x33,0x30});
            } else if(id(homeassistant_weather_state).state == "partlycloudy") {
              command.insert(command.end(), {0x31,0x34,0x32});
            } else {
              command.insert(command.end(), {0x31,0x30,0x31});
            }
            
            int chksum = 0;
            for (uint8_t chk : command) {
                chksum += chk;
            }
            command.insert(command.end(), chksum % 256);

            for (uint8_t byte : command) {
              id(uart_bus).write_byte(byte);
            }

bk72xx:
  board: cbu
  framework:
    options:
      LT_SERIAL_BUFFER_SIZE: 512
    version: 1.8.0

# Enable logging
logger:
  baud_rate: 0

# Enable Home Assistant API
api:
  encryption:
    key: SECRET

ota:
  - platform: esphome
    password: SECRET

wifi:
  ssid: SECRET
  password: SECRET
  domain: SECRET

captive_portal:

uart:
  id: uart_bus
  rx_pin: RX1
  tx_pin: TX1
  baud_rate: 9600
  rx_buffer_size: 512
#  debug:

time:
  - platform: sntp
    id: sntp_time
    timezone: Europe/Prague
    on_time:
      # Every 5 minutes
      - seconds: 0
        minutes: /5
        then:
        - lambda: |-
            std::vector<uint8_t> command = {0x55,0xAA,0x0,0x21,0x0,0x5B,0x1,0x6,0x77,0x2E,0x74,0x65,0x6D,0x70,0x0,0x4,0x0,0x0,0x0,0x0,0xC,0x77,0x2E,0x68,0x75,0x6D,0x69,0x64,0x69,0x74,0x79,0x2E,0x30,0x0,0x4,0x0,0x0,0x0};
            int hum_val = id(homeassistant_weather_humidity).state;
            command.insert(command.end(), hum_val);
            command.insert(command.end(), {0xB,0x77,0x2E,0x63,0x6F,0x6E,0x64,0x69,0x74,0x69,0x6F,0x6E,0x0,0x9,0x0,0x0,0x0,0x0,0x0,0x0,0x31,0x30,0x32,0x6,0x77,0x2E,0x70,0x6D,0x32,0x35,0x0,0x4,0x0,0x0,0x0,0x0,0x10,0x77,0x2E,0x63,0x6F,0x6E,0x64,0x69,0x74,0x69,0x6F,0x6E,0x4E,0x75,0x6D,0x2E,0x30,0x1,0x3});
            
            if (id(homeassistant_weather_state).state == "Sunny") {
              command.insert(command.end(), {0x31,0x32,0x30});
            } else if( id(homeassistant_weather_state).state == "Cloudy") {
              command.insert(command.end(), {0x31,0x30,0x31});
            } else if(id(homeassistant_weather_state).state == "Rainy" or  id(rain_meter_hour).state != 0) {
              command.insert(command.end(), {0x31,0x33,0x30});
            } else if(id(homeassistant_weather_state).state == "partlycloudy") {
              command.insert(command.end(), {0x31,0x34,0x32});
            } else {
              command.insert(command.end(), {0x31,0x30,0x31});
            }
            
            int chksum = 0;
            for (uint8_t chk : command) {
                chksum += chk;
            }
            command.insert(command.end(), chksum % 256);

            for (uint8_t byte : command) {
              id(uart_bus).write_byte(byte);
            }

tuya:
  time_id: sntp_time


switch:
  - platform: "tuya"
    name: "Heat"
    switch_datapoint: 1
    icon: "mdi:heat-wave"
  - platform: "tuya"
    name: "Factory data reset"
    switch_datapoint: 39   
    icon: "mdi:restart"

text_sensor:
  - platform: "tuya"
    name: "Mode"
    internal: true
    sensor_datapoint: 2
    icon: "mdi:thermostat-cog"
  - platform: "tuya"
    name: "Temperature scale"
    sensor_datapoint: 23
    internal: True
  - platform: "tuya"
    name: "Working status"
    sensor_datapoint: 32

  - platform: "tuya"
    name: "Week Program"
    sensor_datapoint: 71

  - platform: homeassistant
    id: homeassistant_weather_state
    entity_id: weather.home

#I don't need this to display @HA
#climate:
#  - platform: tuya
#    name: "Termostat"
#    switch_datapoint: 1
#    target_temperature_datapoint: 3
#    current_temperature_datapoint: 24
#    supports_heat: true


sensor:
  - platform: "tuya"
    name: "Current temperature"
    unit_of_measurement: "℃"    
    icon: "mdi:temperature-celsius"
    sensor_datapoint: 24
    filters:
      - lambda: |-
          if (x > 30) { return id(backup_temp).state;
          }
          else {
          return x;  
          }

#I don't need this to display @HA
#  - platform: "tuya"
#    name: "Humidity display"
#    unit_of_measurement: "%"
#    icon: "mdi:water-percent"
#    sensor_datapoint: 25
#  - platform: "tuya"
#    name: "Blind area temperature"
#    unit_of_measurement: "℃" 
#    icon: "mdi:temperature-celsius"
#    sensor_datapoint: 106

  - platform: homeassistant
    id: homeassistant_weather_humidity
    entity_id: weather.home
    attribute: humidity
  - platform: homeassistant
    id: rain_meter_hour
    entity_id: sensor.rain_meter_hour

select:
  - platform: "tuya"
    name: "Mode"
    icon: "mdi:thermostat-cog"
    enum_datapoint: 2
    options:
      0: Auto
      1: Manual
  - platform: "tuya"
    name: "Display unit"
    enum_datapoint: 23
    options:
      0: ℃
      1: F
  - platform: "tuya"
    icon: "mdi:thermometer-low"
    name: "Sensor Choose"
    enum_datapoint: 101
    options:
      0: in
      1: out

  - platform: "tuya"
    icon: "mdi:thermometer-low"
    name: "Working day setting"
    enum_datapoint: 31
    options:
      0: "5_2"
      1: "6_1"
      2: "7"
  - platform: "tuya"
    icon: "mdi:thermometer-low"
    name: "Working status"
    enum_datapoint: 32
    options:
      0: "Idle"
      1: "Manual"
      2: "Heating"
      3: "N/A"

number:
- platform: "tuya"
  icon: "mdi:numeric"
  number_datapoint: 3
  step: 1
  unit_of_measurement: "℃"
  min_value: 5
  max_value: 45 
  name: "Set temperature"

- platform: "tuya"
  unit_of_measurement: "%"
  number_datapoint: 44
  step: 1
  min_value: 0
  max_value: 100 
  name: "Backlight brightness"

- platform: "tuya"
  unit_of_measurement: "℃"
  id: temp_correction
  number_datapoint: 108
  icon: "mdi:plus-minus"
  step: 1
  min_value: -10
  max_value: 10 
  name: "Temperature correction"

- platform: "tuya"
  icon: "mdi:numeric"
  number_datapoint: 102
  step: 1
  unit_of_measurement: "℃"
  min_value: 5
  max_value: 45 
  name: "High temperature protection"

- platform: "tuya"
  icon: "mdi:numeric"
  number_datapoint: 103
  step: 1
  unit_of_measurement: "℃"
  min_value: 5
  max_value: 45 
  name: "Cryogenic protection"

- platform: template
  id: backup_temp
  internal: True
  step: 1
  optimistic: True
  min_value: 10
  max_value: 30
  initial_value: 23

Some highlights

Device will show weather information (weather icon & humidity) based on TUYA weather service https://developer.tuya.com/en/docs/iot/weather-function-description?id=Ka6dcs2cw4avp, unfortunately, it does not follow the documentation exactly…

MCU requests following weather atributes

  • w.temp
  • w.humidity
  • w.condition
  • w.pm25
  • w.conditionNum

for w.date.1 (actual day)

So weather message to MCU needs to be in that order, but with following names

  • w.temp
  • w.humidity.0
  • w.condition/
  • w.pm25
  • w.conditionNum.0

Based on w.conditionNum.0 icon is choosed, again this does not follow the documentation exactly

  • Sunny = 0x31,0x32,0x30
  • Cloudy = 0x31,0x30,0x31 ≠ Heavy rain from documentation
  • Rainy = 0x31,0x33,0x30 ≠ Light snow shower
  • partlycloudy = 0x31,0x34,0x32 ≠ Cloudy