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

Reverse finding and binding role, initiator and target.

Hello,

Originally, the Zigbee light_control examples ships with a bulb that can call the zb_bdb_finding_binding_target() functionand a light switch that calls zb_bdb_finding_binding_initiator() functionThis allows the light switch to add the bulb to its binding table. Here are the corresponding WireShark packets, which shows the Simple Descriptor Request being sent using the Zigbee ZDP protocol. The request is sent from the switch to the bulb.

I am now trying to implement the reverse. Have the light switch call zb_bdb_finding_binding_target(), then call zb_bdb_finding_binding_initiator() on the bulb, in order to have the bulb add the switch to its binding table. 

However, this does not work. I am using the CLI example in coordinator mode to query the bulb using the zdo mgmt_bind 0xXXXX command to see if there are any entries in the bulbs binding table, but it consistently shows the binding table to be empty.

There are no errors thrown inside the bulb or the switch, and the entire system (joining network, turning bulbs on and off, etc.) works just fine. One possible clue is when using WireShark, I can can see the bulb is never able to send the Simple Descriptor Request that is sent during a normal interaction. Instead, a default response is sent indicating an unsupported cluster. In addition to this, it is not being sent via the Zigbee ZDP protocol, but is instead using Zigbee HA.

I was wondering if you could help me with this dilemma, and hopefully point me in the correct direction.

Thank you,
Angry Oatmeal

Parents
  • Hello Angry Oatmeal,

    I thought I should look into this. What SDK version are you using? I couldn't find that the bulb nor switch called any of these functions. Is this something you have added?

     

    There are no errors thrown inside the bulb or the switch, and the entire system (joining network, turning bulbs on and off, etc.) works just fine.

     Is ERASE_PERSISTENT_CONFIG set to ZB_TRUE or ZB_FALSE when this happens? Are you sure that this is not caused because of previous network settings in the devices? This information is stored in flash, and will not by default be erased when you reprogram the application. Only if you erase all the flash (nrfjprog -e) or set ERASE_PERSISTENT_CONFIG to ZB_TRUE.

    Also, is it possible to send the sniffer trace as a pcapng file?

  • Hello Edvin,

    Sorry, I should have been more clear. I am using the Thread and Zigbee SDK v3.2.

    The zb_bdb_finding_binding_target() does exist in the light bulb code in the SDK version I have. For me, it is located on line 350 (line 12 in the below snippet)

    static void buttons_handler(bsp_event_t evt)
    {
        zb_ret_t zb_err_code;
    
        switch(evt)
        {
            case IDENTIFY_MODE_BSP_EVT:
                /* Check if endpoint is in identifying mode, if not put desired endpoint in identifying mode. */
                if (m_dev_ctx.identify_attr.identify_time == ZB_ZCL_IDENTIFY_IDENTIFY_TIME_DEFAULT_VALUE)
                {
                    NRF_LOG_INFO("Bulb put in identifying mode");
                    zb_err_code = zb_bdb_finding_binding_target(HA_DIMMABLE_LIGHT_ENDPOINT);
                    ZB_ERROR_CHECK(zb_err_code);
                }
                else
                {
                    NRF_LOG_INFO("Cancel F&B target procedure");
                    zb_bdb_finding_binding_target_cancel();
                }
                break;
    
            default:
                NRF_LOG_INFO("Unhandled BSP Event received: %d", evt);
                break;
        }
    }

    However, the zb_bdb_finding_binding_initiator() was added by myself in the light switch. Again, this setup works just fine. It is only when switching the functions around on both devices that abnormal behavior starts popping up.

    ERASE_PERSISTENT_CONFIG is set to ZB_TRUE on both the light switch and the light bulb, as per the default settings in the shipped examples.

    Find attached both pcapng files of the working and non-working wireshark frames. The start of both files is just the switch and bulb joining the newly formed network after the coordinator comes online. The final burst of frames in both files is the find_and_bind function call.

    Wireshark_Working.pcapng

    Wireshark_Not_Working.pcapng

  • Ah, ok. I see it in SDK3.2.0, but couldn't find them in 4.0.0. I guess they are changed.

    Ok, so when you switch them around, you can't operate the light bulb from the light switch. Did you check the callback function for zb_bdb_finding_binding_initator()?

    zb_ret_t zb_bdb_finding_binding_initiator(zb_uint8_t endpoint, zb_bdb_comm_binding_callback_t user_binding_cb);

    Can you try to provide a callback function to this call, and check the status in the callback (if the callback is called)?

    Also, what is the addr, ep, cluster. and does it help if you return true in this callback?

    BR,

    Edvin

  • Hello Edvin,

    I have a callback function ready for the initiator as below. Unfortunately, the bulb never enters this callback function. It simply acts as though nothing has occurred. 

    static zb_bool_t finding_binding_cb(zb_int16_t status, zb_ieee_addr_t addr, zb_uint8_t ep, zb_uint16_t cluster)
    {
        NRF_LOG_INFO("Entering finding_binding_cb");
        NRF_LOG_INFO("status: %d", status);
        NRF_LOG_INFO("addr: %X", addr);
        NRF_LOG_INFO("ep: %d", ep);
        NRF_LOG_INFO("cluster: %d", cluster);
        return ZB_TRUE;
    }

    A point though about my previous comment concerning the lack of errors in the bulb. While the bulb and switch continue to operate normally after calling zb_bdb_finding_binding_initiator() on the bulb, if zb_bdb_finding_binding_initiator() is called twice in a row, the bulb freezes. The bulb will no longer respond if zb_bdb_finding_binding_initiator() is called a 3rd time, and will no longer receive any ON/OFF commands from the light switch. Once this freeze occurs, checking on WireShark reveals the bulb is truly frozen, as It no longer responds to the coordinator's Link Status request, and does not respond to a zdo match_desc() command from the coordinator CLI. A reset is the only option at this point.

    This might be a good time to ask a higher-level question regarding this project. I am trying to implement a scenario where the coordinator can regularly check the topology of the network. By scanning the binding tables of all router devices, the coordinator will be able to form a table of what devices are connected to each other. The reason I am trying to have the binding table on the bulb/router as opposed to the switch/end device, is the because the light switch will be a low power energy harvesting device, hence powered down most of the time, until it is pressed. Other than what I am trying to do now, do you think there are better ways to implement this?

    Thank you,
    Angry Oatmeal

  • Hello Edvin,

    Just as an added note, I've tried to manually bind the switch the bulb using the Zigbee CLI in coordinator mode.

    Using the zdo bind on command followed by the zdo mgmt_bind command, I was able to bind the bulb to the switch and check the switch's binding table to find the bulb (as usual) but I was also able to bind the switch to the bulb, and read the binding table on the bulb to find the switch listed there, as expected.

    It seems then that there is nothing stopping the switch being bound to the bulb, and that the issue might be the the zb_bdb_finding_binding_initiator()/zb_bdb_finding_binding_target() functions. 

    The plot thickens,

    Angry Oatmeal.

  • Hello Mr/Ms Angry Slight smile

    Is it possible to send the projects that you have used so that I can try to replicate this? If you didn't change anything other than main.c, then you only need to send those (please name the two main.c files differently, because there is a bug in devzone that will only allow one file with a specific name per ticket). 

    If you have changed things outside the main.c file, please zip the project folder. If it is too big to upload, delete the build folders, and it should be just fine.

    Best regards,

    Edvin

Reply
  • Hello Mr/Ms Angry Slight smile

    Is it possible to send the projects that you have used so that I can try to replicate this? If you didn't change anything other than main.c, then you only need to send those (please name the two main.c files differently, because there is a bug in devzone that will only allow one file with a specific name per ticket). 

    If you have changed things outside the main.c file, please zip the project folder. If it is too big to upload, delete the build folders, and it should be just fine.

    Best regards,

    Edvin

