I had an older Philips Hue bulb that I wanted to connect directly to Home Assistant. I already had a Zigbee USB dongle and ZHA running, but I did not have a Hue Bridge or a Hue dimmer remote to reset the bulb.
After unsuccessful attempts at power cycling, a small custom Home Assistant integration gave me a working route: scan for the bulb using Touchlink, make it flash to confirm its identity, reset it, then let ZHA discover it.
The original manual method worked, but copying addresses and channels between actions was fiddly. Version 0.3.0 adds a quicker route: run one action to scan, identify and send the reset command, then open ZHA pairing. This guide starts with that method and keeps the manual procedure as a fallback.
What was tested?
- An older Philips Hue White A60 E27 bulb, model 9290011370A.
- A Texas Instruments CC2531 Zigbee USB coordinator already used by ZHA.
- Home Assistant Container running on an Ubuntu server, managed with Docker and Portainer.
- ZHA library 2.3.1, zigpy 2.3.0 and zigpy-znp 1.1.1.
- The manual reset procedure using ZHA Touchlink Preview 0.2.0; the updated download is version 0.3.0.
The manual scan, identify and reset-and-pairing procedure was confirmed on this setup. Version 0.3.0 adds the combined action and passed 26 automated tests covering protocol handling, simulated radio behaviour and action dispatch. The new combined action has not yet been separately confirmed on the physical bulb. Compatibility with other bulbs, coordinator firmware and Home Assistant versions has not been established.
This is an experimental custom integration, not a built-in ZHA feature or an official Philips Hue tool. It uses private library interfaces and deliberately rejects different dependency versions and coordinator models. A Home Assistant update can therefore stop it working until the integration is reviewed and updated. Do not remove those checks just to make it run.
How Touchlink makes this possible
Touchlink allows compatible Zigbee devices to communicate even when they do not belong to the same Zigbee network. That gives a nearby coordinator a way to find, identify and reset a compatible bulb that still remembers an old network.
The bulb needs to support Touchlink in its firmware. Close range matters, and some devices only respond for a limited period after being powered on. The Zigbee2MQTT Touchlink documentation explains these behaviours and provides a separate, established implementation for people already using Zigbee2MQTT.
My custom integration uses the existing ZHA connection to the CC2531. It temporarily pauses normal radio requests, performs the Touchlink exchange, restores normal radio operation, then optionally opens ZHA joining. It does not need a second program to open the dongle’s serial port.
Scanning temporarily interrupts ordinary Zigbee communication. Run this when a brief interruption is acceptable, and avoid coordinator firmware updates or ZHA reloads during an operation.
Install the custom integration
Download the accompanying ZHA-Touchlink-Preview-0.3.0.zip package. These action names only exist after that package has been installed and enabled.
Transfer the ZIP from an iPhone with Termius
- Download ZHA-Touchlink-Preview-0.3.0.zip to your iPhone and use Share → Save to Files. Leave it as a ZIP.
- In Termius, open an SFTP connection to the same Ubuntu server you use for SSH, using its address, your username and SSH credentials. The usual SSH/SFTP port is 22.
- Browse to your Linux user’s home directory, for example /home/dave, choose Upload and select the ZIP from the iPhone Files app.
- Return to the server’s SSH terminal in Termius and run the installation commands below.
No Windows computer or SCP command is needed for this route. Termius has built-in SFTP; see its iPhone App Store listing. If you use another SSH app, look for its SFTP or file upload feature.
Install or upgrade on the Ubuntu server
For my particular Docker installation, the container is called homeassistant and its /config folder is bind-mounted from /opt/homeassistant/config. With the ZIP in my Linux user’s home directory, I used:
unzip -o ~/ZHA-Touchlink-Preview-0.3.0.zip -d /tmp/zha-touchlink-preview-030
sudo bash /tmp/zha-touchlink-preview-030/install.sh
sudo docker restart homeassistant
The supplied installer checks that container name, configuration path and the three library versions above. It backs up configuration.yaml and any existing copy of the component before installing. It adds this top-level configuration entry if it is missing:
zha_touchlink:
The installer is specific to that Docker layout. For another installation type, the integration files belong in custom_components/zha_touchlink under Home Assistant’s configuration directory, with the same top-level configuration entry. Back up your configuration first. Copying it manually does not bypass the integration’s hardware and version requirements, and other installation types have not been validated here.
There is no Add Integration wizard and HACS is not required. After restarting Home Assistant, the actions should be available under Settings → Tools → Actions in the interface used for this test. Other Home Assistant versions may place Actions under Developer Tools. Use an administrator account.
1. Position and power cycle the bulb
Keep the bulb safely fitted in a suitable lamp holder and put its base no more than 10 cm from the Zigbee dongle. A USB extension cable makes positioning easier. Avoid touching a hot bulb or bringing exposed electrical parts near the dongle.
Switch the bulb off for 10 seconds, turn it back on and run the scan promptly. This power cycle is preparation for Touchlink, not a claim that the bulb has been factory reset.
Working with one bulb at a time makes it much easier to recognise the correct one. Switch off other nearby Hue bulbs if necessary.
2. Run the single scan-and-reset action
After installing version 0.3.0 and restarting Home Assistant, open Settings > Tools > Actions, switch to YAML mode and run:
action: zha_touchlink.scan_and_reset
That is the complete action: there is no address or channel to copy, and you do not need to create a script or automation.
The integration performs the following sequence:
- Scan all 16 Zigbee channels before choosing a target.
- Count distinct responding devices by their IEEE address. A bulb answering on several channels counts once.
- If exactly one device is found, check that same device again on its most recently observed response channel. Stop if it is missing or an additional device responds during the final check.
- Send a brief identify/flash command, then a targeted factory-reset command.
- Restore normal radio operation, then ask ZHA to permit joining for 60 seconds.
If no devices or multiple distinct devices respond, it stops without resetting. With one responding device it proceeds automatically: there is no pause for visual confirmation after the flash. Keep only the intended bulb powered nearby. The scan can only count devices that actually answer; it cannot determine which physical lamp you meant to reset.
Allow roughly 15–20 seconds, plus command latency. A factory reset erases the selected bulb’s old network membership. Run this only when you deliberately want to reset a lamp, not on a schedule or at Home Assistant startup.
If you prefer to open pairing yourself, use:
action: zha_touchlink.scan_and_reset
data:
permit_join: false
3. Check that the bulb joins ZHA
Check Settings > Devices & services > Zigbee Home Automation > Devices for the bulb. Use ZHA’s Add device screen to view discovery progress or open joining manually if needed.
reset_command_sent: true means that the coordinator accepted the outgoing command. The bulb does not send a reset acknowledgement through this helper. Discovery in ZHA and successful control of the light are the practical checks that the process worked.
If it does not join, try one ordinary off/on power cycle while ZHA is searching. Check whether it is already in the device list before resetting again: another reset would erase its new membership.
Alternative: the manual scan, identify and reset method
Use this if you want to select a particular bulb or pause to visually check its flash before resetting. These actions are still supported in version 0.3.0. The recent-scan and identify requirements below apply to the manual method; the combined action performs its own checks.
Scan
Open Actions, switch to YAML mode and run:
action: zha_touchlink.scan
This scans all supported Zigbee channels. Results appear in the action response and a Home Assistant notification. A typical response looks like this:
devices:
- ieee: "00:17:88:01:02:b3:9d:89"
channel: 25
link_quality: 160
advertised_channel: 25
factory_new: false
endpoint: 11
count: 1
note: Scan only. No bulb has been reset.
The address above belongs to my test bulb. Use the IEEE address and channel returned by your own scan. The IEEE address identifies the device; it is not the short serial number printed on a Hue bulb. Link quality is LQI, not a signal reading in dBm. The factory_new flag is useful diagnostic information but does not by itself prove which network a bulb belongs to.
My bulb was seen on channel 15 earlier and channel 25 later. Always use the latest result rather than assuming its channel has stayed the same. If you already know the channel, a shorter scan is possible:
action: zha_touchlink.scan
data:
channels: [25]
A scan does not reset anything. An empty result can mean the bulb is too far away, its Touchlink response window has expired, or the hardware or firmware is unsuitable.
Identify the exact bulb
Replace the address and channel below with your scan result, then run:
action: zha_touchlink.identify
data:
ieee: "00:17:88:01:02:b3:9d:89"
channel: 25
The bulb should flash briefly. Confirm that the intended bulb flashes before continuing. Identify does not reset it.
The integration requires a recent scan and a successful identify command for the same address and channel. The records expire after ten minutes, are lost when Home Assistant restarts, and are cleared by another scan. If you scan again, identify again before resetting.
Reset it and open pairing
With the bulb still close to the dongle, run this using the same address and channel:
action: zha_touchlink.factory_reset
data:
ieee: "00:17:88:01:02:b3:9d:89"
channel: 25
permit_join: true
This checks for the exact target again, briefly identifies it, sends a targeted factory-reset command, restores normal radio operation and asks ZHA to permit joining for 60 seconds. Other Zigbee devices already in pairing mode may also join during that window.
A reset removes the bulb’s existing Zigbee network membership. Only reset a bulb you own and intend to move. The helper does not reset the first arbitrary device it finds.
What helped when it was being fiddly?
- No bulb found: move it closer, switch it off for ten seconds, turn it back on and run the action promptly. The combined action already scans all channels.
- Multiple devices found: switch off other nearby Touchlink bulbs and try again, or use the manual method to choose the intended device.
- Found earlier, missing now: repeat the power cycle. Do not assume the earlier channel still applies.
- Manual identify or reset rejected: run scan, then identify, then reset in order. A restart or another scan clears the previous identify record. The combined action does not need a separate identify call.
- Reset sent but pairing did not open: open ZHA Add device manually. Do not treat a joining error as proof that the reset failed.
- Timeout during reset: check ZHA first because the command may already have reached the bulb. Neither method automatically retries a reset transmission. For another intentional manual attempt, scan and identify again; for the combined method, rerun the single action only after checking that the bulb has not already joined.
- Radio restoration error: restart Home Assistant before continuing. On my Docker setup that is sudo docker restart homeassistant.
Removing the helper afterwards
The bulb joins the ordinary ZHA network; this helper is not needed for everyday control. To remove it, remove the top-level zha_touchlink: entry from configuration.yaml, restart Home Assistant and then remove only its custom_components/zha_touchlink directory.
Removing the helper does not factory-reset the bulb or remove it from ZHA. The integration does not patch ZHA’s installed files.
References and source
- Zigbee2MQTT: Touchlink behaviour, proximity and reset guidance.
- zigpy 2.3.0: Zigbee Light Link command schemas.
- zigpy-znp 1.1.1: TI ZNP inter-PAN commands and radio interface.
- Home Assistant Core 2026.10.2 ZHA source: integration wiring reference.
The accompanying custom component is supplied under GPL-3.0-or-later. Keep its licence and reference notices with redistributed copies. This guide records one successful setup and is intended to help others reproduce it within the stated compatibility limits.
