ncs-zigbee - Configuration issue with custom cluster

Hi,

I write this information for the sake of completeness. I don´t need Zigbee2MQTT support from you. Instead, support with the Zigbee firmware is required.

I´m working on an open-source Zigbee environment sensor, based on an nRF54L14, and I have a problem with a customized cluster for VOC. You can find the complete repo with the firmware here:

https://github.com/Kampi/BeeLight

I use Zigbee2MQTT for the Zigbee network, and I get a configuration error when starting the service. The Zigbee2MQTT team told me that this issue is firmware-based. You can see the ticket for this problem here:

https://github.com/Koenkk/zigbee-herdsman-converters/issues/10831#issuecomment-3568319658

I don´t understand why this issue is related to the VOC cluster only, because the cluster looks basically the same. But I don´t have a deep understanding of ZBOSS and the underlying mechanism, so I need some help to understand the issue and a potential solution. The complete VOC cluster looks like this

#ifndef ZB_ZCL_BEELIGHT_VOC_MEASUREMENT_H_
#define ZB_ZCL_BEELIGHT_VOC_MEASUREMENT_H_

#include <zcl/zb_zcl_common.h>
#include <zcl/zb_zcl_commands.h>

enum zb_zcl_voc_cluster_attr_e
{
    ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_VALUE_ID        = 0x0000,
    ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MIN_VALUE_ID    = 0x0001,
    ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MAX_VALUE_ID    = 0x0002,
    ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_TOLERANCE_ID    = 0x0003,
};

/** @brief Default value for VOC Measurement cluster revision global attribute */
#define ZB_ZCL_BEELIGHT_VOC_MEASUREMENT_CLUSTER_REVISION_DEFAULT             ((zb_uint16_t)0x0001U)

/** @brief MeasuredValue attribute unknown value */
#define ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_VALUE_UNKNOWN                   ((zb_uint16_t)0x8000)

/** @brief MinMeasuredValue attribute minimum value */
#define ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MIN_VALUE_MIN_VALUE             ((zb_uint16_t)0x0000)

/** @brief MinMeasuredValue attribute maximum value */
#define ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MIN_VALUE_MAX_VALUE             ((zb_uint16_t)0x2710)

/** @brief MinMeasuredValue attribute invalid value */
#define ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MIN_VALUE_INVALID               ((zb_uint16_t)0x8000)

/** @brief MaxMeasuredValue attribute minimum value */
#define ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MAX_VALUE_MIN_VALUE             ((zb_uint16_t)0x0000)

/** @brief MaxMeasuredValue attribute maximum value */
#define ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MAX_VALUE_MAX_VALUE             ((zb_uint16_t)0x2710)

/** @brief MaxMeasuredValue attribute invalid value */
#define ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MAX_VALUE_INVALID               ((zb_uint16_t)0x8000)

/** @brief Tolerance attribute minimum value */
#define ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_TOLERANCE_MIN_VALUE             ((zb_uint16_t)0x0000)

/** @brief Tolerance attribute maximum value */
#define ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_TOLERANCE_MAX_VALUE             ((zb_uint16_t)0x000F)

/** @brief Default value for Value attribute */
#define ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_VALUE_DEFAULT_VALUE             ((zb_uint16_t)0x0000)

#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_VALUE_ID(data_ptr)          \
{                                                                                               \
    ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_VALUE_ID,                                              \
    ZB_ZCL_ATTR_TYPE_U16,                                                                       \
    ZB_ZCL_ATTR_ACCESS_READ_ONLY | ZB_ZCL_ATTR_ACCESS_REPORTING,                                \
    (ZB_ZCL_NON_MANUFACTURER_SPECIFIC),                                                         \
    (void*) data_ptr                                                                            \
}

#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MIN_VALUE_ID(data_ptr)      \
{                                                                                               \
    ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MIN_VALUE_ID,                                          \
    ZB_ZCL_ATTR_TYPE_U16,                                                                       \
    ZB_ZCL_ATTR_ACCESS_READ_ONLY,                                                               \
    (ZB_ZCL_NON_MANUFACTURER_SPECIFIC),                                                         \
    (void*) data_ptr                                                                            \
}

#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MAX_VALUE_ID(data_ptr)      \
{                                                                                               \
    ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MAX_VALUE_ID,                                          \
    ZB_ZCL_ATTR_TYPE_U16,                                                                       \
    ZB_ZCL_ATTR_ACCESS_READ_ONLY,                                                               \
    (ZB_ZCL_NON_MANUFACTURER_SPECIFIC),                                                         \
    (void*) data_ptr                                                                            \
}

