👍 What we like
- ✓Real door state visible everywhere (app, dashboard, notifications)
- ✓Control from the Home Assistant app, no remote needed
- ✓Everything stays local: the Zigbee network depends on no cloud
- ✓Combines with lighting, alarm or presence detection
👎 What to watch
- ✕Operating the door requires a relay module on top of the sensor
- ✕Wiring on the opener's terminal block: read its manual first
- ✕Remote control does not replace the opener's safety features (photo-eyes, obstacle detection)
🏆 Our picks
Affiliate links · same price for youSonoff Zigbee 3.0 USB Dongle Plus-E (ZBDongle-E)
Amazon · price and availability on the listing
See all our recommended gear in the shop.
📑 Contents ▾
- 01 Prerequisites
- 02 Step-by-step installation
- · Step 1: Plug in the Zigbee coordinator and pass it to the container
- · Step 2: Add the Zigbee Home Automation (ZHA) integration
- · Step 3: Pair the SNZB-04P contact sensor
- · Step 4: Wire and add the relay module
- · Step 5: Create the “Garage” entity
- · Step 6: Get an alert when the door stays open
- · Step 7: Turn on the light at night
- · Step 8 (optional): Scheduled closing in the evening
- 11 Verification checks
- 12 Security and best practices
- 13 Troubleshooting common errors
- 14 FAQ
- · Is my Zigbee sensor compatible with ZHA?
- · ZHA or Zigbee2MQTT?
- · Can I skip the relay module?
- · How do I limit interference with Wi-Fi?
- 19 Read also
A connected garage door is really two separate functions. Knowing its state (open or closed) is the job of a contact sensor, here the Sonoff SNZB-04P on Zigbee. Operating it is the job of a dry-contact relay module that “presses” the opener’s wall button for you. This tutorial sets up the Zigbee coordinator in Home Assistant (running in a Docker container), adds the sensor, combines sensor and relay into a “Garage” entity, then adds careful automations: an alert when the door stays open and lighting at night. We deliberately do not automate opening: a door that opens by itself on a false trigger is a garage open to anyone.
Prerequisites
- Home Assistant running in a Docker container (named
homeassistantin this guide), with Docker Compose - A Zigbee coordinator: here the Sonoff Zigbee 3.0 USB Dongle Plus-E (ZBDongle-E)
- A Zigbee contact sensor: here the Sonoff SNZB-04P
- To operate the door: a dry-contact relay module (Zigbee or Wi-Fi) supported by Home Assistant, and an opener with push-button terminals (see its manual)
- SSH access to the machine running Docker
- The Home Assistant interface on port 8123 of your local network (no need to expose it to the Internet)
Step-by-step installation
Step 1: Plug in the Zigbee coordinator and pass it to the container
Plug the dongle into a USB port, ideally at the end of a short USB extension cable: USB 3 ports and the machine’s case interfere with the 2.4 GHz band. Then find its stable path on the host:
ls -l /dev/serial/by-id/
A line containing Sonoff or ITead should appear, linking to ttyUSB0 or ttyACM0. Use the full /dev/serial/by-id/... path rather than ttyUSB0, which can change after a reboot. Add it to the Home Assistant service in your docker-compose.yml:
services:
homeassistant:
# ... your existing configuration (image, volumes, network_mode, etc.)
devices:
- /dev/serial/by-id/usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_V2_XXXXXX-if00-port0:/dev/zigbee
Replace the part before :/dev/zigbee with the exact path you found, then recreate the container:
docker compose up -d homeassistant
Check that it worked:
docker exec homeassistant ls -l /dev/zigbee
The device should be listed. If the command answers “No such file or directory”, the path in the Compose file does not match the output of ls -l /dev/serial/by-id/.
Step 2: Add the Zigbee Home Automation (ZHA) integration
In Home Assistant, open Settings > Devices & services > Add integration, then choose Zigbee Home Automation. Enter /dev/zigbee when the wizard asks for the serial device and let it create a new Zigbee network. The ZBDongle-E is a Silicon Labs coordinator: if the radio type is requested, choose EZSP.
Check that it worked:
docker logs homeassistant --since 10m 2>&1 | grep -i -E "zha|zigbee" | tail -n 20
No port connection error should appear, and the ZHA integration should show in Devices & services with the coordinator as its first device.
Step 3: Pair the SNZB-04P contact sensor
In the ZHA integration, click Add device. Put the SNZB-04P in pairing mode by holding its button until its LED blinks (the exact duration is in its manual). The sensor usually appears within seconds. Rename its contact entity to binary_sensor.garage_porte so you can copy the examples as they are.
Mount the sensor on the door and its magnet on the frame, aligned when the door is closed. On an up-and-over or sectional door, place it at the top of the door, where the gap with the frame is clearest when opening.
Check that it worked: open Developer tools > States, search for binary_sensor.garage_porte, then open and close the door. The state should switch from off (closed) to on (open) and back.
Step 4: Wire and add the relay module
The relay is wired in parallel with the opener’s wall push-button, on the terminals provided for it (see the opener’s manual). Cut power to the opener before working on it. Then add the relay to Home Assistant with its integration (ZHA if it is Zigbee) and rename its entity to switch.garage_relais.
Most openers expect a pulse: a short contact, like a button press. If your relay has a pulse (“inching”) mode, enable it. Otherwise the script below generates the pulse. Create it in Settings > Automations & scenes > Scripts > Add script, ⋮ menu > Edit in YAML:
alias: "Garage - impulsion"
mode: single
sequence:
- action: switch.turn_on
target:
entity_id: switch.garage_relais
- delay: "00:00:01"
- action: switch.turn_off
target:
entity_id: switch.garage_relais
Save it with the ID garage_impulsion. Run it once with the door in sight: the opener should react as if the button had been pressed.
Step 5: Create the “Garage” entity
A cover entity combines the state (the sensor) and the control (the script) in a single card, with open and close buttons. Add this block to configuration.yaml (in the configuration folder mounted into the container):
template:
- cover:
- name: "Garage"
device_class: garage
state: "{{ 'open' if is_state('binary_sensor.garage_porte', 'on') else 'closed' }}"
open_cover:
- condition: state
entity_id: binary_sensor.garage_porte
state: "off"
- action: script.garage_impulsion
close_cover:
- condition: state
entity_id: binary_sensor.garage_porte
state: "on"
- action: script.garage_impulsion
The condition before each pulse prevents “open” from closing a door that is already open: the opener itself simply reverses its movement on every pulse.
Check the configuration before restarting:
docker exec homeassistant python -m homeassistant --script check_config --config /config
The command should report no error. Then restart Home Assistant (Settings > System > power menu > Restart): the cover.garage entity appears, with the door state and both buttons.
Step 6: Get an alert when the door stays open
This is the most useful automation day to day. Create it in Settings > Automations & scenes > Create automation > Create new automation, ⋮ menu > Edit in YAML. Replace notify.mobile_app_mon_telephone with your phone’s notification service (it appears once the Home Assistant app is installed and signed in):
alias: "Garage left open"
triggers:
- trigger: state
entity_id: binary_sensor.garage_porte
to: "on"
for: "00:10:00"
actions:
- action: notify.mobile_app_mon_telephone
data:
title: "Garage"
message: "The garage door has been open for 10 minutes."
Check that it worked: leave the door open for a little more than 10 minutes. The notification should arrive, and the run shows in the automation’s trace (⋮ menu > Traces).
Step 7: Turn on the light at night
Same method. Replace light.garage with your own light:
alias: "Garage - light at night"
triggers:
- trigger: state
entity_id: binary_sensor.garage_porte
from: "off"
to: "on"
conditions:
- condition: state
entity_id: sun.sun
state: "below_horizon"
actions:
- action: light.turn_on
target:
entity_id: light.garage
Check that it worked: after sunset, open the door. The light should turn on and the trace should show the sun.sun condition as passed.
Step 8 (optional): Scheduled closing in the evening
If your opener has working photo-eyes and obstacle detection, you can have it close at a fixed time when it was left open. Without those safety features, do not do this.
alias: "Garage - evening closing"
triggers:
- trigger: time
at: "22:30:00"
conditions:
- condition: state
entity_id: binary_sensor.garage_porte
state: "on"
actions:
- action: notify.mobile_app_mon_telephone
data:
message: "Garage door left open: closing now."
- action: cover.close_cover
target:
entity_id: cover.garage
Check it on the first evening: the notification should arrive and the door should close. Then confirm in the traces that the condition was evaluated.
Verification checks
docker exec homeassistant ls -l /dev/zigbeelists the coordinatordocker exec homeassistant python -m homeassistant --script check_config --config /configreports no error- In Developer tools > States,
binary_sensor.garage_portefollows the door andcover.garageshowsopenorclosed - The Garage card’s “close” button does nothing when the door is already closed (the condition blocks the pulse)
- The automations from steps 6 and 7 show a trace after a real test
Security and best practices
A garage door control is a key to your home. Do not expose port 8123 directly to the Internet: use a VPN (WireGuard, Tailscale) or an HTTPS reverse proxy. Enable two-factor authentication for Home Assistant accounts and do not give other household members an administrator account. The Zigbee network (the zigbee.db file and ZHA configuration) lives in Home Assistant’s configuration folder: back it up regularly, for example with tar czf ha-config-$(date +%F).tar.gz -C /path/to config, adjusting the path to your volume. Update Home Assistant with docker compose pull homeassistant && docker compose up -d homeassistant after reading the release notes. Finally, keep the original remote and wall button: home automation adds to the opener, it does not replace it.
Troubleshooting common errors
- ZHA cannot find the coordinator -> the device is not passed to the container -> check the
devicessection of the Compose file and the output ofdocker exec homeassistant ls -l /dev/zigbee, then recreate the container. - The sensor pairs, then drops off -> insufficient range or 2.4 GHz interference -> move the dongle away from the machine with a USB extension cable and add a mains-powered Zigbee device (plug or relay), which acts as a repeater.
- The door opens, then immediately closes -> the relay stays closed too long or sends two pulses -> disable the relay’s pulse mode if the script already does it, or the other way round.
- The door state is inverted -> sensor mounted upside down or magnet misaligned -> adjust its position. The step 5
statetemplate assumeson= open. check_configreports acovererror -> YAML indentation, or atemplate:key already present elsewhere inconfiguration.yaml-> group all blocks under a singletemplate:key.
FAQ
Is my Zigbee sensor compatible with ZHA?
Most Zigbee 3.0 sensors are. For a specific model, check the community compatibility database zigbee.blakadder.com, which lists for each device whether it works with ZHA and Zigbee2MQTT.
ZHA or Zigbee2MQTT?
Both work with the ZBDongle-E. ZHA is built into Home Assistant and is enough for this guide. Zigbee2MQTT also needs an MQTT broker, but often supports recent devices sooner and exposes more settings.
Can I skip the relay module?
To know the door state, yes: the sensor is enough, and steps 6 and 7 work without a relay. To operate the door from Home Assistant, you need a relay, or an opener that has its own Home Assistant integration.
How do I limit interference with Wi-Fi?
Zigbee and Wi-Fi share the 2.4 GHz band. Zigbee channels 15, 20 and 25 overlap least with Wi-Fi channels 1, 6 and 11. A USB extension cable that moves the dongle away from the machine helps as much as the channel choice.