Implementing a zigbee endpoint callback handler

I'm stuck trying to add a callback handler to a simple HA ON OFF SWITCH application.

The application is running on the nrf52840 DK. The coordinator is Zigbee2MQTT (Z2M). The SDK is connect SDK 1.9.1

Here's my DK code:

/*
 * Copyright (c) 2021 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
 */

/** @file
 *
 * @brief Zigbee application template.
 */

#include <zephyr.h>
#include <logging/log.h>
#include <dk_buttons_and_leds.h>

#include <zboss_api.h>
#include <zigbee/zigbee_error_handler.h>
#include <zigbee/zigbee_app_utils.h>
#include <zb_nrf_platform.h>

#include "zb_mem_config_med.h"
#include "zboss_api_addons.h"

// Endpoint IDs. Don't use CONFIG_ZIGBEE_FOTA_ENDPOINT, which is by default set to 10.

#define HA_ON_OFF_SWITCH_ENDPOINT		1
#define HA_WINDOW_COVERING_ENDPOINT		2


#define ERASE_PERSISTENT_CONFIG		ZB_TRUE
// Bitfield. bit set = select channel. Valid channels are 11 - 26.
// 0x07fff800U = Scan all channels, (1L << 11) | (1L << 12) = scan only channel 11 and 12.
#define IEEE_CHANNEL_MASK			(ZB_TRANSCEIVER_ALL_CHANNELS_MASK)


/* Version of the application software (1 byte). */
#define INIT_BASIC_APP_VERSION     ZB_ZCL_BASIC_APPLICATION_VERSION_DEFAULT_VALUE

/* Version of the implementation of the Zigbee stack (1 byte). */
#define INIT_BASIC_STACK_VERSION   ZB_ZCL_BASIC_STACK_VERSION_DEFAULT_VALUE

/* Version of the hardware of the device (1 byte). */
#define INIT_BASIC_HW_VERSION      ZB_ZCL_BASIC_HW_VERSION_DEFAULT_VALUE

/* Manufacturer name (32 bytes). */
#define INIT_BASIC_MANUF_NAME      "test_manufacturer"

/* Model number assigned by manufacturer (32-bytes long string). */
#define INIT_BASIC_MODEL_ID        "test_model_name"

/* First 8 bytes specify the date of manufacturer of the device
 * in ISO 8601 format (YYYYMMDD). The rest (8 bytes) are manufacturer specific.
 */
#define INIT_BASIC_DATE_CODE       "20220901"

/* Type of power sources available for the device.
 * For possible values see section 3.2.2.2.8 of ZCL specification.
 */
#define INIT_BASIC_POWER_SOURCE    ZB_ZCL_BASIC_POWER_SOURCE_DC_SOURCE

/* Describes the physical location of the device (16 bytes).
 * May be modified during commisioning process.
 */
#define INIT_BASIC_LOCATION_DESC   "Office desk"

/* Software Version (16 bytes). */
#define INIT_BASIC_SW_VER			"0.1"



/* LED indicating that device successfully joined Zigbee network. */
#define ZIGBEE_NETWORK_STATE_LED            DK_LED1

/* Button used to enter the Identify mode. */
#define IDENTIFY_MODE_BUTTON                DK_BTN1_MSK

LOG_MODULE_REGISTER(app, LOG_LEVEL_INF);

static void switch_send_on_off(zb_bufid_t bufid, zb_uint16_t cmd_id);

typedef struct
{
	zb_uint8_t type;
	zb_uint8_t actions;
} zb_zcl_on_off_switch_attrs_t;

/* Main application customizable context.
 * Stores all settings and static values.
 */
struct zb_device_ctx {
	zb_zcl_basic_attrs_ext_t		basic_attr;
	zb_zcl_identify_attrs_t			identify_attr;
    zb_zcl_scenes_attrs_t			scenes_attr;
    zb_zcl_groups_attrs_t			groups_attr;

	zb_zcl_on_off_switch_attrs_t	on_off_switch_attr;
};

/* Zigbee device application context storage. */
static struct zb_device_ctx dev_ctx;


// ATTRIB_LIST ------------------------------------------------------------------------------------
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_IDENTIFY_ATTRIB_LIST(identify_attr_list,
	&dev_ctx.identify_attr.identify_time
);

ZB_ZCL_DECLARE_SCENES_ATTRIB_LIST(scenes_attr_list,
	&dev_ctx.scenes_attr.scene_count,
	&dev_ctx.scenes_attr.current_scene,
	&dev_ctx.scenes_attr.current_group,
	&dev_ctx.scenes_attr.scene_valid,
	&dev_ctx.scenes_attr.name_support
);

ZB_ZCL_DECLARE_GROUPS_ATTRIB_LIST(groups_attr_list, 
 	&dev_ctx.groups_attr.name_support
);

ZB_ZCL_DECLARE_ON_OFF_SWITCH_CONFIGURATION_ATTRIB_LIST(on_off_switch_attr_list,
	&dev_ctx.on_off_switch_attr.type,
	&dev_ctx.on_off_switch_attr.actions
);


