先做自己的协调器,不从“兼容所有网关”开始
本任务需要一个真正的协调器,所以给两块同款C6分别烧入同工程的coordinator与end-device角色。协调器建网并注册EP1温湿度client,终端EP10用HA profile0x0104/Temperature Sensor0x0302加湿度server。HA在这里是Zigbee Home Automation,不是Home Assistant集成。
官网SDK有标准cluster并不等于本站完成认证、手机或商业网关互通。本文不需要HA、USB协调棒或Zigbee2MQTT;本站只实际构建了两个角色,所有入网/报告/离网检查仍是待实板执行的步骤。
SHT40只接终端,GPIO10按钮两板各一套
两块固定C6-DevKitC-1-N8/WROOM-1 PCB天线,8MB Flash无PSRAM;官方指南标题v1.2,但后期PW-2024-03-0595或0921及以后对应V1.4电路图。不能把指南版本误当PCB电路版本。分别贴COORD和SHT标签,避免将错误角色刷入另一板。
PID4885必须已经可靠焊接排针,未焊请由有条件者处理,松插排针不能当电气连接。用3V3供VIN,保持SDA/SCL的上拉在3.3V域。每块GPIO10各接10k到本板3V3、常开按钮到本板GND;两板之间不拉I²C线。
各自只接USB-UART桥供电,保留J5,桥是UART16/17;原生USB12/13、BOOT9、RGB8不使用。USB类与5V/3V3排针供电互斥。必须断电接线;运行故障注入若需要开断SDA,先断电预装信号开关。
| 终端C6端点 | PID4885/按钮端点 |
|---|---|
| J1-1 3V3 / J1-15 GND | VIN / GND |
| J1-5 GPIO6 | SDA;0x44/100kHz |
| J1-6 GPIO7 | SCL;PID4885板上上拉,内部上拉关闭 |
| 两板各自J1-10 GPIO10 | 各自10k上拉到3V3,按钮到GND;不碰GPIO8/9启动配置脚 |
参考:[1] C6官方指南v1.2:N8、J1/J3、V1.4/PW修订和供电互斥[2] C6电路V1.4:p2 GPIO6/7/10、UART/USB、J5[6] Adafruit PID4885具体模块/连接说明[7] Adafruit模块pinouts:VIN随逻辑电压、10k上拉与0x44[8] SHT4x V6.4 p3/10–12:0xFD、CRC和公式
明确2.0.5的EZB接口和两个角色配置
锁原生esp-zigbee-lib2.0.5/component_hash84562c41…,不是Arduino库,也不把旧ZBOSS1.x接口拼进去。其固定提交README推荐IDF5.5.4,组件manifest允许IDF>=5.0;本包复用5.5.5,两个角色真实编译通过,准确记录这个补丁版映射。
两角色使用不同SDKCONFIG目录:终端CONFIG_ZB_ZED,协调器CONFIG_ZB_ZCZR并在运行时选COORDINATOR/max_children4。自定义分区明确给nvs、phy、factory1.5MB与zb_fct1KB,其余8MB留空,不把未定义空间当可用文件系统。NVS错误不会自动erase,因为那会丢网络保存。
锁channel15、集中式安全、install_code_policy=false。这个可信台架的物理短窗口用来理解入网,不承诺生产认证、安装码策略或跨厂商兼容。组件包顶层Apache2.0/LICENSE、registry Custom标签及专有栈说明各自保留,本站MIT不替代依赖许可。
idf.py -B build-coord -DSDKCONFIG="$PWD/build-coord/sdkconfig" '-DSDKCONFIG_DEFAULTS=sdkconfig.defaults;sdkconfig.coordinator' -DW09_COORDINATOR=1 build
idf.py -B build-sensor -DSDKCONFIG="$PWD/build-sensor/sdkconfig" -DSDKCONFIG_DEFAULTS=sdkconfig.defaults -DW09_COORDINATOR=0 build
# 给对应命令加 -p 对应UART端口 flash monitor;两块板分别操作。参考:[3] Zigbee2.0.5固定提交/README版本映射[4] 2.0.5 bdb.h:open/close/local reset明确边界
完整应用:采样不能阻塞Zigbee主循环
下面main.c和w09_model.h与下载原文件逐字一致。Zigbee任务运行stack mainloop,普通应用循环采按键和I²C;在普通任务调用SDK前获取Zigbee锁,在SDK回调中不再次取锁。I²C等待发生在应用侧,不把6B读传感器塞进网络回调。
按键使用30ms稳定判定、3s单次长按、64位单调时间;上电按住必须先稳定释放才解锁,不会一上电就抹网络。报告接收回调里的value属于栈,只在声明的16位类型下复制后输出。配置/内存/注册等必要操作失败明确停止,不靠可编译成空的assert隐藏注册调用。
// ---- main/w09_model.h ----
// SPDX-License-Identifier: MIT
#pragma once
#include <stdbool.h>
#include <stdint.h>
// Microseconds from esp_timer_get_time(), not a wrapping millis() counter.
typedef struct { bool raw,stable,armed,down,long_sent; uint64_t edge,pressed; } W09Button;
enum { W09_NONE, W09_SHORT, W09_LONG };
static inline int w09_button(W09Button *b,bool low,uint64_t now) {
if (low!=b->raw) { b->raw=low;b->edge=now; }
if (now-b->edge<30000) return W09_NONE;
if (low!=b->stable) {
b->stable=low;
if (!low) {
const bool click=b->armed && b->down && !b->long_sent;
b->armed=true;b->down=false;b->long_sent=false;
return click?W09_SHORT:W09_NONE;
}
if (b->armed) { b->down=true;b->pressed=now;b->long_sent=false; }
}
// A stable released level arms input even if the initial raw/stable values were both false.
if (!low) b->armed=true;
if (b->down && !b->long_sent && now-b->pressed>=3000000) {
b->long_sent=true;return W09_LONG;
}
return W09_NONE;
}
static inline uint8_t w09_crc(const uint8_t *p,unsigned n) {
uint8_t c=0xff;
while(n--) { c^=*p++;for(unsigned i=0;i<8;i++) c=(c&0x80)?(uint8_t)((c<<1)^0x31):(uint8_t)(c<<1); }
return c;
}
static inline bool w09_sample(const uint8_t raw[6],int16_t *temp,uint16_t *rh) {
*temp=(int16_t)0x8000;*rh=0xffff;
if(w09_crc(raw,2)!=raw[2] || w09_crc(raw+3,2)!=raw[5]) return false;
const uint32_t t=(uint32_t)raw[0]*256+raw[1],h=(uint32_t)raw[3]*256+raw[4];
const int32_t tc=-4500+(int32_t)((17500*t+32767)/65535);
// Data-sheet operating range, not the wider raw formula endpoints.
if(tc < -4000 || tc > 12500)return false;
*temp=(int16_t)tc;
int32_t v=-600+(int32_t)((12500*h+32767)/65535);
if(v<0)v=0;
if(v>10000)v=10000;
*rh=(uint16_t)v;return true;
}
// ---- main/main.c ----
// SPDX-License-Identifier: MIT
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "driver/gpio.h"
#include "driver/i2c_master.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "nvs_flash.h"
#include "esp_zigbee.h"
#include "ezbee/zha.h"
#include "ezbee/zcl/cluster/rel_humidity_measurement_desc.h"
#include "w09_model.h"
#ifndef W09_COORDINATOR
#define W09_COORDINATOR 0
#endif
#define REQUIRE(x) do { if(!(x)) { ESP_LOGE("W09", "required operation failed: %s", #x); abort(); } } while(0)
#define SENSOR_EP 10
#define RECEIVER_EP 1
#define NETWORK_READY BIT0
#define STARTUP_READY BIT1
static const char *TAG="W09";
static EventGroupHandle_t state;
static uint64_t us(void){return (uint64_t)esp_timer_get_time();}
static bool zb_ok(ezb_err_t ret,const char *what){
if(ret!=EZB_ERR_NONE) ESP_LOGE(TAG,"%s failed EZB=%d",what,(int)ret);
return ret==EZB_ERR_NONE;
}
static void network_info(void){
ESP_LOGI(TAG,"NETWORK local=0x%04x pan=0x%04x channel=%u role=%s",ezb_nwk_get_short_address(),ezb_nwk_get_panid(),ezb_nwk_get_current_channel(),W09_COORDINATOR?"coordinator":"end-device");
}
static bool signal_handler(const ezb_app_signal_t *s){
const ezb_app_signal_type_t type=ezb_app_signal_get_type(s);
const void *p=ezb_app_signal_get_params(s);
ESP_LOGI(TAG,"signal=%s",ezb_app_signal_to_string(type));
if(type==EZB_ZDO_SIGNAL_SKIP_STARTUP){
zb_ok(ezb_bdb_start_top_level_commissioning(EZB_BDB_MODE_INITIALIZATION),"initialization");
}else if(type==EZB_BDB_SIGNAL_DEVICE_FIRST_START || type==EZB_BDB_SIGNAL_DEVICE_REBOOT){
const ezb_bdb_comm_status_t status=p?*(const ezb_bdb_comm_status_t*)p:EZB_BDB_STATUS_FORMATION_FAILURE;
ESP_LOGI(TAG,"startup status=%u factory_new=%d",status,ezb_bdb_is_factory_new());
if(status==EZB_BDB_STATUS_SUCCESS){
xEventGroupSetBits(state,STARTUP_READY);
if(ezb_bdb_is_factory_new()){
if(W09_COORDINATOR) zb_ok(ezb_bdb_start_top_level_commissioning(EZB_BDB_MODE_NETWORK_FORMATION),"formation");
else ESP_LOGI(TAG,"WAIT_JOIN: short press GPIO10 after coordinator opens 60 s window");
}else{
if(W09_COORDINATOR) zb_ok(ezb_bdb_close_network(),"close after reboot");
xEventGroupSetBits(state,NETWORK_READY);network_info();
}
}else ESP_LOGE(TAG,"STARTUP_FAILED: inspect log; reset to retry, no automatic erase");
}else if(type==EZB_BDB_SIGNAL_FORMATION || type==EZB_BDB_SIGNAL_STEERING){
const ezb_bdb_comm_status_t status=p?*(const ezb_bdb_comm_status_t*)p:EZB_BDB_STATUS_FORMATION_FAILURE;
ESP_LOGI(TAG,"commission status=%u",status);
if(status==EZB_BDB_STATUS_SUCCESS){
if(W09_COORDINATOR) zb_ok(ezb_bdb_close_network(),"closed by default");
xEventGroupSetBits(state,NETWORK_READY);network_info();
}else xEventGroupClearBits(state,NETWORK_READY);
}else if(type==EZB_ZDO_SIGNAL_LEAVE || type==EZB_ZDO_SIGNAL_PARENT_LOST){
xEventGroupClearBits(state,NETWORK_READY);
ESP_LOGW(TAG,"NETWORK_UNAVAILABLE: no application reports until network resumes; short press to retry initialization/join");
}else if(type==EZB_ZDO_SIGNAL_DEVICE_ANNCE && p){
const ezb_zdo_signal_device_annce_params_t *a=p;
ESP_LOGI(TAG,"ANNOUNCE short=0x%04x (not a sensor report)",a->short_addr);
}else if(type==EZB_NWK_SIGNAL_PERMIT_JOIN_STATUS && p){
ESP_LOGI(TAG,"permit_join_remaining_s=%u",*(const uint8_t*)p);
}
return false;
}
static void action(ezb_zcl_core_action_callback_id_t id,void *message){
if(id!=EZB_ZCL_CORE_REPORT_ATTR_CB_ID || !W09_COORDINATOR || !message)return;
ezb_zcl_cmd_report_attr_message_t *m=message;
m->out.result=EZB_ZCL_STATUS_SUCCESS;
if(m->info.status!=EZB_ZCL_STATUS_SUCCESS || m->info.dst_ep!=RECEIVER_EP || !m->in.header)return;
if(m->in.header->src_addr.addr_mode!=EZB_ADDR_MODE_SHORT){ESP_LOGW(TAG,"REPORT_IGNORED: source address is not SHORT");return;}
for(ezb_zcl_report_attr_variable_t *v=m->in.variables;v;v=v->next){
if(v->attr_id!=0 || !v->attr_value)continue;
// Callback storage belongs to stack; copy exactly the specified 16-bit value now.
uint16_t raw;
if(v->attr_type!=EZB_ZCL_ATTR_TYPE_INT16 && v->attr_type!=EZB_ZCL_ATTR_TYPE_UINT16)continue;
memcpy(&raw,v->attr_value,sizeof raw);
if(m->info.cluster_id==EZB_ZCL_CLUSTER_ID_TEMPERATURE_MEASUREMENT && v->attr_type==EZB_ZCL_ATTR_TYPE_INT16){
ESP_LOGI(TAG,"REPORT mono_us=%" PRIu64 " source=0x%04x ep=%u temp_centi=%d valid=%d",us(),m->in.header->src_addr.u.short_addr,m->in.header->src_ep,(int16_t)raw,raw!=0x8000);
}else if(m->info.cluster_id==EZB_ZCL_CLUSTER_ID_REL_HUMIDITY_MEASUREMENT && v->attr_type==EZB_ZCL_ATTR_TYPE_UINT16){
ESP_LOGI(TAG,"REPORT mono_us=%" PRIu64 " source=0x%04x ep=%u rh_centi=%u valid=%d",us(),m->in.header->src_addr.u.short_addr,m->in.header->src_ep,raw,raw<=10000);
}
}
}
static void endpoints(void){
ezb_af_device_desc_t dev=ezb_af_create_device_desc();
REQUIRE(dev);
ezb_af_ep_desc_t ep;
if(W09_COORDINATOR){
ezb_af_ep_config_t cfg={.ep_id=RECEIVER_EP,.app_profile_id=EZB_AF_HA_PROFILE_ID,.app_device_id=EZB_ZHA_CONFIGURATION_TOOL_DEVICE_ID,.app_device_version=1};
ep=ezb_af_create_endpoint_desc(&cfg);REQUIRE(ep);
REQUIRE(zb_ok(ezb_af_endpoint_add_cluster_desc(ep,ezb_zcl_temperature_measurement_create_cluster_desc(NULL,EZB_ZCL_CLUSTER_CLIENT)),"temp client"));
REQUIRE(zb_ok(ezb_af_endpoint_add_cluster_desc(ep,ezb_zcl_rel_humidity_measurement_create_cluster_desc(NULL,EZB_ZCL_CLUSTER_CLIENT)),"humidity client"));
}else{
ezb_zha_temperature_sensor_config_t cfg=EZB_ZHA_TEMPERATURE_SENSOR_CONFIG();
cfg.temp_meas_cfg.measured_value=(int16_t)0x8000;
cfg.temp_meas_cfg.min_measured_value=-4000;cfg.temp_meas_cfg.max_measured_value=12500;
ep=ezb_zha_create_temperature_sensor(SENSOR_EP,&cfg);REQUIRE(ep);
ezb_zcl_rel_humidity_measurement_cluster_config_t rh={.measured_value=0xffff,.min_measured_value=0,.max_measured_value=10000};
REQUIRE(zb_ok(ezb_af_endpoint_add_cluster_desc(ep,ezb_zcl_rel_humidity_measurement_create_cluster_desc(&rh,EZB_ZCL_CLUSTER_SERVER)),"humidity server"));
ezb_zcl_cluster_desc_t basic=ezb_af_endpoint_get_cluster_desc(ep,EZB_ZCL_CLUSTER_ID_BASIC,EZB_ZCL_CLUSTER_SERVER);
static const char manufacturer[]="\x07""HIXINWU",model[]="\x0c""C6-SHT40-W09";
REQUIRE(zb_ok(ezb_zcl_basic_cluster_desc_add_attr(basic,EZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID,manufacturer),"manufacturer"));
REQUIRE(zb_ok(ezb_zcl_basic_cluster_desc_add_attr(basic,EZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID,model),"model"));
}
REQUIRE(zb_ok(ezb_af_device_add_endpoint_desc(dev,ep),"add endpoint"));
REQUIRE(zb_ok(ezb_af_device_desc_register(dev),"register device"));
ezb_zcl_core_action_handler_register(action);
}
static void zigbee_task(void *arg){
(void)arg;
esp_zigbee_config_t cfg={
.device_config={.device_type=W09_COORDINATOR?EZB_NWK_DEVICE_TYPE_COORDINATOR:EZB_NWK_DEVICE_TYPE_END_DEVICE,.install_code_policy=false},
.platform_config={.storage_partition_name="nvs",.radio_config={.radio_mode=ESP_ZIGBEE_RADIO_MODE_NATIVE}}
};
if(W09_COORDINATOR)cfg.device_config.zczr_config.max_children=4;
else {cfg.device_config.zed_config.ed_timeout=EZB_NWK_ED_TIMEOUT_64MIN;cfg.device_config.zed_config.keep_alive=4000;}
ESP_ERROR_CHECK(esp_zigbee_init(&cfg));
REQUIRE(ezb_app_signal_add_handler(signal_handler));
REQUIRE(zb_ok(ezb_bdb_set_primary_channel_set(1U<<15),"primary channel"));
REQUIRE(zb_ok(ezb_bdb_set_secondary_channel_set(0),"secondary channels"));
ezb_aps_secur_enable_distributed_security(false);
endpoints();
ESP_ERROR_CHECK(esp_zigbee_start(false));
ESP_ERROR_CHECK(esp_zigbee_launch_mainloop());
vTaskDelete(NULL);
}
static void report_value(uint16_t cluster,void *value){
const ezb_zcl_status_t status=ezb_zcl_set_attr_value(SENSOR_EP,cluster,EZB_ZCL_CLUSTER_SERVER,0,EZB_ZCL_STD_MANUF_CODE,value,false);
if(status!=EZB_ZCL_STATUS_SUCCESS){ESP_LOGE(TAG,"set attr cluster=%04x status=%u",cluster,status);return;}
if(!(xEventGroupGetBits(state)&NETWORK_READY))return;
ezb_zcl_report_attr_cmd_t cmd={.cmd_ctrl={.dst_addr={.addr_mode=EZB_ADDR_MODE_SHORT,.u.short_addr=0x0000},.dst_ep=RECEIVER_EP,.src_ep=SENSOR_EP,.cluster_id=cluster,.manuf_code=EZB_ZCL_STD_MANUF_CODE,.fc.direction=EZB_ZCL_CMD_DIRECTION_TO_CLI},.payload.attr_id=0};
const ezb_err_t ret=ezb_zcl_report_attr_cmd_req(&cmd);
ESP_LOGI(TAG,"REPORT_REQUEST cluster=%04x ret=%d (request acceptance is not receiver observation)",cluster,(int)ret);
}
void app_main(void){
ESP_ERROR_CHECK(nvs_flash_init()); // Never silently erase saved Zigbee keys on an NVS error.
state=xEventGroupCreate();REQUIRE(state);
gpio_config_t pin={.pin_bit_mask=1ULL<<GPIO_NUM_10,.mode=GPIO_MODE_INPUT,.pull_up_en=GPIO_PULLUP_DISABLE,.pull_down_en=GPIO_PULLDOWN_DISABLE,.intr_type=GPIO_INTR_DISABLE};
ESP_ERROR_CHECK(gpio_config(&pin));
i2c_master_dev_handle_t sht=NULL;
if(!W09_COORDINATOR){
i2c_master_bus_config_t buscfg={.i2c_port=I2C_NUM_0,.sda_io_num=GPIO_NUM_6,.scl_io_num=GPIO_NUM_7,.clk_source=I2C_CLK_SRC_DEFAULT,.glitch_ignore_cnt=7,.flags.enable_internal_pullup=false};
i2c_master_bus_handle_t bus;ESP_ERROR_CHECK(i2c_new_master_bus(&buscfg,&bus));
i2c_device_config_t d={.dev_addr_length=I2C_ADDR_BIT_LEN_7,.device_address=0x44,.scl_speed_hz=100000};
ESP_ERROR_CHECK(i2c_master_bus_add_device(bus,&d,&sht));
}
REQUIRE(xTaskCreate(zigbee_task,"zigbee",8192,NULL,5,NULL)==pdPASS);
W09Button button={0};uint64_t next_sample=0;
for(;;){
const uint64_t now=us();
const int key=w09_button(&button,gpio_get_level(GPIO_NUM_10)==0,now);
if(key!=W09_NONE && (xEventGroupGetBits(state)&STARTUP_READY) && esp_zigbee_lock_acquire(pdMS_TO_TICKS(200))){
if(W09_COORDINATOR){
if(key==W09_SHORT){
if(xEventGroupGetBits(state)&NETWORK_READY)zb_ok(ezb_bdb_open_network(60),"permit join 60 s");
else zb_ok(ezb_bdb_start_top_level_commissioning(EZB_BDB_MODE_NETWORK_FORMATION),"retry formation");
}else zb_ok(ezb_bdb_close_network(),"manual close");
}else if(key==W09_LONG){
if(xEventGroupGetBits(state)&NETWORK_READY){xEventGroupClearBits(state,NETWORK_READY);ezb_bdb_reset_via_local_action();ESP_LOGW(TAG,"LEAVE_REQUEST (wait for LEAVE signal; no automatic rejoin)");}
else ESP_LOGW(TAG,"LEAVE_IGNORED: not on network; use documented erase only for recovery");
}else{
if(ezb_bdb_is_factory_new())zb_ok(ezb_bdb_start_top_level_commissioning(EZB_BDB_MODE_NETWORK_STEERING),"join");
else zb_ok(ezb_bdb_start_top_level_commissioning(EZB_BDB_MODE_INITIALIZATION),"retry saved network");
}
esp_zigbee_lock_release();
}
if(!W09_COORDINATOR && now>=next_sample && (xEventGroupGetBits(state)&STARTUP_READY)){
next_sample=now+10000000;
uint8_t raw[6]={0};const uint8_t cmd=0xfd;
esp_err_t err=i2c_master_transmit(sht,&cmd,1,100);
if(err==ESP_OK){vTaskDelay(pdMS_TO_TICKS(20));err=i2c_master_receive(sht,raw,sizeof raw,100);}
int16_t t=(int16_t)0x8000;uint16_t h=0xffff;
const bool valid=err==ESP_OK && w09_sample(raw,&t,&h);
ESP_LOGI(TAG,"SAMPLE mono_us=%" PRIu64 " valid=%d i2c=%s temp_centi=%d rh_centi=%u",us(),valid,esp_err_to_name(err),t,h);
if(esp_zigbee_lock_acquire(pdMS_TO_TICKS(200))){
report_value(EZB_ZCL_CLUSTER_ID_TEMPERATURE_MEASUREMENT,&t);
report_value(EZB_ZCL_CLUSTER_ID_REL_HUMIDITY_MEASUREMENT,&h);
esp_zigbee_lock_release();
}else ESP_LOGW(TAG,"SAMPLE_NOT_PUBLISHED: Zigbee lock timeout");
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
参考:[4] 2.0.5 bdb.h:open/close/local reset明确边界[5] 2.0.5 ZCL报告请求/接收变量
首次入网:先有网络,再开60秒窗口
先开COORD桥口。首次BDB初始化后自动formation,成功后显式close_network;保存过网络的重启也显式关闭加入。核日志中的PAN、channel15、local0x0000。没有组网时短按重试formation,已组网时短按才open_network(60),长按关闭。
SHT初次factory-new只显示WAIT_JOIN,不自行反复steering。两按钮先释放稳定30ms;开COORD窗口后短按SHT。成功检查依次是SHT的commission status/NETWORK、COORD的ANNOUNCE,然后下一次真正REPORT;仅ANNOUNCE不能记温湿度业务通过。
关闭permit-join后,已入网设备仍可报告;这个动作禁止新的加入,不是删除终端。可以先关窗口再让新出厂终端尝试,保留真实失败记录,随后重新开窗重试,而非把所有失败归为信号弱。
- 做到这里,你应该看到
- 同一个trial中,PAN/channel对应、入网信号和接收端真正的属性报告三类记录都保留。
标准属性的单位、未知值和“请求已接受”
SHT每约10s发0xFD,等待20ms,读6B并分别检查两个CRC(初值0xff、多项式0x31)。t=−45+175*raw/65535、RH=−6+125*raw/65535后换成百分之一,湿度裁到0..10000,ZCL温度Min/Max为−4000/12500;原始温度公式端点−45..130°C不是器件保证工作范围−40..125°C的扩张。
正常温度用signed16,湿度unsigned16;任一CRC或I²C失败,或换算温度超出器件−40..125°C范围,本轮温度写0x8000、湿度0xffff再报告未知,不继续重发上轮正常值。终端对0x0402/0x0405分别单播到0x0000/EP1,不要求读者另外建bind。
REPORT_REQUEST ret0只代表SDK接受请求,必须在COORD真实回调找相同属性。温度和湿度是两个独立ZCL报告,没有原子样本帧、业务应用ACK或丢包重放保证。短地址会在重新入网改变,读者用实物标签/设备IEEE信息记录本次映射,不能用short当永久身份。
COORD只逐条串口输出,不提供自动延用旧值的看板/HA实体。若一段时间没有新REPORT,外部记录应按最后实际接收时刻标为无更新,不能把上一行正常值当仍在线。
参考:[5] 2.0.5 ZCL报告请求/接收变量[6] Adafruit PID4885具体模块/连接说明[7] Adafruit模块pinouts:VIN随逻辑电压、10k上拉与0x44[8] SHT4x V6.4 p3/10–12:0xFD、CRC和公式
离网、重新加入和保留网络的重启各测一次
已入网SHT长按3s立即撤销应用报告资格,调用local reset。等实际LEAVE信号,API会清Zigbee持久网络数据但保留outgoing NWK frame counter;它不会在本例离网后自动steering。重新开COORD窗口、SHT短按,再记录新short与真正REPORT。未入网时长按只警告,不擅自erase。
另做普通断电重上:不擦Flash,核SHT从保存网络恢复的BDB REBOOT/NETWORK和COORD的新报告。首次formation与旧网络恢复是两个用例,不能拿前者替后者。父节点丢失时本例停止应用报告;短按请求保存网络初始化/重试,先检查COORD供电和日志。
最后手段erase-flash只对明确选定板执行,会清所有数据再刷同角色;擦COORD会建新网络,旧终端可能还保存旧网,必须按local reset或明确清除后再入网。NVS初始化失败不会自动丢钥匙“修好”,先保存错误日志。
- 填写空记录表中的首入/关窗/报告/故障未知/离网/重入/断电恢复行。
- 同一用例保存两边原始日志、固件SHA、板卡标签和真实时间。
- 在各步骤真正观察到相应状态以后再填写通过;未做保持pending。
从角色与端点往下查,最后才动清除按钮
无网络先查COORD镜像/供电、channel15和formation;无法加入再查窗口和SHT是否保存旧网络。已入但无值,核EP10→EP1、HA profile、0x0402/0x0405及接收回调,再查I²C/CRC。若要观察断线未知,断电预装SDA开关后运行时打开,闭合后必要RESET;不要带电拔VIN造反供电风险。
桥口找不到先换数据线,BOOT9+RESET恢复下载。错误Zigbee/Thread/其他C6镜像不能互当协调器。本文不宣称已经连接Home Assistant、兼容任意协调棒或通过Zigbee认证。
本轮终端和协调器最终范围检查镜像实际构建Exit0,CRC/换算/按键7组ASan/UBSan通过,仍无SHT实测或无线结果。先回读S01完成有线采样、E07理解锁,再用本包独立完成两板记录。
资料来源与版本说明
官方网页与具体固定源码逐项核对,详见docs/esp32-evidence/wireless-c5-c6.md及sources/builds/hosts JSON。硬件和无线链路没有本站观测。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。