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:
Received Zigbee message from '0xf4ce367b62161a91', type 'commandOff', cluster 'genOnOff', data '{}' from endpoint 1 with groupID 0
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;