arduino-esp32 Zigbee 可调光灯具示例:将 ESP32-C6/H2 打造为 HA Dimmable Light 终端设备

arduino-esp32 Zigbee 可调光灯具示例:将 ESP32-C6/H2 打造为 HA Dimmable Light 终端设备 arduino-esp32 Zigbee 可调光灯具示例将 ESP32-C6/H2 打造为 HA Dimmable Light 终端设备【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32本文围绕 arduino-esp32 仓库中的 Zigbee_Dimmable_Light 示例 展开讲解如何在 Arduino IDE 中把 ESP32-C6 或 ESP32-H2 配置为 Zigbee 终端设备ED并实现一个符合 Home AutomationHA标准的可调光灯泡。读完本文你将掌握如何在 Arduino IDE 中正确选择 Zigbee 模式与分区方案、如何通过ZigbeeDimmableLight端点类接收协调器的 On/Off 与 Level 控制指令、如何用回调驱动 LED 输出以及设备入网失败时factoryReset、openNetwork、setRebootOpenNetwork等排障手段的用法。1. 示例目标与运行前提该示例的目标是把一块 ESP32-C6 或 ESP32-H2 开发板烧录后作为 Zigbee终端设备End Device, ED使用在 Zigbee 网络中扮演一盏 HA 标准的“可调光灯泡”——由协调器或 Z2M/ZHA 等网关下发开关与调光指令设备点亮或调节板载 RGB LED 亮度。1.1 支持的开发板支持目标ESP32-C6ESP32-H2支持情况是是对应的芯片数据资料可参考仓库中 boards.txt 与变体定义其中 variants/esp32c6/pins_arduino.h 定义了示例默认使用的RGB_BUILTIN引脚// RGB_BUILTIN and RGB_BRIGHTNESS can be used in new Arduino API rgbLedWrite() #define RGB_BUILTIN LED_BUILTIN1.2 硬件要求用于供电和烧录的 USB 数据线务必使用质量可靠的线供电不稳是 Zigbee 入网失败的高频原因一块运行 Zigbee_Dimmable_Light.ino 的 ESP32-H2 或 ESP32-C6 开发板作为 Zigbee 终端设备一个 Zigbee 网络/协调器可以是另一块烧录了 Zigbee 开关示例的开发板也可以是 Zigbee2MQTT、Zigbee Home Assistant 等网关应用。1.3 编译期前提Zigbee 模式与分区方案示例源码开头有编译期断言Zigbee 模式没有选对会直接编译失败#include Arduino.h #ifndef ZIGBEE_MODE_ED #error Zigbee end device mode is not selected in Tools-Zigbee mode #endif示例附带的 CI 配置 ci.yml 精确给出了这两个约束可以作为自检清单fqbn_append: PartitionSchemezigbee,ZigbeeModeed requires: - CONFIG_SOC_IEEE802154_SUPPORTEDy - CONFIG_ZB_ENABLEDy也就是说板级必须支持 IEEE 802.154 射频CONFIG_SOC_IEEE802154_SUPPORTEDy、工具链必须启用 Zigbee 栈CONFIG_ZB_ENABLEDy并且 FQBN 中必须追加PartitionSchemezigbee与ZigbeeModeed两个构建选项——这正是 IDE 中两个下拉菜单背后的实际含义。2. Arduino IDE 配置步骤按官方文档README.md的步骤在 Arduino IDE 中完成以下配置选择正确的开发板Tools - Board选择你的 ESP32-C6 或 ESP32-H2 开发板选择 Zigbee 终端设备模式Tools - Zigbee mode: Zigbee ED (end device)——这一步会定义ZIGBEE_MODE_ED宏是示例能编译通过的前提选择 Zigbee 专用分区方案Tools - Partition Scheme: Zigbee 4MB with spiffs——Zigbee 栈的 NVS 配置需要独立的分区布局用默认分区会导致入网状态无法保存选择串口Tools - Port: xxxxxx 为检测到的 COM 端口可选开启详细日志Tools - Core Debug Level: Verbose可以看到完整的 Zigbee 栈日志对入网排障非常有用。此外如果 LED 不是接在板载 RGB LED 上可通过修改示例中的LED_PIN定义对应源码中的led变量更换引脚默认值是RGB_BUILTIN。3. 示例代码逐段剖析下面是 Zigbee_Dimmable_Light.ino 的完整核心实现省略许可证头随后逐段讲解。3.1 设备定义与端点配置#include Zigbee.h /* Zigbee dimmable light configuration */ #define ZIGBEE_LIGHT_ENDPOINT 10 uint8_t led RGB_BUILTIN; uint8_t button BOOT_PIN; ZigbeeDimmableLight zbDimmableLight ZigbeeDimmableLight(ZIGBEE_LIGHT_ENDPOINT);ZIGBEE_LIGHT_ENDPOINT值为 10是设备在 Zigbee 网络中的端点号协调器侧的绑定关系都基于该端点led使用板载 RGB LEDbutton使用 BOOT 按键——后者承担两个本地交互功能短按手动调光、长按 3 秒触发工厂复位ZigbeeDimmableLight是仓库中封装好的 HA 可调光灯具端点类头文件位于 ZigbeeDimmableLight.h。从源码结构看它构造时会把_device_id设为ESP_ZB_HA_DIMMABLE_LIGHT_DEVICE_ID端点配置使用 HA ProfileESP_ZB_AF_HA_PROFILE_ID并初始化六个标准集群见 4.1 节。3.2 LED 输出与 Identify 回调/********************* RGB LED functions **************************/ void setLight(bool state, uint8_t level) { if (!state) { rgbLedWrite(led, 0, 0, 0); return; } rgbLedWrite(led, level, level, level); } // Create a task on identify call to handle the identify function void identify(uint16_t time) { static uint8_t blink 1; log_d(Identify called for %u seconds, time); if (time 0) { // If identify time is 0, stop blinking and restore light as it was used for identify zbDimmableLight.restoreLight(); return; } rgbLedWrite(led, 255 * blink, 255 * blink, 255 * blink); blink !blink; }setLight是灯光变更回调状态为关时直接熄灭为开时以level0~255同时写入 RGB 三通道实现灰度调光identify是 Zigbee Identify 集群的标准行为协调器在发现/绑定设备后会下发 Identify 命令设备以闪烁灯光响应time 0表示识别结束此时调用zbDimmableLight.restoreLight()恢复灯光到当前真实状态。3.3 setup()注册回调、注册端点并启动 Zigbeevoid setup() { Serial.begin(115200); // Init RMT and leave light OFF rgbLedWrite(led, 0, 0, 0); // Init button for factory reset pinMode(button, INPUT_PULLUP); // Set callback function for light change zbDimmableLight.onLightChange(setLight); // Optional: Set callback function for device identify zbDimmableLight.onIdentify(identify); // Optional: Set Zigbee device name and model zbDimmableLight.setManufacturerAndModel(Espressif, ZBLightBulb); // Add endpoint to Zigbee Core Serial.println(Adding ZigbeeLight endpoint to Zigbee Core); Zigbee.addEndpoint(zbDimmableLight); // When all EPs are registered, start Zigbee in End Device mode if (!Zigbee.begin()) { Serial.println(Zigbee failed to start!); Serial.println(Rebooting...); ESP.restart(); } Serial.println(Connecting to network); while (!Zigbee.connected()) { Serial.print(.); delay(100); } Serial.println(); }关键调用链如下onLightChange(setLight)注册灯光变更回调此后凡是 On/Off 或 Level 属性被远程更新本地 API 修改也会触发都会以(state, level)参数调用setLightonIdentify(identify)注册 Identify 回调可选setManufacturerAndModel(Espressif, ZBLightBulb)写入 Basic 集群的厂商/型号字符串会在 Z2M/ZHA 的设备列表中以该名称显示便于识别设备Zigbee.addEndpoint(zbDimmableLight)把端点注册进 Zigbee 核心。注意端点对象必须在begin()之前注册且必须是生命周期覆盖整个程序运行期的对象Zigbee.begin()以终端设备角色启动 Zigbee 栈。从 ZigbeeCore.h 的声明看签名为bool begin(zigbee_role_t role ZIGBEE_END_DEVICE, bool erase_nvs false)——默认角色即 ED第二个参数可用于擦除 NVS 中的 Zigbee 网络配置这与下文排障中的“擦除 flash”是同一思路的代码级替代while (!Zigbee.connected())阻塞等待入网成功。如果一直停在打点状态说明设备找不到网络或网络未开放加入需要参考第 5 节的排障手段。3.4 loop()按键调光与长按工厂复位void loop() { // Checking button for factory reset if (digitalRead(button) LOW) { // Push button pressed // Key debounce handling delay(100); int startTime millis(); while (digitalRead(button) LOW) { delay(50); if ((millis() - startTime) 3000) { // If key pressed for more than 3secs, factory reset Zigbee and reboot Serial.println(Resetting Zigbee to factory and rebooting in 1s.); delay(1000); Zigbee.factoryReset(); } } // Increase blightness by 50 every time the button is pressed zbDimmableLight.setLightLevel(zbDimmableLight.getLightLevel() 50); } delay(100); }这段逻辑实现了两种按键语义长按超过 3 秒调用Zigbee.factoryReset()。从 ZigbeeCore.h 声明看其签名为void factoryReset(bool restart true)默认会清除 Zigbee 栈的网络状态并重启设备——设备随后会以全新身份重新尝试入网短按松手且未满 3 秒在循环中执行setLightLevel(getLightLevel() 50)每次点亮度 50内部自动钳位到 0~255 的 ZCL 有效范围方便在无协调器侧界面前本地验证调光链路是否打通。4. 源码纵深ZigbeeDimmableLight 端点类是怎么工作的上面的示例之所以只有百余行是因为协议细节全部收敛在 ZigbeeDimmableLight.h 和 ZigbeeDimmableLight.cpp 中。4.1 端点承载的六个 ZCL 集群构造函数通过ZIGBEE_DEFAULT_DIMMABLE_LIGHT_CONFIG()宏生成默认配置并在zigbee_dimmable_light_clusters_create()中一次性创建六个集群全部以Server 角色加入端点集群配置文件段作用Basicbasic_cfg厂商/型号、ZCL 版本、电源类型等设备基础信息Identifyidentify_cfg协调器下发的识别闪烁即示例中的identify回调来源Groupsgroups_cfg分组控制支持Scenesscenes_cfg场景存储与调用On/Offon_off_cfg开关状态BoolLevel Controllevel_cfg当前亮度等级0~255这正是一盏 HA 标准可调光灯所要求的最小集群集合。从源码结构看这些集群配置之所以定义在 arduino-esp32 的头文件里是因为底层 ESP Zigbee 库未直接提供该设备的组合封装Arduino 层补齐了这层“标准设备模板”。4.2 远程指令如何变成灯光动作协调器或 Z2M/ZHA下发的属性更新最终进入端点类重写的zbAttributeSet()void ZigbeeDimmableLight::zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) { if (message-info.cluster ESP_ZB_ZCL_CLUSTER_ID_ON_OFF) { if (message-attribute.id ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID message-attribute.data.type ESP_ZB_ZCL_ATTR_TYPE_BOOL) { if (_current_state ! *(bool *)message-attribute.data.value) { _current_state *(bool *)message-attribute.data.value; lightChanged(); } return; } ... } else if (message-info.cluster ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL) { if (message-attribute.id ESP_ZB_ZCL_ATTR_LEVEL_CONTROL_CURRENT_LEVEL_ID message-attribute.data.type ESP_ZB_ZCL_ATTR_TYPE_U8) { if (_current_level ! *(uint8_t *)message-attribute.data.value) { _current_level *(uint8_t *)message-attribute.data.value; lightChanged(); } ... } } }处理路径清晰可见ZCL 属性消息 → 按集群 ID 属性 ID 分发 → 与本地缓存_current_state/_current_level比较 → 变化时调用lightChanged()→ 触发用户注册的_on_light_change回调示例中的setLight。属性 ID 不匹配或集群不支持时会以log_w打印“Received message ignored”开启 Verbose 日志时可直接观察到。4.3 本地 API 修改灯光状态setLightState()/setLightLevel()并非只改本地 LED而是同时回写 ZCL 属性bool ZigbeeDimmableLight::setLight(bool state, uint8_t level) { _current_state state; _current_level level; lightChanged(); // 立即驱动本地回调 ret setClusterAttribute(ESP_ZB_ZCL_CLUSTER_ID_ON_OFF, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID, _current_state, false); ... ret setClusterAttribute(ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_LEVEL_CONTROL_CURRENT_LEVEL_ID, _current_level, false); ... }因此 3.4 节中 BOOT 按键短按 50 亮度时网关侧Z2M/ZHA读到的属性也会同步变化设备状态在本地与网络两侧保持一致。此外还有两个便捷方法getLightState()返回_current_stategetLightLevel()返回_current_level构造时的默认值是“关、亮度 255”见 ZigbeeDimmableLight.cpp 构造函数中_current_state false; _current_level 255;。端点基类 ZigbeeEP.h 则提供了onIdentify、setManufacturerAndModel、绑定管理、OTA 客户端等通用能力ZigbeeDimmableLight作为其子类只补齐了灯光相关逻辑。5. 排障官方 Troubleshooting 全解5.1 终端设备无法连接到协调器按 README.md 的建议烧录前擦除终端设备的整个 flash如果重新烧录过协调器也建议对终端设备执行同样操作。两种做法Arduino IDE 中Tools - Erase All Flash Before Sketch Upload设为Enabled代码中调用Zigbee.factoryReset();重置设备与 Zigbee 栈示例中即用它处理按键长按场景。从 ZigbeeCore.h 看begin()的第二个参数erase_nvs同样指向这一目的——Zigbee 网络配置保存在 NVS 分区中残留的旧网络信息尤其是协调器侧已重建网络后会让终端设备反复尝试加入一个已不存在的网络。5.2 协调器网络默认关闭加入默认情况下协调器重启或烧录新固件后网络是关闭的。终端设备入网失败且自身日志无异常时应优先检查协调器是否开放了加入两个开放手段重启后自动开放在调用Zigbee.begin();之前设置Zigbee.setRebootOpenNetwork(time);time为允许加入的秒数运行中随时开放在应用任意时刻调用Zigbee.openNetwork(time);打开网络一段时间供设备加入。两者在 ZigbeeCore.h 中的声明均为void ...(uint8_t time)参数含义为开放网络的时长秒。5.3 其他常见问题官方文档同时给出了硬件与烧录层面的排查清单LED 不闪检查接线与 IO 选择确认LED_PIN/led变量与实际硬件一致烧录失败Programming Fail尝试降低串口连接速度COM 口未被识别检查 USB 线与 USB 转串口驱动安装情况若错误依旧可到 ESP32 官方论坛求助或按仓库的 CONTRIBUTING.md 流程提交 issue / PR。6. 小结与延伸阅读本文以 Zigbee_Dimmable_Light 示例 为主线完整走通了“IDE 配置Zigbee ED 模式 zigbee 分区方案→ 端点注册与回调绑定 →Zigbee.begin()入网 → 按键本地调光/长按工厂复位 → 入网失败排障”的全流程并下探到 ZigbeeDimmableLight.cpp 的集群装配与zbAttributeSet分发机制说明了“协调器指令 → ZCL 属性 → 灯光回调”这一核心数据通路。仓库中还有大量同族端点可作参考同目录下的 Zigbee_On_Off_Light、Zigbee_Color_Dimmable_Light以及 Zigbee.h 中列出的开关、传感器、插座、窗帘等 30 余种标准设备封装配合 ZigbeeCore.h 的 API 可组合出完整的 Zigbee 家居网络。【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考