Xiaomi Smart Kettle 2 Pro with ESPHome

Hello everyone,

I recently installed ESPHome on my Xiaomi Smart Kettle 2 Pro, and I wanted to share my experience while seeking some advice.

Using this guide, the installation went smoothly and was relatively straightforward. At the moment, the ESP32 successfully connects to my Wi-Fi network when I plug the kettle into the socket. However, that’s where things currently stand.

So far, I’ve only used the basic ESPHome configuration from the guide, but I haven’t added the necessary sensors yet. I could use some help configuring those, as I’m unable to remove the circuit board due to the rotary head holding it in place.

Here’s what needs to be integrated into the configuration:

• Piezo buzzer
• Temperature sensor
• Rotary knob (with lighting and a two-digit display)
• Two sensor buttons
• Heating logic

If anyone has experience with this or could point me in the right direction, I’d greatly appreciate it!




I doubt it’s glued, so find a way to disconnect it, otherwise you are half blind.
Locate all connected hardware and identify sensors.
Observe chips onboard U1…U4 and try to identify them.
Find the ssr (or whatever switch is used) that connects mains to heating element.
Try to trace components that are connected to Esp gpios with multimeter in continuity mode…



Touch buttons can use Esp touch pins or dedicated circuit

Nice job, what are those U2/U4?

I don’t fully understand that yet. Over the next few days/weeks, I will try to control some components with ESPHome (of course only with 3.3V and 5V). If anyone has tips on components and control, feel free to share.

They don’t have any markings?

My current assumption, since I can’t see any markings, is

U1: AMS1117 (3V3 to 5V)
U2: I2C-Expander

Update: 06.02.2025

ESP Home Config:

substitutions:
  device_name: "xiaomi-smart-kettle-2-pro"
  friendly_name: "Xiaomi Smart Kettle 2 Pro"

esphome:
  name: ${device_name}
  friendly_name: ${friendly_name}
  comment: ''


esp32:
  board: esp32doit-devkit-v1
  framework:
    type: esp-idf # Suggest using the ESP-IDF Framework. Changing from 'arduino' to 'esp-idf' needs a cabled flash to correct partitions
    version: recommended

    sdkconfig_options:
      CONFIG_FREERTOS_UNICORE: y
    advanced:
      ignore_efuse_mac_crc: true
      ignore_efuse_custom_mac: true  #added in 2024-11 as a fix https://community.home-assistant.io/t/xiaomi-mi-led-desk-lamp-1s-error-integration/787346

logger:
  level: DEBUG

api:
  encryption:
    key: !secret api_encryption_key

ota:
  - platform: esphome
    id: ota_password
    password: !secret ota_password

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: ${device_name}
    password: !secret wifi_ap_password

captive_portal:

web_server:
  port: 80
  #auth:
  #  username: !secret web_server_username
  #  password: !secret web_server_password

time:
  - platform: homeassistant
    id: homeassistant_time


text_sensor:
  - platform: version
    name: Version
  - platform: wifi_info
    ip_address:
      name: IP Address


globals:


# Pins definieren

  # # https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device:kettle:0000A009:xiaomi-v21:1
  # ################################################################
  # # Service 2 - Main Kettle Functions
  # ################################################################
  
  # # Status sensor (SIID: 2, PIID: 1)
  # # Permissions: Read notification
  # # Values: 0=Idle, 1=Heating, 2=Boiling, 3=Cooling, 4=Keep Warm
  # - id: kettle_status
  #   type: int
  #   restore_value: yes
  #   initial_value: '0'
    
  # # Fault sensor (SIID: 2, PIID: 2)
  # # Permissions: Read notification
  # # Range: 0-255, Step: 1
  # - id: kettle_fault
  #   type: int
  #   restore_value: yes
  #   initial_value: '0'
    
  # Temperature sensor (SIID: 2, PIID: 3)
  # Permissions: Read notification
  # Range: -30°C to 100°C, Step: 1
  - id: kettle_temperature
    type: int
    restore_value: yes
    initial_value: '0'
 #   on_value:
 #     then:
 #     - if:
 #         condition:
 #           or:
 #             - lambda: "return id(kettle_temperature) > 100;"
 #             - lambda: "return id(kettle_temperature) >= id(kettle_target_temperature);"
 #         then:
 #           - switch.turn_off: heater_switch_relay
 #           - switch.turn_off: heater_switch_triac
 #           - logger.log: 
 #               format: "Heater turned off - Temperature: %.1f°C, Target: %d°C"
 #               args: ["id(kettle_temperature)", "id(kettle_target_temperature)"]
    
  # Target temperature (SIID: 2, PIID: 4)
  # Permissions: Read and write, notifications
  # Range: 40°C to 99°C, Step: 1
  - id: kettle_target_temperature
    type: int
    restore_value: yes
    initial_value: '40'
    
  # # Auto keep warm (SIID: 2, PIID: 5)
  # # Permissions: Read and write, notifications
  # - id: kettle_auto_keep_warm
  #   type: bool
  #   restore_value: yes
  #   initial_value: 'false'
    
  # # Keep warm temperature (SIID: 2, PIID: 6)
  # # Permissions: Read and write, notifications
  # # Range: 0°C to 100°C, Step: 1
  # - id: kettle_keep_warm_temperature
  #   type: int
  #   restore_value: yes
  #   initial_value: '40'
    
  # # Switch Status (SIID: 2, PIID: 7)
  # # Permissions: Read and write, notifications
  # - id: kettle_on
  #   type: bool
  #   restore_value: yes
  #   initial_value: 'false'

  # ################################################################
  # # Service 3 - Extended Functions
  # ################################################################
  
  # # Keep warm time (SIID: 3, PIID: 1)
  # # Permissions: Read and write, notifications
  # # Range: 60-1440, Step: 30
  # - id: keep_warm_time
  #   type: int
  #   restore_value: yes
  #   initial_value: '60'
    
  # # Custom knob temperature (SIID: 3, PIID: 2)
  # # Permissions: Read and write, notifications
  # - id: custom_knob_temp
  #   type: bool
  #   restore_value: yes
  #   initial_value: 'false'
    
  # # Lift remember temperature (SIID: 3, PIID: 4)
  # # Permissions: Read and write, notifications
  # - id: lift_remember_temp
  #   type: bool
  #   restore_value: yes
  #   initial_value: 'false'
    
  # # Boiling reminder (SIID: 3, PIID: 5)
  # # Permissions: Read and write, notifications
  # - id: boiling_reminder
  #   type: bool
  #   restore_value: yes
  #   initial_value: 'false'
    
  # # Keep warm reminder (SIID: 3, PIID: 6)
  # # Permissions: Read and write, notifications
  # - id: keep_warm_reminder
  #   type: bool
  #   restore_value: yes
  #   initial_value: 'false'
    
  # # Kettle lifting (SIID: 3, PIID: 7)
  # # Permissions: Read notification
  # - id: kettle_lifting
  #   type: bool
  #   restore_value: yes
  #   initial_value: 'false'
    
  # # Extended mode (SIID: 3, PIID: 8)
  # # Permissions: Read and write, notifications
  # - id: extended_mode
  #   type: string
  #   restore_value: yes
  #   initial_value: '""'
    
  # # Warming time (SIID: 3, PIID: 10)
  # # Permissions: Read notification
  # # Range: 0-720, Step: 1
  # - id: warming_time
  #   type: int
  #   restore_value: yes
  #   initial_value: '0'
    
  # # Target mode (SIID: 3, PIID: 11)
  # # Permissions: Read and write, notifications
  # # Range: 0-128, Step: 1
  # - id: target_mode
  #   type: int
  #   restore_value: yes
  #   initial_value: '0'
    
  # # Heat/Boil modes (SIID: 3, PIID: 12/13)
  # # Permissions: Read and write, notifications
  # - id: heat_mode
  #   type: string
  #   restore_value: yes
  #   initial_value: '""'
    
  # - id: boil_mode
  #   type: string
  #   restore_value: yes
  #   initial_value: '""'

  # ################################################################
  # # Service 4 - Knob Settings
  # ################################################################
  
  # # Knob sequence (SIID: 4, PIID: 1)
  # # Permissions: Read notification
  # - id: knob_sequence
  #   type: string
  #   restore_value: yes
  #   initial_value: '""'
    
  # # Knob positions (SIID: 4, PIID: 2-7)
  # # Permissions: Read and write, notifications
  # - id: knob_one
  #   type: string
  #   restore_value: yes
  #   initial_value: '""'
  # - id: knob_two
  #   type: string
  #   restore_value: yes
  #   initial_value: '""'
  # - id: knob_three
  #   type: string
  #   restore_value: yes
  #   initial_value: '""'
  # - id: knob_four
  #   type: string
  #   restore_value: yes
  #   initial_value: '""'
  # - id: knob_five
  #   type: string
  #   restore_value: yes
  #   initial_value: '""'
  # - id: knob_six
  #   type: string
  #   restore_value: yes
  #   initial_value: '""'

  # ################################################################
  # # Service 5 - Local Timing
  # ################################################################
  
  # # Scene sequence (SIID: 5, PIID: 1)
  # # Permissions: Read notification
  # - id: scene_sequence
  #   type: string
  #   restore_value: yes
  #   initial_value: '""'
    
  # # Scene settings (SIID: 5, PIID: 2-6)
  # # Permissions: Read and write, notifications
  # - id: scene_one
  #   type: string
  #   restore_value: yes
  #   initial_value: '""'
  # - id: scene_two
  #   type: string
  #   restore_value: yes
  #   initial_value: '""'
  # - id: scene_three
  #   type: string
  #   restore_value: yes
  #   initial_value: '""'
  # - id: scene_four
  #   type: string
  #   restore_value: yes
  #   initial_value: '""'
  # - id: scene_five
  #   type: string
  #   restore_value: yes
  #   initial_value: '""'

  # ################################################################
  # # Service 6 - Do Not Disturb
  # ################################################################
  
  # # Do not disturb (SIID: 6, PIID: 1)
  # # Permissions: Read and write, notifications
  # - id: do_not_disturb
  #   type: bool
  #   restore_value: yes
  #   initial_value: 'false'


