How to send readresponse on OnOff Output Cluster to Coordinator?

I am trying to send a OnOff output cluster information to Co-ordinator when a button is pressed in the nrf52840 DK.  The problem is the during the initial registration, the OnOff command is correctly sent, but after the button is pressed, I am unable to find the correct method to send the readresponse

Here is the complete program

#include <zephyr/types.h>
#include <zephyr.h>
#include <device.h>
#include <soc.h>
#include <drivers/pwm.h>
#include <logging/log.h>
#include <dk_buttons_and_leds.h>
#include <settings/settings.h>

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

/* Switch endpoint */
#define HA_SWITCH_ENDPOINT      12

/* Version of the application software (1 byte). */
#define SWITCH_INIT_BASIC_APP_VERSION     01

/* Version of the implementation of the Zigbee stack (1 byte). */
#define SWITCH_INIT_BASIC_STACK_VERSION   10

/* Version of the hardware of the device (1 byte). */
#define SWITCH_INIT_BASIC_HW_VERSION      11

/* Manufacturer name (32 bytes). */
#define SWITCH_INIT_BASIC_MANUF_NAME      "DongleNordic"

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

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

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

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

/* Describes the type of physical environment.
 * For possible values see section 3.2.2.2.10 of ZCL specification.
 */
#define SWITCH_INIT_BASIC_PH_ENV          ZB_ZCL_BASIC_ENV_UNSPECIFIED

#define TOGGLE_SWITCH					DK_BTN3_MSK

/* Nordic PWM nodes don't have flags cells in their specifiers, so
 * this is just future-proofing.
 */
#define FLAGS_OR_ZERO(node) \
	COND_CODE_1(DT_PHA_HAS_CELL(node, pwms, flags), \
		    (DT_PWMS_FLAGS(node)), (0))


#ifndef ZB_ED_ROLE
#error Define ZB_END_DEVICE_ROLE to compile enddevice source code.
#endif

static void switch_send_on_off(zb_bufid_t bufid, zb_uint16_t on_off);

LOG_MODULE_REGISTER(app);

/** 
 * Placeholder for the bulb context 
 * Stores all settings and static values.
 **/
typedef struct 
{
	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_attrs_t            on_off_attr;
} on_off_device_ctx_t;
/* Zigbee device application context storage. */
static on_off_device_ctx_t dev_ctx;

ZB_ZCL_DECLARE_IDENTIFY_ATTRIB_LIST(
	identify_attr_list,
	&dev_ctx.identify_attr.identify_time);

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

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_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);

/* On/Off cluster attributes additions data */
ZB_ZCL_DECLARE_ON_OFF_ATTRIB_LIST(
	on_off_attr_list,
	&dev_ctx.on_off_attr.on_off);

ZB_HA_DECLARE_ON_OFF_OUTPUT_CLUSTER_LIST(on_off_output_clusters,
    on_off_attr_list, basic_attr_list, identify_attr_list, groups_attr_list,
    scenes_attr_list);

ZB_HA_DECLARE_ON_OFF_OUTPUT_EP(on_off_output_ep, HA_SWITCH_ENDPOINT, on_off_output_clusters);

ZB_HA_DECLARE_ON_OFF_OUTPUT_CTX(on_off_output_ctx, on_off_output_ep);

/**@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)
{
	zb_ret_t zb_err_code;
	zb_uint16_t cmd_id = ZB_ZCL_CMD_ON_OFF_ON_ID;
	zb_err_code = zb_buf_get_out_delayed_ext(switch_send_on_off, cmd_id, 0);
	ZB_ERROR_CHECK(zb_err_code);
}

/**@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);
	}
}

/**@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_attr.on_off = (zb_bool_t)ZB_ZCL_ON_OFF_IS_ON;				

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

/**@brief Callback function for handling ZCL commands.
 *
 * @param[in]   bufid   Reference to Zigbee stack buffer
 *                      used to pass received data.
 */
static void zcl_device_cb(zb_bufid_t bufid)
{
	zb_uint8_t cluster_id;
	zb_uint8_t attr_id;
	zb_uint8_t endpoint_invoked;	
	zb_zcl_device_callback_param_t  *device_cb_param =
		ZB_BUF_GET_PARAM(bufid, zb_zcl_device_callback_param_t);
	
	endpoint_invoked = device_cb_param->endpoint;

	LOG_INF("%s id %hd", __func__, device_cb_param->device_cb_id);

	LOG_INF("EndPoint Called is %d ", endpoint_invoked);

	/* Set default response value. */
	device_cb_param->status = RET_OK;
	LOG_INF("ZB Callback started %c", bufid);	

	LOG_INF("%s status: %hd", __func__, device_cb_param->status);
}