// CLUSTER_LIST -----------------------------------------------------------------------------------
ZB_HA_DECLARE_ON_OFF_SWITCH_CLUSTER_LIST(on_off_switch_clusters,
	on_off_switch_attr_list,
	basic_attr_list,
	identify_attr_list
);

// EP ---------------------------------------------------------------------------------------------
ZB_HA_DECLARE_ON_OFF_SWITCH_EP(on_off_switch_ep,
	HA_ON_OFF_SWITCH_ENDPOINT,
	on_off_switch_clusters
);

// ZBOSS_DECLARE_DEVICE_CTX_x_EP ------------------------------------------------------------------
	ZBOSS_DECLARE_DEVICE_CTX_1_EP(app_device_ctx,
		on_off_switch_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   = INIT_BASIC_APP_VERSION;
	dev_ctx.basic_attr.stack_version = INIT_BASIC_STACK_VERSION;
	dev_ctx.basic_attr.hw_version    = 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 wil be encoded as:
	 *   [(0x4), 't', 'e', 's', 't']
	 */
	ZB_ZCL_SET_STRING_VAL(
		dev_ctx.basic_attr.mf_name,
		INIT_BASIC_MANUF_NAME,
		ZB_ZCL_STRING_CONST_SIZE(INIT_BASIC_MANUF_NAME));

	ZB_ZCL_SET_STRING_VAL(
		dev_ctx.basic_attr.model_id,
		INIT_BASIC_MODEL_ID,
		ZB_ZCL_STRING_CONST_SIZE(INIT_BASIC_MODEL_ID));

	ZB_ZCL_SET_STRING_VAL(
		dev_ctx.basic_attr.date_code,
		INIT_BASIC_DATE_CODE,
		ZB_ZCL_STRING_CONST_SIZE(INIT_BASIC_DATE_CODE));

	dev_ctx.basic_attr.power_source = INIT_BASIC_POWER_SOURCE;

	ZB_ZCL_SET_STRING_VAL(
		dev_ctx.basic_attr.location_id,
		INIT_BASIC_LOCATION_DESC,
		ZB_ZCL_STRING_CONST_SIZE(INIT_BASIC_LOCATION_DESC));
	
	dev_ctx.basic_attr.ph_env = ZB_ZCL_BASIC_PHYSICAL_ENVIRONMENT_DEFAULT_VALUE;

	ZB_ZCL_SET_STRING_VAL(
		dev_ctx.basic_attr.sw_ver,
		INIT_BASIC_SW_VER,
		ZB_ZCL_STRING_CONST_SIZE(INIT_BASIC_SW_VER));


	/* Identify cluster attributes data. */
	dev_ctx.identify_attr.identify_time	= ZB_ZCL_IDENTIFY_IDENTIFY_TIME_DEFAULT_VALUE;

	// HA on/off switch
	dev_ctx.on_off_switch_attr.type		= ZB_ZCL_ON_OFF_SWITCH_CONFIGURATION_SWITCH_TYPE_TOGGLE;
	dev_ctx.on_off_switch_attr.actions	= ZB_ZCL_ON_OFF_SWITCH_CONFIGURATION_SWITCH_ACTIONS_DEFAULT_VALUE;
}

/**@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)
{
static bool x = 0;

	// Has the IDENTIFY_MODE_BUTTON button been pressed?
	if (IDENTIFY_MODE_BUTTON & has_changed) 
	{
		if (IDENTIFY_MODE_BUTTON & button_state) 
		{
			/* Button changed its state to pressed */
			LOG_INF("btn is on");
		}
		else
		{
			/* Button changed its state to released */
			LOG_INF("btn is off");
			
			// Send a msg to Zigbee2Mqtt co-ord.
			zb_uint16_t cmd_id = (x == 0) ? ZB_ZCL_CMD_ON_OFF_ON_ID : ZB_ZCL_CMD_ON_OFF_OFF_ID;
			zb_ret_t zb_err_code = zb_buf_get_out_delayed_ext(switch_send_on_off, cmd_id, 0);
			ZB_ERROR_CHECK(zb_err_code);			

			x ^= 1;	// Next time this func is called, send the oppsite state.
		}
	}
}