binary_sensor:
  - platform: status
    name: Device Status
    id: device_status
  - platform: gpio
    pin: GPIO18
    name: "Button Boil - Touch"
    id: button_boil_touch
  - platform: gpio
    pin: GPIO19
    name: "Button Keep Warm - Touch"
    id: button_keepwarm_touch
  - platform: gpio
    pin: GPIO39
    name: "Unknown 39"
  - platform: gpio
    pin: GPIO34
    name: "Unknown 34"
  - platform: gpio
    pin: GPIO35
    name: "Unknown 35"
  - platform: gpio
    pin: GPIO32
    name: "Unknown 32"
  - platform: gpio
    pin: GPIO25
    name: "Unknown 25"
  - platform: gpio
    pin: GPIO14
    name: "Unknown 14"
#  - platform: gpio
#    pin: GPIO33
#    name: "Unknown 33"

sensor:
  - platform: uptime      # Uptime for this device
    name: Uptime
    update_interval: 60s
  - platform: wifi_signal # Wifi Strength
    name: Wifi Signal
    update_interval: 60s
  - platform: template   
    name: "Rotary Value"
    id: rotary_value_sensor
    unit_of_measurement: "°C"
    accuracy_decimals: 0
    update_interval: 60s
    lambda: |-
      return id(kettle_target_temperature);
  - platform: rotary_encoder
    id: rotation
    pin_a: GPIO16
    pin_b: GPIO17
    resolution: 1
    on_value:
      then:
        - lambda: |-
            float step = 1;
            if (abs(x) > 2) step = 2;
            if (abs(x) > 4) step = 5;
            float new_value = id(kettle_target_temperature) + (x * step);
            new_value = std::min(99, std::max(40, static_cast<int>(new_value)));
            id(kettle_target_temperature) = new_value;
            id(rotary_value_sensor).publish_state(new_value);
        - sensor.rotary_encoder.set_value:
            id: rotation
            value: 0

  - platform: ntc
    sensor: resistance_sensor_temperature
    calibration:
      b_constant: 3950
      reference_temperature: 25°C
      reference_resistance: 10kOhm
    name: "NTC Temperature"
  - platform: resistance
    id: resistance_sensor_temperature
    sensor: adc_sensor_temperature
    configuration: UPSTREAM
    reference_voltage: 3.3V
    resistor: 10kOhm
    name: "NTC Resistance Sensor"
  - platform: adc
    id: adc_sensor_temperature
    pin: GPIO36
    attenuation: 11db
    update_interval: 1s
    samples: 64



    # Beispiel: ADC-Sensor zum Einlesen des Spannungspegels
  - platform: adc
    id: neutralspannung
    pin: GPIO33             # Hier wird der analoge Eingang genutzt
    name: "Neutralleiter Spannung"
    update_interval: 2s
    attenuation: 11db          # Für höhere Eingangsspannungen (bis ca. 3.9V)
    filters:
      - median:
          window_size: 7
          send_every: 4
      - sliding_window_moving_average:
          window_size: 15
          send_every: 15
      - multiply: 3.3          # Falls notwendig, um den Messwert in Volt umzurechnen
    unit_of_measurement: "V"
    icon: "mdi:flash-outline"

