Hi,
I'm trying to mimic a fully functional zigbee color light bulb using the nRF52840 dongle.
Starting with the NRF5 Zigbee example light_bulb (examples/zigbee/light_control/), I began familiarizing with the code using EmStudio as recommended.
After flashing the build HEX (nRF connect -> Programmer) to the nRF52840 dongle, I was able to control the nRF52840 dongle using a 3rd party zigbee controller: great!
The next step would be to add the different missing parameters, starting with hue parameter, to make the light_bulb a color_light_bulb. This is where I am reaching out for support.
The closest example code I found to use as a starting point is the ble_zigbee_dynamic_color_light_bulb_thingy (examples/multiprotocol/experiemental/).
I tried to salvage some of its code back into the light_bulb ones, including the "zb_ha_dimmable_color_light.h" and "zigbee_color_light.h" and adapting on the go without success.
Unfortunately, the gap from light_bulb to ble_zigbee_dynamic_color_light_bulb_thingy is too complicated for my current skill level.
I am adding the code below, where I am at now.
Am I going in the right direction? Any tips?
Thanks!
/** * Copyright (c) 2018 - 2020, 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_color_light.h" #include "zb_error_handler.h" #include "zb_nrf52_internal.h" #include "zigbee_helpers.h" #include "zigbee_color_light.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" #define MAX_CHILDREN 10 /**< The maximum amount of connected devices. Setting this value to 0 disables association to this device. */ #define IEEE_CHANNEL_MASK 0x07fff800U /**< Scan only one, predefined channel to find the coordinator.(1l << ZIGBEE_CHANNEL) */ #define HA_COLOR_LIGHT_ENDPOINT 10 /**< Device endpoint, used to receive light controlling commands. */ #define ERASE_PERSISTENT_CONFIG ZB_FALSE /**< 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 !defined ZB_ROUTER_ROLE #error Define ZB_ROUTER_ROLE to compile light bulb (Router) source code. #endif /* Declare context variable and cluster attribute list for first endpoint */ zb_color_light_ctx_t m_color_light_ctx_1; ZB_DECLARE_COLOR_CONTROL_CLUSTER_ATTR_LIST(m_color_light_ctx_1, m_color_light_clusters_1); /* Declare two endpoints for color controllable and dimmable light bulbs */ ZB_ZCL_DECLARE_COLOR_DIMMABLE_LIGHT_EP(m_color_light_ep_1, HA_COLOR_LIGHT_ENDPOINT, m_color_light_clusters_1); /* Declare context for endpoints */ ZBOSS_DECLARE_DEVICE_CTX_1_EP(m_color_light_ctx, m_color_light_ep_1); APP_PWM_INSTANCE(BULB_PWM_NAME, BULB_PWM_TIMER); //static bulb_device_ctx_t m_dev_ctx; /**@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) { } } /**@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); } /**@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_COLOR_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_COLOR_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_COLOR_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 setting the light bulb color. * * @param[in] new_level Light bulb color hue value. */ static void color_control_set_value(zb_uint16_t new_level) { NRF_LOG_INFO("Set color hue value: %i", new_level); ZB_ZCL_SET_ATTRIBUTE(HA_COLOR_LIGHT_ENDPOINT, ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL, ZB_ZCL_CLUSTER_SERVER_ROLE, ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_HUE_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_COLOR_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_COLOR_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); //need to call func to change LED } /**@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_COLOR_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_color_light_ctx_1.level_control_attr.current_level); } else { light_bulb_set_brightness(0U); } } /**@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_color_light_ctx_1.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_COLOR_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; } } /**@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_color_light_ctx_1.basic_attr.zcl_version = ZB_ZCL_VERSION; m_color_light_ctx_1.basic_attr.app_version = BULB_INIT_BASIC_APP_VERSION; m_color_light_ctx_1.basic_attr.stack_version = BULB_INIT_BASIC_STACK_VERSION; m_color_light_ctx_1.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_color_light_ctx_1.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_color_light_ctx_1.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_color_light_ctx_1.basic_attr.date_code, BULB_INIT_BASIC_DATE_CODE, ZB_ZCL_STRING_CONST_SIZE(BULB_INIT_BASIC_DATE_CODE)); m_color_light_ctx_1.basic_attr.power_source = BULB_INIT_BASIC_POWER_SOURCE; ZB_ZCL_SET_STRING_VAL(m_color_light_ctx_1.basic_attr.location_id, BULB_INIT_BASIC_LOCATION_DESC, ZB_ZCL_STRING_CONST_SIZE(BULB_INIT_BASIC_LOCATION_DESC)); m_color_light_ctx_1.basic_attr.ph_env = BULB_INIT_BASIC_PH_ENV; /* Identify cluster attributes data */ m_color_light_ctx_1.identify_attr.identify_time = ZB_ZCL_IDENTIFY_IDENTIFY_TIME_DEFAULT_VALUE; /* On/Off cluster attributes data */ m_color_light_ctx_1.on_off_attr.on_off = (zb_bool_t)ZB_ZCL_ON_OFF_IS_ON; m_color_light_ctx_1.level_control_attr.current_level = ZB_ZCL_LEVEL_CONTROL_LEVEL_MAX_VALUE; m_color_light_ctx_1.level_control_attr.remaining_time = ZB_ZCL_LEVEL_CONTROL_REMAINING_TIME_DEFAULT_VALUE; ZB_ZCL_SET_ATTRIBUTE(HA_COLOR_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_color_light_ctx_1.on_off_attr.on_off, ZB_FALSE); ZB_ZCL_SET_ATTRIBUTE(HA_COLOR_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_color_light_ctx_1.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] bufid Reference to Zigbee stack buffer used to pass received data. */ static zb_void_t zcl_device_cb(zb_bufid_t bufid) { zb_uint8_t cluster_id; zb_uint8_t attr_id; zb_zcl_device_callback_param_t * p_device_cb_param = ZB_BUF_GET_PARAM(bufid, zb_zcl_device_callback_param_t); NRF_LOG_INFO("zcl_device_cb id %hd", p_device_cb_param->device_cb_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 if (cluster_id == ZB_ZCL_CLUSTER_ID_COLOR_CONTROL) { uint16_t value = p_device_cb_param->cb_param.set_attr_value_param.values.data16; NRF_LOG_INFO("color control attribute setting to %hd", value); if (attr_id == ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_HUE_ID) { color_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] bufid Reference to the Zigbee stack buffer used to pass signal. */ void zboss_signal_handler(zb_bufid_t bufid) { /* Update network status LED */ zigbee_led_status_update(bufid, ZIGBEE_NETWORK_STATE_LED); /* No application-specific behavior is required. Call default signal handler. */ ZB_ERROR_CHECK(zigbee_default_signal_handler(bufid)); if (bufid) { zb_buf_free(bufid); } } /**@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(); /* 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_color_light_ctx_1, 0, sizeof(m_color_light_ctx_1))); /* 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(&m_color_light_ctx_1); //warning here during build bulb_clusters_attr_init(); level_control_set_value(m_color_light_ctx_1.level_control_attr.current_level); /** Start Zigbee Stack. */ zb_err_code = zboss_start_no_autostart(); ZB_ERROR_CHECK(zb_err_code); while(1) { zboss_main_loop_iteration(); UNUSED_RETURN_VALUE(NRF_LOG_PROCESS()); } } /** * @} */