ESPreSwitch converts a stock SwitchBot Smart Plug Mini straight to ESPresense over the air, without disassembling the device. It is a fork of SwitchbOTA by Kendall Goto, which does the same for Tasmota.
Why not go through Tasmota? SwitchbOTA's route (stock → Tasmota v11.1.0 → ESPresense) leaves the plug on Tasmota's bootloader and partition table forever. Current ESPresense builds (pure ESP-IDF v5.4) do boot on it, but they were never built or tested for that layout, and one plug hung after an OTA reboot until it was power-cycled. ESPreSwitch writes an ESPresense factory image instead: ESPresense's own bootloader, partition table and app, so the plug ends up laid out like any other ESPresense ESP32-C3 node.
The image it installs is pinned in payload.json (URL + MD5). The stage binary asks the server for the image's MD5 and size, and refuses to erase anything if the image would reach the partition it is running from.
There are two SKUs of the SwitchBot Smart Plug Mini released in the US:
- The original "Smart Plug Mini" with model number W1901400, released in early 2022. Amazon sells these in one-packs, two-packs and four-packs
- The updated "HomeKit Smart Plug Mini" with model number W1901401, released later in 2022. As of 2026-19, they are unavailable.
Some details about the devices are as follows:
- Both devices use identical circuitry. The only difference is the firmware used. More detail can be found in kendallgoto/switchbota#19
- They use Type B NEMA 5-15 plugs and sockets
- They are ultrasonically welded shut, requiring destruction of the plastic housing to access the circuits within. There are no screws to allow access to the internals, the way there is with the Sonoff S31
- They use the Shanghai Belling BL0937 energy measurement IC to track power usage
- They use the Espressif ESP32-C3 chip for WiFi, Bluetooth Low Energy (BLE), and circuit control. See here to learn about the different types of ESP32. The MAC addresses for WiFi and BLE follow the ESP32 standard of having an offset of two. If the WIFI-MAC is
34:85:18:0F:CC:DC, then the BLE-MAC is34:85:18:0F:CC:DE(DC in hex is 220, DE is 222) - The upstream SwitchbOTA flashing process is confirmed to work on both device models using firmware v1.4 or older (v1.3, v1.2). ESPreSwitch uses the same BLE triggers and has been confirmed on a W1901400 running stock firmware v2.7 (2026-10-11), so newer firmware still accepts the plain-HTTP update
Writing to the bootloader over OTA is dangerous and not normally done for good reason. If your device loses power or somehow flashes corrupted data, the device will be bricked. The case is ultrasonically welded, so the only fix is cutting it open and reflashing over serial, and the plug is rarely usable as a plug afterwards. Treat a bricked plug as lost. Of course, do not unplug or otherwise disturb the plug while it is performing the OTA.
This is a proof of concept and provided with no warranty. As of 2026-10-11 it has converted two stock plugs and six plugs already running ESPresense, every one first try. Still, only use it on plugs you can afford to lose.
Already converted a plug via SwitchbOTA and running ESPresense on Tasmota's layout? ESPreSwitch can move it onto ESPresense's own bootloader and layout without Bluetooth: see Plugs already running ESPresense.
- TL;DR: Connect the plug to your WiFi network
- Install the SwitchBot app on your phone https://play.google.com/store/apps/details?id=com.theswitchbot.switchbot
- Sign in or create an account (necessary to allow devices to be added/configured)
- Plug the plug into a wall outlet
- In-app, add a device. It's safe to connect it to your WiFi network.
- Note down the BLE-MAC during this process, as it will be referenced later
- This step uses Bluetooth Low Energy (BLE) to scan for devices and send the WiFi network information
- In-app, check the device firmware by going to its settings. As of 2023-12, there was no firmware update button, and the firmware version was v1.4. If you do see the upgrade button, do not press it and wait for a later part
- DONE! The plug is now ready for the firmware flashing process
- TL;DR: Configure your router & DNS to redirect calls to a local IP. Run a local NodeJS server at that IP
- Note down the LAN/local IP address where the NodeJS server will be run. For this example, it will be
192.168.0.69 - Configure your router and/or DNS to redirect calls to SwitchBot servers to instead hit your NodeJS server
- This means that a DNS override or custom DNS record should be added, redirecting both
www.wohand.comandwohand.comto the IP where the NodeJS server will be run. More detail can be found at kendallgoto/switchbota#3 (comment) - In pfSense, this can be done in the Services->DNS Resolver or Forwarder by adding a Host Override with Host
wwwand Domainwohand.comand a second override with Hostwohandand Domaincom, both having an IP of 192.168.0.69. Save the override and Apply the settings
- This means that a DNS override or custom DNS record should be added, redirecting both
- In PiHole, this can be done in the Local DNS->DNS Records by setting the Domain to
www.wohand.comandwohand.comand the IP to 192.168.0.69 - On a UniFi gateway (UDM, UDM Pro, UCG, UXG), add two Host (A) DNS records in UniFi Network,
wohand.comandwww.wohand.com, both pointing at 192.168.0.69. In Network 9.x this is Settings → Policy Engine → Policy Table → Create New Policy → DNS; in Network 8.x, Settings → Routing → DNS. UniFi records match exactly, so add both names. Power-cycle the plug afterwards so it drops any cached answer - If your router does not support custom DNS entries, it will almost certainly support setting a custom DNS server. This may be found in the DHCP settings or the System settings. Set the custom DNS server to 192.168.0.69
- Run a local NodeJS server to act in place of the real firmware update server and instead deliver modified firmware files: firstly the OTA (stage) file, and then the ESPresense factory image
- Install NodeJS on your computer https://nodejs.org/en/learn/getting-started/how-to-install-nodejs
- Using a CLI or Terminal,
cdinto theserver/directory and runnpm i(short fornpm install) to install the dependencies required for this project - Once the previous command completes, run
node index.jsto run the server.- OR, if you could not set up a DNS record in the previous step, run
node index.js 192.168.0.69. This will start the server, in addition to a Man-in-the-middle DNS server that redirects*wohand.comcalls to the IP of the NodeJS server, rather than the public IP of*wohand.com. All other DNS lookups will be unaffected
- OR, if you could not set up a DNS record in the previous step, run
- The CLI or Terminal should show
Server listening on port 80. Keep this window open for the duration of the flashing process
- Test the DNS functionality by opening a new CLI or Terminal and running
ping wohand.comandping www.wohand.comto confirm that the local IP shows up. In this example, look for 192.168.0.69 - Test the Node server functionality by opening the SwitchBot app and then looking at the terminal where Node is running. A new line should have shown up similar to
::ffff:192.168.0.101 - /version/wocaotech/release.json. This indicates the server was accessed instead of the realwohand.comand that it successfully served a JSON file to the app. The IP 192.168.0.101 is the IP of the phone running the app. - DONE! The network is now configured and the server should now be ready to serve firmware files to the plug
- TL;DR: Send BLE commands to the SwitchBot plug to trigger the firmware upgrade process
- If you saw an upgrade button in the SwitchBot app, it is now safe to hit that button. No additional actions should be needed, and the firmware replacement process should complete. If no button is present in the SwitchBot app, proceed
- Easiest: use espresense.com/espreswitch in Chrome or Edge on desktop or Android. It finds the plug over Bluetooth and sends both commands for you: press 1. Install, then 2. Reboot about a minute after the server logs
WoPlugUS_V12.bin. iPhone Safari has no Web Bluetooth; use Bluefy, or the nRF Connect steps below - Otherwise, send the commands yourself:
- Install an app such as nRF Connect for Mobile. This will be used to connect to the Bluetooth Low Energy (BLE) radio of the plug to trigger the firmware update process. Bluetility for Mac is also reported to work
- Follow the instructions below, or watch the video, or read the GitHub issues outlining the process kendallgoto/switchbota#43 kendallgoto/switchbota#3 (comment)
- Using the BLE app of choice, scan for devices and Connect to the device matching the BLE-MAC noted from the SwitchBot app
- Make sure the app is on the Client tab, then expand "Unknown Service" and look at the options within. The last option, "Unknown Characteristic" should have Properties such as "WRITE" and an upload button on the right side. Hit the Upload button on the right side to bring up the Write screen.
- Select the BYTE_ARRAY type from the dropdown and enter in the hex code
57 0F 0A 01 0C(without spaces). Then it Send to transmit the command to the plug- The last two characters represent the firmware version to be flashed, and must be different from the current firmware version.
0C(as shown above) represents v1.2,0Drepresents v1.3,0Erepresents v1.4.0Cshould be safe to use by default. More detail can be found here
- The last two characters represent the firmware version to be flashed, and must be different from the current firmware version.
- Wait a few seconds and look at the NodeJS log to verify the command worked as it should. You should see something such as
::ffff:192.168.0.131 - /version/wocaotech/firmware/WoPlugUS/WoPlugUS_V12.binwhich indicates the NodeJS server was able to send the first.binfile to the plug - Wait about a minute after seeing the NodeJS server log to give the plug time to get ready for the next step.
- Now send a new BYTE_ARRAY command in the BLE app (nRF Connect) with hex
57 0F 0B. Hit Send to trigger the second and final firmware flash step. This step is required: the first command only downloads the stage binary; this one makes the stock firmware install it and reboot into it. On firmware v2.7 the stage made its first request about 2.5 minutes after the first command, right after57 0F 0B - Wait a few seconds again and then look at the NodeJS log to verify the command worked as it should. You should see something such as
::ffff:192.168.0.131 - /payload.binwhich indicates the NodeJS server was able to send the ESPresense factory image to the plug - DONE! The firmware flashing should be complete
- TL;DR: Join the plug's
espresense-…WiFi, enter your WiFi and MQTT details, then set the plug's pins - The stage binary wipes the start of flash, so the plug boots ESPresense with no settings. It opens an open WiFi network named
espresense-followed by its MAC address. Join it from a phone or laptop; the captive portal opens (or browse to http://192.168.4.1) - Enter your WiFi network, MQTT broker and room name, then save. The plug reboots, joins your network and appears in ESPresense-companion / Home Assistant like any other node
- Open the node's web page, go to Hardware, and set the relay, button and LED pins for the plug. The Plug Mini's GPIO map is in the Blakadder template
- From here on, update the plug like any other ESPresense node. Use the plain
esp32c3firmware; the plug has no USB, so-cdcbuilds buy nothing
Plugs converted the SwitchbOTA way (stock → Tasmota v11.1.0 → ESPresense) still have Tasmota's bootloader and partition table. On ESPresense v5.0.0b2 or later, telemetry reports app0: 1900544 (0x1D0000) means Tasmota's layout, 1966080 (0x1E0000) means ESPresense's own.
To move one onto ESPresense's own layout, with the ESPreSwitch server running and wohand.com pointed at it (Part 2), tell the node to update from the server:
mosquitto_pub -h <broker> -t 'espresense/rooms/<room>/update/set' -m 'http://<server-ip>/app.bin'
The node downloads the stage into its other app slot and reboots into it. The stage copies itself to app1 (0x1E0000) if it landed in app0, writes the ESPresense factory image below 0x174000, and reboots into ESPresense. Its settings are wiped: it opens espresense-xxxxxx, so set it up as in Part 4. It takes about two minutes; the LEDs show progress as in What the LEDs mean.
The stage needs WiFi: it joins the plug's saved network if there is one, else espreswitch/espreswitch, else it opens its rescue network (see The plug can't reach WiFi). Converted this way on 2026-10-11: six plugs, every write first try.
The same command works on other 4 MB ESP32-C3 nodes running ESPresense, as long as their second app slot starts above 0x174000. An Athom Plug V3 (app0 1835008, 0x1C0000) converted this way on 2026-10-12. The payload is built for the Plug Mini, so afterwards:
- The node runs the plain
esp32c3build. If its board needs another flavor (the Athom needsesp32c3-cdc), update it to that build. - Its board settings are wiped along with everything else. Apply the board's template from its page on espresense.com again.
- While the stage runs, it blinks GPIO 7 and 8, the Plug Mini's LED pins. Check nothing on your board objects to those pins being driven.
The included ESP-IDF code is a lightweight OTA client that directly writes to the embedded flash chip, enabling the install of non-app level code, including modfiying the bootloader and partiton table.
- The update is triggered by the app with a BLE message, such as
57 0F 0A 01 0C - The device fetches
http://www.wohand.com/version/wocaotech/firmware/WoPlugUS/WoPlugUS_VXX.binfor the firmware - The request is intercepted and served by the NodeJS web server, which downloads the Espressif binary
- The factory firmware installs the binary to ota_0 or ota_1, depending on past usage
- The binary runs from its OTA partition. If it's on ota_1, it skips to step 6. Otherwise, it performs another OTA, downloading itself into ota_1 and rebooting.
- Once on OTA_1, the device fetches
http://www.wohand.com/payload.binfor OTA - The request is intercepted and served by the NodeJS web server, delivering the payload pinned in
payload.json(an ESPresense factory image) - The Espressif binary fetches the payload's MD5 and size from
/payload.md5, wipes the internal flash up to that size (rounded up to 4 KB) and flashes the payload. It performs two checksums, one of the downloaded binary and another of the flashed binary. It will continue to retry without restarting until the checksums are valid
If a conversion goes wrong, the LEDs may be all you have to go on. While the ESPreSwitch stage is running, both LEDs blink together to show what it's doing.
Nothing erased yet: safe to unplug
| Pattern | Meaning | What to do |
|---|---|---|
| Slow blink, once a second | Looking for WiFi | Wait. After a minute it switches to the rescue network |
| 2 blinks, pause | No WiFi after a minute, so the plug has opened its own rescue network espreswitch-xxxxxx |
Join it and enter a WiFi network (see below) |
| Short blip every 2 seconds | On WiFi, but the ESPreSwitch server isn't answering properly | Check the server is running, the payload has finished downloading, and wohand.com resolves to it |
| 3 blinks, long pause | Refused: the payload is too big for this plug's flash layout | Nothing was erased. Pick a smaller payload or unplug |
| Solid | Talking to the server; about to erase or copying itself to the other slot | Wait |
Flash erased: do NOT unplug
| Pattern | Meaning | What to do |
|---|---|---|
| Fast blink, 5 times a second | Writing ESPresense | Wait; it reboots into ESPresense when done |
| 5 fast blinks, pause | A write attempt failed (WiFi dropped, server unreachable, or checksum mismatch); retrying | Keep it powered. Check the server. If WiFi is the problem, the rescue network still appears after a minute |
Once the erase starts, the LEDs only ever show these two patterns, even if WiFi drops, so "don't unplug" is always visible. Losing power while they show is the one thing that bricks the plug.
Other states
- Stock SwitchBot behaviour: the stage isn't running yet.
- Dark, with an open
espresense-…WiFi network: the conversion worked. ESPresense is running with no LED pins configured. - Dark, with no network at all: the plug isn't booting. If it went dark while the erased patterns were showing, it lost power mid-write and is bricked.
The stage never drives the relay. If the button still switches the relay, the plug is running some other firmware (stock, Tasmota or ESPresense), not the stage.
Two different networks are involved, so keep them apart:
| Network | Created by | What it's for |
|---|---|---|
espreswitch (password espreswitch) |
You, on your router or a phone hotspot | A network the plug joins if it has no saved WiFi. It must resolve wohand.com to the ESPreSwitch server |
espreswitch-xxxxxx (open; xxxxxx matches the espresense-xxxxxx network it opens once converted) |
The plug | A rescue network the plug opens after a minute without WiFi, so you can give it one |
The stage joins the WiFi network saved in the plug (the one you set in the SwitchBot app). Only if the plug has no saved WiFi at all does it join espreswitch instead. If it still has no WiFi after a minute, it opens espreswitch-xxxxxx:
- Join
espreswitch-xxxxxxfrom a phone or laptop. The page should pop up by itself; if not, browse to http://192.168.4.1. - Enter a WiFi network the plug can use. That network has to resolve
wohand.comto the ESPreSwitch server: your normal LAN with the DNS override, or any network whose DHCP hands out the server's IP as DNS. - The
espreswitch-xxxxxxnetwork closes as soon as you save, while the plug joins your network (one radio can't do both reliably). If the plug can't join,espreswitch-xxxxxxcomes back in about a minute: reconnect and check the name and password.
This works even after the flash is erased, as long as the plug stays powered. A network entered before the erase is saved, so it survives the plug's own reboot into its second app slot and you only enter it once. A network entered after the erase is kept in RAM only (the saved-settings area is part of what gets erased), but a power cut after the erase bricks the plug anyway.
- Pick an ESPresense build that publishes
esp32c3.factory.bin(release asset or CI artifact). - Put its URL and MD5 in
payload.json(md5sum esp32c3.factory.bin) and push. - On the server:
git pulland restart it.
The stage binary has nothing payload-specific compiled in: it asks the server for the payload's MD5 and size before erasing anything, so no new ESPreSwitch release is needed.
ESPreSwitch is a fork of SwitchbOTA by Kendall Goto (GPL-3.0); all of the BLE trigger, DNS and stage-binary work is theirs. To support the original author, see their sponsor page.
- The digiblurDIY Youtube channel has a video guide for this process, and the website has additional details, including detail on the alternate firmware ESPHome
- This project is similar to https://github.com/khcnz/Espressif2Arduino
- GitHub Issues/Discussions
- Discussion on OTA and BLE procedure kendallgoto/switchbota#3
- Discussion on Tasmota version and incompatibility with ESPHome kendallgoto/switchbota#31
- Recent write-up on the flashing process kendallgoto/switchbota#43
- Tasmota
- Documentation https://tasmota.github.io/docs/
- GitHub https://github.com/arendst/Tasmota
- Firmware (ESP32-specific) https://ota.tasmota.com/tasmota32/release/
- Template for SwitchBot https://templates.blakadder.com/switchbot_plugmini_W1901400.html
- SwitchBot App
- nRF Connect App