/**@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)
{
	/* 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 error(void)
{
	while (true) {
		/* Spin forever */
		k_sleep(K_MSEC(1000));
	}
}

void main(void)
{
	int err;

	LOG_INF("Starting Switch example");

	/* Initialize */
	configure_gpio();
	err = settings_subsys_init();
	if (err) {
		LOG_ERR("settings initialization failed");
	}

	LOG_INF("Registering Device");
	/* Register callback for handling ZCL commands. */
	ZB_ZCL_REGISTER_DEVICE_CB(zcl_device_cb);

	LOG_INF("Setting Switch Context");
	/* Register dimmer switch device context (all the endpoints). */
	ZB_AF_REGISTER_DEVICE_CTX(&on_off_output_ctx);

	/* Initialize ZCL scene table */
	// zcl_scenes_init();

	/* Settings should be loaded after zcl_scenes_init */
	err = settings_load();
	if (err) {
		LOG_ERR("settings loading failed");
	}

	/* Start Zigbee default thread */
	zigbee_enable();

	LOG_INF("Switch example started");

	while (1) {
		// k_sleep(K_MSEC(RUN_LED_BLINK_INTERVAL));
	}
}

1. When the image is flashed in DK, it properly registers to the Zigbee2MQTT and send the correct response  - please see the the Part 1 in the snapshot below.  The Cluster is 'genOnOff, type 'readResponse' and json data {'OnOff':0}

2. After button is clicked - DK BTN3 in this case, the type changes to 'commandOn' with json data being empty - please see the Part 2 in the snapshot below.  It is obviously due to the macro ZB_ZCL_ON_OFF_SEND_REQ. But I would need the cluster and the payload sent just like # 1.  Which macro should I use to retain the type 'readResponse' for cluster 'genOnOff' and fill in the json data? This is for the OnOff Output ZCL

