Flashing and Configuring Mi Smart Power Plug 2 (ZNCZ07CM, aka chuangmi.plug.212a01) to support ESPHome
Foreword
At one point in time I got fed up with the flaky behaviour of Mi Smart Power Plug 2 (ZNCZ07CM, aka chuangmi.plug.212a01) from its native cloud. Why? Because sometimes it turns the plug off right away and sometimes there is a noticeable lag, and sometimes it even fails to execute commands at all. And since this particular plug is WiFi, not a Zigbee one - it needs cloud connection even if integrated via Xiaomi Miot or any other existing Home Assistant integration. I suspect that this instability could be caused by distance between servers that control operations (they are in Mainland China) and plug location (Eastern Europe), or maybe something else is in the play, who knows, but the fact is that functionality is unstable and something had to be done (and I did not want to just go and buy other plug that works better, I’ll be doing it the hard, engineering way). The solution I was looking for - to reflash it with ESPHome and add it natively to Home Assistant - no remote servers, no lags, only pure HA experience.
ATTENTION
Everything described below is purely my own experience which I would like to share and if you decide to follow this guide further you are doing this at your own risk as it will definitely void any warranty or could even brick your plug. Throughout this guide you’ll need several tools like a sharp knife, soldering iron (and relevant skill), some cables, USB2UART adapter capable of working with 3.3 volts, understanding basics of how UART works and basics of flashing things over UART.
Plug disassembly
In my particular stock firmware version of Mi Smart Power Plug 2 (ZNCZ07CM, aka chuangmi.plug.212a01) I did not find any way of reflashing it over the air. As information on this particular plug is not very open on the net - the decision has been made to disassemble it and investigate what manufacturer put into it. And write the guide for others with such a plug - so you don’t have to go the same way I did, at least partially.
Required tools: short and thin sharp knife, flat screwdriver (or any other flat tool)
In order to open the plug - you have to flip it bottom up so that you see the plug contacts, serial number and some other tech info. Now carefully observe the perimeter of the plug - where the grey bottom plastic joins with the top white one and you’ll notice a gap between them. Put the plug on the sturdy non-slippery surface, hold it firmly and using a knife start cutting into the gap between two plastics throughout the whole perimeter of the plug. It may take some time, but eventually there’s going to be a wider gap which you could gently widen further using a flat screwdriver and eventually remove the top cap of the plug. Congrats, the easiest part is over.
ATTENTION
Until you re-assemble the plug back together - NEVER-EVER-EVER and I mean NEVER plug it into mains - it is dangerous and can cause damage to your health (let alone brick the plug).
Internal evaluation and connecting UART
After disassembly I was lucky (like really) since my version of the plug was based on ESP32-D0WD chip and for those who are unfamiliar - that’s a single core version of ESP32. It means that it should be totally possible to flash it with ESPHome. But with some caveats, as always with such plugs.
In my case on the top part of pcb apart from esp32 there were no other visible chips that could have caught my attention. But there was actually one I was trying to find - the chip that monitors mains voltage and current to provide the user with power consumption. And the reason is that they are on the other, less reachable part of the pcb. But no worries - you would not have to go through any other tedious desoldering or cracking the plug, since I’ve done it already myself, it will be explained later in the guide.
Next question - how to flash that esp32 chip - there were no visible connectors? Well, they are, but on the other side of pcb.
If you hold the plug in a way that you can see the pcb and the button of plug is facing downwards - the esp32 chip would be on the bottom left - now beneath the chip, on the other side of pcb there are the contacts we are looking for. You can observe them between plastic and pcb on the left bottom side.
In total there are 5 soldering pads but basically you’ll need only 3 of these - marked with TX, RX and GND. To power up the esp32 chip you’ll also need a 4th pad which is located on the front side of the pcb, approximately 15 millimeters higher than the esp32 chip (again, given that you are holding the plug so the button is facing downwards).
Soldering and connecting UART
If you have a very thin but powerful soldering iron - you might try soldering the TX, RX and GND without any further disassembly. Unfortunately it was not my case so I had to chip off a small part of plastic in order to comfortably get to those 3 contacts I needed.
I assume that you already have wires soldered or connected to your USB2UART adapter. So the next thing needed is to solder them, to plug. Solder UART TX to plug’s RX, UART RX to plug’s TX, GND to GND and UART VCC/3.3 Volts to plug top pad (the one I mentioned - located on the front side of the pcb, approximately 15 millimeters higher than the esp32 chip). When done - you’re ready to tinker around with the plug. Also - it would be a good idea to fixate soldered wires with a tape, so the connection won’t occasionally break when you accidentally pull one of the wires too hard (I know cases when occasional pulling the wire resulted in breaking the pad and thus resulting in inability to work via uart anymore, so be careful).
Connecting and Flashing
Required tools: Home Assistant with installed ESPHome (obviously), terminal app capable of connecting to com ports (like Putty), ESP tool (GitHub - espressif/esptool: Serial utility for flashing, provisioning, and interacting with Espressif SoCs)
ATTENTION (again)
Until you re-assemble the plug back together - NEVER-EVER-EVER and I mean NEVER plug it into mains.
First of all - let’s ensure that the connection is ok and you did mess up the rx<>tx pair.
Ensure that your USB2UART is working in 3.3v mode - if not - you may brick the esp32 chip, irreversibly.
Connect uart to the computer, open Putty, select the com port that your uart adapter has created (look up in device manager under COM ports) and set the speed to 115200 (a safe bet, though it might be higher for some chips).
If you observe that Putty outputs some readable text and not garbage - then you’ve soldered wires correctly, if it does not provide any output or its garbled - verify that your USB2UART if working in 3.3v mode and then try disconnecting and reconnecting power vcc cable to plug for the plug to reboot. If still nothing - check cables - probably you messed up with the rx>tx rule or the connection is loose somewhere.
If everything is okay - we’re ready to go, almost.
It is always good to make backups, so we will be backing up first.
Download the ESP tool to some folder of choice and unpack it.
Open the command line and navigate to that folder.
Now we need to boot our plug into flashing mode.
To do so:
- Power off the plug (either disconnect vcc wire from uart or disconnect uart from computer).
- Press and hold the button on the plug.
- Without releasing the button - reconnect vcc wire (or reconnect uart to computer)
- Wait 1 second and then release button on the plug
- You should be in flashing mode now.
If you have putty still on - you’ll see a message in the terminal like “Waiting for download…” or something like that.
Important note - before flashing or working with esp tools - close putty and all other terminals that are using com port which has been enabled by uart adapter - each com port can be used by only 1 app at a time.
Now go back to your command line where you navigated to unpacked ESP tool.
Try running following command (Replace COM3 with your actual port):
espefuse --port COM3 summary
If the tool is stuck during the connection stage - try to reboot the plug into flashing mode again.
If you see some output about your esp chip - then you are ready to continue.
Let’s backup existing firmware first.
Try running following command (Replace COM3 with your actual port):
esptool --port COM3 --chip esp32 read_flash 0x00000 0x400000 flash_4M_orig.bin
It could take some time but original firmware will be safe and sound on your hard drive.
When it completes - let’s proceed to actual flashing.
I will not be going heavily into detail on how to add devices in ESPHome as there are already guides for that. After you added a new device - do not rush flashing the firmware - the default config will not work and you’ll fail. Instead - after adding the device - open the yaml config and apply the following config data:
esphome:
name: xiaomiplug-001
friendly_name: XiaomiPlug-001
platformio_options:
board_build.flash_mode: dio
build_flags:
- -DCONFIG_FREERTOS_UNICORE=1
- -DCONFIG_ESP32_CHECK_EFUSE_MAC_CRC=0
- -DCONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE=0
- -DCONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE=0
esp32:
board: esp32dev
framework:
type: esp-idf
sdkconfig_options:
CONFIG_FREERTOS_UNICORE: y # Enables single-core mode
CONFIG_ESP32_CHECK_EFUSE_MAC_CRC: n
CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE: n
CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE: n
CONFIG_BT_ENABLED: n # no Bluetooth
advanced:
ignore_efuse_mac_crc: true
ignore_efuse_custom_mac: true
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "your_generated_key_here"
ota:
- platform: esphome
password: "your_generated_password_here"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# # Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Xiaomiplug-001 Fallback Hotspot"
password: "your_generated_fallback_password_here"
captive_portal:
This config is specifically tailored for Mi Smart Power Plug 2 (ZNCZ07CM, aka chuangmi.plug.212a01) and takes into consideration manufacturing specifics of the chip.
Save the config and Validate in ESPHome that everything is okay using the “3 dot” menu > Validate (most likely you’ll receive some indentation errors, just be careful when copy-pasting). If Validation completes successfully - go to the “3 dot” menu again > Install. In the installation menu - pick Manual download. Now wait till it completes and asks which format to use for download - choose “Factory format”.You’ll download the “.factory.bin” file. Copy it to the esp tool folder.
Now go back to your command line where you navigated to unpacked ESP tool.
Try running following command (Replace COM3 with your actual port and xiaomiplug-001.factory.bin with your actual bin file):
esptool --port COM3 write_flash 0x000000 xiaomiplug-001.factory.bin
When it completes - be sure to confirm that there were no errors. If there were - try rebooting plug into flashing mode and trying again.
If there were no errors - disconnect plug from vcc and reconnect to reboot it normally.
Wait for some time and you should be able to observe that your device in ESPHome becomes online.
If not - connect putty to com port, reboot plug in normal mode and observe messages. There are cases when power provided by the UART is not enough for esp32 wifi to function. If you see errors like “Brownout detector was triggered” - most likely it is your case. In such a case - desolder all wires from plug, re-assemble the plug cover, fixate it with a tape and plug it into the wall mains power supply - it will work (unless you messed up your wifi creds).
Now we should be able to update the plug’s firmware using ESPHome Over The Air without any wires needed.
Further configuration
Required tools: multimeter, electric kettle (both might be optional actually)
The primary purpose of the smart plug is to provide the ability to turn off/on the power from your non-smart appliances.
Let’s add the relay control to config.
Weirdly enough this plug is not using a single GPIO to control the relay, but is rather following a certain pattern to enable and disable the relay.
Relay Control works as follows:
- to Turn Relay OFF - send 0/OFF on GPIO26 and only after that send 0/OFF on GPIO27.
- to Turn Relay ON - send 1/ON on GPIO26 and only after that send 1/ON on GPIO27
Then we need to enable a physical button on the plug to toggle relay as well. GPIO0 on ESP32 is responsible for reading button value, so it will be added to the yaml and trigger our relay control config above.
Alongside Relay - it would be good to add an indication that plug is on or off.
LED Control works as follows:
GPIO18 ON + GPIO23 ON = LED OFF
GPIO18 OFF + GPIO23 OFF = LED White
GPIO18 ON + GPIO23 OFF = LED Orange
GPIO18 OFF + GPIO23 ON = LED Blue-ish
So updated yaml config would look like this:
esp32:
board: esp32dev
framework:
type: esp-idf
sdkconfig_options:
CONFIG_FREERTOS_UNICORE: y # Enables single-core mode
CONFIG_ESP32_CHECK_EFUSE_MAC_CRC: n
CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE: n
CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE: n
CONFIG_BT_ENABLED: n # no Bluetooth
advanced:
ignore_efuse_mac_crc: true
ignore_efuse_custom_mac: true
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "your_generated_key_here"
ota:
- platform: esphome
password: "your_generated_password_here"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# # Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Xiaomiplug-001 Fallback Hotspot"
password: "your_generated_fallback_password_here"
Captive_portal:
binary_sensor:
- platform: gpio
pin:
number: GPIO19
mode: INPUT_PULLUP
inverted: false
name: "GPIO19-RelayOnSensor"
- platform: gpio
pin:
number: GPIO0
mode: INPUT_PULLUP
inverted: true
name: "GPIO0-PlugButton"
on_press:
then:
- switch.toggle: XiaomiPlug_001_Relay
switch:
- platform: gpio
pin:
number: GPIO26
name: "XiaomiPlug-001-Relay"
id: XiaomiPlug_001_Relay
restore_mode: ALWAYS_ON
inverted: true
on_turn_on:
- switch.turn_off: GPIO27
- delay: 10ms #optional
- switch.turn_on: GPIO27
- switch.turn_on: GPIO18
- switch.turn_off: GPIO23
on_turn_off:
- switch.turn_on: GPIO27
- delay: 10ms #optional
- switch.turn_off: GPIO27
- switch.turn_on: GPIO18
- switch.turn_on: GPIO23
- platform: gpio
pin:
number: GPIO27 # works in pair with GPIO26
name: "GPIO27"
id: GPIO27
internal: true # Hide from Home Assistant, but use in ESPHome config
# LED Config
# GPIO18 on + GPIO23 off = LED Orange
# GPIO18 off + GPIO23 on = LED Blue
- platform: gpio
pin:
number: GPIO23
name: "GPIO23"
id: GPIO23
internal: true # Hide from Home Assistant, but use in ESPHome config
- platform: gpio
pin:
number: GPIO18
name: "GPIO18"
id: GPIO18
internal: true # Hide from Home Assistant, but use in ESPHome config
Save the config and Validate in ESPHome that everything is okay using the “3 dot” menu > Validate (most likely you’ll receive some indentation errors, just be careful when copy-pasting). If Validation completes successfully - go to the “3 dot” menu again > Install. In the installation menu - pick Wirelessly. Wait and check if intended firmware uploads and if relay works as expected.
Secondary purpose - is to measure voltage and current to provide analytics about power draw over time.This particular Mi Smart Power Plug 2 (ZNCZ07CM, aka chuangmi.plug.212a01) uses the BL0937 chip to measure voltage and current. That chip is quite a common one and is also used in more affordable Tuya smart plugs (and that was the chip hidden underneath the pcb which made it impossible to identify unless you desolder bulky mains connector and crack up other plug’s components, but now that you know the chip - you don’t have to do it yourself ).
There are 3 pins of this chip that you need to connect to a controller to be able to read values of voltage and current from it - SEL (Pin8), CF1(Pin7) and CF(Pin6).
The connection between ESP32 and BL0937 is as follows:
ESP32 GPIO21 - Connects to CF (Pin6) of BL0937 over a 100 ohm resistor
ESP32 GPIO22 - Connects to CF1 (Pin7) of BL0937 over a 100 ohm resistor
ESP32 GPIO4 - Connects to SEL (Pin8) of BL0937 over a 100 ohm resistor
In ESPHome this chip is controlled by the “HLW8012 Power Sensor” platform with appropriate model selection. Using this platform you can read Voltage, Current, Power and Energy.
I’ve updated my config so that every sensor CAN be updated every second in case the deviation from previous value is more than setting in “- delta:” field but at the same time every sensor WILL get updated at least once per 60 seconds whether it had changes or not. Such approach is useful when you’re working with some analytics and need to have consistent data points (e.g. Grafana integration)
Therefore the updated yaml config would look like this:
esp32:
board: esp32dev
framework:
type: esp-idf
sdkconfig_options:
CONFIG_FREERTOS_UNICORE: y # Enables single-core mode
CONFIG_ESP32_CHECK_EFUSE_MAC_CRC: n
CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE: n
CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE: n
CONFIG_BT_ENABLED: n # no Bluetooth
advanced:
ignore_efuse_mac_crc: true
ignore_efuse_custom_mac: true
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "your_generated_key_here"
ota:
- platform: esphome
password: "your_generated_password_here"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# # Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Xiaomiplug-001 Fallback Hotspot"
password: "your_generated_fallback_password_here"
Captive_portal:
binary_sensor:
- platform: gpio
pin:
number: GPIO19
mode: INPUT_PULLUP
inverted: false
name: "GPIO19-RelayOnSensor"
- platform: gpio
pin:
number: GPIO0
mode: INPUT_PULLUP
inverted: true
name: "GPIO0-PlugButton"
on_press:
then:
- switch.toggle: XiaomiPlug_001_Relay
switch:
- platform: gpio
pin:
number: GPIO26
name: "XiaomiPlug-001-Relay"
id: XiaomiPlug_001_Relay
restore_mode: ALWAYS_ON
inverted: true
on_turn_on:
- switch.turn_off: GPIO27
- delay: 10ms #optional
- switch.turn_on: GPIO27
- switch.turn_on: GPIO18
- switch.turn_off: GPIO23
on_turn_off:
- switch.turn_on: GPIO27
- delay: 10ms #optional
- switch.turn_off: GPIO27
- switch.turn_on: GPIO18
- switch.turn_on: GPIO23
- platform: gpio
pin:
number: GPIO27 # works in pair with GPIO26
name: "GPIO27"
id: GPIO27
internal: true # Hide from Home Assistant, but use in ESPHome config
# LED Config
# GPIO18 on + GPIO23 off = LED Orange
# GPIO18 off + GPIO23 on = LED Blue
- platform: gpio
pin:
number: GPIO23
name: "GPIO23"
id: GPIO23
internal: true # Hide from Home Assistant, but use in ESPHome config
- platform: gpio
pin:
number: GPIO18
name: "GPIO18"
id: GPIO18
internal: true # Hide from Home Assistant, but use in ESPHome config
sensor:
- platform: hlw8012
model: BL0937
current_resistor: 0.002 #double check if black resistor near mains plug on pcb has reading R002, adjust value if it has a different one - e.g. set 0.001 if resistor value is R001
voltage_divider: 2351 #default value
sel_pin:
number: GPIO4
inverted: true
cf_pin:
number: GPIO21
inverted: true
cf1_pin:
number: GPIO22
inverted: true
update_interval: 1s
initial_mode: VOLTAGE
change_mode_every: 5
current:
name: "XiaomiPlug-001-Current"
force_update: true
filters:
- or:
- delta: 0.01
- heartbeat: 60s
voltage:
name: "XiaomiPlug-001-Voltage"
force_update: true
filters:
- or:
- delta: 1.0
- heartbeat: 60s
power:
name: "XiaomiPlug-001-Power"
force_update: true
filters:
- or:
- delta: 0.1
- heartbeat: 60s
energy:
name: "XiaomiPlug-001-Energy"
force_update: true
filters:
- or:
- delta: 0.1
- heartbeat: 60s
Save the config and Validate in ESPHome that everything is okay using the “3 dot” menu > Validate (most likely you’ll receive some indentation errors, just be careful when copy-pasting). If Validation completes successfully - go to the “3 dot” menu again > Install. In the installation menu - pick Wirelessly. Wait and check if intended firmware uploads.
Plug calibration
After flashing with this config you’ll require a multimeter to calibrate the sensor and update the value “voltage_divider: 2351” in config. There is a guide on calibration of this particular sensor chip on ESPHome site.
If you don’t have all necessary tools required - you can still at least approximately calibrate your plug. First - write down the voltage reading with default 2351 value. In my case it was 316 Volts (which was obviously wrong given the voltage in sockets where I live is around 220-230 volts). Secondly - calculate the new divider using formula, lets assume voltage is 220 in your region:
2351 * 220 / 316 = 1636,77
So the new value would be “voltage_divider: 1637” - I rounded it up a bit.
Finally - update it in yaml and update the plug firmware. And you’re done.
Closing words
As final steps it would be good to either apply some instant glue to the perimeter of the plug where the plug has been diassembled so the plug does not fall apart during usage. It is extremely important if you have small kids. In my case I just used some duct tape (since I obviously will want to tinker with the inside of the plug sometime in the future to investigate additional thermal sensor and bluetooth capabilities, but for now the existing config satisfies my needs). All-in-all that was a fun experiment, and I hope your plug survived this “surgery” better than mine.