Just thought I’d post some brief observations about the Anko Smart Kettle (LD-K3068, $65 from K-Mart in Australia) and my experiences replacing the ESP8266 (ESP8285) controller software with esphome.
- I didn’t attempt an OTA exploit, I disassembled it (surprisingly no fancy screws involved), soldered short flyleads to the necessary pins and flashed it over serial.
- The kettle has its own chip that controls the relay, the LEDs, the buttons and the beeper. Core kettle functionality doesn’t rely on the ESP8266 at all. The ESP8266 can do little more than read the temperature, observe the current modes and push some virtual buttons. So if you wanted to be able to do fun things with the LEDs or make the kettle less beepy, you’re out of luck.
- The controller chip is a Tuya device which esphome can communicate with. However Esphome doesn’t currently have a Tuya profile that works with it, so I had to write a few hacky lambdas to push the buttons.
- The temperature sensor is generally quite accurate (or at least within 1° of my Thermoworks ChefAlarm) although it has severe hysteresis i.e. it lags quite a bit. The controller seems to be programmed with this hysteresis in mind, as it does some very basic things to compensate for it. But it doesn’t compensate well when the water level is low.
- For a short period before and after reaching the target, the controller stops giving you accurate thermal readings and instead offers you lies. It’s pretending that it hit the target temperature perfectly when in reality it tends to overshoot quite a bit. (If you ask for 80° the water will get as hot as 90° depending on the quantity of water in the kettle.)
- You can tell when the kettle is not on the base because the reported temperature is zero. Otherwise it will always report the water temperature. It doesn’t seem to ever go to deep sleep.
- I haven’t experimented with its dry boil protection, assuming it has any.
Here’s the current iteration of my YAML:
packages:
core: !include _config/core.yaml
esphome:
platform: ESP8266
board: esp8285
on_boot:
then:
- text_sensor.template.publish:
id: text_state
state: "Ready"
logger:
level: DEBUG
uart:
rx_pin: GPIO13
tx_pin: GPIO15
baud_rate: 9600
tuya:
text_sensor:
- platform: template
name: Kettle State
id: text_state
switch:
- platform: tuya
id: the_relay
switch_datapoint: 1
- platform: tuya
name: Kettle Keep Warm
switch_datapoint: 13
- platform: template
name: Kettle Heat To 80
lambda: |-
return (id(kettle_target).state == 80 && (id(tuya_15).state == 1 || id(tuya_15).state == 3) );
turn_on_action:
- switch.turn_off: the_relay
- lambda: |-
tuya::TuyaDatapoint datapoint{};
datapoint.id = 4;
datapoint.type = tuya::TuyaDatapointType::ENUM;
datapoint.value_enum = 0x0;
tuya_tuya->set_datapoint_value(datapoint);
- sensor.template.publish:
id: kettle_target
state: 80
- switch.turn_on: the_relay
turn_off_action:
- switch.turn_off: the_relay
- platform: template
name: Kettle Heat To 85
lambda: |-
return (id(kettle_target).state == 85 && (id(tuya_15).state == 1 || id(tuya_15).state == 3) );
turn_on_action:
- switch.turn_off: the_relay
- lambda: |-
tuya::TuyaDatapoint datapoint{};
datapoint.id = 4;
datapoint.type = tuya::TuyaDatapointType::ENUM;
datapoint.value_enum = 0x1;
tuya_tuya->set_datapoint_value(datapoint);
- sensor.template.publish:
id: kettle_target
state: 85
- switch.turn_on: the_relay
turn_off_action:
- switch.turn_off: the_relay
- platform: template
name: Kettle Heat To 90
lambda: |-
return (id(kettle_target).state == 90 && (id(tuya_15).state == 1 || id(tuya_15).state == 3) );
turn_on_action:
- switch.turn_off: the_relay
- lambda: |-
tuya::TuyaDatapoint datapoint{};
datapoint.id = 4;
datapoint.type = tuya::TuyaDatapointType::ENUM;
datapoint.value_enum = 0x2;
tuya_tuya->set_datapoint_value(datapoint);
- sensor.template.publish:
id: kettle_target
state: 90
- switch.turn_on: the_relay
turn_off_action:
- switch.turn_off: the_relay
- platform: template
name: Kettle Heat To 95
lambda: |-
return (id(kettle_target).state == 95 && (id(tuya_15).state == 1 || id(tuya_15).state == 3) );
turn_on_action:
- switch.turn_off: the_relay
- lambda: |-
tuya::TuyaDatapoint datapoint{};
datapoint.id = 4;
datapoint.type = tuya::TuyaDatapointType::ENUM;
datapoint.value_enum = 0x3;
tuya_tuya->set_datapoint_value(datapoint);
- sensor.template.publish:
id: kettle_target
state: 95
- switch.turn_on: the_relay
turn_off_action:
- switch.turn_off: the_relay
- platform: template
name: Kettle Heat To 100
lambda: |-
return (id(kettle_target).state == 100 && (id(tuya_15).state == 1 || id(tuya_15).state == 3) );
turn_on_action:
- switch.turn_off: the_relay
- lambda: |-
tuya::TuyaDatapoint datapoint{};
datapoint.id = 4;
datapoint.type = tuya::TuyaDatapointType::ENUM;
datapoint.value_enum = 0x4;
tuya_tuya->set_datapoint_value(datapoint);
- sensor.template.publish:
id: kettle_target
state: 100
- switch.turn_on: the_relay
turn_off_action:
- switch.turn_off: the_relay
sensor:
- platform: template
name: Kettle Target
id: kettle_target
unit_of_measurement: "°C"
accuracy_decimals: 0
- platform: tuya
id: kettle_temp
name: Kettle Temperature
sensor_datapoint: 2
unit_of_measurement: "°C"
accuracy_decimals: 0
filters:
- filter_out: 0
- or:
- throttle: 30min
- delta: 1
on_raw_value: # We don't want filters to affect this
then:
- if:
condition:
lambda: |-
return id(kettle_temp).raw_state == 0;
then:
- if:
condition:
text_sensor.state:
id: text_state
state: "Lifted"
else:
- text_sensor.template.publish:
id: text_state
state: "Lifted"
else:
- if:
condition:
text_sensor.state:
id: text_state
state: "Lifted"
then:
- text_sensor.template.publish:
id: text_state
state: "Ready"
- platform: tuya
id: tuya_15
sensor_datapoint: 15
on_value:
then:
- if:
condition:
lambda: |-
return id(kettle_temp).raw_state == 0;
else:
- text_sensor.template.publish:
id: text_state
state: !lambda |-
switch(static_cast<int>(id(tuya_15).state)) {
case 0: return {"Ready"};
case 1: return {"Heating"};
case 2: return {"Done"};
case 3: return {"Keep warm"};
default: return {""};
}
- platform: tuya
id: tuya_4
sensor_datapoint: 4
on_value:
then:
- sensor.template.publish:
id: kettle_target
state: !lambda |-
switch(static_cast<int>(id(tuya_4).state)) {
case 0: return 80;
case 1: return 85;
case 2: return 90;
case 3: return 95;
case 4: return 100;
default: return 0;
}