At home I have a few Shelly H&T sensors. These sensors provide me with a regular update on the temperature and humidity in a room. They look pretty nice and are tiny, so they can be placed just about anywhere without drawing too much attention. They currently sell in a white/blue and a black/orange variant for about €24, with an optional USB-powered base replacing the CR123A battery sold for €5.
I have one of these on each floor for general temperature monitoring.

Recently, I came accross a video by Intermit.Tech (Quindor) on setting up temperature and humidity monitoring using cheap $4 Xiaomi sensors. These sensors are also pretty tiny, but come with a LCD-screen displaying the sensor data. Their sensor is also pre-calibrated and the devices are optimized for running on battery power.
Because of their small size, and more importantly their small price, they’re ideal for per-room temperature monitoring.

Xiaomi LYWSD03MMC temperature and humidity sensor
Xiaomi LYWSD03MMC sensor

Since they send their data over Bluetooth Low-Energy (BLE) you’re supposed to use the companion app to get the readings.

In this blogpost I’ll explain how to add the Xiaomi thermometers to Home Assistant using an ESP32 board to capture the bluetooth data and ESPHome to import the data in Home Assistant. I’ll discuss flashing the Xiaomi LYWSD03MMC sensors with custom firmware, integrating the Xiaomi LYWSDCGQ, getting the bindkey for the Xiaomi CGG1 running the newer firmware, and setting the clock on the Xiaomi LYWSD02 without using the MiHome app.

Parts needed (shopping list)

For this tutorial, you’ll need:

Flashing the ESP32

If you got a brand new ESP32 board, you’ll need to flash it once over USB to get it setup to work with ESPHome. Once this flashing is done, you’ll be able to push any future updates Over-The-Air (OTA).

ESPHome

We’ll be writing our code for the ESP32 in ESPHome. So make sure you’ve installed the ESPHome Add-on in Home Assistant.
The ESPHome website has a tutorial on installing this add-on in Home Assistant.

Flashing bluetooth discovery firmware

  1. In ESPHome, create a new device by clicking the big + button.
    The details you enter here don’t matter too much. Just make sure you give it a reasonable name (this will also be used to name the config file). E.g., livingroom_esp32

  2. Now edit the device and overwrite the config with the following code. Change the back to what you originally named the device.
    You can enter the WiFi credentials directly in the config, or make use of the secrets file (see below).

    substitutions:
        esphome_name: livingroom_esp32
    
    esphome:
        name: ${esphome_name}
        platform: ESP32
        board: mhetesp32devkit
    
    wifi:
        ssid: !secret wifi_ssid
        password: !secret wifi_pass
    
    api:
        reboot_timeout: 60min
        password: !secret esphome_api_pass
    
    # Sync time with HA
    time:
        - platform: homeassistant
    
    # Optional
    #web_server:
    #    port: 80
    
    ota:
        password: !secret esphome_ota_pass
    
    logger:
    
    # Enable Bluetooth scanning for this ESP32
    esp32_ble_tracker:
    

    Save and Close the editor.

  3. Now click the 3 dots in the upper right corner of your device and click Compile.
    This will compile the code into a firmware binary that we can download to our computer.

  4. While the code is compiling, download and install the ESPHome-Flasher for your system. This software will be used for the initial flashing over USB. Future updates will be done Over-The-Air (OTA).

  5. We’ll now flash the compiled firmware to the ESP32.

    1. Connect the ESP32 using a micro-USB cable to your computer
    2. Open ESPHome-Flasher
    3. Click the refresh button at the top-right
    4. Select the COM port your ESP32 is connected to
    5. Browse to the firmware we’ve just compiled and download to our computer
    6. Click Flash ESP

    If the flashing software is unable to put the ESP32 in flash mode, hold down the Boot button on the ESP32 while it’s trying to flash.
    Once the flashing has started, you can let go of the Boot button.

  6. Once it’s done flashing, the ESP32 will connect to your WiFi network and you’ll be able to control the ESP32 Over-The-Air using ESPHome.

Flashing sensor firmware

