NRF connect: 2.6.1 zigbee - Binding OnOff cluster via Home assistant - how to send 'on' command?

Setup:

  • windows 11
  • VS studio code
  • Toolchain and SDK v2.6.1
  • hardware: nRF52840DK  / PCA10056 (bought June 2024)
  • Using sdk/zigbee-Template as the base for the project.

Goal:

  • Mains powered Zigbee controller with multiple switches to turn on/off multiple devices in a room.
  • Using Home Assistant to bind a device/group to a switch on the controller.

What works:

  • adding an OnOff cluster. 
  • the device and cluster are recognized by Home Assistant
  • I can bind the device to a lightbulb via Home Assistant.
  • Every switch will have it's own endpoint switch one is endpoint 11.



The problem:

In the Switch example I found the following code to send an On or Off command. This example binds the switch directly to a lamp. In my situation the switch and the light are binded first to the coördinator. Then (using home assistant / zigbee2mqtt) the switch binds with the light.

Using Shell commands I can see that the switch knows which bindings exist. How can I modify the code below so that I can control the light?



/**@brief Function for sending ON/OFF requests to the light bulb.
 *
 * @param[in]   bufid    Non-zero reference to Zigbee stack buffer that will be
 *                       used to construct on/off request.
 * @param[in]   cmd_id   ZCL command id.
 */
static void light_switch_send_on_off(zb_bufid_t bufid, zb_uint16_t cmd_id)
{
	LOG_INF("Send ON/OFF command: %d", cmd_id);

	ZB_ZCL_ON_OFF_SEND_REQ(bufid,
			       bulb_ctx.short_addr, 
			       ZB_APS_ADDR_MODE_16_ENDP_PRESENT,
			       bulb_ctx.endpoint,
			       LIGHT_SWITCH_ENDPOINT,
			       ZB_AF_HA_PROFILE_ID,
			       ZB_ZCL_DISABLE_DEFAULT_RESPONSE,
			       cmd_id,
			       NULL);
}