Children
  • Hello Edvin,

    Find attached both main.c files. I've named them according to their function.

    For main_bulb.c the primary changes made are on line 359 - 365.

    /**
     * Copyright (c) 2018 - 2019, Nordic Semiconductor ASA
     *
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     *
     * 1. Redistributions of source code must retain the above copyright notice, this
     *    list of conditions and the following disclaimer.
     *
     * 2. Redistributions in binary form, except as embedded into a Nordic
     *    Semiconductor ASA integrated circuit in a product or a software update for
     *    such product, must reproduce the above copyright notice, this list of
     *    conditions and the following disclaimer in the documentation and/or other
     *    materials provided with the distribution.
     *
     * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
     *    contributors may be used to endorse or promote products derived from this
     *    software without specific prior written permission.
     *
     * 4. This software, with or without modification, must only be used with a
     *    Nordic Semiconductor ASA integrated circuit.
     *
     * 5. Any software provided in binary form under this license must not be reverse
     *    engineered, decompiled, modified and/or disassembled.
     *
     * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
     * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
     * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     *
     */
    /** @file
     *
     * @defgroup zigbee_examples_light_bulb main.c
     * @{
     * @ingroup zigbee_examples
     * @brief Dimmable light sample (HA profile)
     */
    
    #include "sdk_config.h"
    #include "zboss_api.h"
    #include "zboss_api_addons.h"
    #include "zb_mem_config_med.h"
    #include "zb_ha_dimmable_light.h"
    #include "zb_error_handler.h"
    #include "zb_nrf52840_internal.h"
    #include "zigbee_helpers.h"
    
    #include "bsp.h"
    #include "boards.h"
    #include "app_pwm.h"
    #include "app_timer.h"
    
    #include "nrf_log.h"
    #include "nrf_log_ctrl.h"
    #include "nrf_log_default_backends.h"
    
    #include "drv_ws2812.h"
    
    #define MAX_CHILDREN                      10                                    /**< The maximum amount of connected devices. Setting this value to 0 disables association to this device.  */
    #define IEEE_CHANNEL_MASK                 (1l << ZIGBEE_CHANNEL)                /**< Scan only one, predefined channel to find the coordinator. */
    #define HA_DIMMABLE_LIGHT_ENDPOINT        10                                    /**< Device endpoint, used to receive light controlling commands. */
    #define ERASE_PERSISTENT_CONFIG           ZB_TRUE				/**< Do not erase NVRAM to save the network parameters after device reboot or power-off. */
    #define BULB_PWM_NAME                     PWM1                                  /**< PWM instance used to drive dimmable light bulb. */
    #define BULB_PWM_TIMER                    2                                     /**< Timer number used by PWM. */
    
    /* Basic cluster attributes initial values. */
    #define BULB_INIT_BASIC_APP_VERSION       01                                    /**< Version of the application software (1 byte). */
    #define BULB_INIT_BASIC_STACK_VERSION     10                                    /**< Version of the implementation of the ZigBee stack (1 byte). */
    #define BULB_INIT_BASIC_HW_VERSION        11                                    /**< Version of the hardware of the device (1 byte). */
    #define BULB_INIT_BASIC_MANUF_NAME        "Nordic"                              /**< Manufacturer name (32 bytes). */
    #define BULB_INIT_BASIC_MODEL_ID          "Dimable_Light_v0.1"                  /**< Model number assigned by manufacturer (32-bytes long string). */
    #define BULB_INIT_BASIC_DATE_CODE         "20180416"                            /**< First 8 bytes specify the date of manufacturer of the device in ISO 8601 format (YYYYMMDD). The rest (8 bytes) are manufacturer specific. */
    #define BULB_INIT_BASIC_POWER_SOURCE      ZB_ZCL_BASIC_POWER_SOURCE_DC_SOURCE   /**< Type of power sources available for the device. For possible values see section 3.2.2.2.8 of ZCL specification. */
    #define BULB_INIT_BASIC_LOCATION_DESC     "Office desk"                         /**< Describes the physical location of the device (16 bytes). May be modified during commisioning process. */
    #define BULB_INIT_BASIC_PH_ENV            ZB_ZCL_BASIC_ENV_UNSPECIFIED          /**< Describes the type of physical environment. For possible values see section 3.2.2.2.10 of ZCL specification. */
    
    #ifdef  BOARD_PCA10059                                                          /**< If it is Dongle */
    #define IDENTIFY_MODE_BSP_EVT             BSP_EVENT_KEY_0                       /**< Button event used to enter the Bulb into the Identify mode. */
    #define ZIGBEE_NETWORK_STATE_LED          BSP_BOARD_LED_0                       /**< LED indicating that light switch successfully joind ZigBee network. */
    #else
    #define IDENTIFY_MODE_BSP_EVT             BSP_EVENT_KEY_3                       /**< Button event used to enter the Bulb into the Identify mode. */
    #define ZIGBEE_NETWORK_STATE_LED          BSP_BOARD_LED_2                       /**< LED indicating that light switch successfully joind ZigBee network. */
    #endif
    #define BULB_LED                          BSP_BOARD_LED_3                       /**< LED immitaing dimmable light bulb. */
    
    #if (APP_BULB_USE_WS2812_LED_CHAIN)
    #define LED_CHAIN_DOUT_PIN                NRF_GPIO_PIN_MAP(1,7)                 /**< GPIO pin used as DOUT (to be connected to DIN pin of the first ws2812 led in chain) */
    #endif
    
    /* Declare endpoint for Dimmable Light device with scenes. */
    #define ZB_HA_DECLARE_LIGHT_EP(ep_name, ep_id, cluster_list)                         \
      ZB_ZCL_DECLARE_HA_DIMMABLE_LIGHT_SIMPLE_DESC(ep_name, ep_id,                       \
        ZB_HA_DIMMABLE_LIGHT_IN_CLUSTER_NUM, ZB_HA_DIMMABLE_LIGHT_OUT_CLUSTER_NUM);      \
      ZBOSS_DEVICE_DECLARE_REPORTING_CTX(reporting_info## device_ctx_name,               \
                                         ZB_HA_DIMMABLE_LIGHT_REPORT_ATTR_COUNT);        \
      ZBOSS_DEVICE_DECLARE_LEVEL_CONTROL_CTX(cvc_alarm_info## device_ctx_name,           \
                                             ZB_HA_DIMMABLE_LIGHT_CVC_ATTR_COUNT);       \
      ZB_AF_DECLARE_ENDPOINT_DESC(ep_name, ep_id, ZB_AF_HA_PROFILE_ID,                   \
                                  0,     \
                                  NULL,                 \
                                  ZB_ZCL_ARRAY_SIZE(cluster_list, zb_zcl_cluster_desc_t),\
                                  cluster_list,                                          \
                                  (zb_af_simple_desc_1_1_t*)&simple_desc_##ep_name,      \
                                  ZB_HA_DIMMABLE_LIGHT_REPORT_ATTR_COUNT,                \
                                  reporting_info## device_ctx_name,                      \
                                  ZB_HA_DIMMABLE_LIGHT_CVC_ATTR_COUNT,                   \
                                  cvc_alarm_info## device_ctx_name)
    
    #if !defined ZB_ROUTER_ROLE
    #error Define ZB_ROUTER_ROLE to compile light bulb (Router) source code.
    #endif
    
    /* Main application customizable 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_ext_t        on_off_attr;
        zb_zcl_level_control_attrs_t     level_control_attr;
    } bulb_device_ctx_t;
    
    
    APP_PWM_INSTANCE(BULB_PWM_NAME, BULB_PWM_TIMER);
    static bulb_device_ctx_t m_dev_ctx;
    
    ZB_ZCL_DECLARE_IDENTIFY_ATTRIB_LIST(identify_attr_list, &m_dev_ctx.identify_attr.identify_time);
    
    
    ZB_ZCL_DECLARE_GROUPS_ATTRIB_LIST(groups_attr_list, &m_dev_ctx.groups_attr.name_support);
    
    ZB_ZCL_DECLARE_SCENES_ATTRIB_LIST(scenes_attr_list,
                                      &m_dev_ctx.scenes_attr.scene_count,
                                      &m_dev_ctx.scenes_attr.current_scene,
                                      &m_dev_ctx.scenes_attr.current_group,
                                      &m_dev_ctx.scenes_attr.scene_valid,
                                      &m_dev_ctx.scenes_attr.name_support);
    
    ZB_ZCL_DECLARE_BASIC_ATTRIB_LIST_EXT(basic_attr_list,
                                         &m_dev_ctx.basic_attr.zcl_version,
                                         &m_dev_ctx.basic_attr.app_version,
                                         &m_dev_ctx.basic_attr.stack_version,
                                         &m_dev_ctx.basic_attr.hw_version,
                                         m_dev_ctx.basic_attr.mf_name,
                                         m_dev_ctx.basic_attr.model_id,
                                         m_dev_ctx.basic_attr.date_code,
                                         &m_dev_ctx.basic_attr.power_source,
                                         m_dev_ctx.basic_attr.location_id,
                                         &m_dev_ctx.basic_attr.ph_env,
                                         m_dev_ctx.basic_attr.sw_ver);
    
    /* On/Off cluster attributes additions data */
    ZB_ZCL_DECLARE_ON_OFF_ATTRIB_LIST_EXT(on_off_attr_list,
                                          &m_dev_ctx.on_off_attr.on_off,
                                          &m_dev_ctx.on_off_attr.global_scene_ctrl,
                                          &m_dev_ctx.on_off_attr.on_time,
                                          &m_dev_ctx.on_off_attr.off_wait_time);
    
    ZB_ZCL_DECLARE_LEVEL_CONTROL_ATTRIB_LIST(level_control_attr_list,
                                             &m_dev_ctx.level_control_attr.current_level,
                                             &m_dev_ctx.level_control_attr.remaining_time);
    
    ZB_HA_DECLARE_DIMMABLE_LIGHT_CLUSTER_LIST(dimmable_light_clusters,
                                              basic_attr_list,
                                              identify_attr_list,
                                              groups_attr_list,
                                              scenes_attr_list,
                                              on_off_attr_list,
                                              level_control_attr_list);
    
    ZB_HA_DECLARE_LIGHT_EP(dimmable_light_ep,
                           HA_DIMMABLE_LIGHT_ENDPOINT,
                           dimmable_light_clusters);
    
    ZB_HA_DECLARE_DIMMABLE_LIGHT_CTX(dimmable_light_ctx,
                                     dimmable_light_ep);
    
    #if (APP_BULB_USE_WS2812_LED_CHAIN)
    /**@brief Timer responsible for triggering periodic led chain refresh */
    APP_TIMER_DEF(m_ws2812_refresh_timer);
    
    /** @brief Requests a led chain refresh */
    static volatile bool m_ws2812_refresh_request;
    #endif
    
    /**@brief Function for initializing the application timer.
     */
    static void timer_init(void)
    {
        uint32_t error_code = app_timer_init();
        APP_ERROR_CHECK(error_code);
    }
    
    /**@brief Function for initializing the nrf log module.
     */
    static void log_init(void)
    {
        ret_code_t err_code = NRF_LOG_INIT(NULL);
        APP_ERROR_CHECK(err_code);
    
        NRF_LOG_DEFAULT_BACKENDS_INIT();
    }
    
    /**@brief Sets brightness of on-board LED
     *
     * @param[in] brightness_level Brightness level, allowed values 0 ... 255, 0 - turn off, 255 - full brightness
     */
    static void light_bulb_onboard_set_brightness(zb_uint8_t brightness_level)
    {
        app_pwm_duty_t app_pwm_duty;
    
        /* Scale level value: APP_PWM uses 0-100 scale, but ZigBee level control cluster uses values from 0 up to 255. */
        app_pwm_duty = (brightness_level * 100U) / 255U;
    
        /* Set the duty cycle - keep trying until PWM is ready. */
        while (app_pwm_channel_duty_set(&BULB_PWM_NAME, 0, app_pwm_duty) == NRF_ERROR_BUSY)
        {
        }
    }
    
    #if (APP_BULB_USE_WS2812_LED_CHAIN)
    /**@brief Sets brightness of ws2812 led chain
     *
     * @param[in] brightness_level Brightness level, allowed values 0 ... 255, 0 - turn off, 255 - full brightness
     */
    static void light_bulb_ws2812_chain_set_brightness(zb_uint8_t brightness_level)
    {
        uint32_t color;
    
        /* Decrease brightness just to save your eyes. LEDs can be very bright */
        if (brightness_level >= 2U)
        {
            brightness_level /= 2U;
        }
    
        color = ((uint32_t)brightness_level << 16);  /* Red component   */
        color |= ((uint32_t)brightness_level << 8);  /* Green component */
        color |= (uint32_t)brightness_level;         /* Blue component  */
    
        drv_ws2812_set_pixel_all(color);
    
        /* Main loop will take care of refreshing led chain */
        m_ws2812_refresh_request = true;
    }
    #endif
    
    /**@brief Sets brightness of bulb luminous executive element
     *
     * @param[in] brightness_level Brightness level, allowed values 0 ... 255, 0 - turn off, 255 - full brightness
     */
    static void light_bulb_set_brightness(zb_uint8_t brightness_level)
    {
        light_bulb_onboard_set_brightness(brightness_level);
    #if (APP_BULB_USE_WS2812_LED_CHAIN)
        light_bulb_ws2812_chain_set_brightness(brightness_level);
    #endif
    }
    
    /**@brief Function for setting the light bulb brightness.
      *
      * @param[in]   new_level   Light bulb brightness value.
     */
    static void level_control_set_value(zb_uint16_t new_level)
    {
        NRF_LOG_INFO("Set level value: %i", new_level);
    
        ZB_ZCL_SET_ATTRIBUTE(HA_DIMMABLE_LIGHT_ENDPOINT,                                       
                             ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL,            
                             ZB_ZCL_CLUSTER_SERVER_ROLE,                 
                             ZB_ZCL_ATTR_LEVEL_CONTROL_CURRENT_LEVEL_ID, 
                             (zb_uint8_t *)&new_level,                                       
                             ZB_FALSE);                                  
    
        /* According to the table 7.3 of Home Automation Profile Specification v 1.2 rev 29, chapter 7.1.3. */
        if (new_level == 0)
        {
            zb_uint8_t value = ZB_FALSE;
            ZB_ZCL_SET_ATTRIBUTE(HA_DIMMABLE_LIGHT_ENDPOINT, 
                                 ZB_ZCL_CLUSTER_ID_ON_OFF,    
                                 ZB_ZCL_CLUSTER_SERVER_ROLE,  
                                 ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID,
                                 &value,                        
                                 ZB_FALSE);                   
        }
        else
        {
            zb_uint8_t value = ZB_TRUE;
            ZB_ZCL_SET_ATTRIBUTE(HA_DIMMABLE_LIGHT_ENDPOINT, 
                                 ZB_ZCL_CLUSTER_ID_ON_OFF,    
                                 ZB_ZCL_CLUSTER_SERVER_ROLE,  
                                 ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID,
                                 &value,                        
                                 ZB_FALSE);
        }
    
        light_bulb_set_brightness(new_level);
    }
    
    
    /**@brief Function for turning ON/OFF the light bulb.
     *
     * @param[in]   on   Boolean light bulb state.
     */
    static void on_off_set_value(zb_bool_t on)
    {
        NRF_LOG_INFO("Set ON/OFF value: %i", on);
    
        ZB_ZCL_SET_ATTRIBUTE(HA_DIMMABLE_LIGHT_ENDPOINT, 
                             ZB_ZCL_CLUSTER_ID_ON_OFF,    
                             ZB_ZCL_CLUSTER_SERVER_ROLE,  
                             ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID,
                             (zb_uint8_t *)&on,                        
                             ZB_FALSE);
    
        if (on)
        {
            level_control_set_value(m_dev_ctx.level_control_attr.current_level);
        }
        else
        {
            light_bulb_set_brightness(0U);
        }
    }
    
    static zb_bool_t finding_binding_cb(zb_int16_t status, zb_ieee_addr_t addr, zb_uint8_t ep, zb_uint16_t cluster)
    {
        NRF_LOG_INFO("Entering finding_binding_cb");
        NRF_LOG_INFO("status: %d", status);
        NRF_LOG_INFO("addr: %X", addr);
        NRF_LOG_INFO("ep: %d", ep);
        NRF_LOG_INFO("cluster: %d", cluster);
        return ZB_TRUE;
    }
    
    /**@brief Callback for button events.
     *
     * @param[in]   evt      Incoming event from the BSP subsystem.
     */
    static void buttons_handler(bsp_event_t evt)
    {
        zb_ret_t zb_err_code;
    
        switch(evt)
        {
            case IDENTIFY_MODE_BSP_EVT:
                /* Check if endpoint is in identifying mode, if not put desired endpoint in identifying mode. */
                if (m_dev_ctx.identify_attr.identify_time == ZB_ZCL_IDENTIFY_IDENTIFY_TIME_DEFAULT_VALUE)
                {
    //                NRF_LOG_INFO("Bulb put in identifying mode");
    //                zb_err_code = zb_bdb_finding_binding_target(HA_DIMMABLE_LIGHT_ENDPOINT);
    //                ZB_ERROR_CHECK(zb_err_code);
    
    		NRF_LOG_INFO("Bulb Initiator");
                    zb_err_code = zb_bdb_finding_binding_initiator(HA_DIMMABLE_LIGHT_ENDPOINT, finding_binding_cb);
                    ZB_ERROR_CHECK(zb_err_code);
                }
                else
                {
                    NRF_LOG_INFO("Cancel F&B target procedure");
                    zb_bdb_finding_binding_target_cancel();
                }
                break;
    
            default:
                NRF_LOG_INFO("Unhandled BSP Event received: %d", evt);
                break;
        }
    }
    
    
    /**@brief Function for initializing LEDs and a single PWM channel.
     */
    static void leds_buttons_init(void)
    {
        ret_code_t       err_code;
        app_pwm_config_t pwm_cfg = APP_PWM_DEFAULT_CONFIG_1CH(5000L, bsp_board_led_idx_to_pin(BULB_LED));
    
        /* Initialize all LEDs and buttons. */
        err_code = bsp_init(BSP_INIT_LEDS | BSP_INIT_BUTTONS, buttons_handler);
        APP_ERROR_CHECK(err_code);
        /* By default the bsp_init attaches BSP_KEY_EVENTS_{0-4} to the PUSH events of the corresponding buttons. */
    
        /* Initialize PWM running on timer 1 in order to control dimmable light bulb. */
        err_code = app_pwm_init(&BULB_PWM_NAME, &pwm_cfg, NULL);
        APP_ERROR_CHECK(err_code);
    
        app_pwm_enable(&BULB_PWM_NAME);
    
        while (app_pwm_channel_duty_set(&BULB_PWM_NAME, 0, 99) == NRF_ERROR_BUSY)
        {
        }
    }
    
    /**@brief Function for initializing all clusters attributes.
     */
    static void bulb_clusters_attr_init(void)
    {
        /* Basic cluster attributes data */
        m_dev_ctx.basic_attr.zcl_version   = ZB_ZCL_VERSION;
        m_dev_ctx.basic_attr.app_version   = BULB_INIT_BASIC_APP_VERSION;
        m_dev_ctx.basic_attr.stack_version = BULB_INIT_BASIC_STACK_VERSION;
        m_dev_ctx.basic_attr.hw_version    = BULB_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(m_dev_ctx.basic_attr.mf_name,
                              BULB_INIT_BASIC_MANUF_NAME,
                              ZB_ZCL_STRING_CONST_SIZE(BULB_INIT_BASIC_MANUF_NAME));
    
        ZB_ZCL_SET_STRING_VAL(m_dev_ctx.basic_attr.model_id,
                              BULB_INIT_BASIC_MODEL_ID,
                              ZB_ZCL_STRING_CONST_SIZE(BULB_INIT_BASIC_MODEL_ID));
    
        ZB_ZCL_SET_STRING_VAL(m_dev_ctx.basic_attr.date_code,
                              BULB_INIT_BASIC_DATE_CODE,
                              ZB_ZCL_STRING_CONST_SIZE(BULB_INIT_BASIC_DATE_CODE));
    
        m_dev_ctx.basic_attr.power_source = BULB_INIT_BASIC_POWER_SOURCE;
    
        ZB_ZCL_SET_STRING_VAL(m_dev_ctx.basic_attr.location_id,
                              BULB_INIT_BASIC_LOCATION_DESC,
                              ZB_ZCL_STRING_CONST_SIZE(BULB_INIT_BASIC_LOCATION_DESC));
    
    
        m_dev_ctx.basic_attr.ph_env = BULB_INIT_BASIC_PH_ENV;
    
        /* Identify cluster attributes data */
        m_dev_ctx.identify_attr.identify_time = ZB_ZCL_IDENTIFY_IDENTIFY_TIME_DEFAULT_VALUE;
    
        /* On/Off cluster attributes data */
        m_dev_ctx.on_off_attr.on_off = (zb_bool_t)ZB_ZCL_ON_OFF_IS_ON;
    
        m_dev_ctx.level_control_attr.current_level  = ZB_ZCL_LEVEL_CONTROL_LEVEL_MAX_VALUE;
        m_dev_ctx.level_control_attr.remaining_time = ZB_ZCL_LEVEL_CONTROL_REMAINING_TIME_DEFAULT_VALUE;
    
        ZB_ZCL_SET_ATTRIBUTE(HA_DIMMABLE_LIGHT_ENDPOINT, 
                             ZB_ZCL_CLUSTER_ID_ON_OFF,    
                             ZB_ZCL_CLUSTER_SERVER_ROLE,  
                             ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID,
                             (zb_uint8_t *)&m_dev_ctx.on_off_attr.on_off,                        
                             ZB_FALSE);                   
    
        ZB_ZCL_SET_ATTRIBUTE(HA_DIMMABLE_LIGHT_ENDPOINT,                                       
                             ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL,            
                             ZB_ZCL_CLUSTER_SERVER_ROLE,                 
                             ZB_ZCL_ATTR_LEVEL_CONTROL_CURRENT_LEVEL_ID, 
                             (zb_uint8_t *)&m_dev_ctx.level_control_attr.current_level,                                       
                             ZB_FALSE);   
    }
    
    /**@brief Function which tries to sleep down the MCU 
     *
     * Function which sleeps the MCU on the non-sleepy End Devices to optimize the power saving.
     * The weak definition inside the OSIF layer provides some minimal working template
     */
    zb_void_t zb_osif_go_idle(zb_void_t)
    {
        //TODO: implement your own logic if needed
        zb_osif_wait_for_event();
    }
    
    /**@brief Callback function for handling ZCL commands.
     *
     * @param[in]   param   Reference to ZigBee stack buffer used to pass received data.
     */
    static zb_void_t zcl_device_cb(zb_uint8_t param)
    {
        zb_uint8_t                       cluster_id;
        zb_uint8_t                       attr_id;
        zb_buf_t                       * p_buffer = ZB_BUF_FROM_REF(param);
        zb_zcl_device_callback_param_t * p_device_cb_param =
                         ZB_GET_BUF_PARAM(p_buffer, zb_zcl_device_callback_param_t);
    
        NRF_LOG_INFO("zcl_device_cb id %hd", p_device_cb_param->device_cb_id);
        NRF_LOG_INFO("cluster id %hd", p_device_cb_param->cb_param.set_attr_value_param.cluster_id);
    
    
        /* Set default response value. */
        p_device_cb_param->status = RET_OK;
    
        switch (p_device_cb_param->device_cb_id)
        {
            case ZB_ZCL_LEVEL_CONTROL_SET_VALUE_CB_ID:
                NRF_LOG_INFO("Level control setting to %d", p_device_cb_param->cb_param.level_control_set_value_param.new_value);
                level_control_set_value(p_device_cb_param->cb_param.level_control_set_value_param.new_value);
                break;
    
            case ZB_ZCL_SET_ATTR_VALUE_CB_ID:
                cluster_id = p_device_cb_param->cb_param.set_attr_value_param.cluster_id;
                attr_id    = p_device_cb_param->cb_param.set_attr_value_param.attr_id;
    
                if (cluster_id == ZB_ZCL_CLUSTER_ID_ON_OFF)
                {
                    uint8_t value = p_device_cb_param->cb_param.set_attr_value_param.values.data8;
    
                    NRF_LOG_INFO("on/off attribute setting to %hd", value);
                    if (attr_id == ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID)
                    {
                        on_off_set_value((zb_bool_t) value);
                    }
                }
                else if (cluster_id == ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL)
                {
                    uint16_t value = p_device_cb_param->cb_param.set_attr_value_param.values.data16;
    
                    NRF_LOG_INFO("level control attribute setting to %hd", value);
                    if (attr_id == ZB_ZCL_ATTR_LEVEL_CONTROL_CURRENT_LEVEL_ID)
                    {
                        level_control_set_value(value);
                    }
                }
                else
                {
                    /* Other clusters can be processed here */
                    NRF_LOG_INFO("Unhandled cluster attribute id: %d", cluster_id);
                }
                break;
    
            default:
                p_device_cb_param->status = RET_ERROR;
                break;
        }
    
        NRF_LOG_INFO("zcl_device_cb status: %hd", p_device_cb_param->status);
    }
    
    /**@brief ZigBee stack event handler.
     *
     * @param[in]   param   Reference to ZigBee stack buffer used to pass arguments (signal).
     */
    void zboss_signal_handler(zb_uint8_t param)
    {
        zb_zdo_app_signal_hdr_t  * p_sg_p = NULL;
        zb_zdo_app_signal_type_t   sig    = zb_get_app_signal(param, &p_sg_p);
        zb_ret_t                   status = ZB_GET_APP_SIGNAL_STATUS(param);
        zb_bool_t                  comm_status;
    
        switch (sig)
        {
            case ZB_BDB_SIGNAL_DEVICE_FIRST_START:
            case ZB_BDB_SIGNAL_DEVICE_REBOOT:
                if (status == RET_OK)
                {
                    NRF_LOG_INFO("Joined network successfully");
                    bsp_board_led_on(ZIGBEE_NETWORK_STATE_LED);
                }
                else
                {
                    NRF_LOG_ERROR("Failed to join network. Status: %d", status);
                    bsp_board_led_off(ZIGBEE_NETWORK_STATE_LED);
                    comm_status = bdb_start_top_level_commissioning(ZB_BDB_NETWORK_STEERING);
                    ZB_COMM_STATUS_CHECK(comm_status);
                }
                break;
    
            case ZB_ZDO_SIGNAL_PRODUCTION_CONFIG_READY:
                if (status != RET_OK)
                {
                    NRF_LOG_WARNING("Production config is not present or invalid");
                }
                break;
    
            case ZB_ZDO_SIGNAL_LEAVE:
                if (status == RET_OK)
                {
                    bsp_board_led_off(ZIGBEE_NETWORK_STATE_LED);
    
                    zb_zdo_signal_leave_params_t * p_leave_params = ZB_ZDO_SIGNAL_GET_PARAMS(p_sg_p, zb_zdo_signal_leave_params_t);
                    NRF_LOG_INFO("Network left. Leave type: %d", p_leave_params->leave_type);
                }
                else
                {
                    NRF_LOG_ERROR("Unable to leave network. Status: %d", status);
                }
                break;
    
            default:
                /* Unhandled signal. For more information see: zb_zdo_app_signal_type_e and zb_ret_e */
                NRF_LOG_INFO("Unhandled signal %d. Status: %d", sig, status);
                break;
        }
    
        if (param)
        {
            ZB_FREE_BUF_BY_REF(param);
        }
    }
    
    #if (APP_BULB_USE_WS2812_LED_CHAIN)
    static void ws2812_refresh_timer_timeout_handler(void *p_context)
    {
        m_ws2812_refresh_request = true;
    }
    #endif
    
    /**@brief Function for application main entry.
     */
    int main(void)
    {
        zb_ret_t       zb_err_code;
        zb_ieee_addr_t ieee_addr;
    
        /* Initialize timer, logging system and GPIOs. */
        timer_init();
        log_init();
        leds_buttons_init();
    
    #if (APP_BULB_USE_WS2812_LED_CHAIN)
        ret_code_t ret_code;
        ret_code = drv_ws2812_init(LED_CHAIN_DOUT_PIN);
        APP_ERROR_CHECK(ret_code);
    #endif
    
        /* Set ZigBee stack logging level and traffic dump subsystem. */
        ZB_SET_TRACE_LEVEL(ZIGBEE_TRACE_LEVEL);
        ZB_SET_TRACE_MASK(ZIGBEE_TRACE_MASK);
        ZB_SET_TRAF_DUMP_OFF();
    
        /* Initialize ZigBee stack. */
        ZB_INIT("led_bulb");
    
        /* Set device address to the value read from FICR registers. */
        zb_osif_get_ieee_eui64(ieee_addr);
        zb_set_long_address(ieee_addr);
    
        /* Set static long IEEE address. */
        zb_set_network_router_role(IEEE_CHANNEL_MASK);
        zb_set_max_children(MAX_CHILDREN);
        zigbee_erase_persistent_storage(ERASE_PERSISTENT_CONFIG);
        zb_set_keepalive_timeout(ZB_MILLISECONDS_TO_BEACON_INTERVAL(3000));
    
        /* Initialize application context structure. */
        UNUSED_RETURN_VALUE(ZB_MEMSET(&m_dev_ctx, 0, sizeof(m_dev_ctx)));
    
        /* Register callback for handling ZCL commands. */
        ZB_ZCL_REGISTER_DEVICE_CB(zcl_device_cb);
    
        /* Register dimmer switch device context (endpoints). */
        ZB_AF_REGISTER_DEVICE_CTX(&dimmable_light_ctx);
    
        bulb_clusters_attr_init();
        level_control_set_value(m_dev_ctx.level_control_attr.current_level);
    
    #if (APP_BULB_USE_WS2812_LED_CHAIN)
        /* Let's have a timer triggering refresh of led state */
        ret_code = app_timer_create(&m_ws2812_refresh_timer, APP_TIMER_MODE_REPEATED,
                ws2812_refresh_timer_timeout_handler);
        APP_ERROR_CHECK(ret_code);
        ret_code = app_timer_start(m_ws2812_refresh_timer, APP_TIMER_TICKS(5000U), NULL);
        APP_ERROR_CHECK(ret_code);
    #endif
    
        /** Start Zigbee Stack. */
        zb_err_code = zboss_start();
        ZB_ERROR_CHECK(zb_err_code);
    
        while(1)
        {
            zboss_main_loop_iteration();
            UNUSED_RETURN_VALUE(NRF_LOG_PROCESS());
    
    #if (APP_BULB_USE_WS2812_LED_CHAIN)
            if (m_ws2812_refresh_request)
            {
                if (drv_ws2812_display(NULL, NULL) == NRF_SUCCESS)
                {
                    m_ws2812_refresh_request = false;
                }
            }
    #endif
        }
    }
    
    
    /**
     * @}
     */
    

    For main_switch.c the primary changes made are on line 447 - 456.

    /**
     * Copyright (c) 2018 - 2019, Nordic Semiconductor ASA
     *
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     *
     * 1. Redistributions of source code must retain the above copyright notice, this
     *    list of conditions and the following disclaimer.
     *
     * 2. Redistributions in binary form, except as embedded into a Nordic
     *    Semiconductor ASA integrated circuit in a product or a software update for
     *    such product, must reproduce the above copyright notice, this list of
     *    conditions and the following disclaimer in the documentation and/or other
     *    materials provided with the distribution.
     *
     * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
     *    contributors may be used to endorse or promote products derived from this
     *    software without specific prior written permission.
     *
     * 4. This software, with or without modification, must only be used with a
     *    Nordic Semiconductor ASA integrated circuit.
     *
     * 5. Any software provided in binary form under this license must not be reverse
     *    engineered, decompiled, modified and/or disassembled.
     *
     * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
     * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
     * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     *
     */
    /** @file
     *
     * @defgroup zigbee_examples_light_switch main.c
     * @{
     * @ingroup zigbee_examples
     * @brief Dimmer switch for HA profile implementation.
     */
    
    #include "zboss_api.h"
    #include "zb_mem_config_min.h"
    #include "zb_error_handler.h"
    #include "zigbee_helpers.h"
    
    #include "app_timer.h"
    #include "bsp.h"
    #include "boards.h"
    
    #include "nrf_log.h"
    #include "nrf_log_ctrl.h"
    #include "nrf_log_default_backends.h"
    
    #define IEEE_CHANNEL_MASK                   (1l << ZIGBEE_CHANNEL)              /**< Scan only one, predefined channel to find the coordinator. */
    #define LIGHT_SWITCH_ENDPOINT               1                                   /**< Source endpoint used to control light bulb. */
    #define MATCH_DESC_REQ_START_DELAY          (2 * ZB_TIME_ONE_SECOND)            /**< Delay between the light switch startup and light bulb finding procedure. */
    #define MATCH_DESC_REQ_TIMEOUT              (5 * ZB_TIME_ONE_SECOND)            /**< Timeout for finding procedure. */
    #define MATCH_DESC_REQ_ROLE                 ZB_NWK_BROADCAST_RX_ON_WHEN_IDLE    /**< Find only non-sleepy device. */
    #define ERASE_PERSISTENT_CONFIG             ZB_TRUE                            /**< Do not erase NVRAM to save the network parameters after device reboot or power-off. NOTE: If this option is set to ZB_TRUE then do full device erase for all network devices before running other samples. */
    #define ZIGBEE_NETWORK_STATE_LED            BSP_BOARD_LED_2                     /**< LED indicating that light switch successfully joind ZigBee network. */
    #define BULB_FOUND_LED                      BSP_BOARD_LED_3                     /**< LED indicating that light witch found a light bulb to control. */
    #define LIGHT_SWITCH_BUTTON_ON              BSP_BOARD_BUTTON_0                  /**< Button ID used to switch on the light bulb. */
    #define LIGHT_SWITCH_BUTTON_OFF             BSP_BOARD_BUTTON_1                  /**< Button ID used to switch off the light bulb. */
    #define SLEEPY_ON_BUTTON                    BSP_BOARD_BUTTON_2                  /**< Button ID used to determine if we need the sleepy device behaviour (pressed means yes). */
    #define FIND_BIND_INIT_BUTTON               BSP_BOARD_BUTTON_3
    
    #define LIGHT_SWITCH_DIMM_STEP              15                                  /**< Dim step size - increases/decreses current level (range 0x000 - 0xfe). */
    #define LIGHT_SWITCH_DIMM_TRANSACTION_TIME  2                                   /**< Transition time for a single step operation in 0.1 sec units. 0xFFFF - immediate change. */
    
    #define LIGHT_SWITCH_BUTTON_THRESHOLD       ZB_TIME_ONE_SECOND                      /**< Number of beacon intervals the button should be pressed to dimm the light bulb. */
    #define LIGHT_SWITCH_BUTTON_SHORT_POLL_TMO  ZB_MILLISECONDS_TO_BEACON_INTERVAL(50)  /**< Delay between button state checks used in order to detect button long press. */
    #define LIGHT_SWITCH_BUTTON_LONG_POLL_TMO   ZB_MILLISECONDS_TO_BEACON_INTERVAL(300) /**< Time after which the button state is checked again to detect button hold - the dimm command is sent again. */
    
    #if !defined ZB_ED_ROLE
    #error Define ZB_ED_ROLE to compile light switch (End Device) source code.
    #endif
    
    
    typedef struct light_switch_bulb_params_s
    {
      zb_uint8_t  endpoint;
      zb_uint16_t short_addr;
    } light_switch_bulb_params_t;
    
    typedef struct light_switch_button_s
    {
      zb_bool_t in_progress;
      zb_time_t timestamp;
    } light_switch_button_t;
    
    typedef struct light_switch_ctx_s
    {
      light_switch_bulb_params_t bulb_params;
      light_switch_button_t      button;
    } light_switch_ctx_t;
    
    
    static zb_void_t find_light_bulb_timeout(zb_uint8_t param);
    
    static light_switch_ctx_t m_device_ctx;
    static zb_uint8_t         m_attr_zcl_version   = ZB_ZCL_VERSION;
    static zb_uint8_t         m_attr_power_source  = ZB_ZCL_BASIC_POWER_SOURCE_UNKNOWN;
    static zb_uint16_t        m_attr_identify_time = 0;
    
    /* Declare attribute list for Basic cluster. */
    ZB_ZCL_DECLARE_BASIC_ATTRIB_LIST(basic_attr_list, &m_attr_zcl_version, &m_attr_power_source);
    
    /* Declare attribute list for Identify cluster. */
    ZB_ZCL_DECLARE_IDENTIFY_ATTRIB_LIST(identify_attr_list, &m_attr_identify_time);
    
    /* Declare cluster list for Dimmer Switch device (Identify, Basic, Scenes, Groups, On Off, Level Control). */
    /* Only clusters Identify and Basic have attributes. */
    ZB_HA_DECLARE_DIMMER_SWITCH_CLUSTER_LIST(dimmer_switch_clusters,
                                             basic_attr_list,
                                             identify_attr_list);
    
    /* Declare endpoint for Dimmer Switch device. */
    ZB_HA_DECLARE_DIMMER_SWITCH_EP(dimmer_switch_ep,
                                   LIGHT_SWITCH_ENDPOINT,
                                   dimmer_switch_clusters);
    
    /* Declare application's device context (list of registered endpoints) for Dimmer Switch device. */
    ZB_HA_DECLARE_DIMMER_SWITCH_CTX(dimmer_switch_ctx, dimmer_switch_ep);
    
    /**@brief Function for the Timer initialization.
     *
     * @details Initializes the timer module. This creates and starts application timers.
     */
    static void timers_init(void)
    {
        ret_code_t err_code;
    
        // Initialize timer module.
        err_code = app_timer_init();
        APP_ERROR_CHECK(err_code);
    }
    
    /**@brief Function for initializing the nrf log module.
     */
    static void log_init(void)
    {
        ret_code_t err_code = NRF_LOG_INIT(NULL);
        APP_ERROR_CHECK(err_code);
    
        NRF_LOG_DEFAULT_BACKENDS_INIT();
    }
    
    /**@brief Function for sending ON/OFF requests to the light bulb.
     *
     * @param[in]   param    Non-zero reference to ZigBee stack buffer that will be used to construct on/off request.
     * @param[in]   on_off   Requested state of the light bulb.
     */
    static zb_void_t light_switch_send_on_off(zb_uint8_t param, zb_uint16_t on_off)
    {
        zb_uint8_t           cmd_id;
        zb_buf_t           * p_buf = ZB_BUF_FROM_REF(param);
    
        if (on_off)
        {
            cmd_id = ZB_ZCL_CMD_ON_OFF_ON_ID;
        }
        else
        {
            cmd_id = ZB_ZCL_CMD_ON_OFF_OFF_ID;
        }
    
        NRF_LOG_INFO("Send ON/OFF command: %d", on_off);
    
        ZB_ZCL_ON_OFF_SEND_REQ(p_buf,
                               m_device_ctx.bulb_params.short_addr,
                               ZB_APS_ADDR_MODE_16_ENDP_PRESENT,
                               m_device_ctx.bulb_params.endpoint,
                               LIGHT_SWITCH_ENDPOINT,
                               ZB_AF_HA_PROFILE_ID,
                               ZB_ZCL_DISABLE_DEFAULT_RESPONSE,
                               cmd_id,
                               NULL);
    }
    
    /**@brief Function for sending step requests to the light bulb.
     *
     * @param[in]   param        Non-zero reference to ZigBee stack buffer that will be used to construct step request.
     * @param[in]   is_step_up   Boolean parameter selecting direction of step change.
     */
    static zb_void_t light_switch_send_step(zb_uint8_t param, zb_uint16_t is_step_up)
    {
        zb_uint8_t           step_dir;
        zb_buf_t           * p_buf = ZB_BUF_FROM_REF(param);
    
        if (is_step_up)
        {
            step_dir = ZB_ZCL_LEVEL_CONTROL_STEP_MODE_UP;
        }
        else
        {
            step_dir = ZB_ZCL_LEVEL_CONTROL_STEP_MODE_DOWN;
        }
    
        NRF_LOG_INFO("Send step level command: %d", is_step_up);
    
        ZB_ZCL_LEVEL_CONTROL_SEND_STEP_REQ(p_buf,
                                           m_device_ctx.bulb_params.short_addr,
                                           ZB_APS_ADDR_MODE_16_ENDP_PRESENT,
                                           m_device_ctx.bulb_params.endpoint,
                                           LIGHT_SWITCH_ENDPOINT,
                                           ZB_AF_HA_PROFILE_ID,
                                           ZB_ZCL_DISABLE_DEFAULT_RESPONSE,
                                           NULL,
                                           step_dir,
                                           LIGHT_SWITCH_DIMM_STEP,
                                           LIGHT_SWITCH_DIMM_TRANSACTION_TIME);
    }
    
    /**@brief Perform local operation - leave network.
     *
     * @param[in]   param   Reference to ZigBee stack buffer that will be used to construct leave request.
     */
    static void light_switch_leave_nwk(zb_uint8_t param)
    {
        zb_ret_t zb_err_code;
    
        /* We are going to leave */
        if (param)
        {
            zb_buf_t                  * p_buf = ZB_BUF_FROM_REF(param);
            zb_zdo_mgmt_leave_param_t * p_req_param;
    
            p_req_param = ZB_GET_BUF_PARAM(p_buf, zb_zdo_mgmt_leave_param_t);
            UNUSED_RETURN_VALUE(ZB_BZERO(p_req_param, sizeof(zb_zdo_mgmt_leave_param_t)));
    
            /* Set dst_addr == local address for local leave */
            p_req_param->dst_addr = ZB_PIBCACHE_NETWORK_ADDRESS();
            p_req_param->rejoin   = ZB_FALSE;
            UNUSED_RETURN_VALUE(zdo_mgmt_leave_req(param, NULL));
        }
        else
        {
            zb_err_code = ZB_GET_OUT_BUF_DELAYED(light_switch_leave_nwk);
            ZB_ERROR_CHECK(zb_err_code);
        }
    }
    
    /**@brief Function for starting join/rejoin procedure.
     *
     * param[in]   leave_type   Type of leave request (with or without rejoin).
     */
    static zb_void_t light_switch_retry_join(zb_uint8_t leave_type)
    {
        zb_bool_t comm_status;
    
        if (leave_type == ZB_NWK_LEAVE_TYPE_RESET)
        {
            comm_status = bdb_start_top_level_commissioning(ZB_BDB_NETWORK_STEERING);
            ZB_COMM_STATUS_CHECK(comm_status);
        }
    }
    
    /**@brief Function for leaving current network and starting join procedure afterwards.
     *
     * @param[in]   param   Optional reference to ZigBee stack buffer to be reused by leave and join procedure.
     */
    static zb_void_t light_switch_leave_and_join(zb_uint8_t param)
    {
        if (ZB_JOINED())
        {
            /* Leave network. Joining procedure will be initiated inisde ZigBee stack signal handler. */
            light_switch_leave_nwk(param);
        }
        else
        {
            /* Already left network. Start joining procedure. */
            light_switch_retry_join(ZB_NWK_LEAVE_TYPE_RESET);
    
            if (param)
            {
                ZB_FREE_BUF_BY_REF(param);
            }
        }
    }
    
    /**@brief Callback function receiving finding procedure results.
     *
     * @param[in]   param   Reference to ZigBee stack buffer used to pass received data.
     */
    static zb_void_t find_light_bulb_cb(zb_uint8_t param)
    {
        zb_buf_t                   * p_buf  = ZB_BUF_FROM_REF(param);                              // Resolve buffer number to buffer address
        zb_zdo_match_desc_resp_t   * p_resp = (zb_zdo_match_desc_resp_t *) ZB_BUF_BEGIN(p_buf);    // Get the begining of the response
        zb_apsde_data_indication_t * p_ind  = ZB_GET_BUF_PARAM(p_buf, zb_apsde_data_indication_t); // Get the pointer to the parameters buffer, which stores APS layer response
        zb_uint8_t                 * p_match_ep;
        zb_ret_t                     zb_err_code;
    
        if ((p_resp->status == ZB_ZDP_STATUS_SUCCESS) && (p_resp->match_len > 0) && (m_device_ctx.bulb_params.short_addr == 0xFFFF))
        {
            /* Match EP list follows right after response header */
            p_match_ep = (zb_uint8_t *)(p_resp + 1);
    
            /* We are searching for exact cluster, so only 1 EP may be found */
            m_device_ctx.bulb_params.endpoint   = *p_match_ep;
            m_device_ctx.bulb_params.short_addr = p_ind->src_addr;
    
            NRF_LOG_INFO("Found bulb addr: %d ep: %d", m_device_ctx.bulb_params.short_addr, m_device_ctx.bulb_params.endpoint);
    
            zb_err_code = ZB_SCHEDULE_ALARM_CANCEL(find_light_bulb_timeout, ZB_ALARM_ANY_PARAM);
            ZB_ERROR_CHECK(zb_err_code);
    
            bsp_board_led_on(BULB_FOUND_LED);
        }
    
        if (param)
        {
            ZB_FREE_BUF_BY_REF(param);
        }
    }
    
    /**@brief Function for sending ON/OFF and Level Control find request.
     *
     * @param[in]   param   Non-zero reference to ZigBee stack buffer that will be used to construct find request.
     */
    static zb_void_t find_light_bulb(zb_uint8_t param)
    {
        zb_buf_t                  * p_buf = ZB_BUF_FROM_REF(param); // Resolve buffer number to buffer address
        zb_zdo_match_desc_param_t * p_req;
    
        /* Initialize pointers inside buffer and reserve space for zb_zdo_match_desc_param_t request */
        UNUSED_RETURN_VALUE(ZB_BUF_INITIAL_ALLOC(p_buf, sizeof(zb_zdo_match_desc_param_t) + (1) * sizeof(zb_uint16_t), p_req));
    
        p_req->nwk_addr         = MATCH_DESC_REQ_ROLE;              // Send to devices specified by MATCH_DESC_REQ_ROLE
        p_req->addr_of_interest = MATCH_DESC_REQ_ROLE;              // Get responses from devices specified by MATCH_DESC_REQ_ROLE
        p_req->profile_id       = ZB_AF_HA_PROFILE_ID;              // Look for Home Automation profile clusters
    
        /* We are searching for 2 clusters: On/Off and Level Control Server */
        p_req->num_in_clusters  = 2;
        p_req->num_out_clusters = 0;
        /*lint -save -e415 // Suppress warning 415 "likely access of out-of-bounds pointer" */
        p_req->cluster_list[0]  = ZB_ZCL_CLUSTER_ID_ON_OFF;
        p_req->cluster_list[1]  = ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL;
        /*lint -restore */
        m_device_ctx.bulb_params.short_addr = 0xFFFF; // Set 0xFFFF to reset short address in order to parse only one response.
        UNUSED_RETURN_VALUE(zb_zdo_match_desc_req(param, find_light_bulb_cb));
    }
    
    /**@brief Finding procedure timeout handler.
     *
     * @param[in]   param   Reference to ZigBee stack buffer that will be used to construct find request.
     */
    static zb_void_t find_light_bulb_timeout(zb_uint8_t param)
    {
        zb_ret_t zb_err_code;
    
        if (param)
        {
            NRF_LOG_INFO("Bulb not found, try again");
            zb_err_code = ZB_SCHEDULE_ALARM(find_light_bulb, param, MATCH_DESC_REQ_START_DELAY);
            ZB_ERROR_CHECK(zb_err_code);
            zb_err_code = ZB_SCHEDULE_ALARM(find_light_bulb_timeout, 0, MATCH_DESC_REQ_TIMEOUT);
            ZB_ERROR_CHECK(zb_err_code);
        }
        else
        {
            zb_err_code = ZB_GET_OUT_BUF_DELAYED(find_light_bulb_timeout);
            ZB_ERROR_CHECK(zb_err_code);
        }
    }
    
    static zb_bool_t finding_binding_cb(zb_int16_t status, zb_ieee_addr_t addr, zb_uint8_t ep, zb_uint16_t cluster)
    {
        NRF_LOG_INFO("Finding_binding_cb");
        return ZB_TRUE;
    }
    
    
    /**@brief Callback for detecting button press duration.
     *
     * @param[in]   button   BSP Button that was pressed.
     */
    static zb_void_t light_switch_button_handler(zb_uint8_t button)
    {
        zb_time_t current_time;
        zb_bool_t short_expired;
        zb_bool_t on_off;
        zb_ret_t zb_err_code;
    
        current_time = ZB_TIMER_GET();
    
        switch(button)
        {
    	case LIGHT_SWITCH_BUTTON_ON:
    	case LIGHT_SWITCH_BUTTON_OFF:
    
    	    if (button == LIGHT_SWITCH_BUTTON_ON)
    	    {
    		on_off = ZB_TRUE;
    	    }
    	    else
    	    {
    		on_off = ZB_FALSE;
    	    }
    
                if (ZB_TIME_SUBTRACT(current_time, m_device_ctx.button.timestamp) > LIGHT_SWITCH_BUTTON_THRESHOLD)
    	    {
    		short_expired = ZB_TRUE;
    	    }
    	    else
    	    {
    		short_expired = ZB_FALSE;
    	    }
    	    
    	    /* Check if button was released during LIGHT_SWITCH_BUTTON_SHORT_POLL_TMO. */
    	    if (!bsp_button_is_pressed(button))
    	    {
    		if (!short_expired)
    		{
    		    /* Allocate output buffer and send on/off command. */
    		    zb_err_code = ZB_GET_OUT_BUF_DELAYED2(light_switch_send_on_off, on_off);
    		    ZB_ERROR_CHECK(zb_err_code);
    		}
    
    		/* Button released - wait for accept next event. */
    		m_device_ctx.button.in_progress = ZB_FALSE;
    	    }
    	    else
    	    {
    		if (short_expired)
    		{
    		    /* The button is still pressed - allocate output buffer and send step command. */
    		    zb_err_code = ZB_GET_OUT_BUF_DELAYED2(light_switch_send_step, on_off);
    		    ZB_ERROR_CHECK(zb_err_code);
    		    zb_err_code = ZB_SCHEDULE_ALARM(light_switch_button_handler, button, LIGHT_SWITCH_BUTTON_LONG_POLL_TMO);
    		    ZB_ERROR_CHECK(zb_err_code);
    		}
    		else
    		{
    		    /* Wait another LIGHT_SWITCH_BUTTON_SHORT_POLL_TMO, until LIGHT_SWITCH_BUTTON_THRESHOLD will be reached. */
    		    zb_err_code = ZB_SCHEDULE_ALARM(light_switch_button_handler, button, LIGHT_SWITCH_BUTTON_SHORT_POLL_TMO);
    		    ZB_ERROR_CHECK(zb_err_code);
    		}
    	    }
    	    break;
    	case FIND_BIND_INIT_BUTTON:
    //	    NRF_LOG_INFO("Initiating find and bind.");
    //            zb_err_code = zb_bdb_finding_binding_initiator(LIGHT_SWITCH_ENDPOINT, finding_binding_cb);
    //            ZB_ERROR_CHECK(zb_err_code);
    //            m_device_ctx.button.in_progress = ZB_FALSE;
    
    	    NRF_LOG_INFO("Target find and bind.");
                zb_err_code = zb_bdb_finding_binding_target(LIGHT_SWITCH_ENDPOINT);
                ZB_ERROR_CHECK(zb_err_code);
                m_device_ctx.button.in_progress = ZB_FALSE;
    	    break;
    
    	default:
                NRF_LOG_INFO("Unhandled BSP Event received: %d");
                break;
        }
    }
    
    /**@brief Callback for button events.
     *
     * @param[in]   evt      Incoming event from the BSP subsystem.
     */
    static void buttons_handler(bsp_event_t evt)
    {
        zb_ret_t zb_err_code;
        zb_uint32_t button;
    
        if (m_device_ctx.bulb_params.short_addr == 0xFFFF)
        {
            /* No bulb found yet. */
            return;
        }
    
        switch(evt)
        {
            case BSP_EVENT_KEY_0:
                button = LIGHT_SWITCH_BUTTON_ON;
                break;
    
            case BSP_EVENT_KEY_1:
                button = LIGHT_SWITCH_BUTTON_OFF;
                break;
    
    	case BSP_EVENT_KEY_3:
    	    button = FIND_BIND_INIT_BUTTON;
    	    break;
    
            default:
                NRF_LOG_INFO("Unhandled BSP Event received: %d", evt);
                return;
        }
    
        if (!m_device_ctx.button.in_progress)
        {
            m_device_ctx.button.in_progress = ZB_TRUE;
            m_device_ctx.button.timestamp = ZB_TIMER_GET();
    
            zb_err_code = ZB_SCHEDULE_ALARM(light_switch_button_handler, button, LIGHT_SWITCH_BUTTON_SHORT_POLL_TMO);
            ZB_ERROR_CHECK(zb_err_code);
        }
    }
    
    /**@brief Function for initializing LEDs and buttons.
     */
    static zb_void_t leds_buttons_init(void)
    {
        ret_code_t error_code;
    
        /* Initialize LEDs and buttons - use BSP to control them. */
        error_code = bsp_init(BSP_INIT_LEDS | BSP_INIT_BUTTONS, buttons_handler);
        APP_ERROR_CHECK(error_code);
        /* By default the bsp_init attaches BSP_KEY_EVENTS_{0-4} to the PUSH events of the corresponding buttons. */
    
        bsp_board_leds_off();
    }
    
    /**@brief Function to set the Sleeping Mode according to the SLEEPY_ON_BUTTON state.
    */
    static zb_void_t sleepy_device_setup(void)
    {
        zb_set_rx_on_when_idle(bsp_button_is_pressed(SLEEPY_ON_BUTTON) ? ZB_FALSE : ZB_TRUE);
    }
    
    /**@brief ZigBee stack event handler.
     *
     * @param[in]   param   Reference to ZigBee stack buffer used to pass arguments (signal).
     */
    void zboss_signal_handler(zb_uint8_t param)
    {
        zb_zdo_app_signal_hdr_t      * p_sg_p         = NULL;
        zb_zdo_app_signal_type_t       sig            = zb_get_app_signal(param, &p_sg_p);
        zb_ret_t                       status         = ZB_GET_APP_SIGNAL_STATUS(param);
        zb_ret_t                       zb_err_code;
    
        switch(sig)
        {
            case ZB_BDB_SIGNAL_DEVICE_FIRST_START:
            case ZB_BDB_SIGNAL_DEVICE_REBOOT:
                if (status == RET_OK)
                {
                    NRF_LOG_INFO("Joined network successfully");
                    bsp_board_led_on(ZIGBEE_NETWORK_STATE_LED);
    
                    /* Check the light device address */
                    if (m_device_ctx.bulb_params.short_addr == 0xFFFF)
                    {
                        zb_err_code = ZB_SCHEDULE_ALARM(find_light_bulb, param, MATCH_DESC_REQ_START_DELAY);
                        ZB_ERROR_CHECK(zb_err_code);
                        zb_err_code = ZB_SCHEDULE_ALARM(find_light_bulb_timeout, 0, MATCH_DESC_REQ_TIMEOUT);
                        ZB_ERROR_CHECK(zb_err_code);
                        param = 0; // Do not free buffer - it will be reused by find_light_bulb callback
                    }
                }
                else
                {
                    NRF_LOG_ERROR("Failed to join network. Status: %d", status);
                    bsp_board_led_off(ZIGBEE_NETWORK_STATE_LED);
                    zb_err_code = ZB_SCHEDULE_ALARM(light_switch_leave_and_join, 0, ZB_TIME_ONE_SECOND);
                    ZB_ERROR_CHECK(zb_err_code);
                }
                break;
    
            case ZB_ZDO_SIGNAL_LEAVE:
                if (status == RET_OK)
                {
                    bsp_board_led_off(ZIGBEE_NETWORK_STATE_LED);
    
                    zb_zdo_signal_leave_params_t * p_leave_params = ZB_ZDO_SIGNAL_GET_PARAMS(p_sg_p, zb_zdo_signal_leave_params_t);
                    NRF_LOG_INFO("Network left. Leave type: %d", p_leave_params->leave_type);
                    light_switch_retry_join(p_leave_params->leave_type);
                }
                else
                {
                    NRF_LOG_ERROR("Unable to leave network. Status: %d", status);
                }
                break;
    
            case ZB_COMMON_SIGNAL_CAN_SLEEP:
                zb_sleep_now();
                break;
    
            case ZB_ZDO_SIGNAL_PRODUCTION_CONFIG_READY:
                if (status != RET_OK)
                {
                    NRF_LOG_WARNING("Production config is not present or invalid");
                }
                break;
    
            default:
                /* Unhandled signal. For more information see: zb_zdo_app_signal_type_e and zb_ret_e */
                NRF_LOG_INFO("Unhandled signal %d. Status: %d", sig, status);
        }
    
        if (param)
        {
            ZB_FREE_BUF_BY_REF(param);
        }
    }
    
    /**@brief Function for application main entry.
     */
    int main(void)
    {
        zb_ret_t       zb_err_code;
        zb_ieee_addr_t ieee_addr;
    
        /* Initialize timers, loging system and GPIOs. */
        timers_init();
        log_init();
        leds_buttons_init();
    
        /* Set ZigBee stack logging level and traffic dump subsystem. */
        ZB_SET_TRACE_LEVEL(ZIGBEE_TRACE_LEVEL);
        ZB_SET_TRACE_MASK(ZIGBEE_TRACE_MASK);
        ZB_SET_TRAF_DUMP_OFF();
    
        /* Initialize ZigBee stack. */
        ZB_INIT("light_switch");
    
        /* Set device address to the value read from FICR registers. */
        zb_osif_get_ieee_eui64(ieee_addr);
        zb_set_long_address(ieee_addr);
    
        zb_set_network_ed_role(IEEE_CHANNEL_MASK);
        zigbee_erase_persistent_storage(ERASE_PERSISTENT_CONFIG);
    
        zb_set_ed_timeout(ED_AGING_TIMEOUT_64MIN);
        zb_set_keepalive_timeout(ZB_MILLISECONDS_TO_BEACON_INTERVAL(3000));
        sleepy_device_setup();
    
        /* Initialize application context structure. */
        UNUSED_RETURN_VALUE(ZB_MEMSET(&m_device_ctx, 0, sizeof(light_switch_ctx_t)));
        
        /* Set default bulb short_addr. */
        m_device_ctx.bulb_params.short_addr = 0xFFFF;
    
        /* Register dimmer switch device context (endpoints). */
        ZB_AF_REGISTER_DEVICE_CTX(&dimmer_switch_ctx);
    
        /** Start Zigbee Stack. */
        zb_err_code = zboss_start();
        ZB_ERROR_CHECK(zb_err_code);
    
        while(1)
        {
            zboss_main_loop_iteration();
            UNUSED_RETURN_VALUE(NRF_LOG_PROCESS());
        }
    }
    
    
    /**
     * @}
     */
    

    Hope this helps,
    Angry Oatmeal.

  • Hello Edvin, 

    Just as another side note, I found the the reason for the crash on multiple zb_bdb_finding_binding_initiator() calls. 

    It was occurring due to the second call returning an error 4 (RET_BUSY). For some strange reason, the ZB_ERROR_CHECK(zb_err_code) was not catching this, and causing the system to freeze. The solution is strange so maybe you could shed some light on it.

    The fix was to delete the NRF_LOG_INFO("Bulb Initiator") on line 363. Once this is deleted, the error check fires normally, and I can catch the error and deal with it appropriately using the below code:

            zb_err_code = zb_bdb_finding_binding_initiator(HA_DIMMABLE_LIGHT_ENDPOINT, finding_binding_cb);
    		if(zb_err_code == RET_BUSY)
    		{
    		    NRF_LOG_INFO("initiator is busy, cancel");
    		    zb_bdb_finding_binding_initiator_cancel();
    		}
    
     

    Just thought you should know,
    Angry Oatmeal.

  • Hello, 

    With the main.c files that you sent me, the bulb and switch will join the network, and the switch can control the light bulb. However, Is that not what you see?

    However, it is the bulb that initiate this in your project in the ZB_BDB_SIGNAL_DEVICE_REBOOT event. 

    zb_err_code = ZB_SCHEDULE_ALARM(find_light_bulb, param, MATCH_DESC_REQ_START_DELAY); ->

    zb_zdo_match_desc_req -> find_light_bulb_cb, and this will set the address and andpoint for the bulb. This address and endpoint is used directly in the button event handler buttons_handler(). Do you want me to remove the find_light_bulb call:

    zb_err_code = ZB_SCHEDULE_ALARM(find_light_bulb, param, MATCH_DESC_REQ_START_DELAY);?

  • Hello Edvin,

    With the main.c files that you sent me, the bulb and switch will join the network, and the switch can control the light bulb. However, Is that not what you see?

    I agree the bulb and switch will join the network without a problem. I am seeing this myself and that part is working as expected.

    However, it is the bulb that initiate this in your project in the ZB_BDB_SIGNAL_DEVICE_REBOOT event. 

    I'm a bit confused. I don't understand how the bulb initiates "this" from its ZB_BDB_SIGNAL_DEVICE_REBOOT event, could you please elaborate? From what I understand, the ZB_BDB_SIGNAL_DEVICE_REBOOT event on the bulb calls the bdb_start_top_level_commissioning(ZB_BDB_NETWORK_STEERING); function, which allows the bulb to join the network.

    The ZB_BDB_SIGNAL_DEVICE_REBOOT event on the switch however, goes through the steps you mentioned as: 

    zb_err_code = ZB_SCHEDULE_ALARM(find_light_bulb, param, MATCH_DESC_REQ_START_DELAY) -> zb_zdo_match_desc_req -> find_light_bulb_cb.

    Do you want me to remove the find_light_bulb call

    I don't think there will be any need for that, unless it can help with my finding and binding issues.

    Again, just to reiterate my issue. The normal operation of the light control examples works perfectly, there are no issues there. The issue comes from the added zb_bdb_finding_binding_target() and zb_bdb_finding_binding_initiator() functions. The rest of the code operates fine, it is simply this add-on feature which is not working as expected.

    If I call zb_bdb_finding_binding_target() on the bulb, then call zb_bdb_finding_binding_initiator()  on the switch, everything works fine, and when I check the switch's binding table, I can see the bulb there. Perfect.

    However, if I reverse this, and call zb_bdb_finding_binding_target() on the switch, then call zb_bdb_finding_binding_initiator() on the bulb, the switch is not able to bind to the bulb. In fact nothing actually happens. This is my primary issue.

    Would it perhaps help if I opened up a private support ticket for this issue? It seems this might need some more looking into than a quick forum post.

    Thank you,
    Angry Oatmeal

Related