This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

ZCL Tunneling - RequestTunnel not received

I am trying to establish a tunnel from ZigBee client to ZigBee server on nRF52840 using "nRF5 SDK for Thread and Zigbee v4.1.0".

Starting with the examples "examples\zigbee\light_control\light_bulb" (server) and "light_switch" (client) a HA_DIMMABLE_LIGHT_ENDPOINT connection gets established.

Establishing a tunnel fails, the server does not even receive the RequestTunnel message (on ZB_ZCL_TUNNELING_SEND_REQUEST_TUNNEL)  though from my point of view, the server is set up correctly. Is there any example code available showing how to utilize ZigBee ZCL Smart Energy Tunneling?

This is how the server is set up:

#define TUNNELING_ENDPOINT_ID 11
#define TUNNELING_PROFILE_ID 0x10a
#define TUNNELING_DEVICE_ID 0x500
ZB_ZCL_DECLARE_TUNNELING_ATTRIB_LIST(tunneling_attr_list, &m_dev_ctx.tunneling_attr.close_tunnel_timeout);
zb_zcl_cluster_desc_t tunneling_clusters[]={
    ZB_ZCL_CLUSTER_DESC(ZB_ZCL_CLUSTER_ID_TUNNELING,
        ZB_ZCL_ARRAY_SIZE(tunneling_attr_list, zb_zcl_attr_t),
        tunneling_attr_list,
        ZB_ZCL_CLUSTER_SERVER_ROLE,
        MANUF_CODE)
};
zb_af_simple_desc_1_1_t simple_desc_tunnel={
    TUNNELING_ENDPOINT_ID,
    TUNNELING_PROFILE_ID,
    TUNNELING_DEVICE_ID,
    1, 0,
    1, 0,
    { ZB_ZCL_CLUSTER_ID_TUNNELING }
};
ZB_AF_DECLARE_ENDPOINT_DESC(tunneling_ep, TUNNELING_ENDPOINT_ID,
    TUNNELING_PROFILE_ID, 0, NULL,
    ZB_ZCL_ARRAY_SIZE(tunneling_clusters, zb_zcl_cluster_desc_t), tunneling_clusters,
    &simple_desc_tunnel,
    0, NULL, 0, NULL);
ZBOSS_DECLARE_DEVICE_CTX_2_EP(dimmable_light_ctx, dimmable_light_ep, tunneling_ep);
...
ZB_AF_REGISTER_DEVICE_CTX(&dimmable_light_ctx);
ZB_ZCL_CLUSTER_ID_TUNNELING_SERVER_ROLE_INIT();
ZB_AF_SET_ENDPOINT_HANDLER(TUNNELING_ENDPOINT_ID,
  zb_tunnel_endpoint_handler); // <---- zb_tunnel_endpoint_handler never gets invoked upon client RequestTunnel

How the client is set up:

#define TUNNELING_ENDPOINT_ID 11
#define TUNNELING_PROFILE_ID 0x10a
#define TUNNELING_DEVICE_ID 0x500
#define TUNNELING_PROTOCOL_ID 200
zb_zcl_cluster_desc_t tunneling_clusters[]={
    ZB_ZCL_CLUSTER_DESC(ZB_ZCL_CLUSTER_ID_TUNNELING,
        0, NULL,
        ZB_ZCL_CLUSTER_CLIENT_ROLE,
        TUNNELING_MANUF_CODE)
};
zb_af_simple_desc_1_1_t simple_desc_tunnel={
    TUNNELING_ENDPOINT_ID,
    TUNNELING_PROFILE_ID,
    TUNNELING_DEVICE_ID,
    1, 0,
    0, 1,
    { ZB_ZCL_CLUSTER_ID_TUNNELING }
};
ZB_AF_DECLARE_ENDPOINT_DESC(tunneling_ep, TUNNELING_ENDPOINT_ID,
    TUNNELING_PROFILE_ID, 0, NULL,
    ZB_ZCL_ARRAY_SIZE(tunneling_clusters, zb_zcl_cluster_desc_t), tunneling_clusters,
    &simple_desc_tunnel,
    0, NULL, 0, NULL);
ZBOSS_DECLARE_DEVICE_CTX_2_EP(dimmer_switch_ctx, dimmer_switch_ep, tunneling_ep);