Parents
  • Hi,

    Can you clarify what you mean by the read response? Is this something the DK should send, or the coordinator? Is this as a response to the ON/OFF command, or are you thinking about the Read Attributes command?

    I see you are sending the ON/OFF command to the endpoint of the light switch. Is the endpoint on the coordinator also 12, so that the command is sent to the correct endpoint?

    Best regards,

    Marte

  • Hi Marte,

    Can you clarify what you mean by the read response? Is this something the DK should send, or the coordinator? Is this as a response to the ON/OFF command, or are you thinking about the Read Attributes command?

    1.    If you check the screenshot above (pasted the text below), it has this debug messages in Zigbee2Mqtt (Z2M). This is issued just when the DK connects to Z2M.  So the initial setup of the OnOff Cluster generates the type 'readResponse' from cluster genOnOff  and send to co-ordinator

    Debug Received Zigbee message from 'dev_board', type 'readResponse', cluster 'genOnOff', data '{"onOff":0}' from endpoint 12 with groupID 0

    However, when I press the button, as I explained in the above post, I call the macro 'ZB_ZCL_ON_OFF_SEND_REQ which generates the following. Looks like this call modifies the type to 'commandOn' and data payload is empty. So my question is there a macro which generates the same zigbee message which is called during initialization? This could be reused during button press and send to co-ordinator

    Debug Received Zigbee message from 'dev_board', type 'commandOn', cluster 'genOnOff', data '{}' from endpoint 12 with groupID 0

    2.  For the second point you asked

    I see you are sending the ON/OFF command to the endpoint of the light switch. Is the endpoint on the coordinator also 12, so that the command is sent to the correct endpoint?

    I am just sending the ON/OFF command to the co-ordinator. There is technically no LightSwitch.  The Z2M (running as a co-ordinator) just receives the Zigbee messages and converts to MQTT messages.  So it does not matter which endpoint the zigbee command from the DK is sent to.  As long as the messages is received by the co-ordinator (running Z2M) it it good. And this is what I am trying to indicate, during the initial connect when DK connects, the Zigbee Mesages generate has a correct 'type' - readResponse and 'data' {"onOff":0} , but after button press, I believe the Macro changes the type to 'CommandOn' and the data JSON is empty.  Hence, wanted to know how can the same method / macro be used which might be called during initial connect.

    Hope I am clear.  You may check the main.c for more details

  • Hi,

    So the coordinator does configure reporting, but only with a reportable interval and not on change? From looking at the last code you shared this seems to be correct. If it is the coordinator sending the configure reporting command, do you know where the command is filled and sent? In order to configure reporting on when the attribute value is changed you need to set the reportable change field to the minimum change to the attribute that will result in a report being issued.

    Manju_rn said:
    However, the default reporting takes the min interval value as 0, and the max interval value as 3600 (see the snapshot for Z2M). However, the intention was to enable enable reporting based on the value change, so the max interval value should be 0 I believe. 

    It should not be necessary to set the max interval to 0 in order to get reportable value change as far as I am aware.

    It would also be useful to get a sniffer log of what packets the coordinator is sending and to be able to see the fields of the packets. Do you have an additional nRF52840 DK or Dongle? If so you can use it as a sniffer using nRF Sniffer for 802.15.4. If you get a sniffer log of this you can upload the log as a pcap file here and I will take a look at it. If so, you need to either start the sniffer before the coordinator starts the network so the packets are decrypted, or share the network key so I can decrypt the packets.

    Best regards,

    Marte

  • So the coordinator does configure reporting, but only with a reportable interval and not on change?

    Yes, here is the code. There is a override also - see the #2 below, I need to figure out how to use it.

    1. As given before the code for the device to be registered

    https://github.com/Koenkk/zigbee2mqtt.io/blob/master/docs/externalConvertersExample/switch.js#L19

    2. The code for the reporting library used in the above code

    https://github.com/Koenkk/zigbee-herdsman-converters/blob/master/lib/reporting.js#L66

    You can see that this variable repInterval.HOUR is populated by default for Max interval.  However, there is a provision to use override and need to figure out how to use when in #1

        onOff: async (endpoint, overrides) => {
            const p = payload('onOff', 0, repInterval.HOUR, 0, overrides);
            await endpoint.configureReporting('genOnOff', p);

    So can I say that that Z2M is always invoking Client to Server Reporting request.  Hence, my DK code did not require the reporting configuration? 

    It should not be necessary to set the max interval to 0 in order to get reportable value change as far as I am aware.

    Well, Z2M allows the max interval to be changed from its UI as well. When I change Max and Min interval to 0, then it sends a configuration request to DK and then DK sends the reporting only on Change.

    Hopefully, if the override works fine than I need not configure any reporting on DK.  But yeah I am aware that reporting then would not work in other zigbee co-ordinator where such provision may not be there.

    Yes, I do have the dongle, will configure this as sniffer later today to capture the packets

  • Hi,

    Manju_rn said:
    So can I say that that Z2M is always invoking Client to Server Reporting request.  Hence, my DK code did not require the reporting configuration? 

    To configure attribute reporting only one of the devices, either the device reporting the attributes or the one receiving the attributes, need to send a Configure Reporting command. So if the coordinator configures it, then you do not need to do anything extra on the DK. The DK should start reporting the attribute automatically if the Configure Reporting command is successful and the attribute is reportable.

    Manju_rn said:
    Well, Z2M allows the max interval to be changed from its UI as well. When I change Max and Min interval to 0, then it sends a configuration request to DK and then DK sends the reporting only on Change.

    So it behaves as expected now? You are correct that setting max reporting interval to 0 will turn off periodic reporting, causing it to only report on attribute change if this is configured. If this is non-zero it will keep reporting periodically regardless of whether it also reports on attribute change.

    Manju_rn said:
    But yeah I am aware that reporting then would not work in other zigbee co-ordinator where such provision may not be there.

    You will always need one of the devices to configure the reporting anyways. If a device is expected to receive attribute reports from a certain device, then it makes sense that the receiving device is able to configure attribute reporting. All it needs to do is to create a binding for the cluster that should be reported by sending a bind request, and then send a configure reporting command.

    Best regards,

    Marte

  • The DK should start reporting the attribute automatically if the Configure Reporting command is successful and the attribute is reportable.

    Thanks. While the manual push (via Z2M UI) of the configure reporting from Z2M works (I still have not be able to do it automatically when DK joins the Z2M), I would still want to make it work from the DK.  I used the methods you mentioned in earlier post, but the reporting still is not working. Can you please let me know from where I should call this?

    Can you please indicate where should I call this? in the void zboss_signal_handler(zb_bufid_t bufid) for the case ZB_BDB_SIGNAL_STEERING: ?  Since I could not find anything like case like ZB_BDB_JOIN.

    my nrf dongle is broken, I think during one of the experimentation in another thread, so will have to check that on weekend to recover. So sniffer data will have to wait a bit longer

  • Here is the status. 

    1. I was not able to get the reporting configure from server to client (Dongle/DK to Z2M).

    2. I was able to get the reporting automatically configured  from Z2M to Dongle (without clicking anything on the Z2M). Hence there was no need to add any reporting configuration code in the dongle / DK

    So if somebody is working with Z2M and needs to have the reporting configured from client to server, they could utilize the below. The key point here is to use both reporting.bind  & endpoint.configureReporting calls in the .js file.  Not sure why the bind call is required, but without the bind the configure reporting call is not sent to Dongle/ DK from Z2M.   The bind is technically doing nothing as the endpoint specified can be open

    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: ['DongleSwitch'], // The model ID from: Device with modelID 'lumi.sens' is not supported.
        model: '1.0', // Vendor model number, look on the device for a model number
        vendor: 'DongleNordic', // Vendor of the device (only used for documentation and startup logging)
        description: 'dongle_52840', // Description of the device, copy from vendor site. (only used for documentation and startup logging)
        fromZigbee: [fz.on_off],
        toZigbee: [tz.on_off],
        exposes: [e.switch()],
        configure: async (device, coordinatorEndpoint, logger) => {
           const endpoint = device.getEndpoint(12);
           await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']);
           const payload = [{
               attribute: 'onOff',
               minimumReportInterval: 0,
               maximumReportInterval: 0,
                reportableChange: 0,
               }];
           await endpoint.configureReporting('genOnOff', payload);
       },
    };

    I will spend sometime later to sniff the packets from Dongle / DK to Z2M when the server side reporting is configured and find out why this is not working. This will be required, since server (Dongle / DK) as end device are expected to contain all the functionality than depending upon coordinator / network to provide that functionality

