目录
2.2 Home Assistant Connect ZBT-1 配置
前言
在物联网与智能家居开发中,基于 Zigbee 协议实现设备间的低功耗、自组网通信,并接入开放平台进行统一管控,是极具代表性的实战场景。
本文以 ESP32-C6 为核心硬件,基于 ESP-IDF 开发环境,从零开始手把手带你在 Linux 系统下配置 Home Assistant 智能家居平台、开发 Zigbee 终端设备代码,并成功接入。全文贯穿实战,附完整代码与避坑指南,助你快速上手。

一、项目背景与硬件准备
1.1 为什么选这套方案?
| 组件 | 选择理由 |
|---|---|
| ESP32-C6 | 原生支持 Zigbee 3.0 / Thread / Matter,性价比高 |
| Home Assistant Connect ZBT-1 | 官方 Zigbee 协调器,开箱即用,支持 ZHA 和 Zigbee2MQTT |
| Home Assistant OS (虚拟机) | 功能完整,支持插件商店,方便扩展 |
1.2 硬件清单
-
ESP32-C6 开发板
-
Home Assistant Connect ZBT-1 协调器(含 USB 延长线)
-
运行 Home Assistant 的主机(本文使用 VMware 虚拟机,操作系统为 Ubuntu)
-
USB 数据线(用于烧录 ESP32-C6)
注意:ZBT-1 建议使用 USB 延长线,USB 3.0 接口会对 2.4GHz Zigbee 信号产生严重干扰。
二、Home Assistant 环境搭建
2.1 VMware 虚拟机安装 HAOS
从 Home Assistant 官网下载 VMware 专用镜像(.vmdk 格式)


本项目以 Windows 安装 VMware 虚拟机为例,基于 Ubuntu 操作系统,需要改动配置如下:
1、 磁盘文件地址改成 Home Assistant 镜像文件地址

如果你是创建虚拟机时设置的,可以直接改;如果你是原有虚拟机改装过来的,磁盘文件地址改不了,可以将 Home Assistant 镜像文件复制到原有磁盘文件地址(如:L:\ubuntu),然后删除原有磁盘文件,将 Home Assistant 镜像文件重命名为原有磁盘文件(如:Ubuntu_64-000001)


2、 网络适配器改成为桥接模式

3、 USB 改成 3.0 及以上

4、 修改 .vmx 配置文件

添加 firmware = "efi" 启用 UEFI 引导

做完这些,就可以启动虚拟机,等待配置,如果出现以下界面,则操作成功:

访问 http://homeassistant.local:8123 ,一开始需要注册,注册完就是类似如下界面:

2.2 Home Assistant Connect ZBT-1 配置
将 ZBT-1 插入电脑(要选择连接虚拟机,而不是主机),Home Assistant 一般会自动发现。如果是以下显示就是连接成功:

在 Home Assistant 界面,点击设置 → 设备与服务:

点击发现的 ZBT-1,选择 “用作 Zigbee 适配器”,选择 “推荐安装”(使用 ZHA 集成),按提示将适配器分配到某个区域(如“客厅”),然后点击完成(finish),Zigbee 网络就创建好了。


注意:如果自动发现失败,可以检查 VMware 的 USB 直通设置,确保 ZBT-1 已连接到虚拟机。
三、ESP32-C6 代码实现
3.1 配置
在写代码之前需要确定 zigbee 的 sdk 配置,看看你 ESP32-IDF 的文件里有没有 esp-zigbee-sdk 。

如果没有,克隆 esp-zigbee-sdk :
在 cmd 中,进入 ESP-IDF 的目录(如:Q:\ESP32\IDF\Espressif):
cd Q:\ESP32\IDF\Espressif
然后克隆:
git clone https://github.com/espressif/esp-zigbee-sdk.git
3.2 核心代码
在 ESP32-IDF 的 SDK配置编辑器中,修改配置如下:

主文件夹的 CMakeLists.txt
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/../../esp-zigbee-sdk/components)
list(PREPEND SDKCONFIG_DEFAULTS "sdkconfig.defaults")
set(EXCLUDE_COMPONENTS esp-zigbee-console) # 排除不兼容的组件
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(7_zigbee)
main 文件夹的 CMakeLists.txt
idf_component_register(SRC_DIRS "."
PRIV_INCLUDE_DIRS "."
PRIV_REQUIRES vfs nvs_flash BSP Middlewares)
idf_component.yml
## IDF Component Manager Manifest File
dependencies:
## Required IDF version
idf:
version: ">=5.2"
espressif/esp-zigbee-lib:
version: ">=2.0.0"
main.c
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <stdio.h>
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_zigbee.h"
#include "ezbee/zha.h" // EZBee ZHA 设备创建 API
#include "driver/gpio.h"
#include "led.h"
#include "myadc.h"
#include "zigbee.h"
static const char *TAG = "MAIN"; // 日志标签
static void zigbee_task(void *pv)
{
zigbee_init();
// 注册信号处理器(处理栈事件)
ESP_ERROR_CHECK(ezb_app_signal_add_handler(app_signal_handler));
// 创建设备端点
create_my_device();
// 启动 Zigbee 栈(非阻塞)
ESP_ERROR_CHECK(esp_zigbee_start(false));
// 进入 Zigbee 主事件循环(阻塞,直到栈停止)
esp_zigbee_launch_mainloop();
// 正常不会执行到这里,但为了完整保留
esp_zigbee_deinit();
vTaskDelete(NULL);
}
// 上报温度任务
void report_task(void *pv)
{
while (1)
{
// 短地址为 0xFFFE 表示未入网
if (ezb_nwk_get_short_address() != 0xFFFE)
{
int adc_data = myadc_read();
zigbee_report(adc_data);
}
else
{
ESP_LOGI(TAG, "未入网, waiting...");
}
vTaskDelay(pdMS_TO_TICKS(15000));
}
}
void app_main(void)
{
// 初始化 NVS(用于 Zigbee 存储)
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(nvs_flash_init_partition("zb_storage"));
led_init();
myadc_init();
// 创建 Zigbee 任务(栈大小 4096,优先级 5)
xTaskCreate(zigbee_task, "zigbee", 4096, NULL, 5, NULL);
}
zigbee.c
#include "zigbee.h"
static const char *TAG = "MY_ZIGBEE";
void zigbee_init(void)
{
// 配置 Zigbee 栈
esp_zigbee_config_t config = {
.device_config = {
.device_type = EZB_NWK_DEVICE_TYPE_END_DEVICE, // 终端设备
.install_code_policy = false,
.zed_config = {
.ed_timeout = EZB_NWK_ED_TIMEOUT_64MIN, // 休眠超时
.keep_alive = 4000,
},
},
.platform_config = {
.storage_partition_name = "zb_storage", // NVS 分区名
.radio_config = { .radio_mode = ESP_ZIGBEE_RADIO_MODE_NATIVE },
},
};
ESP_ERROR_CHECK(esp_zigbee_init(&config));
// 启用所有 Zigbee 信道 (11-26) 进行扫描
ezb_aps_secur_enable_distributed_security(false);
ESP_ERROR_CHECK(ezb_bdb_set_primary_channel_set(0xFFFF)); // 所有主信道
ESP_ERROR_CHECK(ezb_bdb_set_secondary_channel_set(0xFFFF)); // 所有备用信道
}
// 上报到 Zigbee 网络
void zigbee_report(int adc_data)
{
int16_t measured_value = (int16_t)adc_data;
// 获取 Zigbee 栈锁,保护属性写入
esp_zigbee_lock_acquire(portMAX_DELAY);
// 设置adc_data属性
ezb_zcl_set_attr_value(TEMP_ENDPOINT,
EZB_ZCL_CLUSTER_ID_TEMPERATURE_MEASUREMENT,
EZB_ZCL_CLUSTER_SERVER,
EZB_ZCL_ATTR_TEMPERATURE_MEASUREMENT_MEASURED_VALUE_ID,
EZB_ZCL_STD_MANUF_CODE, // 标准制造商码
(uint8_t *)&measured_value,
false); // 不发送响应
esp_zigbee_lock_release();
printf("Reported adc_data: %.d\r\n", adc_data);
}
// 重试入网流程
void zigbee_retry_commissioning(alarm_timer_arg_t arg)
{
esp_zigbee_lock_acquire(portMAX_DELAY);
ezb_bdb_start_top_level_commissioning(arg);
esp_zigbee_lock_release();
}
// 处理栈启动、入网等事件
bool app_signal_handler(const ezb_app_signal_t *signal)
{
ezb_app_signal_type_t type = ezb_app_signal_get_type(signal);
switch (type)
{
// 栈初始化完成,开始顶层入网流程
case EZB_ZDO_SIGNAL_SKIP_STARTUP:
ESP_LOGI(TAG, "Initialize Zigbee stack");
ezb_bdb_start_top_level_commissioning(EZB_BDB_MODE_INITIALIZATION);
break;
// 设备首次启动或重启后的状态
case EZB_BDB_SIGNAL_DEVICE_FIRST_START:
case EZB_BDB_SIGNAL_DEVICE_REBOOT: {
ezb_bdb_comm_status_t status = *(ezb_bdb_comm_status_t *)ezb_app_signal_get_params(signal);
// 启动成功
if (status == EZB_BDB_STATUS_SUCCESS)
{
ESP_LOGI(TAG, "设备启动, %s factory-new", ezb_bdb_is_factory_new() ? "" : "not");
// 如果是出厂新设备,则启动网络引导(入网)
if (ezb_bdb_is_factory_new())
{
ezb_bdb_start_top_level_commissioning(EZB_BDB_MODE_NETWORK_STEERING);
}
else
{
ezb_bdb_start_top_level_commissioning(EZB_BDB_MODE_NETWORK_STEERING);
}
}
else
{
ESP_LOGW(TAG, "设备启动失败 (0x%02x), 1秒后重试", status);
alarm_timer_schedule(zigbee_retry_commissioning, (alarm_timer_arg_t)EZB_BDB_MODE_INITIALIZATION, 1000);
}
break;
}
// 入网结果
case EZB_BDB_SIGNAL_STEERING: {
ezb_bdb_comm_status_t status = *(ezb_bdb_comm_status_t *)ezb_app_signal_get_params(signal);
if (status == EZB_BDB_STATUS_SUCCESS)
{
ESP_LOGI(TAG, "Joined network! PAN ID: 0x%04x", ezb_nwk_get_panid());
// 创建上报任务(只创建一次)
static bool reported_created = false;
if (!reported_created)
{
xTaskCreate(report_task, "report", 2048, NULL, 5, NULL);
reported_created = true;
}
}
else
{
// 失败重试(使用 alarm_timer)
ESP_LOGW(TAG, "入网失败 (0x%02x), 3秒后重试", status);
alarm_timer_schedule(zigbee_retry_commissioning, (alarm_timer_arg_t)EZB_BDB_MODE_NETWORK_STEERING, 3000);
}
break;
}
default:
ESP_LOGI(TAG, "Signal: %s (0x%02x)", ezb_app_signal_to_string(type), type);
break;
}
return true;
}
// ZCL 核心动作回调
static void esp_zigbee_zcl_core_action_handler(ezb_zcl_core_action_callback_id_t callback_id, void *message)
{
switch (callback_id)
{
// 收到 Set Attribute 请求
case EZB_ZCL_CORE_SET_ATTR_VALUE_CB_ID:
// 处理 Attribute 命令
ezb_zcl_set_attr_value_message_t *set_attr_msg = (ezb_zcl_set_attr_value_message_t *)message;
if (!set_attr_msg) return;
ESP_LOGI(TAG, "ZCL SetAttributeValue: endpoint=%d, cluster=0x%04x",
set_attr_msg->info.dst_ep, set_attr_msg->info.cluster_id);
// 只处理 On/Off 簇的命令
if (set_attr_msg->info.cluster_id == EZB_ZCL_CLUSTER_ID_ON_OFF)
{
// 检查属性 ID 是否为 OnOff 属性(0x0000)
if (set_attr_msg->in.attribute.id == EZB_ZCL_ATTR_ON_OFF_ON_OFF_ID)
{
uint8_t value = *(uint8_t *)set_attr_msg->in.attribute.data.value;
led_set(value);
printf("LED set to %d\r\n", value);
}
}
break;
// 收到默认响应(通常用于命令确认)
case EZB_ZCL_CORE_DEFAULT_RSP_CB_ID: {
ezb_zcl_cmd_default_rsp_message_t *default_rsp = (ezb_zcl_cmd_default_rsp_message_t *)message;
ESP_LOGI(TAG, "收到默认响应 with status(0x%02x)", default_rsp->in.status_code);
break;
}
default:
ESP_LOGW(TAG, "ZCL Core Action: ID(0x%04lx)", callback_id);
break;
}
}
// 创建设备端点
void create_my_device(void)
{
// 创建设备描述符
ezb_af_device_desc_t dev_desc = ezb_af_create_device_desc();
// 温度传感器端点(Endpoint 10)
ezb_zha_temperature_sensor_config_t temp_cfg = EZB_ZHA_TEMPERATURE_SENSOR_CONFIG();
// 配置测量范围:-10℃ ~ 80℃
temp_cfg.temp_meas_cfg.min_measured_value = -1000; // -10℃
temp_cfg.temp_meas_cfg.max_measured_value = 8000; // 80℃
// 温度描述符
ezb_af_ep_desc_t temp_ep = ezb_zha_create_temperature_sensor(TEMP_ENDPOINT, &temp_cfg);
ezb_af_device_add_endpoint_desc(dev_desc, temp_ep);
// On/Off 灯端点(Endpoint 11)
ezb_zha_on_off_light_config_t onoff_cfg = EZB_ZHA_ON_OFF_LIGHT_CONFIG();
// 灯描述符
ezb_af_ep_desc_t onoff_ep = ezb_zha_create_on_off_light(ONOFF_ENDPOINT, &onoff_cfg);
ezb_af_device_add_endpoint_desc(dev_desc, onoff_ep);
// 注册设备描述符到 Zigbee 栈
ESP_ERROR_CHECK(ezb_af_device_desc_register(dev_desc));
// 注册 ZCL 回调,用于接收命令
ezb_zcl_core_action_handler_register(esp_zigbee_zcl_core_action_handler);
}
zigbee.h
#ifndef ZIGBEE_H
#define ZIGBEE_H
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <stdio.h>
#include "esp_log.h"
#include "driver/gpio.h"
#include "esp_zigbee.h"
#include "ezbee/zha.h" // EZBee ZHA 设备创建 API
#include "myadc.h"
#include "led.h"
#include "alarm_timer.h"
#include "ezbee/bdb.h"
// 端点定义
#define TEMP_ENDPOINT 10 // 温度传感器端点
#define ONOFF_ENDPOINT 11 // On/Off 灯端点
extern void report_task(void *pv);
void zigbee_init(void);
void zigbee_report(int adc_data);
bool app_signal_handler(const ezb_app_signal_t *signal);
void create_my_device(void);
#endif
alarm_timer.c
#include "alarm_timer.h"
typedef struct alarm_timer_s {
esp_timer_handle_t timer;
alarm_timer_callback_t cb;
alarm_timer_arg_t arg;
} alarm_timer_t;
static void alarm_timer_cb(void *arg)
{
alarm_timer_t *alarm = (alarm_timer_t *)arg;
esp_timer_delete(alarm->timer);
alarm->cb(alarm->arg);
free(alarm);
}
esp_err_t alarm_timer_schedule(alarm_timer_callback_t cb, alarm_timer_arg_t arg, uint32_t time_ms)
{
if (cb == NULL) {
return ESP_ERR_INVALID_ARG;
}
alarm_timer_t *alarm = malloc(sizeof(alarm_timer_t));
if (alarm == NULL) {
return ESP_ERR_NO_MEM;
}
alarm->cb = cb;
alarm->arg = arg;
alarm->timer = NULL;
esp_timer_create_args_t timer_args = {
.callback = alarm_timer_cb,
.arg = alarm,
};
esp_err_t err = esp_timer_create(&timer_args, &alarm->timer);
if (err != ESP_OK) {
free(alarm);
return err;
}
uint64_t time_us = (uint64_t)time_ms * 1000;
err = esp_timer_start_once(alarm->timer, time_us);
if (err != ESP_OK) {
esp_timer_delete(alarm->timer);
free(alarm);
return err;
}
return ESP_OK;
}
alarm_timer.h
#ifndef ALARM_TIMER_H
#define ALARM_TIMER_H
#pragma once
#include <stdlib.h>
#include "esp_timer.h"
#include <stdint.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef uintptr_t alarm_timer_arg_t;
typedef void (*alarm_timer_callback_t)(alarm_timer_arg_t arg);
esp_err_t alarm_timer_schedule(alarm_timer_callback_t cb, alarm_timer_arg_t arg, uint32_t time_ms);
#ifdef __cplusplus
}
#endif
#endif // ALARM_TIMER_H
3.3 构建运行
烧录程序,在 Home Assistant 界面中,点击 Zigbee Home Automation → 添加设备,Home Assistant 会自动识别 zigbee 终端设备,完成添加。如果没有识别到,试试 ESP32c6 复位上电。
ESP32c6 调试如果是以下结果,则连接成功:

在 Zigbee Home Automation 可以看到对应的 zigbee 终端设备:


四、调试结果
温度上传 Home Assistant 界面效果:

灯光指令控制效果:
结语
本文通过一个完整的实战项目,展示了 ESP32-C6 如何快速接入 Home Assistant ZHA,实现 Zigbee 温度数据上报和 LED 远程控制。核心步骤包括:
-
Home Assistant 虚拟机部署:替换硬盘镜像,桥接网络等
-
ZBT-1 协调器配置:ZHA 集成,开箱即用
-
ESP32-C6 固件开发:Zigbee 终端设备 + 温度传感器 + On/Off 灯
由于水平有限,文中难免存在疏漏或不当之处,欢迎大家批评指正。如果你在尝试过程中遇到问题,欢迎留言交流、共同探讨。


被折叠的 条评论
为什么被折叠?