You can now disconnect the ESP32 and install it where it will be able to receive the bluetooth data of your sensors. Power it using a phone charger, like the one in the Shopping List above.

  1. Back in the ESPHome dashboard, you can now click the LOGS button to view the output of the ESP32 board.
    In the past you’d see broadcast messages containing temperature and humidity information, including the device’s MAC address. Nowadays you’ll need to look for lines Found device XX:XX:XX:XX:XX:XX (...) Name: 'xxxxxxxxxx'. You’ll want to note down the MAC address of the ATC device (if flashed) or with the original name (e.g., LYWSD03MMC).
    BLE_tracker showing discovered devices
    BLE_tracker output

    Note: To make identifying the correct Xiaomi device easier, make sure the others are out of bluetooth range or are turned off. You may also want to label each sensor after you’ve identified it.
  2. We’ll now edit the device’s code again and add the sensor entities for the Xiaomi sensor(s) we’ll be monitoring.
    Open the config editor and paste the following code below the code we already had:
    # Add this to the substitions block:
    # substitutions:
        sensor_name_lywsd03: Living room
        sensor_name_lywsd02: Kitchen
        sensor_name_cgg1: Master Bedroom
        sensor_name_lywsdcgq: Office
    
    switch:
        - platform: gpio
          name: "${esphome_name} - Onboard LED"
          pin: 2
          inverted: True
    
        - platform: restart
          name: "${esphome_name} - Restart"
          id: restart_switch
    
    sensor:
        # General
        - platform: uptime
          name: "${esphome_name} - Uptime Sensor"
    
        - platform: wifi_signal
          name: "${esphome_name} - WiFi Signal"
          update_interval: 60s
    
        # LYWSD03MMC - Advertising Type: Mi Like
        - platform: xiaomi_lywsd03mmc
          mac_address: "XX:XX:XX:XX:XX:XX" # Replace with MAC Address of sensor
          bindkey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # Replace with bind key, use a dummy value when flashed with custom firmware
          temperature:
            name: "${sensor_name_lywsd03} - Temperature"
          humidity:
            name: "${sensor_name_lywsd03} - Humidity"
          battery_level:
            name: "${sensor_name_lywsd03} - Battery Level"
    
        # LYWSD03MMC - Advertising Type: Custom
        - platform: atc_mithermometer
          mac_address: "XX:XX:XX:XX:XX:XX" # Replace with MAC Address of sensor
          temperature:
            name: "${sensor_name_lywsd03} - Temperature"
          humidity:
            name: "${sensor_name_lywsd03} - Humidity"
          battery_level:
            name: "${sensor_name_lywsd03} - Battery-Level"
          battery_voltage:
            name: "${sensor_name_lywsd03} - Battery-Voltage"
    
        # LYWSD02
        - platform: xiaomi_lywsd02
          mac_address: "XX:XX:XX:XX:XX:XX" # Replace with MAC Address of sensor
          temperature:
            name: "${sensor_name_lywsd02} Temperature"
          humidity:
            name: "${sensor_name_lywsd02} Humidity"
          battery_level:
    		name: "${sensor_name_lywsd02} Battery Level"
    
        # CGG1
        - platform: xiaomi_cgg1
          mac_address: "XX:XX:XX:XX:XX:XX" # Replace with MAC Address of sensor
          bindkey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # Replace with bind key, requires different ESPHome_version
          temperature:
            name: "${sensor_name_cgg1} Temperature"
          humidity:
            name: "${sensor_name_cgg1} Humidity"
          battery_level:
            name: "${sensor_name_cgg1} Battery Level"
    
        # LYWSDCGQ
        - platform: xiaomi_lywsdcgq
          mac_address: "XX:XX:XX:XX:XX:XX" # Replace with MAC Address of sensor
          temperature:
            name: "${sensor_name_lywsdcgq} Temperature"
          humidity:
            name: "${sensor_name_lywsdcgq} Humidity"
          battery_level:
            name: "${sensor_name_lywsdcgq} Battery Level"
    
        # Remove or add extra sensors by deleting/copying any of the blocks above.
        # Change MAC address, bindkey and sensor names
    
  3. Press the Upload bottom at the bottom right of the editor.
    This will save, compile and upload the new firmware to the ESP32 (OTA).

Storing WiFi creds and OTA/API passwords in secret file