#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_TOLERANCE_ID(data_ptr)      \
{                                                                                               \
    ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_TOLERANCE_ID,                                          \
    ZB_ZCL_ATTR_TYPE_U8,                                                                        \
    ZB_ZCL_ATTR_ACCESS_READ_ONLY,                                                               \
    (ZB_ZCL_NON_MANUFACTURER_SPECIFIC),                                                         \
    (void*) data_ptr                                                                            \
}

/** @brief Number of attributes mandatory for reporting in BeeLight VOC Measurement cluster */
#define ZB_ZCL_BEELIGHT_VOC_MEASUREMENT_REPORT_ATTR_COUNT 1

/** @brief Declare attribute list for BeeLight VOC Measurement cluster - server side
    @param attr_list - attribute list name
    @param value - pointer to variable to store MeasuredValue attribute
    @param min_value - pointer to variable to store MinMeasuredValue attribute
    @param max_value - pointer to variable to store MAxMeasuredValue attribute
    @param tolerance - pointer to variable to store Tolerance attribute
*/
#define ZB_ZCL_DECLARE_BEELIGHT_VOC_MEASUREMENT_ATTRIB_LIST(attr_list, value, min_value, max_value, tolerance)  \
    ZB_ZCL_START_DECLARE_ATTRIB_LIST_CLUSTER_REVISION(attr_list, ZB_ZCL_BEELIGHT_VOC_MEASUREMENT)               \
    ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_VALUE_ID, (value))                                \
    ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MIN_VALUE_ID, (min_value))                        \
    ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MAX_VALUE_ID, (max_value))                        \
    ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_TOLERANCE_ID, (tolerance))                        \
    ZB_ZCL_FINISH_DECLARE_ATTRIB_LIST

void zb_zcl_beelight_voc_measurement_init_server(void);
void zb_zcl_beelight_voc_measurement_init_client(void);

#define ZB_ZCL_CLUSTER_ID_BEELIGHT_VOC_MEASUREMENT_SERVER_ROLE_INIT zb_zcl_beelight_voc_measurement_init_server
#define ZB_ZCL_CLUSTER_ID_BEELIGHT_VOC_MEASUREMENT_CLIENT_ROLE_INIT zb_zcl_beelight_voc_measurement_init_client

#endif /* ZB_ZCL_BEELIGHT_VOC_MEASUREMENT_H_ */

#include "zb_common.h"

#if defined (ZB_ZCL_SUPPORT_CLUSTER_BEELIGHT_VOC_MEASUREMENT)

#include "zb_zcl.h"
#include "zb_aps.h"
#include "zcl/zb_zcl_common.h"

zb_ret_t check_value_beelight_voc_measurement_server(zb_uint16_t attr_id, zb_uint8_t endpoint, zb_uint8_t *value);
void zb_zcl_beelight_voc_measurement_write_attr_hook_server(zb_uint8_t endpoint, zb_uint16_t attr_id, zb_uint8_t *new_value, zb_uint16_t manuf_code);

void zb_zcl_beelight_voc_measurement_init_server(void)
{
    zb_zcl_add_cluster_handlers(ZB_ZCL_CLUSTER_ID_BEELIGHT_VOC_MEASUREMENT,
                                ZB_ZCL_CLUSTER_SERVER_ROLE,
                                check_value_beelight_voc_measurement_server,
                                zb_zcl_beelight_voc_measurement_write_attr_hook_server,
                                (zb_zcl_cluster_handler_t)NULL);
}

void zb_zcl_voc_measurement_init_client(void)
{
    zb_zcl_add_cluster_handlers(ZB_ZCL_CLUSTER_ID_BEELIGHT_VOC_MEASUREMENT,
                                ZB_ZCL_CLUSTER_CLIENT_ROLE,
                                (zb_zcl_cluster_check_value_t)NULL,
                                (zb_zcl_cluster_write_attr_hook_t)NULL,
                                (zb_zcl_cluster_handler_t)NULL);
}