static void tunnel_request(zb_bufid_t bufid)
{
    ZB_ZCL_TUNNELING_SEND_REQUEST_TUNNEL(bufid,
        m_device_ctx.bulb_params.short_addr,
        ZB_APS_ADDR_MODE_16_ENDP_PRESENT,
        TUNNELING_ENDPOINT_ID,
        LIGHT_SWITCH_ENDPOINT, // <--- With TUNNELING_ENDPOINT_ID, tunnel_request_cb does not get invoked!
        TUNNELING_PROFILE_ID,
        ZB_ZCL_DISABLE_DEFAULT_RESPONSE,
        tunnel_request_cb, // <---- tunnel_request_cb gets invoked!
        TUNNELING_PROTOCOL_ID,
        MANUF_CODE,
        ZB_FALSE,
        100);
}
...
ZB_AF_REGISTER_DEVICE_CTX(&dimmer_switch_ctx);
ZB_ZCL_CLUSTER_ID_TUNNELING_CLIENT_ROLE_INIT();
ZB_AF_SET_ENDPOINT_HANDLER(TUNNELING_ENDPOINT_ID, zb_tunnel_endpoint_handler);

  • Meanwhile I managed to create a Wireshark nRF Sniffer log. There can be seen
    - t=50 s: Request Tunnel
    - t=51 s: Request Tunnel Response
    (- t=53 s: Client Transfer Data expected, RET_ERROR)
    (- t=55 s: Server Transfer Data expected, RET_ERROR)
    - t=57 s: Close Tunnel

    tunnel.pcapng

  • Hello,

    I am sorry for the late reply.

    Where is the RET_ERROR coming from? Do you see them in your log? What function or callback do they point you to?

  • RET_ERROR results from the calls of ZB_ZCL_TUNNELING_CLIENT_SEND_TRANSFER_DATA and ZB_ZCL_TUNNELING_SERVER_SEND_TRANSFER_DATA, respectively (client and server functions tunnel_send_transfer_data after tunnel is established).

    Client:

    <info> app: tunnel_request: 4
    <info> app: tunnel_request_cb: 4
    <info> app: zb_tunnel_endpoint_handler: 0
    <info> app: Tunneling request response: 72/0/0
    <info> app: Transfer data: 72/11
    <info> zboss: 07 00 00 00 DE AD 0A 02|........
    <info> zboss: D8 06 24 00 BD F8 30 01|..$...0.
    <info> zboss: DE AD 0E 02 B4 02 23 00|......#.
    <info> zboss: 7A 6E 7D 01 04 00 00 00|zn}.....
    <error> app: ERROR 1 [RET_ERROR] at ../../../main.c:393

    Server:

    <info> app: zb_tunnel_endpoint_handler: 0
    <info> app: Tunneling request: 200/3910/0
    <info> app: Tunneling request response
    <info> app: Transfer data: 72/11

    <info> zboss: 06 00 00 00 DE AD 0E 02|........
    <info> zboss: 47 0A 24 00 BB F8 05 02|G.$.....
    <error> app: ERROR 1 [RET_ERROR] at ../../../main.c:321

  • Hi

    Sorry about the delayed reply. Edvin is currently out of office and I've been tasked with looking after this case. I got an update from one of our Zigbee experts today with a few suggestions to get the client side up and running.

    • Register zb_tunnel_endpoint_handler with ZB_ZCL_REGISTER_DEVICE_CB instead of ZB_AF_SET_ENDPOINT_HANDLER
    • Change enum values in mentioned callback to the ones from zboss_api_zcl.h (beginning with ZB_ZCL_TUNNELING_)

    If callback at the server side is registered in a respective way there will be no need to manually fill the tunnel request response.

    Here are some examples of the client and server callbacks. It's not perfect but I believe it shows how to implement them.

    // server side
    static void zb_tunnel_endpoint_handler(zb_bufid_t bufid){    switch(ZB_ZCL_DEVICE_CMD_PARAM_CB_ID(bufid))
        {
            case ZB_ZCL_TUNNELING_REQUEST_TUNNEL_CB_ID:
            {
                zb_zcl_tunneling_request_tunnel_t tun;
                zb_zcl_parse_status_t status;
                zb_zcl_device_callback_param_t *param =
                        ZB_BUF_GET_PARAM(bufid, zb_zcl_device_callback_param_t);
                zb_zcl_tunnel_request_params_out_t *params =
                        (zb_zcl_tunnel_request_params_out_t*)param->cb_param.gnr.out;
     
                ZB_ZCL_TUNNELING_GET_REQUEST_TUNNEL(&tun, bufid, status);
                NRF_LOG_INFO("Tunneling request: %d/%d/%d %d",
                             tun.protocol_id, tun.manufacturer_code, status,
     tun.max_incoming_transfer_size);
                params->tunnel_status = ZB_ZCL_TUNNELING_STATUS_SUCCESS;
                params->max_incoming_to_srv_transfer_size = 100;//tun.max_incoming_transfer_size;
                ZB_ZCL_DEVICE_CMD_PARAM_STATUS(bufid) = RET_OK;
     
                if (status==ZB_ZCL_PARSE_STATUS_SUCCESS &&
                    tun.protocol_id==TUNNELING_PROTOCOL_ID &&
                    tun.manufacturer_code==TUNNELING_MANUF_CODE)
                {
                    zb_bufid_t buf_resp = zb_buf_get_out();
                    zb_ret_t zb_err_code=ZB_SCHEDULE_APP_ALARM(tunnel_send_transfer_data, buf_resp, 5*ZB_TIME_ONE_SECOND);
                    ZB_ERROR_CHECK(zb_err_code);
                }
                break;
            }
            case ZB_ZCL_TUNNELING_CLOSE_TUNNEL_CB_ID:
            {
                zb_zcl_tunneling_close_tunnel_t tun;
                zb_zcl_parse_status_t status;
                ZB_ZCL_TUNNELING_GET_CLOSE_TUNNEL(&tun, bufid, status);
                NRF_LOG_INFO("Tunnel close: %d/%d", tun.tunnel_id, status);
                zb_buf_free(bufid);
                break;
            }
     
            default:
            {
                NRF_LOG_INFO("Unhandled cmd");
                break;
            }
        }
    }

    // client side 
    static void zb_tunnel_endpoint_handler(zb_bufid_t bufid){
     switch(ZB_ZCL_DEVICE_CMD_PARAM_CB_ID(bufid))
     {
     case ZB_ZCL_TUNNELING_REQUEST_TUNNEL_RESPONSE_CB_ID:
     {
     zb_zcl_tunneling_request_tunnel_response_t tun;
     zb_zcl_parse_status_t status;
     ZB_ZCL_TUNNELING_GET_REQUEST_TUNNEL_RESPONSE(&tun, bufid, status);
     NRF_LOG_INFO("Tunneling request response: %d/%d/%d-max transfer:%d", tun.tunnel_id, tun.tunnel_status, status, tun.max_incoming_transfer_size);
     if (status==ZB_ZCL_PARSE_STATUS_SUCCESS &&
     tun.tunnel_status==ZB_ZCL_TUNNELING_STATUS_SUCCESS)
     {
     tunnel_id=tun.tunnel_id;
     zb_ret_t zb_err_code=ZB_SCHEDULE_APP_ALARM(tunnel_send_transfer_data, bufid, 2*ZB_TIME_ONE_SECOND);
     ZB_ERROR_CHECK(zb_err_code);
     NRF_LOG_INFO("Tunnel response parse OK");
     }
     else
     {
     zb_buf_free(bufid);
     }
     break;
     }
     case ZB_ZCL_TUNNELING_SRV_CMD_TUNNEL_CLOSURE_NOTIFICATION:
     {
     NRF_LOG_INFO("Tunnel closed/timeout");
     break;
     }
     
     case ZB_ZCL_TUNNELING_SRV_CMD_TRANSFER_DATA:
     {
     zb_zcl_tunneling_transfer_data_payload_t data;
     zb_zcl_parse_status_t status;
     ZB_ZCL_TUNNELING_GET_TRANSFER_DATA(&data, bufid, status);
     NRF_LOG_INFO("Transfer data: %d/%d", data.data_size, status);
     if (status==ZB_ZCL_PARSE_STATUS_SUCCESS)
     {
     NRF_LOG_INFO("%s", data.tun_data);
     }
     zb_buf_free(bufid);
     break;
     }
     
     default:
     NRF_LOG_INFO("Unhandled command");
     }
    }

    Best regards,

    Simon

  • Another note: Each command is handled by the callback registered by ZB_ZCL_REGISTER_DEVICE_CB must set the command statucs (ZB_ZCL_DEVICE_CMD_PARAM_STATUS(bufid)) to RET_OK if it's a success, and RET_ERROR respectively. That's how the lower layer knows if the callback is handling the command or not.

    Best regards,

    Simon

Related