8 minutes
Home Assistant Container Part 10: Zigbee2MQTT
Intro ¶
Zigbee is a wireless protocol, like WiFi, but developed specifically for (battery powered) Internet-of-Things devices. Devices connect in a mesh network, where mains powered devices (e.g. light bulbs and smart plugs) act as repeaters to extend the range of the network. The Zigbee coordinator is at the heart of the Zigbee network and allows interconnectivity with non-Zigbee devices.
Home Assistant offers 2 Zigbee integrations: the built-in ZHA, and deCONZ which works via an add-on. Another popular alternative is Zigbee2MQTT which makes use of Home Assistant’s MQTT Discovery to create devices and entities in Home Assistant.
Which coordinator you use is up to you. I advise to check the Zigbee Device Compatibility Repository by Blakadder to check which coordinator supports the device you plan to buy.
This post will discuss the installation of Zigbee2MQTT as it makes use off a Docker container for the coordinator (we already installed the MQTT broker).
Note: This post got delayed somewhat because I had to wait for my USB extension cable to arrive. Without it, my Zigbee controller was experiencing interference from my 2.4GHz WiFi on my miniPC.
Install Zigbee2MQTT container ¶
Locate Zigbee coordinator ¶
In our Docker compose config, we’ll need to assign the Zigbee coordinator, which’ll be connected via USB, to the Docker container.
Plug in your Zigbee coordinator (e.g. Conbee II, Sonof Zigbee 3.0 Plus, …) into your machine. Then run the following command to get the unique path to the device.
ls -l /dev/serial/by-id/
usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_eexxxxxxxxxxxxxxxxxxxxxxxxxxxx52-if00-port0 -> ../../ttyUSB0
We’ll use this unique by-id
path in our config later instead of the typical /dev/ttyUSB0
bevause there’s always a risk of the device getting a new TTY assigned after a reboot, espcially with other devices pluggin in as well.
Docker-compose ¶
We expand our docker-compose.yaml
with the config for the Zigbee2MQTT container.
services:
[...]
zigbee2mqtt:
container_name: zigbee2mqtt
image: koenkk/zigbee2mqtt
restart: unless-stopped
ports:
- "8099:8099/tcp"
environment:
- TZ=Europe/Brussels
volumes:
- /opt/zigbee2mqtt/data:/app/data
- /run/udev:/run/udev:ro
devices:
- /dev/serial/by-id/usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_eexxxxxxxxxxxxxxxxxxxxxxxxxxxx52-if00-port0:/dev/ttyACM0
Note that I changed the port mapping for this container. Port 8080 is already in use by adminer so I changed the Zigbee2MQTT front-end port to 8099 (see below) and mapped that one in my Docker configuration.
We won’t start our container just yet, as we first want to setup our configuration.
Create MQTT user ¶
When we set up the Mosquitto MQTT broker, I said I prefer to create a MQTT user per service. So let’s create a user for our Z2M container.
docker exec -it mosquitto mosquitto_passwd /mosquitto/config/mqttuser z2m_mqtt
Password: # Enter a password
Reenter password: # Repeat the password
cat mosquitto/config/mqttuser
[...]
z2m_mqtt:$7$101$1xcB1yrF********$O$XR******************2/N************************************************************g==
Zigbee2MQTT config ¶
To get a copy of the default config, run sudo wget https://raw.githubusercontent.com/Koenkk/zigbee2mqtt/master/data/configuration.yaml
in the folder where the config will be stored (/opt/zigbee2mqtt/data
).
Then open and edit it using sudo nano /opt/zigbee2mqtt/data/configuration.yaml
.
We’ll tell Z2M to connect to our MQTT broker, use the syntax Home Assistant device detection understands, set up the webserver, and let Z2M generate some keys used to setup the Zigbee network.
More info on what the settings in this config do, can be found in the Z2M docs.
# Adapter settings
serial:
port: /dev/ttyACM0
# MQTT
mqtt:
base_topic: zigbee2mqtt
server: '!secret server'
user: '!secret user'
password: '!secret password'
client_id: zigbee
# Zigbee network
permit_join: false # Do not allow random devices to connect automatically
# Webserver
frontend:
port: 8099 # Custom port
url: 'http://<ip.of.our.box>:8099' # Update IP here
# Devices and groups
# Extract config to separate files
devices: devices.yaml
groups: groups.yaml
# Home Assistant integration
homeassistant: true
advanced:
# Zigbee network - auto-generate new keys
pan_id: GENERATE
network_key: GENERATE
# Zigbee network - set channel to avoid interference with 2.4GHz WiFi
channel: 24
With this configuration in place, we can start the container using docker-compose up -d zigbee2mqtt
.
If you check the logs via Portainer or docker-compose logs zigbee2mqtt
, you’ll see our container start up nicely:
Using '/app/data' as data directory
Zigbee2MQTT:info 2022-10-04 12:09:47: Logging to console and directory: '/app/data/log/2022-10-04.12-09-47' filename: log.txt
Zigbee2MQTT:info 2022-10-04 12:09:47: Starting Zigbee2MQTT version 1.28.0 (commit #03ba647)
Zigbee2MQTT:info 2022-10-04 12:09:47: Starting zigbee-herdsman (0.14.62)
Zigbee2MQTT:info 2022-10-04 12:09:48: zigbee-herdsman started (resumed)
Zigbee2MQTT:info 2022-10-04 12:09:48: Coordinator firmware version: '{"meta":{"maintrel":1,"majorrel":2,"minorrel":7,"product":1,"revision":20220219,"transportrev":2},"type":"zStack3x0"}'
Zigbee2MQTT:info 2022-10-04 12:09:48: Currently 0 devices are joined:
Zigbee2MQTT:info 2022-10-04 12:09:48: Zigbee: disabling joining new devices.
Zigbee2MQTT:info 2022-10-04 12:09:48: Connecting to MQTT server at mqtt://<ip.of.our.box>:1883
Zigbee2MQTT:info 2022-10-04 12:09:48: Connected to MQTT server
Zigbee2MQTT:info 2022-10-04 12:09:48: MQTT publish: topic 'zigbee2mqtt/bridge/state', payload '{"state":"online"}'
Zigbee2MQTT:info 2022-10-04 12:09:48: Started frontend on port 0.0.0.0:8099
Zigbee2MQTT:info 2022-10-04 12:09:48: MQTT publish: topic 'zigbee2mqtt/bridge/state', payload '{"state":"online"}'
Home Assistant ¶
Sidebar ¶
Repeating what we did in Part 3, Part 5, Part 6, Part 7, and Part 9, we add an entry for ESPHome to the sidebar of our Home Assistant dashboard using panel-iframe
.
Add the following lines to configuration.yaml
and restart Home Assistant.
panel_iframe:
portainer: # part 3
[...]
nodered: # part 5
[...]
configurator: # part 6
[...]
duplicati: # part 7
[...]
esphome: # part 9
[...]
zigbee2mqtt:
title: Zigbee2MQTT
icon: mdi:zigbee
url: http://192.168.10.106:8099
require_admin: true

Pressing the Permit join
button on the top-right will temporarily allow any Zigbee device set to pairing mode to be picked up by our coordinator.

Controlling Z2M ¶
Since Zigbee2MQTT’s configuration can be adapted over MQTT, we can control Z2M not only from the Frontend we just exposed, but also from within our dashboard. This is also explained in the Z2M Home Assistant integration docs.
First, we add some more YAML to our configuration.yaml
via the Configurator.
Copy and paste this code add the end of the file.
If you’re used to splitting up your config and/or working with packages, you can put it in a zigbee2mqtt.yaml
package file as well.
# Input select for Zigbee2MQTT debug level
input_select:
zigbee2mqtt_log_level:
name: Zigbee2MQTT Log Level
options:
- debug
- info
- warn
- error
initial: info
icon: mdi:format-list-bulleted
# Input number for joining time remaining (in minutes)
input_number:
zigbee2mqtt_join_minutes:
name: "Zigbee2MQTT join minutes"
initial: 2
min: 1
max: 5
step: 1
mode: slider
# Input text to input Zigbee2MQTT friendly_name for scripts
input_text:
zigbee2mqtt_old_name:
name: Zigbee2MQTT Old Name
initial: ""
zigbee2mqtt_new_name:
name: Zigbee2MQTT New Name
initial: ""
zigbee2mqtt_remove:
name: Zigbee2MQTT Remove
initial: ""
# Input boolean to set the force remove flag for devices
input_boolean:
zigbee2mqtt_force_remove:
name: Zigbee2MQTT Force Remove
initial: false
icon: mdi:alert-remove
# Scripts for renaming & removing devices
script:
zigbee2mqtt_rename:
alias: Zigbee2MQTT Rename
sequence:
service: mqtt.publish
data_template:
topic: zigbee2mqtt/bridge/request/device/rename
payload_template: >-
{
"from": "{{ states.input_text.zigbee2mqtt_old_name.state | string }}",
"to": "{{ states.input_text.zigbee2mqtt_new_name.state | string }}"
}
zigbee2mqtt_remove:
alias: Zigbee2MQTT Remove
sequence:
service: mqtt.publish
data_template:
topic: zigbee2mqtt/bridge/request/device/remove
payload_template: >-
{
"id": "{{ states.input_text.zigbee2mqtt_remove.state | string }}",
"force": {% if states.input_boolean.zigbee2mqtt_force_remove.state == "off" %}false{% else %}true{% endif %}
}
# Timer for joining time remaining (254 sec)
timer:
zigbee_permit_join:
name: Time remaining
duration: 254
mqtt:
sensor:
# Sensor for monitoring the bridge state
- name: Zigbee2MQTT Bridge state
unique_id: zigbee2mqtt_bridge_state_sensor
state_topic: "zigbee2mqtt/bridge/state"
icon: mdi:router-wireless
# Sensor for Showing the Zigbee2MQTT Version
- name: Zigbee2MQTT Version
unique_id: zigbee2mqtt_version_sensor
state_topic: "zigbee2mqtt/bridge/info"
value_template: "{{ value_json.version }}"
icon: mdi:zigbee
# Sensor for Showing the Coordinator Version
- name: Zigbee2MQTT Coordinator Version
unique_id: zigbee2mqtt_coordinator_version_sensor
state_topic: "zigbee2mqtt/bridge/info"
value_template: "{{ value_json.coordinator.meta.revision }}"
icon: mdi:chip
- name: Zigbee2mqtt Networkmap
unique_id: zigbee2mqtt_networkmap_sensor
# if you change base_topic of Zigbee2mqtt, change state_topic accordingly
state_topic: zigbee2mqtt/bridge/networkmap/raw
value_template: >-
{{ now().strftime('%Y-%m-%d %H:%M:%S') }}
# again, if you change base_topic of Zigbee2mqtt, change json_attributes_topic accordingly
json_attributes_topic: zigbee2mqtt/bridge/networkmap/raw
# Switch for enabling joining
switch:
- name: "Zigbee2MQTT Main join"
unique_id: zigbee2mqtt_main_join_switch
state_topic: "zigbee2mqtt/bridge/info"
value_template: '{{ value_json.permit_join | lower }}'
command_topic: "zigbee2mqtt/bridge/request/permit_join"
payload_on: "true"
payload_off: "false"
automation:
# Automation for sending MQTT message on input select change
- alias: Zigbee2MQTT Log Level
initial_state: "on"
trigger:
platform: state
entity_id: input_select.zigbee2mqtt_log_level
action:
- service: mqtt.publish
data:
payload_template: "{{ states('input_select.zigbee2mqtt_log_level') }}"
topic: zigbee2mqtt/bridge/request/config/log_level
# Automation to start timer when enable join is turned on
- id: zigbee_join_enabled
alias: Zigbee Join Enabled
trigger:
platform: state
entity_id: switch.zigbee2mqtt_main_join
to: "on"
action:
service: timer.start
entity_id: timer.zigbee_permit_join
data_template:
duration: "{{ '00:0%i:00' % (states('input_number.zigbee2mqtt_join_minutes') | int ) }}"
# Automation to stop timer when switch turned off and turn off switch when timer finished
- id: zigbee_join_disabled
alias: Zigbee Join Disabled
trigger:
- platform: event
event_type: timer.finished
event_data:
entity_id: timer.zigbee_permit_join
- platform: state
entity_id: switch.zigbee2mqtt_main_join
to: "off"
action:
- service: timer.cancel
data:
entity_id: timer.zigbee_permit_join
- service: switch.turn_off
entity_id: switch.zigbee2mqtt_main_join
- id: "zigbee2mqtt_create_notification_on_successful_interview"
alias: Zigbee Device Joined Notification
trigger:
platform: mqtt
topic: 'zigbee2mqtt/bridge/event'
condition:
condition: template
value_template: '{{trigger.payload_json.type == "device_interview" and trigger.payload_json.data.status == "successful" and trigger.payload_json.data.supported}}'
action:
- service: persistent_notification.create
data_template:
title: Device joined the Zigbee2MQTT network
message: "Name: {{trigger.payload_json.data.friendly_name}},
Vendor: {{trigger.payload_json.data.definition.vendor}},
Model: {{trigger.payload_json.data.definition.model}},
Description: {{trigger.payload_json.data.definition.description}}"
Check your YAML config in the developer tools and restart Home Assistant.
Once that’s done, you can add a custom card to your Dashboard containing the following entries:
title: Zigbee2MQTT
type: entities
show_header_toggle: false
entities:
- entity: sensor.zigbee2mqtt_bridge_state
- entity: sensor.zigbee2mqtt_version
- entity: sensor.zigbee2mqtt_coordinator_version
- entity: input_select.zigbee2mqtt_log_level
- type: divider
- entity: switch.zigbee2mqtt_main_join
- entity: input_number.zigbee2mqtt_join_minutes
- entity: timer.zigbee_permit_join
- type: divider
- entity: input_text.zigbee2mqtt_old_name
- entity: input_text.zigbee2mqtt_new_name
- entity: script.zigbee2mqtt_rename
- type: divider
- entity: input_text.zigbee2mqtt_remove
- entity: input_boolean.zigbee2mqtt_force_remove
- entity: script.zigbee2mqtt_remove

Network map ¶
Another nice thing to add to Home Assistant is a map of your connect Zigbee devices.
Since we don’t have any devices connected yet, this map will be fairly empty. However, you’ll enjoy seeing your network grow each time you add a new device :)
This will require you to add a custom card, either manually or using HACS. I suggest you have a look at the documentation of both to decide which method you prefer.

Don’t see the map or got an error about the custom card not existing? Clear your cache and refresh the page before trying again.