[Zigbee] Manufacturer Code clarification

Setup: 

nrf52840DK (Zigbee Cordinator based on Zigbee Coordinator sample)

nrf52840DK (ZED)

nrf52840 dongle (Wireshark sniffer)

NCS v2.4.1

Hi,

In my application I am using Manufacturer specific ZCL commands and attributes. All of that seems working fine but I am little bit confused regarding the Manufacturer Code usage.

There are two magic values in NCS: 
0x1234 defined as:

#define ZB_MANUFACTURER_CODE_DSR            0x1234U
/* That is not a joke, our manufacturer code is really 1234! */
/*! @endcond */ /* internals_doc */

#define ZB_DEFAULT_MANUFACTURER_CODE ZB_MANUFACTURER_CODE_DSR

Manufacturer Code of DSR for all applications based on ZBOSS.

0x127F defined as: 

config ZIGBEE_FOTA_MANUFACTURER_ID
	hex "Manufacturer ID"
	default 0x127F
	range 0x0000 0xFFFF
	help
	  This is the ZigBee assigned identifier for each member company.
	  0x127F - Nordic Semiconductor
	  0xFFFF - wild card value has a 'match all' effect.

Manufacturer Code of Nordic Semiconductor used only for FOTA purposes to fill up OTA Header with it.

  1. What happens if the manufacturer code placed in the OTA Header of the received image is different from the one set on the device (0x127F by default)?
  2. Which Manufacturer Code should be used in Cluster descriptor ?
    /** @brief ZCL cluster description. */
    typedef ZB_PACKED_PRE struct zb_zcl_cluster_desc_s
    {
      zb_uint16_t cluster_id; /**< ZCL 16-bit cluster id. @see zcl_cluster_id */
      zb_uint16_t attr_count;  /**< Attributes number supported by the cluster */
      struct zb_zcl_attr_s *attr_desc_list; /**< List of cluster attributes,
             declared using ZB_ZCL_DECLARE_<CLUSTER_NAME>_ATTRIB_LIST() macro */
      zb_uint8_t role_mask;   /**< Cluster role, refer to zcl_cluster_role */
      zb_uint16_t manuf_code; /**< Manufacturer code for cluster and its attributes */
      zb_zcl_cluster_init_t cluster_init;
    } ZB_PACKED_STRUCT
    zb_zcl_cluster_desc_t;
  3. Is it possible to overwrite #ZB_DEFAULT_MANUFACTURER_CODE ?
  4. Why #ZB_DEFAULT_MANUFACTURER_CODE is not overwritten by Nordic Semiconductor Manufacturer Code (0x127F) ? 

I am looking forward to hearing from you,