output:
  - platform: ledc
    pin: GPIO4 
    id: buzzer_output

number:
  - platform: template
    name: "Buzzer Control"
    id: buzzer_slider
    min_value: 0
    max_value: 100
    step: 1
    unit_of_measurement: "%"
    optimistic: true
    set_action:
      then:
        - if:
            condition:
              lambda: 'return x > 0;'
            then:
              - output.turn_on: buzzer_output
              - output.ledc.set_frequency:
                  id: buzzer_output
                  frequency: 1000Hz
              - output.set_level:
                  id: buzzer_output
                  level: !lambda "return x / 100.0;"
            else:
              - output.turn_off: buzzer_output

switch:
  - platform: gpio
    name: "Button Boil - Light"
    pin: GPIO5
    id: button_boil_light
    inverted: true
  - platform: gpio
    name: "Button Keep Warm - Light"
    pin: GPIO21
    id: button_keepwarm_light
    inverted: true
  - platform: gpio
    name: "Heater Main-Switch (Relay)"
    pin: GPIO22 
    id: heater_switch_relay
  - platform: gpio
    name: "Heater Secondary-Switch (Triac)"
    pin: GPIO23
    id: heater_switch_triac
  - platform: gpio
    name: "Unknown Switch 12"
    pin: GPIO12
    id: switch_12



#https://github.com/ssieb/esphome_components/tree/master/components/ht16k33_alpha

display:
  platform: tm1637
  id: tm1637_display
  clk_pin: GPIO26
  dio_pin: GPIO27
  length: 4  # Anzeige von 4 Zeichen
  update_interval: 1s  # Update-Intervall auf 1 Sekunde setzen
  intensity: 4  # Helligkeit des Displays
  lambda: |-
    char buf[5];  
    sprintf(buf, "%02d", id(kettle_target_temperature));  
    it.print(buf); 

Status:

2x touch button:
The two touch buttons are controlled via an SC02A sensor IC temperature-touch-magnet-sensors. This chip is connected to the ESP32 via GPIO18 and GPIO19. The status can be determined using HIGH or LOW.
Status: OK

2x status LED:
Two LEDs are built into the touch button, these can be switched via GPIO21 and GPIO5.
Status: OK

1x buzzer
The buzzer can be controlled via GPIO4.
Status: 80% (signal pattern still needs to be defined in the config)

1x Rotary Encoder Sensor
The rotary encoder sensor can be used to set the temperature. It is controlled via GPIO16 and GPIO17.
Status: OK

1x Heater Relay
The large relay K1 can be controlled via GPIO22 to control the heating element of the kettle.
Status: OK

1x Heater Triac
The triac Q1 can be controlled via GPIO23 to control the heating element of the kettle. The triac can be switched independently of the relay.
So far I have only tried on/off, but it may be possible to control it continuously (with the KO3211 Chip). The triac is probably used for noiseless switching of the kettle, for the keep-warm function.
Status: 50%

TX, RX, GPIO0:
A new firmware can be initially installed via TX,RX. GPIO0 puts the ESP32 into flash mode.

Thermometer:
The thermometer can be addressed via GPIO36 using NTC. NTC Sensor — ESPHome
The temperature can be measured precisely.
When the cooking pot is removed, the resistance changes considerably, allowing the status of the cooking pot to be recognized.
Status: 90%

Thermometer self-test:
I currently assume that a thermomenter self-test can be carried out via GPIO12. When transistor Q5 is activated, the resistance and thus the temperature changes by approx. 70 °C, but only when the cooking pot is plugged in.
This means that the thermometer can be tested and the status of the cooking pot can be detected 100%.
However, ESPHome config is required to use the functions
Status: 40%

Neutral conductor detection:
GPIO33 controls transistor Q4. I currently assume that this is a safety function that somehow monitors the neutral conductor.
Status: 20%

Display:
The display is addressed via GPIO27 (Clock) and GPIO26 (Data).
The display has the following functions:

  • 2x 7-segment display to show numbers from 01 to 99.
  • 1x Wifi symbol
  • 1x °C symbol
  • Red and blue LED.

At the moment I have not yet succeeded in controlling the display, with the TM1637 driver at least everything lights up and it flashes at refresh intervals.
Status: 10%

1 Like

Voltage regulator is AZ1117EH

What would be connected to I2C-expander? Generally expander is only needed if you have multiple I2C devices that are identic (all have same address).