Just like with Home Assistant, ESPHome has a secrets.yaml in which you can store stuff like WiFi credentials.
This allows you to keep secret data away from your config files and also adds consistency accross your devices.

To make things even easier, you can reference your Home Assistant secrets.yaml from the ESPHome’s one, allowing you to keep everything in one central location.

Create a file /config/esphome/secrets.yaml with the following content:

<<: !include ../secrets.yaml

Now in /config/secrets.yaml you can add your WiFi credentials etc.

wifi_ssid: "MyVerySecretWiFiNetwork"
wifi_pass: "MyverySecretWiFiPassword"
esphome_ota_pass: "SecretEsphomeOtaPass"
esphome_api_pass: "SecretEsphomeApiPass"

You can now reference these secrets from your config using e.g. ssid: !secret wifi_ssid.

Adding the sensors in Home Assistant

Adding the sensors to Home Assistant is really easy. If the sensor for some reason doesn’t show up yet in the guide below, give Home Assistant a reboot and wait a few minutes.

  1. In Home Assistant, go to Configuration > Integrations.
  2. If the ESP32 isn’t automatically discovered yet by Home Assistant, click the big + button to add a new integration.
    Choose ESPHome from the list.
  3. Enter the IP address or hostname of your ESP32 board, keep the port in its default value.
  4. Enter your API and/or OTA password.
  5. Add the device to a room if you wish to do so.

Within the Configuration page you should now see an entry for your device (livingroom_esp32) under ESPHome.
Clicking this device will show you the different sensors for the temperature, humidity and battery level. You should also have a switch to turn on the onboard LED (useful to identify the device) and to reboot it. You may also have a sensor for the WiFi signal strength and device uptime.

It may take a while before sensor data will appear for the device.
Since the sensors are battery powered, it may take until a certain temperature change (or the next update interval).

ESPHome entities in Home Assistant
ESPHome entities in Home Assistant
Temperature readings
Temperature readings

Device specific configurations

Getting the bindkey of the LYWSD03MMC and flashing custom firmware

Sadly enough, more recent models of the Xiaomi (LYWSD03MMC) sensors come with a new firmware that encrypts the data send over bluetooth. Luckily Aaron Christophel came up with a nice way of hacking the firmware on these sensors. Not only does his hack provide a method of retrieving the bluetooth encryption key (allowing us to decrypt the data) but he even wrote his own firmware. Using Aaron’s ATC_MiThermometer firmware, you can also control the update interval, allowing you to choose sensor update frequency over battery life.

Update 2021-05-18: There’s a fork of Aaron’s firmware which appears to offer more functions including non-volatile storage and a better low power management. To use this version, follow the same steps as below but grab the binary from Victor’s repository.

ATC MiThermometer by Aaron Christophel
ATC MiThermometer

Note: Flashing the custom firmware isn’t strictly necessary to get the sensor data imported into Home Assistant.
If you don’t feel like flashing the sensor, follow the guide below until step 5 and skip the rest of the flashing.

The flashing will be done from the browser a phone or tablet, since you need to connect to the sensor over bluetooth.
Use the default browser of the device or the Chrome browser. If it doesn’t work with one device, try it again on another.
I had issues trying this on a OnePlus and Firefox. It succeeded using a Samsung Galaxy Tab and its default browser.

  1. Get the ATC_MiThermometer.bin binary from Aaron’s GitHub and save it to your phone/tablet.

  2. Still on the GitHub page, at the top of the Readme documentation, you’ll find a link to the Web Flasher tool also written by Aaron. Open this link in the default/Chrome browser of your phone/tablet.

  3. On the Web Flasher page, check the Hide unsupported checkbox and click Connect.

  4. From the popup that should now have appeared, select the sensor you want to flash. The name should match the device ID from the shopping list above (e.g., LYWSD03MMC).
    Once connected, the “Temp/Humi” field – located about halfway the webpage – should be displaying sensor readings. At the bottom of the page, the logs should also show the device having connected.

  5. Press the Do activation button.
    A Mi Token and Mi Bind Key should appear a bit below the “Temp/Humi” field of the previous step.
    Note these down (or copy them into an email, Google Keep note, …) in a way that you can easily get to them from your computer/laptop.
    This token and key are what you need to import the Xiaomi sensor into Home Assistant without flashing the custom firmware.

    If you don’t want to flash the Xiaomi sensor with the custom firmware, stop here and continue with the next part.

  6. Below the “Do activation” button, there’s a Choose File button.
    Press this button and select the ATC_MiThermometer.bin you downloaded earlier.
    Make sure to select the correct file here, or you may/will brick the sensor!

  7. Press the Start Flashing button.
    You’ll see that status of the flashing appearing below the button. Don’t use the phone for now, it’ll only take a minute or so.

  8. Once the update is succesful, press the Connect button again and look for the flashed sensor.
    The device should now appear as ATC_xxxxxx. The logs at the bottom of the page should show you that you’ve connected to the device.

  9. Using the newly acquired features, we can now change some settings on the device.
    Note that the changes don’t always go through to the device, so press each button like 5 times to make sure it was set succesfully.

    1. set the Advertising Type to Mi Like
      Note: Since ESPHome v1.16.0 Advertising Type: Custom is supported.
    2. set the Advertising Interval to your liking. Shorter interval means shorter battery life but less detailed measurements.

