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 0No 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;