zb_ret_t check_value_beelight_voc_measurement_server(zb_uint16_t attr_id, zb_uint8_t endpoint, zb_uint8_t *value)
{
    zb_ret_t ret = RET_OK;
    zb_int16_t val = ZB_ZCL_ATTR_GET16(value);

    TRACE_MSG(TRACE_ZCL1, "> check_value_beelight_voc_measurement, attr_id %d, val %d", (FMT__D_D, attr_id, val));

    switch (attr_id)
    {
        case ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_VALUE_ID:
            if (ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_VALUE_UNKNOWN == val)
            {
                ret = RET_OK;
            }
            else
            {
                zb_zcl_attr_t *attr_desc = zb_zcl_get_attr_desc_a(endpoint,
                    ZB_ZCL_CLUSTER_ID_BEELIGHT_VOC_MEASUREMENT,
                    ZB_ZCL_CLUSTER_SERVER_ROLE,
                    ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MIN_VALUE_ID);
                ZB_ASSERT(attr_desc);

                ret = (ZB_ZCL_GET_ATTRIBUTE_VAL_16(attr_desc) == ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MIN_VALUE_INVALID ||
                    ZB_ZCL_GET_ATTRIBUTE_VAL_16(attr_desc) <= val)
                    ? RET_OK : RET_ERROR;

                if (ret)
                {
                    attr_desc = zb_zcl_get_attr_desc_a(endpoint,
                        ZB_ZCL_CLUSTER_ID_BEELIGHT_VOC_MEASUREMENT,
                        ZB_ZCL_CLUSTER_SERVER_ROLE,
                        ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MAX_VALUE_ID);
                    ZB_ASSERT(attr_desc);

                    ret = ZB_ZCL_GET_ATTRIBUTE_VAL_16(attr_desc) == ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MAX_VALUE_INVALID ||
                            val <= ZB_ZCL_GET_ATTRIBUTE_VAL_16(attr_desc)
                        ? RET_OK : RET_ERROR;
                }
            }
            break;

        case ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MIN_VALUE_ID:
            ret = ((ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MIN_VALUE_MIN_VALUE <= val) &&
                    (val <= ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MIN_VALUE_MAX_VALUE)) ||
                    (ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MIN_VALUE_INVALID == val)
                    ? RET_OK : RET_ERROR;
            break;

        case ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MAX_VALUE_ID:
            ret = ( (ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MAX_VALUE_MIN_VALUE <= ZB_ZCL_ATTR_GET16(value)) &&
                    (ZB_ZCL_ATTR_GET16(value) <= ZB_ZCL_ATTR_BEELIGHT_VOC_MEASUREMENT_MAX_VALUE_MAX_VALUE) )
                    ? RET_OK : RET_ERROR;
            break;

        default:
            break;
    }

    TRACE_MSG(TRACE_ZCL1, "< check_value_beelight_voc_measurement ret %hd", (FMT__H, ret));

    return ret;
}

void zb_zcl_beelight_voc_measurement_write_attr_hook_server(zb_uint8_t endpoint, zb_uint16_t attr_id, zb_uint8_t *new_value, zb_uint16_t manuf_code)
{
    ZVUNUSED(new_value);
    ZVUNUSED(endpoint);
    ZVUNUSED(manuf_code);
    ZVUNUSED(attr_id);

    TRACE_MSG(TRACE_ZCL1, ">> zb_zcl_beelight_voc_measurement_write_attr_hook endpoint %hd, attr_id 0x%x, manuf_code 0x%x",
                (FMT__H_D_D, endpoint, attr_id, manuf_code));

    /* All attributes in this cluster are read-only. Do nothing */

    TRACE_MSG(TRACE_ZCL1, "<< zb_zcl_beelight_voc_measurement_write_attr_hook", (FMT__0));
}

#endif /* ZB_ZCL_SUPPORT_CLUSTER_VOC_MEASUREMENT */

As far as I understand the error message, the error happens somewhere during the configuration of this cluster. Where does this configuration happen and how can I enable debug outputs to see the issue in the firmware? Or is something wrong with the cluster definition (it´s based on the temperature measurement cluster). The current firmware doesn´t output any Zigbee-related errors yet.