Getting the bindkey of the CGG1

Xiaomi CGG1
Xiaomi CGG1

MiHome Mod app

Note: These steps must be followed with an Android device

  1. Download the MiHome mod apk.
    The site is in Russian, so use Google Translate to translate if needed.
  2. Configure your device to allow installation of untrusted sources.
  3. While installing the app, grant permission to the local device storage
  4. Once you open the app, a new folder will be created: /devicestorage/vevs.
  5. Close the app and create a new folder within this folder: /devicestorage/vevs/logs
  6. Open the app again and login with your Mi account.
  7. Add the CGG1 in the app.
  8. The bindkey is now stored on your device.
    Transfer /devicestorage/vevs/logs/misc/pairings.txt to your computer (over USB/bluetooth).
  9. Extract the bindkey from the file you just transfered.

The pairings.txt will look like this:

Did:		blt.4.xxxxxxxxxxxxx
Token:		xxxxxxxxxxxxxxxxxxxxxxxx
Bindkey:		xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Mac:		xx:xx:xx:xx:xx:xx

ESPHome

To use this bindkey in your configuration, you’ll need to load an adapted version of ESPHome.

Update 2021-05-18: Flameeyes’ code has been imported into the main ESPHome code base since ESPHome v1.17.1. It’s no longer necessary to move to Flameeyes’ codebase to configure the bindkey in your ESPHome config.

  1. Open the Supervisor Dashboard in Home Assistnat
  2. Click the ESPHome add-on and go to the Configuration tab
  3. Under “esphome_version” enter Flameeyes:cgg1-enc
    ESPHome_version: Flameeyes:cgg1-enc
    Change esphome_version

    In the YAML this would look like:
    ssl: true
    certfile: fullchain.pem
    keyfile: privkey.pem
    esphome_version: 'Flameeyes:cgg1-enc'
    relative_url: ''
    
  4. Save and restart the add-on

You can now add a bindkey to the config/firmware.

Update ESP32

If you already had your ESP32 configured, you’ll need to update it to the custom ESPHome_version.

  1. Open the ESPHome dashboard.
  2. You’ll see the Update symbol at the top-right of the device’s entry.
  3. To update, click the 3 dots at the top-right of your screen and click “Update All”.
    ESPHome - Update all
    Update All

Setting the clock on the LYWSD02

Xiaomi LYWSD02
Xiaomi LYWSD02

The LYWSD02 also displays the current time.

Normally you need to install the MiHome app to sync the time. But installing a Chinese app just to sync the time on your sensors, might seem a bit useless. Luckily, another fantastic Github user, saso5, created a simple web app that allows you to set the time on your LYWSD02.

  • Visit https://saso5.github.io/LYWSD02-clock-sync/ with the same device you used to flash your LYWSD03.
  • Pick your timezone (between UTC/GMT-12 and UTC/GMT+14)
  • Click the Update time button
  • Select the LYWSD02 in the popup that appears and click Connect

The time on your LYWSD02 should now be correct.

Make sure you close the browser tab and disconnect your device from the Xiaomi.
Otherwise your device will “claim” the Bluetooth connection and the ESP32 won’t be able to connect.

