Introduction
Home Assistant is an extremely popular open source smart home automation platform. It supports a large array of integrations for various smart home solutions and is extremely versatile. I use Home Assistant to power my smart home.
ESPHome is an open-source system that allows you to create custom firmware for smart home devices running on popular micro controller like the ESP32 using simple YAML configuration files. It is deeply integrated with Home Assistant.
Wyze specializes in affordable smart home products, primarily wireless cameras and other connected devices. The Wyze Outdoor Plug is a cheap ($16), yet highly robust and capable Wi-Fi smart plug for outdoor applications. I have two of the devices installed in my backyard for over 5 years and use them to automate string lights and a fountain.
Cloud dependence
My main issue with the Wyze Outdoor Plugs is their cloud dependence. I don’t want my string lights and fountain to depend on the availability of some cheap Chinese cloud service running on AWS, especially because:
- The security of Wyze’s cloud service is questionable. A company selling cheap smart home products will likely have a hard time to hire top talent to secure their services.
- I don’t want a Chinese company to have access to my home network. I could of course move the devices to a separate Wi-Fi SSID, but I already have too many to maintain.
- A smart plug is a long lived product. I’ve been successfully using the Wyze Outdoor Plugs for at least 5 years, and I can probably continue using them for another 15. Who knows if Wyze will still be in business by then and if their cloud service will be operational and the plugs supported.
Soldering your way to freedom – not so fast
Most Wyze hardware utilizes the popular ESP32 or ESP8266 line of micro controllers. This means they can in theory be flashed with ESPHome. I found this blog post by digiblurDIY from 2023, which describes how to disassemble the Wyze Outdoor Plug, solder a USB TTL adapter to the flashing pads on the device and upload the ESPHome firmware. I was ready to embark on this project, I even bought a cheap soldering iron, but then I remembered that I’m a software guy and that I really suck at soldering. There must be an easier way that doesn’t require hardware hacks, I thought.
A software-only solution?
Of course there is an easier way, there always is. I found the WyzeUpdater project on GitHub, which seemed promising. Unfortunately it doesn’t officially support the Wyze Outdoor Plug, although it claims to support the indoor version. The tool is supposed to work this way:
- Connect to Wyze’s cloud services via their unofficial API.
- Issue an API call to upgrade the firmware of the device. The API command accepts arbitrary URLs, so we can point it to a local web server, where the ESPHome-built firmware is waiting.
- Success.
Unfortunately Wyze has made multiple changes to improve the security of their cloud and devices, which breaks this procedure:
- They have limited the hostnames that the API accepts for the firmware downloads to just the hostnames of their AWS buckets.
- They no longer allow HTTP URLs, only HTTPS is accepted.
- They changed the API, which now requires an API key and also support two factor authentication and Passkeys.
Can we bypass the above security measures? Of course we can.
Working solution as of June 2025
Preparation
What you need:
- A working installation of Home Assistant either as a Docker container or as Home Assistant OS (preferred).
- A working installation of ESPHome, either as a Docker container or HAOS add-on (preferred)
- A working Wi-Fi network with internet access (duh).
- A computer with git and Python installed. Linux is preferred, but it should work on Windows or Mac OS, too.
- Ability to override DNS records on your Wi-Fi network. Something like Pihole or AdGuard Home will work, or you can run your own DNS server like Dnsmasq or Unbound or Bind.
- The Wyze Outdoor Plug, model WLPPO. Note that there are at least three different hardware revisions. I have only tested this procedure with the WLPPO, which is the oldest one.
What we will do:
- Downgrade the firmware on the device to an old version that doesn’t require HTTPS URLs.
- Use ESPHome to build a firmware image for the ESP32 micro controller that the plug uses.
- Point DNS records for Wyze’s firmware update servers to our own computer.
- Tell the device to update the firmware again, but this time it will pull it from our local server, where the ESPHome-built firmware is waiting.
First, log into your Wyze cloud account and if you have enabled them, disable two factor authentication and Passkeys. In my experience the Python script doesn’t support them correctly and will fail if you leave them enabled. Next, log into the Wyze app and disable automatic firmware updates for your device. We will be messing with the firmware versions and we don’t want Wyze’s automation to override us.
Next, clone the WyzeUpdater GitHub repository with the command below. Note that this is a different (newer) version of the WyzeUpdater from the one I mentioned above.
git clone https://github.com/gtxaspec/WyzeUpdater
Log into Wyze’s developer API console portal and create an API key: https://developer-api-console.wyze.com/
Change to the WyzeUpdater directory and run the wyze_updater.py script “list” command as follows:
cd WyzeUpdater
./wyze_updater.py list
The script will ask you for your Wyze cloud account username and password, API key id and the API key. If you have done everything correctly, you should now see a list of all the devices in your account, with the exact model numbers, MAC addresses and IP addresses. If the script crashes, you likely have two factor authentication or passkeys enabled on the account, or you have mistyped the username, password or API key.
Newer Wyze firmware versions insist on HTTPS URLs only and they actually verify the certificates. To bypass this, we will force a downgrade to an old and vulnerable version.
Issue the following command:
sudo ./wyze_updater.py --debug update \
-d AABBCCDDEEFF \
-f bogus \
--url-host s3-us-west-2.amazonaws.com \
--url-path wuv2/upgrade/WLPPO/firmware/1.2.0.73.bin \
--ssl
Replace AABBCCDDEEFF with the MAC address of your device. The MAC address is displayed in the “wyze_updater list” command output. Only take the first part of the name, before the dash (i.e. it is AABBCCDDEEFF and not AABBCCDDEEFF-0001). Note that we have to run the script as root, as it will try to spin up a web server on port 443, which only privileged users can do.
The script will display the output of the API call and then start a web server to serve the firmware. We don’t need the web server right now, because the device is downloading the older firmware from Amazon’s servers instead of ours. If the API result shows “success”, that means the firmware downgrade should be in progress and you can stop the script by pressing ctrl-C. Give the device about 5 minutes and then check using “wyze_updater.py list” again, or use the Wyze app on your phone to check the firmware version – the device should now be running 1.2.0.73. Do not proceed until your device is running the old version!
Building the firmware using ESPHome
Next we need to build the new customer firmware for our device. Visit the ESPHome dashboard in Home Assistant and click on the green New Device button at the bottom right corner. It will ask you for the name of the device, give each device a unique name. If you have not used ESPHome before, it will then ask you for your Wi-Fi SSID and password. Those will be stored in ESPHome for future use, so you don’t have to enter them again. Next it will ask you to connect the device, click on “Skip this step”. On the next screen select ESP32 as the device type and the “Skip” again.
You should now see your new device name displayed in a box on the screen with an Edit and Logs button underneath and a big red “Offline” mark in the upper right corner. Click on “Edit” and in the editor that comes up erase the entire contents of the YAML and replace them with the following:
substitutions:
# Higher value gives lower watt readout
current_res: "0.001"
# Lower value gives lower voltage readout
voltage_div: "770"
update_time: 10s
esphome:
name: outdoor-plug-1
friendly_name: Outdoor Plug 1
name_add_mac_suffix: false
esp32:
board: esp32dev
framework:
type: esp-idf
logger:
api:
ota:
platform: esphome
captive_portal:
mdns:
#web_server:
# disabled to due potential memory issues
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
esp32_ble_tracker:
scan_parameters:
active: true
bluetooth_proxy:
active: true
switch:
- platform: gpio
name: "Relay1"
pin:
number: GPIO15
inverted: false
id: relay1
on_turn_on:
- light.turn_on: relay1_led
on_turn_off:
- light.turn_off: relay1_led
- platform: gpio
name: "Relay2"
pin:
number: GPIO32
inverted: false
id: relay2
on_turn_on:
- light.turn_on: relay2_led
on_turn_off:
- light.turn_off: relay2_led
- platform: restart
name: Restart
output:
- platform: gpio
pin: GPIO19
inverted: True
id: relay1_led_gpio
- platform: gpio
pin: GPIO16
inverted: True
id: relay2_led_gpio
light:
- platform: binary
name: "Relay1 LED"
id: relay1_led
internal: true
output: relay1_led_gpio
- platform: binary
name: "Relay2 LED"
id: relay2_led
internal: true
output: relay2_led_gpio
sensor:
- platform: adc
internal: true
pin: GPIO34
name: LUX
id: lux_sensor
update_interval: 10s
attenuation: 11db
- platform: hlw8012
sel_pin:
number: GPIO25
inverted: true
cf_pin: GPIO27
cf1_pin: GPIO26
current_resistor: ${current_res}
voltage_divider: ${voltage_div}
change_mode_every: 3
update_interval: 3s
current:
name: "Amps"
unit_of_measurement: A
accuracy_decimals: 2
voltage:
name: "Volts"
unit_of_measurement: V
accuracy_decimals: 1
power:
name: "Watts"
unit_of_measurement: W
accuracy_decimals: 0
filters:
- calibrate_linear:
- 0.0 -> 0.0
- 134 -> 58
binary_sensor:
- platform: gpio
internal: true
pin:
number: GPIO18
mode: INPUT_PULLDOWN
inverted: True
name: "Button1"
on_press:
- switch.toggle: relay1
- platform: gpio
internal: true
pin:
number: GPIO17
mode: INPUT_PULLDOWN
inverted: True
name: "Button2"
on_press:
- switch.toggle: relay2
- platform: template
name: "Daylight"
device_class: light
lambda: |-
if (id(lux_sensor).state > 2) {
return true;
} else {
return false;
}
status_led:
pin:
number: GPIO5
inverted: true
Change the “name” and “friendly_name” fields in the above YAML to be unique for each device. Use dashes, not underscores for the name. The name will become the entity name in Home Assistant with dashes converted to underscores and it will also be the host name of the device.
Click “Save” in the upper right corner and then click “Install”. When it asks you how you want to install the firmware, select “Manual download”. ESPHome will then start building the firmware based on the YAML description provided above. This can take 10 minutes or more, depending on the speed of the computer ESPHome is running on.
Once the firmware is built, download the OTA format (called “legacy” in previous versions of ESPHome). Nothing bad will happen if you make the mistake of using the factory version instead of the OTA version, Wyze’s firmware is clever enough to at least check the firmware signature and reject the firmware update if it doesn’t recognize the file.
Redirecting the firmware update server to our own
Now head to the configuration page or file for your DNS server. Add a DNS server entry for the host name s3-us-west-2.amazonaws.com and point it to the IP address of your computer. Use “dig” or “nslookup” to verify that it is working correctly.
Upload the ESPHome firmware
Now we are ready to upload our custom firmware. Execute the following command:
./wyze_updater.py --debug update \
-d AABBCCDDEEFF \
-f outdoor-plug-1.ota.bin \
--url-host s3-us-west-2.amazonaws.com \
--url-path wuv2/upgrade/WLPPO/firmware/1.2.0.73.bin \
-p 18080
Replace AABBCCDDEEFF with the MAC address of your plug and outdoor-plug-1.ota.bin with the path name of the custom firmware you have downloaded from ESPHome.
The script will again issue an API call to upgrade the firmware, but this time the request will be redirected to the web server that was spun up by the script itself and is serving our custom firmware. Make sure that you have disabled any firewalls, port 18080 on your computer should be reachable. Also make sure that the plug and you computer are connected to the same network so there’s no firewall between them.
If you have done everything correctly, the script should see an incoming HTTP GET request from the plug. It only takes a couple of seconds for the plug to download your custom firmware. About 30 seconds later, the device should become “online” in ESPHome. Congratulations, you have just successfully replaced Wyze’s firmware with your own! The device should now also appear in Home Assistant, and you should be able to control the plug and see the sensors on the device.
Troubleshooting
Alright, so you tried to follow the instructions above and it didn’t work. Here’s a couple of things that could trip you up:
- Make sure you have turned off two factor authentication and passkeys in your Wyze cloud account.
- Double check the username, password, API key ID and API key.
- Make sure that you have disabled any firewalls on the computer that WyzeUpdater is running on and that there are no other firewalls between your computer and the plug.
- Make sure that your device is on firmware 1.2.0.73 before you try to upload your custom firmware.
- Note that the device IP will likely change when you upload your custom firmware. If you use “ping” to verify that your device is online, don’t freak out when you can’t ping it after the firmware upgrade. You can try to ping it via its multicast DNS name (for example “ping outdoor-plug-1.local”), which should work if the device is online and on the same LAN as your computer.
Bonus features
We embarked on this project to free our outdoor plugs of the cloud shackles, but this little projects comes with some bonus features.
The outdoor plug has multiple sensors, which the Wyze HACS integration doesn’t expose. It has a lux sensor that can sense daylight, it can also monitor current, power (wattage) and voltage. It even has a built-in Bluetooth proxy, that can help your Home Assistant connect to distant BLE devices like trackers.