/**@brief Function for sending ON/OFF requests to the network / co-ordinator.
 *
 * @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 switch_send_on_off(zb_bufid_t bufid, zb_uint16_t cmd_id)
{
	LOG_INF("Send ON/OFF command: %d", cmd_id);
	zb_uint16_t coordinator_address = 0x0000;
	dev_ctx.on_off_switch_attr.actions = (zb_bool_t)cmd_id;				

	ZB_ZCL_ON_OFF_SEND_REQ(bufid,
				coordinator_address,
				ZB_APS_ADDR_MODE_16_ENDP_PRESENT,
				HA_ON_OFF_SWITCH_ENDPOINT,
				HA_ON_OFF_SWITCH_ENDPOINT,
				ZB_AF_HA_PROFILE_ID,
				ZB_ZCL_ENABLE_DEFAULT_RESPONSE,
				cmd_id,
				NULL);
}


static zb_uint8_t on_off_switch_ep_handler(zb_bufid_t bufid)
{
//	zb_zcl_device_callback_param_t *device_cb_param = ZB_BUF_GET_PARAM(bufid, zb_zcl_device_callback_param_t);


	return ZB_FALSE;
}

/**@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)
{
	zb_zdo_app_signal_hdr_t *p_sg_p = NULL;
	zb_zdo_app_signal_type_t sig = zb_get_app_signal(bufid, &p_sg_p);
	zb_ret_t status = ZB_GET_APP_SIGNAL_STATUS(bufid);


	/* Update network status LED. */
	zigbee_led_status_update(bufid, ZIGBEE_NETWORK_STATE_LED);

	switch (sig) 
	{
		case ZB_BDB_SIGNAL_DEVICE_REBOOT:
		/* fall-through */

		case ZB_BDB_SIGNAL_STEERING:
			/* Call default signal handler. */
			ZB_ERROR_CHECK(zigbee_default_signal_handler(bufid));
			if (status == RET_OK) 
			{
			}
			break;

		default:
			/* Call default signal handler. */
			ZB_ERROR_CHECK(zigbee_default_signal_handler(bufid));
			break;
	}

	/* 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 main(void)
{
	LOG_INF("Starting Zigbee application");

	/* Initialize */
	configure_gpio();
	
	zb_set_network_ed_role(IEEE_CHANNEL_MASK);
	zigbee_erase_persistent_storage(ERASE_PERSISTENT_CONFIG);

	
	/* Register device context (endpoints). */
	memset( &dev_ctx, 0, sizeof(dev_ctx) );
	ZB_AF_REGISTER_DEVICE_CTX(&app_device_ctx);

	app_clusters_attr_init();

	ZB_AF_SET_ENDPOINT_HANDLER(HA_ON_OFF_SWITCH_ENDPOINT, on_off_switch_ep_handler);

	zigbee_enable();
	

	LOG_INF("Zigbee application started");
}

Problem 1:

Z2M allows the DK device to join.  During Z2M initialisation, Z2M attempts to read data from the DK, but results in an error. I think this is what the EP callback should be handling, but I cannot find any information of how to implement the EP handler.

Problem 2:

When a button is pressed on the DK, the ON OFF state is sent to Z2M, Z2M logs show this:

Debug 2022-09-06 13:44:42
Received Zigbee message from '0xf4ce367b62161a91', type 'commandOff', cluster 'genOnOff', data '{}' from endpoint 1 with groupID 0
Debug 2022-09-06 13:44:42
No converter available for 'test_manuf' with cluster 'genOnOff' and type 'commandOff' and data '{}'

Z2M custom device file (placed in a .js file next to the configuration.yaml file) :

const fz = require('zigbee-herdsman-converters/converters/fromZigbee');
const tz = require('zigbee-herdsman-converters/converters/toZigbee');
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const reporting = require('zigbee-herdsman-converters/lib/reporting');
const extend = require('zigbee-herdsman-converters/lib/extend');
const e = exposes.presets;
const ea = exposes.access;

const definition = {
    zigbeeModel: ['test_model_name'],
    model: 'test_manuf',
    vendor: 'test_vendor',
    description: 'nRF zigbee test device',
    fromZigbee: [fz.on_off, fz.ignore_basic_report],
//    fromZigbee: [fz.on_off],
    toZigbee: [tz.on_off], // Should be empty, unless device can be controlled (e.g. lights, switches).
    exposes: [e.switch()], // Defines what this device exposes, used for e.g. Home Assistant discovery and in the frontend

    configure: async (device, coordinatorEndpoint, logger) => {
        const endpoint = device.getEndpoint(1);
        await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']);
        await reporting.onOff(endpoint);
    },
};

module.exports = definition;

Parents
  • OK, let's deal with problem 1: how to receive data sent to the DK.

    The image shows the data being sent to the dev kit. 

    How can I receive this command? It's an ON command.

    I've created this code in the endpoint callback, but the 

    device_cb_param->device_cb_id
    and
    device_cb_param->endpoint
    are not what I'd expect.
    static zb_uint8_t on_off_switch_ep_handler_cb(zb_bufid_t bufid)
    {
    	zb_zcl_device_callback_param_t *device_cb_param = ZB_BUF_GET_PARAM(bufid, zb_zcl_device_callback_param_t);
    
    	LOG_INF("%s() device_cb_id=%d, endpoint=%d ", __func__, device_cb_param->device_cb_id, device_cb_param->endpoint);
    
    	return ZB_FALSE;
    }
    Output:
  • JnRF said:
    How can I receive this command? It's an ON command.

    You are using a ZCL commands handler not a endpoint handler. You can see how it is done in the light bulb sample, line 538 register the callback function and the implementing is on line 434.

    Regards,
    Jonathan

Reply Children
No Data
Related