There’s a feature request on the ESPHome GitHub to have the LYWSD02 sync with an SNTP server.

Full ESP32 configuration

The complete configuration file looks like this:

substitutions:
    esphome_name: livingroom_esp32
    sensor_name_lywsd03: Living room
    sensor_name_lywsd02: Kitchen
    sensor_name_cgg1: Master Bedroom
    sensor_name_lywsdcgq: Office

esphome:
    name: ${esphome_name}
    platform: ESP32
    board: mhetesp32devkit

wifi:
    ssid: !secret wifi_ssid
    password: !secret wifi_pass

api:
    reboot_timeout: 60min
    password: !secret esphome_api_pass

ota:
    password: !secret esphome_ota_pass

# Sync time with HA
time:
    - platform: homeassistant

# Optional
#web_server:
#    port: 80

logger:

# Enable Bluetooth scanning for this ESP32
esp32_ble_tracker:

switch:
    - platform: gpio
      name: "${esphome_name} - Onboard LED"
      pin: 2
      inverted: True

    - platform: restart
      name: "${esphome_name} - Restart"
      id: restart_switch

sensor:
    # General
    - platform: uptime
      name: "${esphome_name} - Uptime Sensor"

    - platform: wifi_signal
      name: "${esphome_name} - WiFi Signal"
      update_interval: 60s
    
    # LYWSD03MMC - Advertising Type: Mi Like
    - platform: xiaomi_lywsd03mmc
      mac_address: "XX:XX:XX:XX:XX:XX" # Replace with MAC Address of sensor
      bindkey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # Replace with bind key, use a dummy value when flashed with custom firmware
      temperature:
        name: "${sensor_name_lywsd03} - Temperature"
      humidity:
        name: "${sensor_name_lywsd03} - Humidity"
      battery_level:
        name: "${sensor_name_lywsd03} - Battery Level"

    # LYWSD03MMC - Advertising Type: Custom
    - platform: atc_mithermometer
      mac_address: "XX:XX:XX:XX:XX:XX" # Replace with MAC Address of sensor
      temperature:
        name: "${sensor_name_lywsd03} - Temperature"
      humidity:
        name: "${sensor_name_lywsd03} - Humidity"
      battery_level:
        name: "${sensor_name_lywsd03} - Battery-Level"
      battery_voltage:
        name: "${sensor_name_lywsd03} - Battery-Voltage"
        
    # LYWSD02
    - platform: xiaomi_lywsd02
      mac_address: "XX:XX:XX:XX:XX:XX" # Replace with MAC Address of sensor
      temperature:
        name: "${sensor_name_lywsd02} Temperature"
      humidity:
        name: "${sensor_name_lywsd02} Humidity"
      battery_level:
        name: "${sensor_name_lywsd02} Battery Level"
    	
    # CGG1
    - platform: xiaomi_cgg1
      mac_address: "XX:XX:XX:XX:XX:XX" # Replace with MAC Address of sensor
      bindkey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # Replace with bind key, requires different ESPHome_version
      temperature:
        name: "${sensor_name_cgg1} Temperature"
      humidity:
        name: "${sensor_name_cgg1} Humidity"
      battery_level:
        name: "${sensor_name_cgg1} Battery Level"
    	
    # LYWSDCGQ
    - platform: xiaomi_lywsdcgq
      mac_address: "XX:XX:XX:XX:XX:XX" # Replace with MAC Address of sensor
      temperature:
        name: "${sensor_name_lywsdcgq} Temperature"
      humidity:
        name: "${sensor_name_lywsdcgq} Humidity"
      battery_level:
        name: "${sensor_name_lywsdcgq} Battery Level"
        
    # Remove or add extra sensors by deleting/copying any of the blocks above.
    # Change MAC address, bindkey and sensor names

Changelog

  • 2020-02-09 - Newer versions of CGG1 have encrypted Bluetooth messages. Flameeyes:cgg1-enc firmware adds support for bindkey.
  • 2021-02-14 - ESPHome v1.16.0 adds support for ATC_MiThermometer using Advertising Type: Custom
  • 2021-05-18 - Flameeyes:cgg1-enc merged into main ESPHome codebase to support CGG1 with bindkey. ATC_MiThermometer fork adds extra features.