Reply
  • Here is the status. 

    1. I was not able to get the reporting configure from server to client (Dongle/DK to Z2M).

    2. I was able to get the reporting automatically configured  from Z2M to Dongle (without clicking anything on the Z2M). Hence there was no need to add any reporting configuration code in the dongle / DK

    So if somebody is working with Z2M and needs to have the reporting configured from client to server, they could utilize the below. The key point here is to use both reporting.bind  & endpoint.configureReporting calls in the .js file.  Not sure why the bind call is required, but without the bind the configure reporting call is not sent to Dongle/ DK from Z2M.   The bind is technically doing nothing as the endpoint specified can be open

    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: ['DongleSwitch'], // The model ID from: Device with modelID 'lumi.sens' is not supported.
        model: '1.0', // Vendor model number, look on the device for a model number
        vendor: 'DongleNordic', // Vendor of the device (only used for documentation and startup logging)
        description: 'dongle_52840', // Description of the device, copy from vendor site. (only used for documentation and startup logging)
        fromZigbee: [fz.on_off],
        toZigbee: [tz.on_off],
        exposes: [e.switch()],
        configure: async (device, coordinatorEndpoint, logger) => {
           const endpoint = device.getEndpoint(12);
           await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']);
           const payload = [{
               attribute: 'onOff',
               minimumReportInterval: 0,
               maximumReportInterval: 0,
                reportableChange: 0,
               }];
           await endpoint.configureReporting('genOnOff', payload);
       },
    };

    I will spend sometime later to sniff the packets from Dongle / DK to Z2M when the server side reporting is configured and find out why this is not working. This will be required, since server (Dongle / DK) as end device are expected to contain all the functionality than depending upon coordinator / network to provide that functionality

Children
  • Hi

    I am glad to hear that you figured this out, and thank you for sharing your solution!

    Manju_rn said:
    Not sure why the bind call is required, but without the bind the configure reporting call is not sent to Dongle/ DK from Z2M.

    When you configure reporting from client to server you need to first create a binding on the cluster. From the ZCL spec when the direction field of the configure reporting command indicates that it is sent from client to server:

    The receiver of the Configure Reporting command SHALL Configure Reporting to send to each destination as resolved by the bindings for the cluster hosting the attributes to be reported.

    So in order for the server to configure reporting on a cluster, you must first have sent a bind request to create a binding on that cluster.

    Manju_rn said:
    I will spend sometime later to sniff the packets from Dongle / DK to Z2M when the server side reporting is configured and find out why this is not working. This will be required, since server (Dongle / DK) as end device are expected to contain all the functionality than depending upon coordinator / network to provide that functionality

    Please upload the sniffer logs when you do so, and I can help you look. If you are able to share your code I can also test it on my side and see if I can figure out why this is not working.

    Best regards,

    Marte

Related