On a custom Zigbee End Device application for the Thingy:53 (NCS v2.9.3), ZCL Configure Reporting commands sent by Zigbee2MQTT are rejected with ZCL status FAILURE for almost every cluster/attribute except Temperature Measurement and two genOnOff instances, even though the attribute descriptors, endpoint declarations and reporting-context sizing all look correct against the ZBOSS headers. Separately, the device spontaneously and continuously emits malformed Report Attributes frames for msRelativeHumidity (~8 msg/s, confirmed to survive a full device power-cycle), independent of whatever reporting configuration is active. We've ruled out everything visible from prj.conf, the ZBOSS module's own Kconfig, and our application source — the remaining behaviour appears to be inside the precompiled ZBOSS reporting engine.
Environment
|
Board |
Thingy:53 (nRF5340) |
|
SDK |
nRF Connect SDK v2.9.3, ZBOSS production libraries |
|
Zephyr |
v3.7.99-ncs2-3 |
|
Role |
Zigbee End Device |
|
Coordinator / gateway |
Zigbee2MQTT (external converter), zigbee-herdsman |
|
App |
Custom multi-endpoint sensor app — 7 endpoints |
Endpoint / cluster map:
|
EP |
Clusters |
Reportable attributes |
|---|---|---|
|
10 (env) |
Basic, Identify, |
temp, pressure, humidity, illuminance, battery voltage/percentage |
|
11 (imu) |
custom 0xFC01, |
6 custom float attrs, motion (boolean) |
|
12 (mag) |
custom 0xFC02 |
4 custom float attrs |
|
13 (air) |
custom 0xFC03 |
3 custom float attrs |
|
30 (light) |
|
onOff (boolean), currentLevel (uint8) |
|
31 (buzzer) |
|
onOff (boolean) |
|
32 (cfg) |
custom 0xFC05 |
(write-only config, not reported) |
Each custom endpoint is built with a small macro (ZB_DECLARE_CUSTOM_EP) wrapping ZB_AF_DECLARE_ENDPOINT_DESC() + ZBOSS_DEVICE_DECLARE_REPORTING_CTX(ctx, 8) — 8 reporting slots per endpoint, well above the 1–6 reportable attributes any single endpoint actually has.
Symptom 1 — Configure Reporting rejected with ZCL status FAILURE for most clusters
After joining, our Z2M external converter's configure() runs ~17 bind + Configure Reporting + Read commands across the 7 endpoints. In a log capture triggered by an explicit bridge/request/device/configure MQTT request (bypassing Z2M's own reconfigure-gating, so we know for certain this actually executed on the device), the device responded:
msTemperatureMeasurement.configReport([{attribute:'measuredValue', minimumReportInterval:10, maximumReportInterval:300, reportableChange:10}]) -> SUCCESS
msRelativeHumidity.configReport([{attribute:'measuredValue', minimumReportInterval:0xFFFF, maximumReportInterval:0xFFFF, reportableChange:0}]) -> FAILURE
msPressureMeasurement.configReport([{attribute:'measuredValue', minimumReportInterval:10, maximumReportInterval:300, reportableChange:1}]) -> FAILURE
msIlluminanceMeasurement.configReport([{attribute:'measuredValue', minimumReportInterval:10, maximumReportInterval:300, reportableChange:1}]) -> FAILURE
genPowerCfg.configReport([{attribute:'batteryPercentageRemaining', minimumReportInterval:60, maximumReportInterval:3600, reportableChange:1}]) -> FAILURE
64513 (custom IMU cluster).configReport([...6 float attrs...]) -> FAILURE
64514 (custom MAG cluster).configReport([...4 float attrs...]) -> FAILURE
64515 (custom AIR cluster).configReport([...3 float attrs...]) -> FAILURE
genBinaryInput.configReport([{attribute:'presentValue', minimumReportInterval:0, maximumReportInterval:300, reportableChange:1}]) -> FAILURE
genLevelCtrl.configReport([{attribute:'currentLevel', minimumReportInterval:0, maximumReportInterval:300, reportableChange:1}]) -> FAILURE
genOnOff.configReport([{attribute:'onOff', minimumReportInterval:0, maximumReportInterval:300, reportableChange:0}]) (light, EP30) -> SUCCESS
genOnOff.configReport([{attribute:'onOff', minimumReportInterval:0, maximumReportInterval:300, reportableChange:0}]) (buzzer, EP31) -> SUCCESS
Initially this looked like "only the first Configure Reporting call per endpoint in a burst succeeds, every later one fails" (temperature was 1st on EP10, both onOff calls were the only/1st call on their endpoints). We tested this directly:
- Added a 300 ms delay after every individual bind/configureReporting/read step (not just between endpoint blocks) — made zero difference, identical failures, even though the whole sequence now took ~10x longer. This rules out a command-queue/timing race.
- Reordered so
msPressureMeasurementis sent FIRST on EP10 (before temperature) andgenLevelCtrlFIRST on EP30 (before onOff) — in that run, pressure succeeded in that position, butgenLevelCtrlstill failed even as the first command sent to its endpoint, and critically, even the calls that stopped showingFAILUREstill never produced live attribute reports in Home Assistant/Z2M afterwards. So "no FAILURE status" does not reliably mean the device's reporting engine is actually scheduling/sending reports for that attribute.
Symptom 2 — Malformed, continuous Report Attributes flood for msRelativeHumidity
Independent of the above, the device continuously emits a Report Attributes frame for msRelativeHumidity's measuredValueroughly every 120 ms (~8.35 msg/s), confirmed via zigbee-herdsman's Failed to parse frame errors and Zigbee2MQTT's own Health page message-rate counter (which matched almost exactly). This is not throttled by minimumReportInterval/maximumReportInterval in any configuration we've sent.
Decoded byte-for-byte from the raw payload ("data":[12,0,0,<seq>,10], <seq> incrementing as expected):
|
Byte |
Value |
Meaning |
|---|---|---|
|
0 |
|
ZCL frame control: global command, direction server→client, Manufacturer-Specific bit incorrectly SET |
|
1–2 |
|
Consumed as a (bogus, always-zero) manufacturer code, because of the MS bit above |
|
3 |
|
Transaction sequence number — correct, increments normally |
|
4 |
|
Command ID = Report Attributes — correct |
|
— |
(none) |
Buffer ends here — attribute ID / type / value payload is completely missing |
zigbee-herdsman correctly rejects this as unparseable (RangeError [ERR_OUT_OF_RANGE] ... offset is out of range). This is not a herdsman/Z2M bug — the frame genuinely is malformed/truncated as transmitted by the device.
Tested and ruled out:
- Simply no longer requesting this attribute's reporting — flood continues unchanged (an already-active device-side report isn't cancelled by not re-asking for it).
- Explicitly sending the ZCL spec's "deactivate reporting" sentinel (
minimumReportInterval = maximumReportInterval = 0xFFFF) — confirmed delivered to the device (via an explicit MQTT-triggered Reconfigure), initially rejected withFAILURE; on a later attempt it was accepted (SUCCESS), but the flood continued completely unaffectedeven after the deactivation command was accepted. - Full device power-cycle (not a reflash — just power off/on) — flood resumed at an unchanged rate (~7.8 msg/s measured over a 5-minute post-boot window), ruling out any theory involving corrupted/stale RAM state accumulated across repeated test rounds. This reproduces identically from a cold boot.
What we've already ruled out
- Attribute access flags. Compared the ZBOSS header macros for Temperature/Pressure/Humidity/Illuminance's
MeasuredValuedescriptors byte-for-byte — all four are declared identically (ZB_ZCL_ATTR_ACCESS_READ_ONLY | ZB_ZCL_ATTR_ACCESS_REPORTING, non-manufacturer-specific). Not a permissions issue. - Per-endpoint reporting slot table size.
ZBOSS_DEVICE_DECLARE_REPORTING_CTX(ctx, 8)— 8 slots per endpoint, no endpoint has more than 6 reportable attributes.zb_zcl_reporting.handzboss_api_af.hconfirmrep_info_count/reporting_infoare genuinely per-endpoint fields ("moved here from device descriptor"), correctly wired viaZB_AF_DECLARE_ENDPOINT_DESC(). - Device-context structure. In this ZBOSS version,
zb_af_device_ctx_t(zboss_api_zcl.h) only hasep_count/ep_desc_list— no device-wide reporting fields exist that could be mis-set. - Command ordering / timing / device radio queue. A 300 ms delay after every individual step made no difference (see Symptom 1); reordering which cluster is sent first on an endpoint also didn't produce a consistent "first always succeeds" pattern.
prj.conf/prj_release.conf. No Zigbee/ZCL/reporting-related Kconfig tunable of any kind — only library/driver enables (CONFIG_ZIGBEE=y,CONFIG_ZIGBEE_ROLE_END_DEVICE=y,CONFIG_ZIGBEE_USE_SOFTWARE_AES=y,CONFIG_ZIGBEE_CHANNEL_SELECTION_MODE_MULTI=y, sensor/ADC/PWM/log/USB settings).- ZBOSS module's own Kconfig (
nrfxlib/zboss/Kconfig, read in full) — only offers library-variant selection (production / development /ZIGBEE_ENABLE_TRACESdebug libraries) and Green Power feature set. Nothing reporting-related. - Our own sensor-push application code.
zb_device_update_env()(the function that callszb_zcl_set_attr_val()for temp/pressure/humidity/illuminance every sampling cycle) treats all four attributes with the byte-for-byte identical call pattern. The periodic sampling loop (ZB_SCHEDULE_APP_ALARM-based self-reschedule) pushes fresh values for every sensor unconditionally, every cycle, with no attribute special-cased or skipped.
Minimal repro
The endpoint/cluster declaration pattern (same for every endpoint, only cluster IDs differ):
#define RO_REPORTABLE (ZB_ZCL_ATTR_ACCESS_READ_ONLY | ZB_ZCL_ATTR_ACCESS_REPORTING)
ZB_ZCL_DECLARE_PRESSURE_MEASUREMENT_ATTRIB_LIST(pressure_attr_list, &env_ctx.pres_measure_value,
&env_ctx.pres_min_measure_value,
&env_ctx.pres_max_measure_value,
&env_ctx.pres_tolerance);
static zb_zcl_cluster_desc_t env_cluster_list[] = {
/* ... Basic, Identify, Temp ... */
ZB_ZCL_CLUSTER_DESC(ZB_ZCL_CLUSTER_ID_PRESSURE_MEASUREMENT,
ZB_ZCL_ARRAY_SIZE(pressure_attr_list, zb_zcl_attr_t),
(pressure_attr_list), ZB_ZCL_CLUSTER_SERVER_ROLE,
ZB_ZCL_MANUF_CODE_INVALID),
/* ... Humidity, Illuminance, PowerConfig ... */
};
#define ZB_DECLARE_CUSTOM_EP(ep_var, ep_id, n_in, cluster_list_arr, ...) \
ZB_AF_SIMPLE_DESC_TYPE(n_in, 0) simple_desc_##ep_var = { ep_id, ZB_AF_HA_PROFILE_ID, \
ZB_HA_CUSTOM_ATTR_DEVICE_ID, 0, 0, n_in, 0, { __VA_ARGS__ } }; \
ZBOSS_DEVICE_DECLARE_REPORTING_CTX(reporting_info_##ep_var, 8); \
ZB_AF_DECLARE_ENDPOINT_DESC(ep_var, ep_id, ZB_AF_HA_PROFILE_ID, 0, NULL, \
ZB_ZCL_ARRAY_SIZE(cluster_list_arr, zb_zcl_cluster_desc_t), cluster_list_arr, \
(zb_af_simple_desc_1_1_t *)&simple_desc_##ep_var, 8, reporting_info_##ep_var, 0, NULL)
ZB_DECLARE_CUSTOM_EP(env_ep, EP_ENV, 7, env_cluster_list,
ZB_ZCL_CLUSTER_ID_BASIC, ZB_ZCL_CLUSTER_ID_IDENTIFY,
ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT, ZB_ZCL_CLUSTER_ID_PRESSURE_MEASUREMENT,
ZB_ZCL_CLUSTER_ID_REL_HUMIDITY_MEASUREMENT,
ZB_ZCL_CLUSTER_ID_ILLUMINANCE_MEASUREMENT, ZB_ZCL_CLUSTER_ID_POWER_CONFIG);
Steps:
- Flash the app, pair with Zigbee2MQTT.
- From the coordinator, bind + send Configure Reporting for
msTemperatureMeasurement.measuredValue— succeeds, and periodic reports arrive correctly. - Bind + send Configure Reporting for
msPressureMeasurement.measuredValuewith the same shape of request — device responds with ZCL statusFAILURE(or, in some runs,SUCCESSbut with no periodic reports ever actually sent). - Observe
msRelativeHumidityspontaneously emitting malformed Report Attributes frames (data: [0x0C, 0x00, 0x00, <seq>, 0x0A]) at ~8/s, with no Configure Reporting ever having been accepted for it, and unaffected by sending the 0xFFFF/0xFFFF deactivation command or power-cycling the device.
Questions
- Is there a known limitation in ZBOSS v2.9.3's production libraries on how many distinct clusters/attributes a single endpoint (or the whole device) can have actively reporting at once, beyond the application-supplied
ZBOSS_DEVICE_DECLARE_REPORTING_CTX()slot count — something not exposed via Kconfig or the public headers? - Is Configure Reporting known to behave differently for
genOnOff/msTemperatureMeasurementversus other standard clusters (e.g.msPressureMeasurement,genPowerCfg,genLevelCtrl,genBinaryInput) on an End Device role specifically? - Could a stale/corrupted reporting-table entry on the device explain the
msRelativeHumidityframe corruption (Manufacturer-Specific bit set when it shouldn't be) even across a full power-cycle — or does that point at something else entirely (e.g. the attribute's type/size metadata)? - Is
CONFIG_ZIGBEE_ENABLE_TRACESthe right path to get actionable binary traces for this, and if so, what's the expected workflow for submitting them against a support ticket? - Are there any known issues in this ZBOSS version specific to
ZB_ZCL_CLUSTER_SERVER_ROLE_INIT = NULLfor custom manufacturer-range clusters (0xFC01–0xFC05) interacting with the reporting engine, versus a built-in cluster with a real init function?
Happy to provide the full src/ tree, .mjs converter, and raw Z2M debug logs on request.