Parents
  • Hello,

    Sorry for the late reply. 

    Please see this ticket:

     Trying to send from a switch to a coordinator 

    Try using ZB_ZCL_SET_ATTRIBUTE instead of ZB_ZCL_ON_OFF_SEND_REQ(). Then you can bind the cluster to the coordinator (from the coordinator side), and I believe that is it. Give it a go and let me know if it works.

    Best regards,

    Edvin

  • Hello Edvin,

    Thank you for the reply. I find it awesome that I got a notification that "an engineer had been assigned".

    Unfortunatelu I have not found out how to make your suggestion work.

    Here is the snippet I used to set the attribute, and also the rest of the code. My zigbee sniffer shows no signal after setting the attribute.

    void light_switch_send_on_off(int onOff)
    {
    	if(onOff){LOG_INF("Send on command:");}
    	else{LOG_INF("send off command:");}
    	
    
    	ZB_ZCL_SET_ATTRIBUTE(
    		ON_OFF_SWITCH_ENDPOINT,
    		ZB_ZCL_CLUSTER_ID_ON_OFF,
    		ZB_ZCL_CLUSTER_ANY_ROLE,
    		ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID,
    		onOff,
    		false);
    }


    /*
     * Copyright (c) 2021 Nordic Semiconductor ASA
     *
     * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
     */
    
    /** @file
     *
     * @brief Zigbee application template.
     */
    
    #include <zephyr/kernel.h>
    #include <zephyr/logging/log.h>
    #include <dk_buttons_and_leds.h>
    
    #include <zboss_api.h>
    #include <zboss_api_addons.h>
    #include <zigbee/zigbee_error_handler.h>
    #include <zigbee/zigbee_app_utils.h>
    #include <zigbee/zigbee_zcl_scenes.h>
    #include <zb_nrf_platform.h>
    #include "zb_range_extender.h"
    #include <stdio.h>
    #include <zboss_api_aps.h>
    #include <zb_zcl_common.h>
    
    /* Device endpoint, used to receive ZCL commands. */
    #define APP_TEMPLATE_ENDPOINT 10
    
    /* Device endpoint, used for on/off switch. */
    #define ON_OFF_SWITCH_ENDPOINT 11
    
    /* Version of the application software (1 byte). */
    #define REMOTE_INIT_BASIC_APP_VERSION 01
    
    /* Version of the implementation of the Zigbee stack (1 byte). */
    #define REMOTE_INIT_BASIC_STACK_VERSION 10
    
    /* Version of the hardware of the device (1 byte). */
    #define REMOTE_INIT_BASIC_HW_VERSION 11
    
    /* Manufacturer name (32 bytes). */
    #define REMOTE_INIT_BASIC_MANUF_NAME "DIY"
    
    /* Model number assigned by manufacturer (32-bytes long string). */
    #define REMOTE_INIT_BASIC_MODEL_ID "Light_Controller_v0.2"
    
    /* First 8 bytes specify the date of manufacturer of the device
     * in ISO 8601 format (YYYYMMDD). The rest (8 bytes) are manufacturer specific.
     */
    #define REMOTE_INIT_BASIC_DATE_CODE "20240629"
    
    /* Type of power sources available for the device.
     * For possible values see section 3.2.2.2.8 of ZCL specification.
     */
    #define REMOTE_INIT_BASIC_POWER_SOURCE ZB_ZCL_BASIC_POWER_SOURCE_DC_SOURCE
    
    /* Describes the physical location of the device (16 bytes).
     * May be modified during commissioning process.
     */
    #define REMOTE_INIT_BASIC_LOCATION_DESC "general"
    
    /* Describes the type of physical environment.
     * For possible values see section 3.2.2.2.10 of ZCL specification.
     */
    #define REMOTE_INIT_BASIC_PH_ENV ZB_ZCL_BASIC_ENV_UNSPECIFIED
    
    /* LED indicating that device successfully joined Zigbee network. */
    #define ZIGBEE_NETWORK_STATE_LED DK_LED3
    
    /* LED used for device identification. */
    #define IDENTIFY_LED DK_LED4
    
    /* Button used to enter the Identify mode. */
    #define IDENTIFY_MODE_BUTTON DK_BTN4_MSK
    
    /* Button to start Factory Reset */
    #define FACTORY_RESET_BUTTON IDENTIFY_MODE_BUTTON
    
    LOG_MODULE_REGISTER(app, LOG_LEVEL_INF);
    
    /* Main application customizable context.
     * Stores all settings and static values.
     */
    struct zb_device_ctx
    {
    	zb_zcl_basic_attrs_ext_t basic_attr;   // Should always be there
    	zb_zcl_identify_attrs_t identify_attr; // Should always be there
    	zb_uint8_t on_off_switch_type_attr;	   // for this device
    	zb_uint8_t on_off_switch_actions_attr; // for this device
    };
    
    /* Zigbee device application context storage. */
    static struct zb_device_ctx dev_ctx;
    
    ZB_ZCL_DECLARE_IDENTIFY_ATTRIB_LIST(
    	identify_attr_list,
    	&dev_ctx.identify_attr.identify_time);
    
    ZB_ZCL_DECLARE_BASIC_ATTRIB_LIST_EXT(
    	basic_attr_list,
    	&dev_ctx.basic_attr.zcl_version,
    	&dev_ctx.basic_attr.app_version,
    	&dev_ctx.basic_attr.stack_version,
    	&dev_ctx.basic_attr.hw_version,
    	dev_ctx.basic_attr.mf_name,
    	dev_ctx.basic_attr.model_id,
    	dev_ctx.basic_attr.date_code,
    	&dev_ctx.basic_attr.power_source,
    	dev_ctx.basic_attr.location_id,
    	&dev_ctx.basic_attr.ph_env,
    	dev_ctx.basic_attr.sw_ver);
    
    ZB_ZCL_DECLARE_ON_OFF_SWITCH_CONFIGURATION_ATTRIB_LIST(
    	on_off_switch_attr_list,
    	&dev_ctx.on_off_switch_type_attr,
    	&dev_ctx.on_off_switch_actions_attr);
    
    ZB_HA_DECLARE_ON_OFF_SWITCH_CLUSTER_LIST(
    	on_off_switch_clusters,
    	on_off_switch_attr_list,
    	basic_attr_list,
    	identify_attr_list);
    
    ZB_HA_DECLARE_ON_OFF_SWITCH_EP(
    	on_off_switch_ep,
    	ON_OFF_SWITCH_ENDPOINT,
    	on_off_switch_clusters);
    
    ZB_DECLARE_RANGE_EXTENDER_CLUSTER_LIST(
    	app_template_clusters,
    	basic_attr_list,
    	identify_attr_list);
    
    ZB_DECLARE_RANGE_EXTENDER_EP(
    	app_template_ep,
    	APP_TEMPLATE_ENDPOINT,
    	app_template_clusters);
    
    ZBOSS_DECLARE_DEVICE_CTX_2_EP( // This is a macro to declare 2 endpoints
    	app_template_ctx,
    	on_off_switch_ep,
    	app_template_ep);
    
    /**@brief Function for initializing all clusters attributes. */
    static void app_clusters_attr_init(void)
    {
    	/* Basic cluster attributes data */
    	dev_ctx.basic_attr.zcl_version = ZB_ZCL_VERSION;
    	dev_ctx.basic_attr.app_version = REMOTE_INIT_BASIC_APP_VERSION;
    	dev_ctx.basic_attr.stack_version = REMOTE_INIT_BASIC_STACK_VERSION;
    	dev_ctx.basic_attr.hw_version = REMOTE_INIT_BASIC_HW_VERSION;
    
    	/* Use ZB_ZCL_SET_STRING_VAL to set strings, because the first byte
    	 * should contain string length without trailing zero.
    	 *
    	 * For example "test" string will be encoded as:
    	 *   [(0x4), 't', 'e', 's', 't']
    	 */
    	ZB_ZCL_SET_STRING_VAL(
    		dev_ctx.basic_attr.mf_name,
    		REMOTE_INIT_BASIC_MANUF_NAME,
    		ZB_ZCL_STRING_CONST_SIZE(REMOTE_INIT_BASIC_MANUF_NAME));
    
    	ZB_ZCL_SET_STRING_VAL(
    		dev_ctx.basic_attr.model_id,
    		REMOTE_INIT_BASIC_MODEL_ID,
    		ZB_ZCL_STRING_CONST_SIZE(REMOTE_INIT_BASIC_MODEL_ID));
    
    	ZB_ZCL_SET_STRING_VAL(
    		dev_ctx.basic_attr.date_code,
    		REMOTE_INIT_BASIC_DATE_CODE,
    		ZB_ZCL_STRING_CONST_SIZE(REMOTE_INIT_BASIC_DATE_CODE));
    
    	dev_ctx.basic_attr.power_source = REMOTE_INIT_BASIC_POWER_SOURCE;
    
    	ZB_ZCL_SET_STRING_VAL(
    		dev_ctx.basic_attr.location_id,
    		REMOTE_INIT_BASIC_LOCATION_DESC,
    		ZB_ZCL_STRING_CONST_SIZE(REMOTE_INIT_BASIC_LOCATION_DESC));
    
    	dev_ctx.basic_attr.ph_env = REMOTE_INIT_BASIC_PH_ENV;
    
    	/* Identify cluster attributes data. */
    	dev_ctx.identify_attr.identify_time =
    		ZB_ZCL_IDENTIFY_IDENTIFY_TIME_DEFAULT_VALUE;
    }
    
    /**@brief Function for sending ON/OFF requests to the binded light bulb.
     *
     * 
     */
    void light_switch_send_on_off()
    {
    	LOG_INF("Send OFF command:");
    
    	ZB_ZCL_SET_ATTRIBUTE(
    		ON_OFF_SWITCH_ENDPOINT,
    		ZB_ZCL_CLUSTER_ID_ON_OFF,
    		ZB_ZCL_CLUSTER_ANY_ROLE,
    		ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID,
    		ZB_ZCL_ON_OFF_IS_OFF,
    		true);
    
    			ZB_ZCL_SET_ATTRIBUTE(
    		ON_OFF_SWITCH_ENDPOINT,
    		ZB_ZCL_CLUSTER_ID_ON_OFF,
    		ZB_ZCL_CLUSTER_ANY_ROLE,
    		ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID,
    		ZB_ZCL_ON_OFF_IS_ON,
    		true);
    }
    
    
    /**@brief Function to toggle the identify LED
     *
     * @param  bufid  Unused parameter, required by ZBOSS scheduler API.
     */
    static void toggle_identify_led(zb_bufid_t bufid)
    {
    	static int blink_status;
    
    	dk_set_led(IDENTIFY_LED, (++blink_status) % 2);
    	ZB_SCHEDULE_APP_ALARM(toggle_identify_led, bufid, ZB_MILLISECONDS_TO_BEACON_INTERVAL(100));
    	
    }
    
    /**@brief Function to handle identify notification events on the first endpoint.
     *
     * @param  bufid  Unused parameter, required by ZBOSS scheduler API.
     */
    static void identify_cb(zb_bufid_t bufid)
    {
    	zb_ret_t zb_err_code;
    
    	if (bufid)
    	{
    		/* Schedule a self-scheduling function that will toggle the LED */
    		ZB_SCHEDULE_APP_CALLBACK(toggle_identify_led, bufid);
    	}
    	else
    	{
    		/* Cancel the toggling function alarm and turn off LED */
    		zb_err_code = ZB_SCHEDULE_APP_ALARM_CANCEL(toggle_identify_led, ZB_ALARM_ANY_PARAM);
    		ZVUNUSED(zb_err_code);
    
    		dk_set_led(IDENTIFY_LED, 0);
    	}
    }
    
    /**@brief Starts identifying the device.
     *
     * @param  bufid  Unused parameter, required by ZBOSS scheduler API.
     */
    static void start_identifying(zb_bufid_t bufid)
    {
    	ZVUNUSED(bufid);
    
    	if (ZB_JOINED())
    	{
    		/* Check if endpoint is in identifying mode,
    		 * if not put desired endpoint in identifying mode.
    		 */
    		if (dev_ctx.identify_attr.identify_time ==
    			ZB_ZCL_IDENTIFY_IDENTIFY_TIME_DEFAULT_VALUE)
    		{
    
    			zb_ret_t zb_err_code = zb_bdb_finding_binding_target(
    				APP_TEMPLATE_ENDPOINT);
    
    			if (zb_err_code == RET_OK)
    			{
    				LOG_INF("Enter identify mode");
    			}
    			else if (zb_err_code == RET_INVALID_STATE)
    			{
    				LOG_WRN("RET_INVALID_STATE - Cannot enter identify mode");
    			}
    			else
    			{
    				ZB_ERROR_CHECK(zb_err_code);
    			}
    		}
    		else
    		{
    			LOG_INF("Cancel identify mode");
    			zb_bdb_finding_binding_target_cancel();
    		}
    	}
    	else
    	{
    		LOG_WRN("Device not in a network - cannot enter identify mode");
    	}
    }
    
    /**@brief Callback for button events.
     *
     * @param[in]   button_state  Bitmask containing buttons state.
     * @param[in]   has_changed   Bitmask containing buttons
     *                            that have changed their state.
     */
    static void button_changed(uint32_t button_state, uint32_t has_changed)
    {
    	if (IDENTIFY_MODE_BUTTON & has_changed)
    	{
    		light_switch_send_on_off();
    		if (IDENTIFY_MODE_BUTTON & button_state)
    		{
    			/* Button changed its state to pressed */
    		}
    		else
    		{
    			/* Button changed its state to released */
    			if (was_factory_reset_done())
    			{
    				/* The long press was for Factory Reset */
    				LOG_DBG("After Factory Reset - ignore button release");
    			}
    			else
    			{
    				/* Button released before Factory Reset */
    
    				/* Start identification mode */
    				ZB_SCHEDULE_APP_CALLBACK(start_identifying, 0);
    			}
    		}
    	}
    
    	check_factory_reset_button(button_state, has_changed);
    }
    
    /**@brief Function for initializing LEDs and Buttons. */
    static void configure_gpio(void)
    {
    	int err;
    
    	err = dk_buttons_init(button_changed);
    	if (err)
    	{
    		LOG_ERR("Cannot init buttons (err: %d)", err);
    	}
    
    	err = dk_leds_init();
    	if (err)
    	{
    		LOG_ERR("Cannot init LEDs (err: %d)", err);
    	}
    }
    
    /**@brief Zigbee stack event handler.
     *
     * @param[in]   bufid   Reference to the Zigbee stack buffer
     *                      used to pass signal.
     */
    void zboss_signal_handler(zb_bufid_t bufid)
    {
    	/* Update network status LED. */
    	zigbee_led_status_update(bufid, ZIGBEE_NETWORK_STATE_LED);
    
    	/* No application-specific behavior is required.
    	 * Call default signal handler.
    	 */
    	ZB_ERROR_CHECK(zigbee_default_signal_handler(bufid));
    
    	/* All callbacks should either reuse or free passed buffers.
    	 * If bufid == 0, the buffer is invalid (not passed).
    	 */
    	if (bufid)
    	{
    		zb_buf_free(bufid);
    	}
    }
    
    void print_ieee_addr(zb_ieee_addr_t addr)
    {
    	for (int i = 0; i < 8; i++)
    	{
    		printf("%02X", addr[i]);
    		if (i < 7)
    			printf(":");
    	}
    }
    
    static void GetBindingTable(void)
    { // hierdoor crasht alles. dus nog niet de oplossing
    	// So far this seems to be needed to controll a device that is binded through Home Assistant.
    	// But outside a shell command this function seems unavailabe.
    	// ZB_NVRAM_APS_BINDING_DATA Binding data wordt opgeslagen in de zb_nvram
    
    	zb_zdo_binding_table_record_t buffer;
    	// zb_nvram_read_data(1, ZB_NVRAM_APS_BINDING_DATA, buffer,1000 );
    	zb_ret_t ret;
    	ret = zb_nvram_read_data(1, ZB_NVRAM_APS_BINDING_DATA, (zb_uint8_t *)&buffer, 200);
    
    	if (ret == 0)
    	{ // Assuming 0 is success
    		printf("Source Address: ");
    		print_ieee_addr(buffer.src_address);
    		printf("\n");
    
    		printf("Source Endpoint: %d\n", buffer.src_endp);
    		printf("Cluster ID: 0x%04X\n", buffer.cluster_id);
    		printf("Destination Address Mode: %d\n", buffer.dst_addr_mode);
    
    		printf("Destination Address: ");
    		if (buffer.dst_addr_mode == 2)
    		{ // Assuming 2 means IEEE address
    			print_ieee_addr(buffer.dst_address.addr_long);
    		}
    		else
    		{
    			printf("0x%04X", buffer.dst_address.addr_short);
    		}
    		printf("\n");
    
    		if (buffer.dst_addr_mode == 2)
    		{ // Only if 64-bit extended address
    			printf("Destination Endpoint: %d\n", buffer.dst_endp);
    		}
    	}
    	else
    	{
    		printf("Failed to read data from NVRAM\n");
    	}
    }
    
    
    
    int main(void)
    {
    	LOG_INF("Starting Zigbee application template example");
    
    	/* Initialize */
    	configure_gpio();
    	register_factory_reset_button(FACTORY_RESET_BUTTON);
    
    	/* Register device context (endpoints). */
    	ZB_AF_REGISTER_DEVICE_CTX(&app_template_ctx);
    
    	app_clusters_attr_init();
    
    	/* Register handlers to identify notifications */
    	ZB_AF_SET_IDENTIFY_NOTIFICATION_HANDLER(APP_TEMPLATE_ENDPOINT, identify_cb);
    
    	/* Start Zigbee default thread */
    	zigbee_enable();
    
    	LOG_INF("Zigbee application template started");
    
    
    	
    
    	return 0;
    }
    

  • Hello,

    Joeri_ said:
    Here is the snippet I used to set the attribute, and also the rest of the code. My zigbee sniffer shows no signal after setting the attribute.

    Did you bind the attribute to your coordinator before capturing the sniffer trace? If not, then the device has nowhere to send the update, so in that case, I don't think you will see it in a sniffer trace.

    Best regards,

    Edvin

Reply
  • Hello,

    Joeri_ said:
    Here is the snippet I used to set the attribute, and also the rest of the code. My zigbee sniffer shows no signal after setting the attribute.

    Did you bind the attribute to your coordinator before capturing the sniffer trace? If not, then the device has nowhere to send the update, so in that case, I don't think you will see it in a sniffer trace.

    Best regards,

    Edvin

Children
  • Hi Edvin,

    I assumed that the sniffers showed every message send over the network.
    I just tried again:

    Use Zigbee2mqtt in HomeAssistant I first bound endpoint 11 with OnOff attribute to the coördinator. After that I bound it to a light bulb. You can see the wireshark log when binding to the coördinator.

    those messages I see. My device is '0x2727'. 

    Further there are no messages from or to my device regarding the onOff attribute.


  • Hello,

    I am sorry, but I am not able to decrypt your trace without the network key. 

    I see that there are some packets from 0x2727 to destination 0x0000 later, such as 288, but I am not able to tell what it is (without decrypting). 

    You may not want to upload your network key in a public ticket. I suggest that you create a private ticket, where you can link to this ticket, where you upload your trace and your network key, so that we can have a look.

    Best regards,

    Edvin

Related