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