Zigbee is a protocol that has been around for quite a long time, and you can purchase many cheap smart sensors to incorporate into your own Zigbee network. Zigbee is its own network, meaning that it runs separately from your Wi-Fi, and devices connect to your Zigbee gateway (via a Zigbee coordinator dongle) directly. However, you can actually build your own low-cost Zigbee sensors using an ESP32-C6, ESP32-H2, or ESP32-H4.

There are two ways you can do this: the first is through the ESP-IDF Zigbee SDK, which I tested to begin with, and through an external ESPHome component. I stuck with the latter, as not only did it come more intuitively to me (and I'm sick of writing in C++ recently, to be honest), but I often see Zigbee as a requested feature for ESPHome. Plus, there's plenty of documentation out there for building a Zigbee device using the Espressif SDK, and not so much for doing it in ESPHome.

It's also worth noting that despite the relationship Zigbee and Thread have in terms of their development, with some Zigbee devices even being capable of upgrading to Thread, they are still different technologies that both utilize the LR-WPAN wireless network standard. I have seen conflicting reports on social media that seemed to herald the addition of OpenThread to ESPHome in the 2025.6 update as the advent of Zigbee support, which isn't the case. Thankfully, through the zigbee_esphome external component, it's still pretty easy to make it work.

What is the ESP32-C6?

It's a low-cost ESP32 with Thread and Zigbee

Espressif has many different types of ESP32 devices, and while "ESP32" is used as a general expression to refer to the line of microcontrollers in general, each one has different properties, capabilities, and pin layouts. "ESP32" is a catch-all phrase, and the ESP32 we're talking about here is the ESP32-C6.

The ESP32-C6 is a cut-down version of the ESP32-C5, capable of 2.4GHz Wi-Fi 6 connectivity. It has a single RISC-V core clocked at 160 MHz alongside a 20 MHz low-power core and packs 512 KB of SRAM on board. Thanks to Wi-Fi 6, though not relevant to this article, it supports OFDMA uplink/downlink and Target Wake-Time, with the latter enabling better power-saving options.

The ESP32-C6 is special in that it doesn't just pack Wi-Fi and Bluetooth like most other ESP32 devices; it supports both Zigbee and Thread, too. ESPHome recently added OpenThread support to allow you to build your own ESP32-C5 and ESP32-C6 devices and connect them to your Thread network, but Zigbee support doesn't seem to be on the way any time soon. To get around this, you can use the esp-zigbee-sdk from Espressif in Arduino or PlatformIO, or use an external component, which we'll be using.

It's worth noting that the Zigbee radio on the ESP32-C6 appears to be especially weak, so be prepared to place it close to your Zigbee coordinator. Right next to a Zigbee repeater that I have, it still only maintained a Link Quality Indicator of around 120-130, which is fine but not great.

Setting up Zigbee in ESPHome

Be prepared to do some reading

For the purposes of this article, we'll assume that you have some experience with ESPHome, as adding and using external components are typically unsupported, given that the code is not built or audited by the Open Home Foundation. Make sure to take a look at the zigbee_esphome GitHub repository first for code examples and getting started, just to familiarize yourself with the structure and placement of the Zigbee-related fields.

To get started with Zigbee in ESPHome, including the external component is the same as with any other external component you might use. You just need the following in your YAML:

external_components:
- source: github://luar123/zigbee_esphome
components: [zigbee]

This will then pull in nearly everything you need for Zigbee deployments in ESPHome. The only thing you'll need is the partitions_zb.csv file, which is in the GitHub repository and should be placed in the root of your ESPHome directory. This defines the partition layout for a Zigbee device on an ESP32, as both zb_storage and zb_fct need to be defined names for a Zigbee device to store both connection data and factory reset data. You then need to add it to the "partitions" field under the esp32 heading, like so:

esp32:
board: esp32-c6-devkitc-1
partitions: partitions_zb.csv
framework:
type: esp-idf

Define your sensor as you normally would, but make sure to give it an ID field for later referencing. For my motion sensor, I did the following:

binary_sensor:
- platform: gpio
pin: GPIO22
name: "PIR Sensor"
id: occupancy
device_class: motion

Now, finally, is the actual Zigbee part. Defining a singular device is quite easy, but depending on the sensor you want to use, you'll need to play around and also read some of the documentation on the esp-zigbee-sdk to find device types that you can use. Here's my code:

zigbee:
id: "zb"
endpoints:
- num: 1
device_type: ON_OFF_OUTPUT
clusters:
- id: OCCUPANCY_SENSING
attributes:
- attribute_id: 0
id: occu_sens
type: bool
device: occupancy
report: true
value: false
on_join:
then:
- logger.log: "Joined network"

For example, the OCCUPANCY_SENSING ID that I found was found through the documentation and a bit of guesswork. I found the documentation for a motion sensor in the SDK, and noticed that "OCCUPANCY_SENSING" was common to all of them, so I set that, and it worked. You'll likely need to do the same kind of research for other device types.

Other fields you need to pay attention to are the "device" and "type" fields. The "device" field directly references my motion sensor as the field to collect data from, and the value defaults to false, meaning that the area is clear. When it came to "type", I entered a value of "1" so that I could see an error with the valid values. This is where I found "bool", which, for a binary sensor, is perfect.

When we power it on, it'll instantly seek to join a network, so whether you use Zigbee2MQTT or Zigbee Home Automation, you should permit new devices to join the network before flashing it.

Before we flash it, though, make sure to remove the "ap" flag and any nested values under the "wifi" key. AP mode cannot coexist alongside Zigbee. As well, if you run into problems, it can be worth disabling Wi-Fi entirely, as it can use much-needed RAM and could potentially cause interference, too.

Joining our Zigbee network

I used Zigbee2MQTT

Once our device joins our Zigbee network, the service you're using will attempt to automatically discover the "attributes" (endpoints that report data) to figure out what type of device it is and what data it sends. I suspect that using the endpoints defined in the Espressif documentation made it easy for Zigbee2MQTT to figure out what type of device it was, but Zigbee2MQTT also provides instructions on adding non-standard devices if it can't automatically discover features or figure out what they do.

From there, you're done! You can just keep everything connected, and your ESP32-C6 will report data to your Zigbee coordinator like any other Zigbee device would. I can see it in my Home Assistant MQTT integration, and I can use this to build all kinds of devices. You can build temperature sensors, motion detectors, and more, and because the ESP32-C6 is incredibly cheap (and sensors are even cheaper), you can chop and change your deployments as you see fit.

There are many different kinds of ESP32-C6, and the XIAO ESP32-C6 has a built-in charge controller if you want to hook it up to a battery. I'm using a regular ESP32-C6 devkit here, which can be found between $4 and $7 depending on where you buy it, and the sensor can be found for as little as $0.50. It's a great way to get started building your own Zigbee devices, and you can delve into the world of the esp32-zigbee-sdk at any time, too.