Parents
  • Hello,

    Can you please specify exactly what error message you are seeing? Also, what HW are you running on? nRF54L15 DK or custom HW? And do you have access to UART Logging? USB logging? RTT Logging?

    And what NCS version are you using?

    Best regards,

    Edvin

  • Hi Edvin,

    it´s a custom hardware with an nRF54L15 module and I have RTT logging available. I´m using NCS v2.9.2 with ncs-zigbee main.

    https://github.com/Kampi/BeeLight/blob/2.1_Dev/firmware/app/west.yml

    The error message from Z2M is the following

    [2025-11-23 13:10:04] error:    z2m: Failed to configure '0x86d79ac5bae41fa3', attempt 4 (Error: ZCL command 0x86d79ac5bae41fa3/10 msBeelightVOC.configReport([{"minimumReportInterval":300,"maximumReportInterval":3600,"reportableChange":5,"attribute":"measuredValue"}], {"timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":true,"direction":0,"reservedBits":0,"writeUndiv":false}) failed (Status 'FAILURE')

  • Hi Edwin,

    the only thing I have is this for all of the three clusters:

    I´ve used the humidity cluster as an example. But these clusters also don´t set the handler, so I assume that these clusters don´t support reporting as well?

  • Hello,

    This doesn't tell me much, I am afraid.

    You do have an app running on the nRF54L15, and it is not identical to any of our samples, right? In this application, did you implement a zb_zcl_cluster_handler_t handler that processes the ZCL_CMD_CONFIG_REPORT and stores the reporting configuration? If so, can you point me to it?

    Edvin said:

    Have you tried debugging the zb_zcl_status_t zb_zcl_set_attr_val() function, to see if it encounters any errors?

    BR,
    Edvin

  • Hi Edvin,

    sorry for the delay but I lost the topic for a while (was busy with different stuff...). I´m not that familar with Zigbee so I took your answer, my code and asked Claude to explain it to me and I got this answer:

    "The cluster IDs 0x1A0A, 0x1A0B, and 0x1A0C are not in the ZBOSS native ZCL catalog. For such custom clusters, ZBOSS routes all incoming ZCL commands (including general commands such as configReport 0x06) through the cluster_handler. Since this is NULL everywhere, ZBOSS responds directly with FAILURE."

    The recommendation is to change the init_server to this

    /** @brief  Cluster handler for the CO2 Measurement cluster.
     *          Returns ZB_FALSE so that ZBOSS processes general ZCL commands
     *          (e.g. ZCL_CMD_CONFIG_REPORT) with the default handler.
     */
    static zb_bool_t cluster_handler_co2_measurement(zb_uint8_t param)
    {
        ZVUNUSED(param);
        return ZB_FALSE;
    }
    
    void zb_zcl_co2_measurement_init_server(void)
    {
        zb_zcl_add_cluster_handlers(ZB_ZCL_CLUSTER_ID_CO2_MEASUREMENT,
                                    ZB_ZCL_CLUSTER_SERVER_ROLE,
                                    check_value_co2_measurement_server,
                                    (zb_zcl_cluster_write_attr_hook_t)NULL,
                                    cluster_handler_co2_measurement);

    What do you think about this change? Does it make sense to you?

  • Yes. Please see how zb_zcl_add_cluster_handlers() is already used by other clusters in the SDK (in v2.9.2).

    Best regards,

    Edvin

  • Hi,

    can you please share a link? I can´t find it on GitHub. Also, I´m confused because 2.9.2 is the NCS release?

Reply Children
  • Are you not using NCS v2.9.2? Or are you using an addon?

    You can search for API documentation here:

    https://ncsdoc.z6.web.core.windows.net/zboss-r23

    And it does have the description of the zb_zcl_add_cluster_handlers() API. However, it is the same information that is present in the documentation, so if you just search for zb_zcl_add_cluster_handlers() in the nRF Connect SDK that you are using, you should find the headers describing the functions.

    Best regards,

    Edvin

  • I use NCS v2.9.2 with the ncs-zigbee addon.

    Thanks for the link, I will take a look into it.

  • Hi Edwin,

    mmh it confuses me even more. I don´t get the difference between my clusters and the default rel. humidity cluster. The default rel. Zigbee2MQTT can configure the humidity cluster 

    But the same logic doesn´t work for my cluster.

  • Hello,

    The picture you tried to upload didn't load properly. Can you please try to upload it again?

    Is there some way for me to reproduce what you are seeing using some DKs, without a 3rd party Zigbee coordinator? Are you able to reproduce it using the ncs-zigbee\samples\shell, and try to do the same that your zigbee coordinator is doing? 

    Best regards,

    Edvin

  • Hi Edvin,

    I will try it with the NCS Zigbee Shell on my nRF54L15 DK and my sensor. I get this output during the first test of the shell sample:

    uart:~$ bdb start
    Started router
    Done
    [00:00:04.469,697] <inf> zigbee_app_utils: Production configuration is not present or invalid (status: -1)
    [00:00:04.469,918] <inf> zigbee_app_utils: Zigbee stack initialized
    [00:00:04.471,944] <inf> zigbee_app_utils: Device started for the first time
    [00:00:04.471,952] <inf> zigbee_app_utils: Start network steering
    [00:00:04.471,959] <inf> zigbee_app_utils: Started network rejoin procedure.
    [00:00:08.167,488] <inf> zigbee_app_utils: Network steering was not successful (status: -1)
    [00:00:12.831,348] <inf> zigbee_app_utils: Network steering was not successful (status: -1)
    [00:00:19.488,889] <inf> zigbee_app_utils: Network steering was not successful (status: -1)
    [00:00:30.138,843] <inf> zigbee_app_utils: Network steering was not successful (status: -1)
    [00:00:48.788,114] <inf> zigbee_app_utils: Network steering was not successful (status: -1)
    

    And I flashed the Zigbee config before by running

    nrfjprog --program zigbee_config.hex --verify

    With this config

    # Enable channel 11 and 16
    channel_mask: 0x10800
    
    # You can add an installation code here
    # NOTE: This code must be added to the Zigbee network too!
    #install_code: 83FED3407A939723A5C639B26916D505
    
    # The Zigbee device address
    extended_address: 86D79AC5BAE41F11
    
    tx_power: 0
    
    # Use this to pass specific data to the application
    #app_data: 01ABCD
    

    Is there anything special to take care of when using this config file, or why isn´t the MCU using this config?

Related