arduino-esp32 Zigbee 模拟量输入输出实战:用 ZigbeeAnalog 类在 ESP32-C6/H2 上构建 HA 模拟量设备
2026/9/14 13:18:34 网站建设 项目流程

arduino-esp32 Zigbee 模拟量输入输出实战:用 ZigbeeAnalog 类在 ESP32-C6/H2 上构建 HA 模拟量设备

【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32

本文以仓库中的 Zigbee_Analog_Input_Output 示例 为主体,完整讲解如何在 arduino-esp32 核心上配置一台 Zigbee 从设备(End Device),使其以 Home Automation(HA)模拟量输入/输出设备身份入网上报 ADC 读数、接收网络侧的模拟量输出指令。读完本篇,你可以掌握 Arduino IDE 下 Zigbee 编译模式与分区表的选择、ZigbeeAnalog 端点类的簇配置与报告机制,以及组网失败、网络关闭等典型问题的排查手段。

一、示例概述与适用目标

该示例演示如何配置一台 Zigbee 从设备,作为 HA 模拟量输入/输出设备工作:

  • 板子上电后,示例会在本地配置为“模拟量值变化时或每 30 秒”上报一次模拟量输入;
  • 按下板上的 BOOT 按键,设备会立即把当前测量值上报到网络。

根据 README,当前示例支持的目标芯片为:

支持目标说明
ESP32-C6支持 2.4 GHz Zigbee Radio,可在boards.txt中找到ZigbeeMode.ed/ZigbeeMode.zczr编译选项
ESP32-H2支持 2.4 GHz Zigbee Radio(802.15.4),同样提供 ED / ZCZR 两种编译模式

示例目录结构如下:

  • Zigbee_Analog_Input_Output.ino:示例主体代码;
  • ci.yml:CI 编译配置,声明了fqbn_append: PartitionScheme=zigbee_zczr,ZigbeeMode=zczr并要求CONFIG_ZB_ENABLED=y,即 CI 使用 ZCZR 模式 + Zigbee ZCZR 分区表编译,这为本文第四节的编译配置提供了直接的仓库证据。

二、硬件准备与引脚配置

硬件需求很简单:

  • 一根 USB 线,同时用于供电和烧录。

示例中涉及的两个引脚在 Zigbee_Analog_Input_Output.ino 中定义,可按需修改:

uint8_t analogPin = A0; // ADC 输入引脚,默认 A0 uint8_t button = BOOT_PIN; // 按键引脚,默认 BOOT_PIN(ESP32-C6/H2 板上的 BOOT 按键)
  • analogPin:连接模拟传感器(如分压后的电位器)的 ADC 引脚,示例中将其解析度设置为 10 位(analogReadResolution(10));
  • button:使用内部上拉(INPUT_PULLUP),按下为低电平,用于触发即时上报和长按出厂复位。

官方提醒:务必使用质量可靠的 USB 线和稳定的电源,不稳定的供电是 Zigbee 入网失败最常见的原因之一。

三、Arduino IDE 编译与烧录配置

按 README 的说明,编译前需要在 IDE 中完成以下选择:

  • 选择正确的开发板:Tools -> Board
  • 选择 Zigbee 模式:Tools -> Zigbee mode: ...
  • 选择 Zigbee 分区表:Tools -> Partition Scheme: Zigbee 4MB with spiffs
  • 选择串口号:Tools -> Port: xxx
  • 可选:将Tools -> Core Debug Level设为Verbose,可以看到 Zigbee 协议栈的全部日志。

关于 Zigbee mode 的一个重要修正(以当前仓库源码为准):README 文字提到选择 “Zigbee ED (end device)”,但当前示例代码开头有一个编译期硬检查:

#ifndef ZIGBEE_MODE_ZCZR #error "Zigbee coordinator/router device mode is not selected in Tools->Zigbee mode" #endif