Pawel

  • Hello Pawel,

    I am terribly sorry for the delayed reply. 

    pwpot said:
    I couldn't find any information regarding this. I think Option 2 is incorrect, and we should consider Options 1 and 3.

    I agree. But I will forward this to out Zigbee team for sanity checking.

  • Hello Pawel,

    After consulting with our Zigbee team, this is what I got:

    ----------------------------

    You need to use manufacturer code for any manufacturer specific extension.
    As the spec (ZCL8 ch. 2.3.3) states, you can extend the standard in the following ways:

    Add manufacturer specific clusters to a standard device endpoint.

    Add manufacturer specific commands to a standard cluster.

    Add manufacturer specific attributes to a standard cluster.

    In all cases, you need to specify that the extension is manufacturer specific. So in the customer’s case, they will need to add ZB_ZCL_ATTR_MANUF_SPEC to the manufacturer specific attributes. The cluster itself does not need manufacturer code if it is a standard cluster.

    When it comes to communication, all communication regarding the manufacturer specific extension must be transmitted with the manufacturer sub-field set to 1 and with the manufacturer code. That goes both for the device itself and for remote devices, e.g., a remote device sending a read attributes command to read the manufacturer specific attribute.

    ----------------------------

    I hope this helps. Basically, all custom clusters, commands and attributes needs to set the manufacturer sub-field to 1, and use the manufacturer code that corresponds with this feature.

    Best regards,

    Edvin

  • Hi Edvin,

    Thank you for the detailed description.

    I am a little bit unsure about the manufacturer-specific attribute within the standard cluster scenario in terms of attribute reporting.

    See zb_zcl_send_report_attr_command function: 

    void zb_zcl_send_report_attr_command(zb_zcl_reporting_info_t *rep_info, zb_uint8_t param)
    {
      zb_uint8_t *cmd_data;
      zb_zcl_reporting_info_t *cur_rep_info;
      zb_zcl_attr_t *attr_desc;
      zb_uint16_t bytes_avail;
      zb_uint8_t attr_size;
      zb_uint8_t send_mode;
      zb_uint8_t is_manuf_spec;
    
      TRACE_MSG(
          TRACE_ZCL1,
          ">> zb_zcl_send_report_attr_command rep_info %p, param %hd",
          (FMT__P_H, rep_info, param));
    
      attr_desc =
        zb_zcl_get_attr_desc_a(rep_info->ep, rep_info->cluster_id, rep_info->cluster_role, rep_info->attr_id);
    
      is_manuf_spec = !!ZB_ZCL_IS_ATTR_MANUF_SPEC(attr_desc);
    
      /* ZCL spec, 2.4.11 Report Attributes Command */
      /* Read attribute command
         | ZCL header 3 b | Attr Report 1 XX b | Attr Report 1 XX b | ...
    
         Attr Report format
         | attr id 2 b |  Attr data type 1 b | attr value XX b |
      */
      /* Construct packet header */
      /* Use buffer specified by input param */
      cmd_data = ZB_ZCL_START_PACKET(param);
    
      /* NOTE: currently, manufacturer specific is not supported */
      ZB_ZCL_CONSTRUCT_GENERAL_COMMAND_REQ_FRAME_CONTROL_A(
        cmd_data, ZB_ZCL_FRAME_DIRECTION_TO_CLI,
        is_manuf_spec, ZB_ZCL_ENABLE_DEFAULT_RESPONSE);
    
      if (is_manuf_spec)
      {
        zb_af_endpoint_desc_t *ep_desc = zb_af_get_endpoint_desc(rep_info->ep);
        zb_zcl_cluster_desc_t *cluster_desc = get_cluster_desc(ep_desc, rep_info->cluster_id, rep_info->cluster_role);
    
        ZB_ASSERT(ep_desc && cluster_desc && cluster_desc->manuf_code != ZB_ZCL_MANUF_CODE_INVALID);
    
        ZB_ZCL_CONSTRUCT_COMMAND_HEADER_EXT(cmd_data, ZB_ZCL_GET_SEQ_NUM(), ZB_TRUE, cluster_desc->manuf_code, ZB_ZCL_CMD_REPORT_ATTRIB);
      }
      else
      {
        ZB_ZCL_CONSTRUCT_COMMAND_HEADER(cmd_data, ZB_ZCL_GET_SEQ_NUM(), ZB_ZCL_CMD_REPORT_ATTRIB);
      }
    
      cur_rep_info = rep_info;
    
      while (cur_rep_info)
      {
        attr_desc =
          zb_zcl_get_attr_desc_a(cur_rep_info->ep, cur_rep_info->cluster_id, cur_rep_info->cluster_role, cur_rep_info->attr_id);
        TRACE_MSG(TRACE_ZCL3, "attr_desc %p", (FMT__P, attr_desc));
    
        /* attribute description could not be absent, it is checked while accepting configure report
           command */
        ZB_ASSERT(attr_desc);
    
        bytes_avail = ZB_ZCL_GET_BYTES_AVAILABLE(param, cmd_data,
                                                 cur_rep_info->dst.profile_id, cur_rep_info->cluster_id);
        TRACE_MSG(TRACE_ZCL3, "bytes_avail %hd", (FMT__H, bytes_avail));
    
        TRACE_MSG(
            TRACE_ZCL3,
            "attribute: id 0x%x, type 0x%hx",
            (FMT__D_H, attr_desc->id, attr_desc->type));
        attr_size = zb_zcl_get_attribute_size(attr_desc->type, attr_desc->data_p);
        TRACE_MSG(TRACE_ZCL3, "attr_size %hd", (FMT__H, attr_size));
    
        /* Decrement read attr response by sizeof(zb_uint8_t), because attr value size is
         * calculated separately by attr_size  */
        if (bytes_avail >= (sizeof(zb_zcl_report_attr_req_t) - sizeof(zb_uint8_t) + attr_size))
        {
          ZB_ZCL_PACKET_PUT_DATA16_VAL(cmd_data, attr_desc->id);
          ZB_ZCL_PACKET_PUT_DATA8(cmd_data, attr_desc->type);
          cmd_data = zb_zcl_put_attribute_value(cmd_data, attr_desc, attr_desc->data_p, attr_size);
    
          zb_zcl_save_reported_value(cur_rep_info, attr_desc);
    
          ZB_ZCL_CLR_REPORTING_FLAG(cur_rep_info, ZB_ZCL_REPORT_ATTR);
          ZB_ZCL_CLR_REPORTING_FLAG(cur_rep_info, ZB_ZCL_REPORT_IS_ALLOWED);
          ZB_ZCL_CLR_REPORTING_FLAG(cur_rep_info, ZB_ZCL_REPORT_TIMER_STARTED);
          ZB_ZCL_SET_REPORTING_FLAG(cur_rep_info, ZB_ZCL_REPORT_IS_SENT);
        }
        else
        {
          TRACE_MSG(TRACE_ZCL1, "ERROR, buffer is full", (FMT__0));
          break;
        }
    
        /* get next report info for sending report to the same remote device */
        cur_rep_info = zb_zcl_get_next_reporting_info(rep_info, is_manuf_spec);
        TRACE_MSG(TRACE_ZCL1, "cur_rep_info %p", (FMT__P, cur_rep_info));
      }
    
      /* We should always use bindings for reporting (ZCL8, 2.5.7.1.2; ZCL8, 2.5.11.2) */
      send_mode = ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT;
      TRACE_MSG(TRACE_ZCL1, "ZCL_REPORT send mode ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT:", (FMT__0));
    
      ZB_ZCL_FINISH_N_SEND_PACKET(param, cmd_data,
          rep_info->dst.short_addr,
          send_mode,
          rep_info->dst.endpoint,
          rep_info->ep /* src ep */,
          rep_info->dst.profile_id,
          rep_info->cluster_id,
          zb_zcl_reporting_cb);
    
      TRACE_MSG(TRACE_ZCL1, "<< zb_zcl_send_report_attr_command ", (FMT__0));
    }

    Especially that part : 

     if (is_manuf_spec)
      {
        zb_af_endpoint_desc_t *ep_desc = zb_af_get_endpoint_desc(rep_info->ep);
        zb_zcl_cluster_desc_t *cluster_desc = get_cluster_desc(ep_desc, rep_info->cluster_id, rep_info->cluster_role);
    
        ZB_ASSERT(ep_desc && cluster_desc && cluster_desc->manuf_code != ZB_ZCL_MANUF_CODE_INVALID);
    
        ZB_ZCL_CONSTRUCT_COMMAND_HEADER_EXT(cmd_data, ZB_ZCL_GET_SEQ_NUM(), ZB_TRUE, cluster_desc->manuf_code, ZB_ZCL_CMD_REPORT_ATTRIB);
      }

    is_manuf_spec is determined based on the attribute descriptor.

    Assuming the manufacturer-specific attribute within the standard cluster has a REPORTING access set, then we have to pass the ASSERT condition which is: 

    ZB_ASSERT(ep_desc && cluster_desc && cluster_desc->manuf_code != ZB_ZCL_MANUF_CODE_INVALID)
    AFAIU that single line forces us to give the standard cluster Manufacturer-specific code.

    Let me know what you think.

    Maybe things changed in NCS 2.6.0 and we are not aligned :) 

    I am looking forward to hearing from you

  • In fact, this is what it looks like in NCS v2.6.0:

    void zb_zcl_send_report_attr_command(zb_zcl_reporting_info_t *rep_info, zb_uint8_t param)
    {
      zb_uint8_t *cmd_data;
      zb_zcl_reporting_info_t *cur_rep_info;
      zb_zcl_attr_t *attr_desc;
      zb_uint16_t bytes_avail;
      zb_uint8_t attr_size;
      zb_uint8_t send_mode;
      zb_uint8_t is_manuf_spec;
    
      TRACE_MSG(
          TRACE_ZCL1,
          ">> zb_zcl_send_report_attr_command rep_info %p, param %hd",
          (FMT__P_H, rep_info, param));
    
      attr_desc =
        zb_zcl_get_attr_desc_manuf_a(
          rep_info->ep,
          rep_info->cluster_id,
          rep_info->cluster_role,
          rep_info->attr_id,
          rep_info->manuf_code);
    
      is_manuf_spec = !!ZB_ZCL_IS_ATTR_MANUF_SPEC(attr_desc);
    
      /* ZCL spec, 2.4.11 Report Attributes Command */
      /* Read attribute command
         | ZCL header 3 b | Attr Report 1 XX b | Attr Report 1 XX b | ...
    
         Attr Report format
         | attr id 2 b |  Attr data type 1 b | attr value XX b |
      */
      /* Construct packet header */
      /* Use buffer specified by input param */
      cmd_data = ZB_ZCL_START_PACKET(param);
    
      /* NOTE: currently, manufacturer specific is not supported */
      ZB_ZCL_CONSTRUCT_GENERAL_COMMAND_REQ_FRAME_CONTROL_A(
        cmd_data, ZB_ZCL_FRAME_DIRECTION_TO_CLI,
        is_manuf_spec, ZB_ZCL_ENABLE_DEFAULT_RESPONSE);
    
      if (is_manuf_spec)
      {
        TRACE_MSG(TRACE_ERROR, "rep_info->manuf_code 0x%x", (FMT__D, rep_info->manuf_code));
        ZB_ASSERT(rep_info->manuf_code != ZB_ZCL_NON_MANUFACTURER_SPECIFIC);
    
        ZB_ZCL_CONSTRUCT_COMMAND_HEADER_EXT(cmd_data, ZB_ZCL_GET_SEQ_NUM(), ZB_TRUE, rep_info->manuf_code, ZB_ZCL_CMD_REPORT_ATTRIB);
      }

    And it looks like ZB_ZCL_MANUF_CODE_INVALID = 0x0000 and ZB_ZCL_NON_MANUFACTURER_SPECIFIC = 0xFFFF. I guess the callstack up until this is a bit different as well. Perhaps the default (if nothing else is specified) manuf_spec code has changed from 0x0000 to 0xFFFF. 

    pwpot said:
    AFAIU that single line forces us to give the standard cluster Manufacturer-specific code.

    But only if 

    is_manuf_spec = !!ZB_ZCL_IS_ATTR_MANUF_SPEC(attr_desc);

    is true, right?

    Does this return true if you are using a standard cluster as well?

    To me, it looks like if the command is for a manufacturer specific cluster, then it checks whether the manufacturer code is not 0x0000 (in v2.4.0) and not 0xFFFF (in 2.6.0). Are you seeing something else?

    BR,

    Edvin

  • Hi,

    is_manuf_spec = !!ZB_ZCL_IS_ATTR_MANUF_SPEC(attr_desc);

    returns true indeed. That is because my attribute descriptor looks as follows: 

    #define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_DIAGNOSTIC_FAULT_CODE_ID(data_ptr) \
        { \
            ZB_ZCL_ATTR_DIAGNOSTIC_FAULT_CODE_ID, ZB_ZCL_ATTR_TYPE_U16, \
                ZB_ZCL_ATTR_ACCESS_READ_ONLY | ZB_ZCL_ATTR_ACCESS_REPORTING | ZB_ZCL_ATTR_MANUF_SPEC, (void *)data_ptr \
        }

    Then: 

    ZB_ASSERT(ep_desc && cluster_desc && cluster_desc->manuf_code != ZB_ZCL_MANUF_CODE_INVALID);

    Manufacturer Code shall be different than 0x0000, otherwise the ASSERT occurs.

    I see that the reporting mechanism is slightly different in 2.6.0. I am going to migrate to 2.6.0 but no matter the version my question will be the same :) 

    At this point, In my opinion in the case of manufacturer-specific attributes within the standard cluster, the cluster descriptor shall use a Manufacturer Code different than 0x0000 and that is valid for NCS 2.4.1.

    Maybe, that exact scenario is solved in 2.6.0 because the ASSERT does not check cluster_desc->manuf_code but rep_info->manuf_code. 

    What do you think? 

    Regards,

    Pawel

Related