It was neither :slight_smile:
I got the buzzer, rotary and four switches (I don’t know exactly what they are for) to work.

the two 8-pin ICs are a power management chip and a touch controller (see picture, the designation is there).

Do you have any idea how I could control the other components in ESP?
I did an i2c scan of the touch controller and nothing was found.

which ones?

  • There are 5 transistors to switch things, so far I’ve only really controlled one (Buzzer) with ESPHome. Another one is for the big relay, the others are not quite clear to me yet.
  • How could I control the touch controller?
  • How could I control the OLED display?
  • How could the thermomenter be controlled?

Those are simple. Just follow the trace from relay transistors base to Esp. Use gpio switch with option inverted.

Touch controller is trickier, if you are lucky it’s just high/low output on those pins that go to Esp.
If not… :slightly_frowning_face:

For display try with SSD1306 OLED Display component (I2C).

If thermometer is NTC (you wrote it has only two wires), you use ADC component.

Edit: no optocoupler so relay switch is not inverted

The touch buttons are now working.
I was also able to get the display to light up, but it is not an oled display but a 7-segment display (something like a TM1637).
So far the display just lights up, I can’t control the segments yet.

I still have problems with the following things:

  • The thermostat (GPIO12), I have tried the following but I get an error message when compiling.
    # ERROR: ESP32 doesn't support ADC on this pin when Wi-Fi is configured.
#  - platform: ntc
#    sensor: resistance_sensor
#    calibration:
#      b_constant: 3950
#      reference_temperature: 25°C
#      reference_resistance: 10kOhm
#    name: "NTC Temperature"
#  - platform: resistance
#    id: resistance_sensor
#    sensor: adc_sensor
#    configuration: DOWNSTREAM
#    reference_voltage: 3.3V
#    resistor: 10kOhm
#    name: "Resistance Sensor"
#  - platform: adc
#    id: adc_sensor
#    pin: GPIO12
#    attenuation: 6db
#    update_interval: 5s
#    samples: 10

The current esphome config is above.

If you only set plain adc_sensor with attenuation auto, what readings you get ?

Anyway the whole setup is very strange, GPIO12 is strapping pin, also it’s ADC2 pin and that has conflict with wifi. Wouldn’t be my first choice to use it for analog readings. Also, based your tracing it’s connected through Q5… You need to trace all the circuit left from Q5.

Based on your tracing relay is triggered by Gpio22 through Q2. That is clear circuit.
But what is that big boy with heat sink doing? It’s connected to Gpio23 through Q3 and I doubt that the two outer legs are connected to the resistor like you traced. And where is the middle leg going?? I see physical separation on PCB, could be triac, but what is it controlling if heating element is switched by relay?

Edit:
I’m quite sure NTC is connected to ADC1 pin. Can you trace the pad “NTC” to some upper left gpio, 32 for example…?

The NTC thermomenter is running

I do not yet understand the Relay Q5.
When it is activated, the temperature changes from 18.9C (13190.4 Ohm) to 92.5 C (898.3 Ohm).

I have not yet been able to activate the display either, it just lights up.

The current status can be found here.

Nice job.
To understand Q4, you need to identify RF1. Resettable fuse? Thermal fuse? Or some reed/tilt switch? Q4 gives detection of that.

For the display, you identified it MD52 OLED earlier. Not that?
Pcb markings let expect I2C data.
Is the driver chip visible?
If you remove that TM1637 component, can you detect I2C address for the display when you scan I2C bus?

RF1 is likely one of three electrical protection components in the system:

  1. NTC Thermometer - Measures the water temperature.

  2. NTC Self-Test - Ensures proper sensor functionality.

  3. RF1 as a Shunt Resistor for Overload Detection - Protects against excessive current draw.

How RF1 Works

RF1 acts as a shunt resistor, meaning that as more current flows, the voltage drop across it increases. The ESP32 reads this voltage via its ADC, allowing it to monitor the current consumption.

If the measured value exceeds a set threshold, an overload condition is detected, and the system can respond accordingly (e.g., shutting off power).

Manufacturer Description:

Triple electrical protection

Peace of mind during use

With high-temperature fuse protection, it automatically shuts off in case of dry boiling or when the water inside reaches boiling point.

Xiaomi Smart Kettle 2 Pro – Official Page

I initially thought it was an OLED display, but it is a seven-segment display with two additional symbols. Unfortunately, an I2C scan didn’t help, the chip is glued together so that you can’t see it