从 boards.txt 可见,ZigbeeMode.zczr选项才会向构建传入-DZIGBEE_MODE_ZCZR(而ZigbeeMode.ed传入的是-DZIGBEE_MODE_ED,会触发上面的#error);ci.yml 也明确使用ZigbeeMode=zczr。因此按当前代码实际编译时,应选择Zigbee ZCZR (coordinator/router)模式——该模式同时支持协调器/路由器角色。

运行时角色:示例在setup()中调用Zigbee.begin(ZIGBEE_ROUTER),即以路由器角色入网。角色枚举定义在 ZigbeeCore.h:ZIGBEE_COORDINATOR = 0ZIGBEE_ROUTER = 1ZIGBEE_END_DEVICE = 2

分区表:README 要求选择Zigbee 4MB with spiffsboards.txt中 C6/H2 还提供了Zigbee 2MB with spiffsZigbee 8MB with spiffs以及面向协调器/路由器的Zigbee ZCZR 4MB with spiffsPartitionScheme.zigbee_zczr)等变体。由于本示例以 ZCZR 模式编译,建议与 CI 保持一致,选择Zigbee ZCZR 4MB with spiffs;若你的板子是 2MB/8MB Flash,再按容量切换。

四、示例源码逐段精讲

4.1 端点定义:一个“模拟量输入+输出”端点与三个纯输入端点

示例注册了 4 个ZigbeeAnalog端点,端点号从 1 开始连续分配(源码):

#define ANALOG_DEVICE_ENDPOINT_NUMBER 1 ZigbeeAnalog zbAnalogDevice = ZigbeeAnalog(ANALOG_DEVICE_ENDPOINT_NUMBER); // EP1: 输入+输出 ZigbeeAnalog zbAnalogTemp = ZigbeeAnalog(ANALOG_DEVICE_ENDPOINT_NUMBER + 1); // EP2: 温度 ZigbeeAnalog zbAnalogFan = ZigbeeAnalog(ANALOG_DEVICE_ENDPOINT_NUMBER + 2); // EP3: 转速 ZigbeeAnalog zbAnalogPercent= ZigbeeAnalog(ANALOG_DEVICE_ENDPOINT_NUMBER + 3); // EP4: 百分比

这种“同一 ADC 采样值拆分成多个不同应用类型的端点”的写法,是为了让 HA 控制器能分别识别为不同的物理量。setup()中的完整配置如下:

void setup() { Serial.begin(115200); pinMode(button, INPUT_PULLUP); analogReadResolution(10); // ADC 解析度设为 10 位 // EP1:模拟输入 = 功耗(瓦特),模拟输出 = 风扇转速(RPM) zbAnalogDevice.setManufacturerAndModel("Espressif", "ZigbeeAnalogDevice"); zbAnalogDevice.addAnalogInput(); zbAnalogDevice.setAnalogInputApplication(ESP_ZB_ZCL_AI_POWER_IN_WATTS_CONSUMPTION); zbAnalogDevice.setAnalogInputDescription("Power Consumption (Watts)"); zbAnalogDevice.setAnalogInputResolution(0.01); zbAnalogDevice.addAnalogOutput(); zbAnalogDevice.setAnalogOutputApplication(ESP_ZB_ZCL_AI_RPM_OTHER); zbAnalogDevice.setAnalogOutputDescription("Fan Speed (RPM)"); zbAnalogDevice.setAnalogOutputResolution(1); // HA 会用 Min/Max PresentValue 属性限制模拟输出的取值范围 zbAnalogDevice.setAnalogOutputMinMax(-10000, 10000); // -10000 ~ 10000 RPM // 网络侧修改模拟输出值时的本地回调 zbAnalogDevice.onAnalogOutputChange(onAnalogOutputChange); // EP2:温度 zbAnalogTemp.addAnalogInput(); zbAnalogTemp.setAnalogInputApplication(ESP_ZB_ZCL_AI_TEMPERATURE_OTHER); zbAnalogTemp.setAnalogInputDescription("Temperature"); zbAnalogTemp.setAnalogInputResolution(0.1); // EP3:转速 zbAnalogFan.addAnalogInput(); zbAnalogFan.setAnalogInputApplication(ESP_ZB_ZCL_AI_RPM_OTHER); zbAnalogFan.setAnalogInputDescription("RPM"); zbAnalogFan.setAnalogInputResolution(1); // EP4:百分比 zbAnalogPercent.addAnalogInput(); zbAnalogPercent.setAnalogInputApplication(ESP_ZB_ZCL_AI_PERCENTAGE_OTHER); zbAnalogPercent.setAnalogInputDescription("Percentage"); zbAnalogPercent.setAnalogInputResolution(0.01); // 把 4 个端点注册进 Zigbee 核心 Zigbee.addEndpoint(&zbAnalogDevice); Zigbee.addEndpoint(&zbAnalogTemp); Zigbee.addEndpoint(&zbAnalogFan); Zigbee.addEndpoint(&zbAnalogPercent); // 所有端点注册完毕后启动协议栈(路由器角色) if (!Zigbee.begin(ZIGBEE_ROUTER)) { Serial.println("Zigbee failed to start!"); ESP.restart(); } // 阻塞等待入网 while (!Zigbee.connected()) { Serial.print("."); delay(100); } // 配置自动报告:最小间隔 0s、最大间隔 30s、变化量阈值 10 zbAnalogDevice.setAnalogInputReporting(0, 30, 10); }

几点说明:

  • setAnalogInputApplication/setAnalogOutputApplication传入的是 ZCL 规范中的“应用类型”(Application Type)常量,例如ESP_ZB_ZCL_AI_POWER_IN_WATTS_CONSUMPTIONESP_ZB_ZCL_AI_TEMPERATURE_OTHERESP_ZB_ZCL_AI_RPM_OTHERESP_ZB_ZCL_AI_PERCENTAGE_OTHER,完整取值可查阅 ESP-IDF 的esp_zigbee_zcl_analog_input.h/esp_zigbee_zcl_analog_output.h
  • setAnalogInputResolution告诉协调器/控制器“本设备的量化步长”,示例中功耗端点精度 0.01 W、温度端点 0.1、转速端点 1;
  • setAnalogOutputMinMax(-10000, 10000)用于向 HA 声明输出量的合法范围,控制器据此做限幅;
  • setAnalogInputReporting(min_interval, max_interval, delta)配置 ZCL Report Attributes 机制:值变化超过delta(10)即可在min_intervalmax_interval(0~30 秒)窗口内自动上报,这正是 README 中“值变化时或每 30 秒上报一次”的实现来源。

4.2 主循环:周期采样上报与按键交互

void onAnalogOutputChange(float analog_output) { Serial.printf("Received analog output change: %.1f\r\n", analog_output); } void loop() { static uint32_t timeCounter = 0; // 每 2s(100ms x 20)读一次 ADC,更新 4 个端点的模拟量并主动上报 if (!(timeCounter++ % 20)) { float analog = (float)analogRead(analogPin); Serial.printf("Updating analog input to %.1f\r\n", analog); zbAnalogDevice.setAnalogInput(analog); zbAnalogTemp.setAnalogInput(analog / 100); zbAnalogFan.setAnalogInput(analog); zbAnalogPercent.setAnalogInput(analog / 10); // 主动触发 Report Attributes zbAnalogDevice.reportAnalogInput(); zbAnalogTemp.reportAnalogInput(); zbAnalogFan.reportAnalogInput(); zbAnalogPercent.reportAnalogInput(); } // 按键处理 if (digitalRead(button) == LOW) { delay(100); // 消抖 int startTime = millis(); while (digitalRead(button) == LOW) { delay(50); if ((millis() - startTime) > 3000) { // 长按超过 3 秒:Zigbee 出厂复位并重启 Serial.println("Resetting Zigbee to factory and rebooting in 1s."); delay(1000); Zigbee.factoryReset(); } } // 短按演示:模拟输出值 +100 并立即上报 zbAnalogDevice.setAnalogOutput(zbAnalogDevice.getAnalogOutput() + 100); zbAnalogDevice.reportAnalogOutput(); } delay(100); }

行为归纳:

  • 每 2 秒:读一次 ADC,同时更新 4 个端点的 PresentValue(其中 EP2/EP4 做了/100/10的缩放),并对每个端点调用reportAnalogInput()主动发送一次报告;
  • 短按 BOOT:模拟输出当前值 +100 并reportAnalogOutput(),演示“设备侧主动改变模拟输出并上报”;
  • 长按 BOOT 超过 3 秒:调用Zigbee.factoryReset()擦除 Zigbee 网络凭证并重启,便于设备重新入网。

五、ZigbeeAnalog 类源码深度解析

头文件声明的公开接口与 实现 是理解示例行为的钥匙。

5.1 构造函数:HA Simple Sensor 设备身份

ZigbeeAnalog::ZigbeeAnalog(uint8_t endpoint) : ZigbeeEP(endpoint) { _device_id = ESP_ZB_HA_SIMPLE_SENSOR_DEVICE_ID; _on_analog_output_change = nullptr; _cluster_list = esp_zb_zcl_cluster_list_create(); esp_zb_cluster_list_add_basic_cluster(_cluster_list, esp_zb_basic_cluster_create(NULL), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); esp_zb_cluster_list_add_identify_cluster(_cluster_list, esp_zb_identify_cluster_create(NULL), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); _ep_config = {.endpoint = _endpoint, .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, .app_device_id = ESP_ZB_HA_SIMPLE_SENSOR_DEVICE_ID, .app_device_version = 0}; }

(ZigbeeAnalog.cpp#L19-L29)

可以看到:每个ZigbeeAnalog端点默认携带BasicIdentify两个服务器簇,设备类型固定为 HA 设备库中的ESP_ZB_HA_SIMPLE_SENSOR_DEVICE_ID(Simple Sensor Device),应用轮廓为ESP_ZB_AF_HA_PROFILE_ID(HA Profile)。这就是为什么 HA 网关会把它识别为“模拟量传感器类”设备的原因。

5.2 模拟输入/输出簇的默认属性

addAnalogInput()(L31-L80)创建 Analog Input 簇时写入一组默认属性,随后示例中再逐项覆盖:

属性默认值(簇创建时)示例中覆盖为
Description"Analog Input"(ZCL 字符串,首字节为长度 0x0C)"Power Consumption (Watts)"
Application TypeGroup 位为ESP_ZB_ZCL_AI_GROUP_ID,类型 0ESP_ZB_ZCL_AI_POWER_IN_WATTS_CONSUMPTION
Resolution0.10.01 / 0.1 / 1
Min/Max Present Value-FLT_MAX/FLT_MAX(近似全浮点范围)可通过setAnalogInputMinMax覆盖

addAnalogOutput()(L102-L151)逻辑对称,默认 Resolution 为 1。setAnalogInputApplication/setAnalogOutputApplication会把应用类型的高字节(Group 位)自动补上:(ESP_ZB_ZCL_AI_GROUP_ID << 24) | application_type,因此调用方只需传入具体类型常量。

描述字符串有长度限制:setAnalogInputDescription/setAnalogOutputDescription内部按 ZCL 字符串格式编码(首字节存长度),超过ZB_MAX_NAME_LENGTH会直接返回false并打Description is too long日志(L282-L320)。

5.3 值更新与报告:setAnalogInput / reportAnalogInput

setAnalogInput(float)把参数写入 Analog Input 簇的PresentValue属性(L195-L208),而reportAnalogInput()(L225-L242)构造一条 ZCLReport Attributes命令:

report_attr_cmd.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; // 不发往指定目标,走绑定表 report_attr_cmd.attributeID = ESP_ZB_ZCL_ATTR_ANALOG_INPUT_PRESENT_VALUE_ID; report_attr_cmd.direction = ESP_ZB_ZCL_CMD_DIRECTION_TO_CLI; // 服务器 -> 客户端 report_attr_cmd.clusterID = ESP_ZB_ZCL_CLUSTER_ID_ANALOG_INPUT; report_attr_cmd.zcl_basic_cmd.src_endpoint = _endpoint;

两个细节值得注意:

  1. address_modeDST_ADDR_ENDP_NOT_PRESENT,说明报告不指定单播目标,而是依靠 APS 绑定表把报告投给绑定的协调器/控制器端点;
  2. 若从未配置过setAnalogInputReporting,控制器侧就没有对应的 Reporting Configuration,主动报告与自动报告都会缺少接收方,这正是示例在入网成功后才调用setAnalogInputReporting(0, 30, 10)的原因。

setAnalogInputReporting(L263-L280)把min_intervalmax_intervaldelta填入esp_zb_zcl_reporting_info_t后调用setClusterReportingdelta以单精度浮点(delta.s32)传递,与setAnalogInputResolution声明的量化精度相配合。

5.4 模拟输出回调:zbAttributeSet 的覆盖机制

当网络侧(如 HA 面板)向 Analog Output 簇写入新的PresentValue时,ZCL 消息分发到端点的zbAttributeSet虚函数。ZigbeeAnalog重写了它(L174-L193):

void ZigbeeAnalog::zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) { if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_ANALOG_OUTPUT) { if (message->attribute.id == ESP_ZB_ZCL_ATTR_ANALOG_OUTPUT_PRESENT_VALUE_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_SINGLE) { _output_state = *(float *)message->attribute.data.value; analogOutputChanged(); // 触发用户回调 } } }

analogOutputChanged()调用onAnalogOutputChange注册的用户回调(示例中打印到串口),未注册时仅打印警告。注意setAnalogOutput本地改值时也会同步_output_state并触发同一回调,因此回调中不要做阻塞操作。getAnalogOutput()返回缓存的_output_state,示例短按按键时就用它做 +100 的增量修改。

以上类均通过 Zigbee.h 统一导出(#include "ep/ZigbeeAnalog.h"),该头文件同时汇总了库提供的全部端点类(开关、灯、传感器、网关、中继器等),ZigbeeAnalog位于“Sensors”分组。

六、组网管理:入网等待、开放网络与出厂复位

示例使用Zigbee.connected()阻塞等待入网完成。协调器/网关侧的配套操作在 README 与 ZigbeeCore 中都有对应 API:

  • 默认情况下,协调器网络在重启或刷写新固件后是关闭的,从设备无法加入。README 给出两种打开方式:
    • 重启后自动开放:在协调器setup()中、Zigbee.begin()之前调用Zigbee.setRebootOpenNetwork(time);time为开放时长);
    • 运行时随时开放:Zigbee.openNetwork(time);
    • 需要时也可用Zigbee.closeNetwork()关闭。
  • 从设备连不上协调器:刷写示例前先擦除从设备 Flash,尤其是重新刷写过协调器之后。做法二选一:
    • Arduino IDE 的Tools菜单中把Erase All Flash Before Sketch Upload设为Enabled
    • 或在代码里调用Zigbee.factoryReset();(头文件签名factoryReset(bool restart = true),默认复位后自动重启),示例正是用它实现 BOOT 长按出厂复位。

此外,若组网困难可检查协调器侧的扫描参数,ZigbeeCore提供了setPrimaryChannelMask(默认扫描全部 11~26 信道,掩码0x07FFF800)与setScanDuration(1 最快 ~ 4 最慢)等接口(ZigbeeCore.h#L169-L174)。

七、故障排查(README Troubleshooting 全解)

现象排查手段
从设备无法连接协调器先擦除从设备 Flash 再刷写(IDE 开启Erase All Flash Before Sketch Upload,或代码中Zigbee.factoryReset(););重新刷写过协调器时尤应如此
协调器重启后从设备无法再入网网络默认关闭:用Zigbee.setRebootOpenNetwork(time);begin()之前)或运行时Zigbee.openNetwork(time);开放网络
LED 不闪检查接线与 IO 选择是否正确
烧录失败(Programming Fail)降低串口波特率重试
COM 口识别不到检查 USB 线与 USB 转串口驱动安装
供电不稳换用优质 USB 线与稳定电源(官方特别强调)

若设置Tools -> Core Debug Level: Verbose后仍无法定位问题,可参考仓库的 CONTRIBUTING.md 参与讨论或提交 Issue。

八、小结与延伸阅读

本示例展示了 arduino-esp32 Zigbee 库的标准开发闭环:选对编译模式与分区表 → 用端点类声明簇与属性 → 入网后配置报告 → 主循环中读传感器/响应按键并主动报告 → 用出厂复位与开放网络接口运维组网。核心文件索引:

  • 示例说明与排障:libraries/Zigbee/examples/Zigbee_Analog_Input_Output/README.md
  • 示例代码:libraries/Zigbee/examples/Zigbee_Analog_Input_Output/Zigbee_Analog_Input_Output.ino
  • 端点类接口:libraries/Zigbee/src/ep/ZigbeeAnalog.h、libraries/Zigbee/src/ep/ZigbeeAnalog.cpp
  • 核心库与网络接口:libraries/Zigbee/src/ZigbeeCore.h、libraries/Zigbee/src/Zigbee.h
  • 编译选项(Zigbee mode / 分区表):boards.txt

需要提醒的适用前提:本文所有配置以当前仓库代码为准——示例要求 ZCZR 编译模式 + ZCZR 分区表、运行时以ZIGBEE_ROUTER角色入网;README 中“Zigbee ED (end device)”的表述与代码编译守卫不一致,请以代码与ci.ymlZigbeeMode=zczr配置为准。若后续需要真正的电池供电 End Device 角色,可参考ZigbeeCore::begin(zigbee_role_t role, bool erase_nvs)ZIGBEE_END_DEVICE选项,并结合ZigbeeCore.h中的 ED 电池模式相关配置项做调整。

【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询