Turn a Tuya / Smart Life Wi-Fi plug into a local Apple HomeKit Outlet without reflashing or opening the plug.
This project uses:
- Python + TinyTuya to discover and verify local Tuya control first
- ESP32 + HomeSpan to expose the plug to Apple HomeKit
- local LAN control for switching on/off
The original Tuya / Smart Life firmware stays on the plug.
In plain language: the ESP32 behaves like a small translator. Apple Home talks to the ESP32 using HomeKit, and the ESP32 talks to the Tuya plug using the plug's local Tuya protocol.
If you already know Python, Arduino IDE, ESP32 flashing, and HomeKit pairing, follow the phases in this README.
If you are new to those tools, use the beginner walkthrough first:
The beginner guide explains each step in smaller pieces, including what to install, what values to copy, what should happen after every step, and when to stop instead of guessing.
For maintainers preparing a GitHub release, use:
Current development branch: v2.1 Smart Setup & Diagnostics.
- tested with one Tesla Smart Plug / Tuya protocol
3.4 - local Python control works through TinyTuya
- ESP32 local Tuya POC works for
status,on, andoff - HomeSpan sketch exposes one Tuya relay as a HomeKit
Outlet,Light, orSwitch - HomeSpan sketch has a step-based setup web wizard, so users no longer need to edit source code for normal setup
- admin dashboard shows HomeKit, Tuya, network and diagnostics status after the ESP32 joins Wi-Fi
- experimental LAN scan and read-only DPS inspector are available from the web UI
This is still experimental. Treat the project as a working reference build for one known device, not as a universal Tuya bridge.
| Device | Brand | Protocol | Relay DPS | Status | Notes |
|---|---|---|---|---|---|
| Tesla Smart Plug | Tesla | 3.4 |
1 |
Working | Verified by author with local Tuya LAN control |
Please open an issue if you verify another device. Do not include local keys, Wi-Fi passwords, Tuya Access Secret values, or unredacted API responses.
The recommended firmware is:
esp32/homespan_tuya_outlet/homespan_tuya_outlet.ino
On first boot, the ESP32 starts a setup access point:
TuyaHomeKit-Setup
Open:
http://192.168.4.1/
The v2.1 wizard is step-based:
- Wi-Fi setup
- Find Tuya device
- Enter Tuya credentials
- Test connection
- HomeKit settings
- Save and reboot
The wizard still supports manual entry for Wi-Fi SSID/password, Tuya IP, device ID, local key, protocol version, relay DPS, HomeKit name/type/pairing code, and polling interval.
After normal boot, Serial Monitor prints a local admin URL, for example:
Admin URL: http://192.168.1.50:8080
The dashboard shows:
- HomeKit accessory name, type, relay state and polling interval
- Tuya IP, protocol version, relay DPS, last status, latency and failed poll count
- Wi-Fi SSID, ESP32 IP address, RSSI, uptime and free heap
- recent in-memory diagnostics events
Available dashboard actions:
- Test Tuya connection
- Scan DPS
- Restart ESP32
- Reset configuration
- Reset HomeKit pairing
The Find Tuya devices button scans the ESP32's local subnet for candidates with common Tuya LAN ports open, especially 6668 and 6669.
Results are intentionally labelled as possible Tuya device or likely Tuya device. An open port is not proof that the device is Tuya. If a candidate looks right, use Use this IP and then run Test Tuya connection with the correct device ID and local key.
LAN scan limitations:
- ESP32 and plug must be on the same subnet/VLAN
- some routers or IoT networks block local client-to-client traffic
- scan timeouts are short, so slow devices may be missed
- this does not discover the local key
The Scan DPS button queries the Tuya status payload and displays returned datapoints.
This v2.1 inspector is read-only. It does not toggle unknown datapoints. Boolean values are labelled as possible relay/switch candidates, and numeric values are labelled as possible sensor/energy/power values.
Use Use as relay DPS only when the boolean datapoint clearly represents the relay state.
- The setup and admin pages currently have no authentication.
- Use them only on a trusted LAN, guest network, or IoT network.
- Do not expose the ESP32 dashboard to the internet.
- Wi-Fi password and Tuya local key are stored in ESP32 flash memory.
- The web UI does not show saved Wi-Fi passwords or local keys back in the form; leave those fields blank to keep saved values when editing.
The intended flow is local: HomeKit talks to the ESP32, and the ESP32 talks to the Tuya plug on your Wi-Fi network.
This is an experimental local bridge for existing Tuya Wi-Fi plugs. The ESP32 joins your Wi-Fi network, talks directly to the Tuya plug over the LAN, and exposes a HomeKit Outlet accessory via HomeSpan.
The proven test setup was:
- Tesla Smart Plug
- Tuya / Smart Life Wi-Fi plug
- Tuya protocol
3.4 - relay datapoint
1 - ESP32 development board
Other Tuya devices may work, but they must be verified first. Do not assume protocol version, datapoints, or local-key behavior.
- It does not flash the Tuya plug.
- It does not require opening the plug.
- It does not use Tuya cloud for HomeKit on/off commands.
- It is not a universal Tuya device bridge yet.
- It is not intended for critical loads or safety-critical switching.
- ESP32 development board
- Existing Tuya / Smart Life Wi-Fi plug
- 2.4 GHz Wi-Fi network
- A safe test load, such as a small lamp
Optional but strongly recommended:
- DHCP reservation for the Tuya plug
- DHCP reservation for the ESP32
DHCP reservation means telling your router to always give the same IP address to the same device. This prevents the bridge from breaking when the router changes the plug's IP address.
.
├── .env.example
├── requirements.txt
├── docs/
│ ├── beginner-guide.md
│ └── release-checklist.md
├── scripts/
│ ├── scan_devices.py
│ ├── test_plug.py
│ └── export_esp32_secrets.py
└── esp32/
├── tuya_local_poc/
└── homespan_tuya_outlet/
Tuya plugs switch mains voltage. Test with a small non-critical load first.
Do not use this project for:
- heaters without independent thermal protection
- medical equipment
- pumps or appliances where unexpected switching can cause damage
- any load that would be dangerous after a reboot, Wi-Fi outage, or software bug
Never commit:
.envtinytuya.jsondevices.jsontuya-raw.jsonsnapshot.jsonesp32/**/secrets.h
These files are ignored by .gitignore because they can contain device IDs, cloud credentials, Wi-Fi passwords, or Tuya local keys.
If you pasted a Tuya Access Secret somewhere public, rotate it in the Tuya IoT Platform.
Do this before flashing the ESP32. If local TinyTuya control does not work, HomeKit will not work reliably either.
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r requirements.txtIf you already know the plug IP, pass it as --target-ip.
Run:
python scripts/scan_devices.py --target-ip 192.168.1.123Look for:
- plug IP address
- Device ID
- Tuya protocol version
- product information
Confirm the IP address in your router/DHCP lease table. TinyTuya may not always report the MAC address.
You need the plug's local_key.
This is a 16-character device secret used by the local Tuya protocol. Without it, the ESP32 cannot decrypt or send local commands.
Common route:
- Open Tuya IoT Platform.
- Create a Tuya IoT account if you do not already have one.
- Create a Tuya IoT cloud project if you do not already have one.
- Link your Smart Life / Tuya app account.
- Add the device to the project.
- Use TinyTuya wizard or Tuya IoT Platform API Explorer to retrieve
local_key.
You need both a Tuya IoT account and a cloud project. The Smart Life mobile-app account alone is not enough to access the API Explorer.
TinyTuya wizard:
python -m tinytuya wizardTuya IoT Platform API Explorer:
- Open Tuya IoT Platform.
- Open your cloud project.
- Select the correct data center, for example
Central Europe Data Center. - Go to
API Explorer. - Select
IoT Core. - Open
Device Management. - Choose
Query Device Details. - Enter the device ID found by TinyTuya scan.
- Click
Submit Request. - In the JSON response, copy the
result.local_keyvalue into your local.env.
The API Explorer page can also show a generated curl command. Treat both the curl command and the JSON response as secrets because they may contain:
local_keyclient_id- request signature
- access token
- location and device metadata
Do not paste the API Explorer response, request URL, or generated curl command into issues, README files, screenshots, or commits unless all secrets are redacted.
Tuya sometimes requires an active IoT Core / Cloud Development plan to retrieve device details. The cloud is used only to get local_key; switching is local after that.
cp .env.example .envFill:
DEVICE_ID=your_device_id
DEVICE_IP=your_plug_lan_ip
LOCAL_KEY=your_16_byte_local_key
DEVICE_VERSION=3.4
Use the version discovered by scan. Do not guess.
Read status first:
python scripts/test_plug.py statusThen test switching:
python scripts/test_plug.py on
python scripts/test_plug.py offFor the tested plug, the relay datapoint was:
dps["1"] = true / false
If your device uses a different datapoint, update the ESP32 configuration before flashing.
Use this sketch before HomeSpan:
esp32/tuya_local_poc/tuya_local_poc.ino
Generate local ESP32 secrets:
python scripts/export_esp32_secrets.pyEdit:
nano esp32/tuya_local_poc/secrets.hFill Wi-Fi:
#define WIFI_SSID "..."
#define WIFI_PASSWORD "..."Flash the sketch, open Serial Monitor at 115200, and test:
status
on
off
Only continue when this works reliably.
This step deliberately avoids HomeKit. It proves that the ESP32 can control the plug locally before adding another layer.
Use:
esp32/homespan_tuya_outlet/homespan_tuya_outlet.ino
Install in Arduino IDE:
- ESP32 board support
- HomeSpan library
Recommended Arduino IDE settings for a common 4 MB ESP32 board:
- Board:
ESP32 Dev Module - Partition Scheme:
No OTA (2MB APP/2MB SPIFFS)or larger - Upload Speed: any stable value for your board
The default ESP32 partition is usually too small for HomeSpan plus the setup web wizard.
Flash the sketch, open Serial Monitor at 115200, and wait for setup mode.
On first boot, or when no saved config exists, the ESP32 starts its own setup Wi-Fi access point:
TuyaHomeKit-Setup
The setup Wi-Fi password is randomly generated for each setup session and printed in Serial Monitor. Connect to that Wi-Fi network from a phone or computer and open:
http://192.168.4.1/
Fill:
- Wi-Fi SSID
- Wi-Fi password
- Tuya plug IP address
- Tuya device ID
- Tuya local key
- Tuya protocol version, default
3.4 - relay DPS, default
1 - HomeKit accessory name
- HomeKit type, default
Outlet - optional HomeKit pairing code
- polling interval in seconds, default
30
The setup page uses plain HTTP on the temporary ESP32 setup network. Configure it near the ESP32 and do not leave setup mode running longer than needed. Saved Wi-Fi passwords and Tuya local keys are stored in ESP32 Preferences in plaintext, but they are not shown back in the form; leave those fields blank to keep existing saved values when reconfiguring. Use an IoT or guest Wi-Fi network if possible.
Use Test Tuya connection before saving if you want a quick check from the setup page.
Click Save and restart. On the next boot, the ESP32 loads the saved config from flash, connects to Wi-Fi, starts HomeSpan, and exposes the plug to HomeKit.
After normal boot, Serial Monitor prints an admin URL with the ESP32's LAN IP address, for example:
Admin URL: http://192.168.1.50:8080
Open that URL from the same Wi-Fi network to edit the saved Tuya/HomeKit settings, test the Tuya connection, clear bridge configuration, or clear HomeKit pairing on the ESP32. The admin page is plain local HTTP without login, so use it only on a trusted LAN or IoT network.
If Wi-Fi connection fails repeatedly during boot, the ESP32 falls back to setup mode and periodically retries the saved Wi-Fi. If the network comes back, the ESP32 restarts into normal HomeSpan mode.
Reset options:
- Edit settings: open the admin URL and save new values.
- Clear saved config: use the admin/setup page button. This removes Wi-Fi and Tuya settings, restarts, and opens setup mode. It does not clear HomeKit pairing.
- Clear HomeKit pairing: use the admin page button, then remove the accessory in Apple Home too.
- Restart: press
EN. This is the ESP32 hardware reset button. - Factory reset: while the ESP32 is running, hold
BOOT / GPIO0for about 8 seconds. This clears bridge configuration, HomeKit pairing data, and the HomeKit device ID, then restarts into setup mode.
Holding GPIO0 / BOOT while the ESP32 starts still forces setup mode by clearing bridge configuration. On some dev boards, holding BOOT before reset enters the bootloader; if that happens, release BOOT and reset again.
Status LED:
- setup mode: fast blink
- after Wi-Fi connects successfully: 10 slow blinks, then off
- normal running, paired with HomeKit, no current error: steady dim light around 1% brightness
- Wi-Fi disconnected or Tuya plug not responding: dim SOS blink pattern
The default status LED pin is GPIO2, which matches many ESP32 Dev Module boards. If your board uses another LED pin, change STATUS_LED_PIN in the sketch. If the built-in LED is wired differently, adjust STATUS_LED_ON.
The HomeSpan sketch no longer requires esp32/homespan_tuya_outlet/secrets.h. Secrets are stored in ESP32 flash memory through the setup wizard and must still never be committed or shared.
The HomeKit pairing code is the 8-digit code Apple Home asks for when adding the accessory. It is not the Wi-Fi password and it is not the temporary setup AP password.
The setup/admin page has an optional HomeKit pairing code field:
- enter your own 8-digit code before pairing and write it down
- type it without hyphens, for example
11223344 - Apple Home displays it with hyphens, for example
112-23-344 - if left blank and never changed, HomeSpan uses its default code
466-37-726 - if forgotten, enter a new code in the admin page and save/restart
HomeSpan stores a hashed pairing code internally. The real code cannot be read back from the ESP32 later.
Equivalent arduino-cli compile check:
arduino-cli compile --fqbn esp32:esp32:esp32:PartitionScheme=no_ota esp32/homespan_tuya_outletFor security, do not hardcode the HomeKit pairing code in the sketch.
As an alternative to the web field, open Serial Monitor at 115200 and use the HomeSpan CLI command:
S 11223344
Rules:
- the code must be exactly 8 digits
- type it without hyphens in Serial Monitor
- write it down before pairing
- use your own code, not
11223344
When Apple Home asks for the setup code, enter it in the usual HomeKit format. For example:
112-23-344
for the Serial command S 11223344.
The setup/admin page can expose the same Tuya relay as a HomeKit Outlet, Light, or generic Switch.
Use:
Outletfor a physical smart plugLightonly when the plug controls a lampSwitchfor a generic on/off device
If you change HomeKit type or the accessory appears stale in Apple Home, remove the accessory in Apple Home, click Clear HomeKit pairing in the ESP32 admin page, restart, and add it again with the pairing code.
The HomeSpan sketch exposes:
- HomeKit service: configured as
Outlet,Light, orSwitch - HomeKit
On: Tuya relay DPS1 - HomeKit
OutletInUse: same value as relay state when type isOutlet
OutletInUse is currently not real power detection. It only means the relay is on.
The sketch polls local Tuya status at the configured interval, default 30 seconds, so HomeKit can notice changes made from Smart Life or the physical button.
For long-term use:
- reserve a fixed LAN IP for the plug
- reserve a fixed LAN IP for the ESP32
- keep the ESP32 close enough to the Wi-Fi access point
- use a stable USB power supply
- test for several days before relying on it
The biggest unknown is Tuya firmware behavior. A future plug firmware update could change local protocol behavior.
| Problem | Possible cause | Fix |
|---|---|---|
| Device found but connection test fails | Wrong local key | Verify local_key with TinyTuya or Tuya IoT Platform |
| No Tuya device found | Device on another VLAN/subnet | Put ESP32 and plug on the same LAN, or enter the IP manually |
| Port is open but test still fails | Wrong protocol version or local key | Keep protocol 3.4 for the verified plug and re-check credentials |
| Relay DPS not found | Plug uses a different datapoint | Use Scan DPS and choose the correct boolean DPS |
| HomeKit state updates slowly | Polling interval is high | Lower polling interval if your network and plug tolerate it |
| Admin page is unreachable | ESP32 IP changed | Check Serial Monitor or reserve the ESP32 IP in your router |
Check:
LOCAL_KEYDEVICE_VERSION- plug IP address
- same LAN / VLAN
- Smart Life app is not holding a conflicting local session
Go back to the ESP32 local POC and verify status/on/off over Serial Monitor.
If the POC works, restart the HomeSpan sketch into setup mode by holding GPIO0 / BOOT while the sketch starts. On some dev boards, pressing BOOT too early enters the bootloader. Re-enter the Tuya IP, device ID, local key, protocol version, and relay DPS, then use Test Tuya connection.
Check Serial Monitor. The sketch prints the setup reason, such as missing config, invalid saved local key, or repeated Wi-Fi connection failure.
The sketch polls at the configured interval. Wait for one poll cycle.
If state never updates, check Serial Monitor for Tuya status/decrypt errors.
If the ESP32 was paired before, clear HomeSpan pairing data using the HomeSpan Serial CLI, then pair again.
For questions, bug reports, or compatible-device reports, open a GitHub issue:
github.com/Lisejnik/tuya-esp32-homekit-bridge/issues
When opening an issue, include:
- device model
- Tuya protocol version
- relay datapoint if known
- ESP32 board type
- sanitized Serial Monitor logs
Do not include local_key, Wi-Fi passwords, Tuya Access Secret, generated curl commands, or unredacted API Explorer responses.
Before pushing to GitHub, verify that local secrets are ignored:
git status --ignored --shortThese files must not appear as tracked files:
.env
tinytuya.json
devices.json
tuya-raw.json
snapshot.json
esp32/**/secrets.h
If you accidentally committed a secret, remove it from git history and rotate the affected key.
Working prototype:
- local TinyTuya control verified
- ESP32 Tuya
3.4local POC verified - HomeSpan HomeKit Outlet verified
Still experimental:
- broader Tuya device support
- automatic datapoint discovery
- robust recovery for every network edge case

