diff --git a/CMakeLists.txt b/CMakeLists.txt index 4bfb8830..eeb3fbbc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,5 +4,7 @@ # This CMake file is picked by the Zephyr build system because it is defined # as the module CMake entry point (see zephyr/module.yml). +zephyr_include_directories(include) + add_subdirectory(lib) -add_subdirectory(subsys) \ No newline at end of file +add_subdirectory(subsys) diff --git a/include/zigbee/zigbee_app_utils.h b/include/zigbee/zigbee_app_utils.h new file mode 100644 index 00000000..b29159d3 --- /dev/null +++ b/include/zigbee/zigbee_app_utils.h @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef ZIGBEE_APP_UTILS_H__ +#define ZIGBEE_APP_UTILS_H__ + +#include +#include + +#include + +/** @file zigbee_app_utils.h + * + * @defgroup zigbee_app_utils Zigbee application utilities library. + * @{ + * @brief Library with helper functions and routines. + * + * @details Provides Zigbee default handler, helper functions for parsing + * and converting Zigbee data, indicating status of the device at a network + * using onboard LEDs. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/**@brief Function for setting the Erase persistent storage, + * depending on the erase pin. + * + * If the erase pin (1.39 by default, defined in zigbee_app_utils.c) + * is shortened to the ground, then the persistent storage is erased. + * Otherwise, whether the storage is erased is decided upon the input + * parameter 'erase'. This behaviour is only valid if PCA10056 is used. + * + * @param[in] erase Whether to erase the persistent storage in case + * the erase pin is not shortened to the ground. + */ +void zigbee_erase_persistent_storage(zb_bool_t erase); + +/**@brief Function for converting an input buffer to a hex string. + * + * @param[out] out Pointer to the output buffer. + * @param[in] out_size Size of the output buffer. + * @param[in] in Pointer to the input buffer. + * @param[in] in_size Size of the input buffer. + * @param[in] reverse If true, data output happens in the reverse order. + * + * @return snprintf-compatible value. Less than zero means encoding error. + * Non-negative value is the number of characters that + * would have been written if the supplied buffer had been + * large enough. Value greater than or equal to buf_len means + * that the supplied buffer was too small. + * + * @note Null terminator is written if buf_len is large enough, + * but does not count for the return value. + */ +int to_hex_str(char *out, uint16_t out_size, const uint8_t *in, + uint8_t in_size, bool reverse); + +/**@brief Read array of uint8_t from hex string. + * + * @param in_str Pointer to the input hex string. + * @param in_str_len Length, in characters, of the input string. + * @param out_buff Pointer to the output uint8_t array. + * @param out_buff_size Size, in bytes, of the output uint8_t array. + * @param reverse If true then parse from end to start. + * + * @retval true if the conversion succeed + * @retval false if the conversion failed + */ +bool parse_hex_str(char const *in_str, uint8_t in_str_len, + uint8_t *out_buff, uint8_t out_buff_size, bool reverse); + +/**@brief Parse a hex string to uint8_t. + * + * The function verifies if input is valid, i.e., if all input characters + * are valid hex digits. If an invalid character is found then function fails. + * + * @param s Pointer to input string. + * @param value Pointer to output value. + * + * @retval true if the conversion succeed + * @retval false if the conversion failed + */ +static inline bool parse_hex_u8(char const *s, uint8_t *value) +{ + return parse_hex_str(s, strlen(s), value, sizeof(*value), true); +} + +/**@brief Parse a hex string to uint16_t. + * + * The function verifies if input is valid, i.e., if all input characters + * are valid hex digits. If an invalid character is found then function fails. + * + * @param s Pointer to input string. + * @param value Pointer to output value. + * + * @retval true if the conversion succeed + * @retval false if the conversion failed + */ +static inline bool parse_hex_u16(char const *s, uint16_t *value) +{ + return parse_hex_str(s, strlen(s), (uint8_t *)value, sizeof(*value), true); +} + +/**@brief Function for converting 64-bit address to hex string. + * + * @param[out] str_buf Pointer to output buffer. + * @param[in] buf_len Length of the buffer pointed by str_buf. + * @param[in] in Zigbee IEEE address to be converted to string. + * + * @return snprintf-compatible value. Less than zero means encoding error. + * Non-negative value is the number of characters that would + * have been written if the supplied buffer had been large enough. + * Value greater than or equal to buf_len means that the supplied + * buffer was too small. + * + * @note Null terminator is written if buf_len is large enough, + * but does not count for the return value. + */ +int ieee_addr_to_str(char *str_buf, uint16_t buf_len, + const zb_ieee_addr_t in); + +/**@brief Address type. + * + * @ref ADDR_SHORT and @ref ADDR_LONG correspond to APS addressing + * mode constants and must not be changed. + */ +typedef enum { + ADDR_INVALID = 0, + ADDR_ANY = 1, + ADDR_SHORT = 2, /* ZB_APS_ADDR_MODE_16_ENDP_PRESENT */ + ADDR_LONG = 3, /* ZB_APS_ADDR_MODE_64_ENDP_PRESENT */ +} addr_type_t; + +/**@brief Function for parsing a null-terminated string of hex characters + * into 64-bit or 16-bit address. + * + * The function will skip 0x suffix from input if present. + * + * @param input Pointer to the input string string representing + * the address in big endian. + * @param output Pointer to the resulting zb_addr_u variable. + * @param addr_type Expected address type. + * + * @return Conversion result. + */ +addr_type_t parse_address(const char *input, zb_addr_u *output, + addr_type_t addr_type); + +/**@brief Function for parsing a null-terminated string of hex characters + * into a 64-bit address. + * + * The function will skip 0x suffix from input if present. + * + * @param input Pointer to the input string representing the address + * in big endian. + * @param addr Variable where the address will be placed. + * + * @retval true if the conversion succeed + * @retval false if the conversion failed + */ +static inline bool parse_long_address(const char *input, zb_ieee_addr_t addr) +{ + return (parse_address(input, (zb_addr_u *)addr, + ADDR_LONG) != ADDR_INVALID); +} + +/**@brief Function for parsing a null-terminated string of hex characters + * into 16-bit address. + * + * The function will skip 0x suffix from input if present. + * + * @param input Pointer to the input string representing + * the address in big endian. + * @param addr Pointer to the variable where address will be placed. + * + * @retval true if the conversion succeed + * @retval false if the conversion failed + */ +static inline bool parse_short_address(const char *input, zb_uint16_t *addr) +{ + return (parse_address(input, (zb_addr_u *)addr, + ADDR_SHORT) != ADDR_INVALID); +} + +/**@brief Function for passing signals to the default + * Zigbee stack event handler. + * + * @note This function does not free the Zigbee buffer. + * + * @param[in] bufid Reference to the Zigbee stack buffer used to pass signal. + * + * @return RET_OK on success or error code on failure. + */ +zb_ret_t zigbee_default_signal_handler(zb_bufid_t bufid); + +/**@brief Function for indicating the Zigbee network connection + * status on LED. + * + * If the device is successfully commissioned, the LED is turned on. + * If the device is not commissioned or has left the network, + * the LED is turned off. + * + * @note This function does not free the Zigbee buffer. + * + * @param[in] bufid Reference to the Zigbee stack buffer + * used to pass signal. + * @param[in] led_idx LED index, as defined in the board-specific + * BSP header. The index starts from 0. + */ +void zigbee_led_status_update(zb_bufid_t bufid, uint32_t led_idx); + +/**@brief Function for indicating the default signal handler + * about user input on the device. + * + * If the device is not commissioned, the rejoin procedure is started. + * + * @note This function is to be used with End Devices only. + * + */ +#if defined CONFIG_ZIGBEE_ROLE_END_DEVICE +void user_input_indicate(void); + +/**@brief Function for enabling sleepy behavior of End Device. Must be called + * before zigbee_enable() is called. Also must be called after ZB_INIT(), + * which is called when the system is initializing. + * + * @note This function is to be used with End Devices only. + * + * @param[in] enable Boolean value indicating if sleepy behavior + * should be enabled or disabled. + */ +void zigbee_configure_sleepy_behavior(bool enable); +#endif /* CONFIG_ZIGBEE_ROLE_END_DEVICE */ + +#if defined CONFIG_ZIGBEE_FACTORY_RESET + +/**@brief Registers which button and for how long has to be pressed in order to do Factory Reset. + * + * @note Must be called once before check_factory_reset_button function can be used. + * + * @param[in] button Development Kit button to be used as Factory Reset button. + */ +void register_factory_reset_button(uint32_t button); + +/**@brief Checks if Factory Reset button was pressed. + * If so, it initiates the procedure of checking if it was pressed for specified time. + * + * @note register_factory_reset_button function has to be called before (once) + * + * @param[in] button_state state of Development Kit buttons; passed from button handler + * @param[in] has_changed determines which buttons changed; passed from button handler + */ +void check_factory_reset_button(uint32_t button_state, uint32_t has_changed); + +/**@brief Indicates whether Factory Reset was started as a result of a button press or not. + * + * @return true if Factory Reset was started; false if Factory Reset was not started + */ +bool was_factory_reset_done(void); + +#endif /* CONFIG_ZIGBEE_FACTORY_RESET */ + +#ifdef __cplusplus +} +#endif + +/**@} */ + +#endif /* ZIGBEE_APP_UTILS_H__ */ diff --git a/include/zigbee/zigbee_error_handler.h b/include/zigbee/zigbee_error_handler.h new file mode 100644 index 00000000..083bebc8 --- /dev/null +++ b/include/zigbee/zigbee_error_handler.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @file + * @defgroup zb_error ZBOSS error handling utilities + * @{ + * @brief This file defines the error handler utilities for ZBOSS error codes. + */ + +#ifndef ZIGBEE_ERROR_HANDLER_H__ +#define ZIGBEE_ERROR_HANDLER_H__ + +#include + +#include +#include + +#ifdef CONFIG_ZBOSS_ERROR_PRINT_TO_LOG +#include "zb_error_to_string.h" +#include + + +/**@brief Macro for calling the error handler function if the supplied + * ZBOSS return code is different than RET_OK. + * + * @param[in] ERR_CODE Error code supplied to the error handler. + */ +#define ZB_ERROR_CHECK(ERR_CODE) \ + do { \ + const uint32_t LOCAL_ERR_CODE = (uint32_t) (-ERR_CODE); \ + if (LOCAL_ERR_CODE != RET_OK) { \ + LOG_ERR("ERROR %u [%s] at %s:%u", \ + LOCAL_ERR_CODE, \ + zb_error_to_string_get(LOCAL_ERR_CODE), \ + __FILE__, \ + __LINE__); \ + zb_osif_abort(); \ + } \ + } while (0) + +/**@brief Macro for calling the error handler function + * if the return code for bdb_start_top_level_commissioning + * indicates that the BDB procedure did not succeed. + * + * @param[in] COMM_STATUS Value returned by the + * bdb_start_top_level_commissioning function. + */ +#define ZB_COMM_STATUS_CHECK(COMM_STATUS) \ + do { \ + if (COMM_STATUS != ZB_TRUE) { \ + LOG_ERR("Unable to start BDB commissioning at %s:%u", \ + __FILE__, \ + __LINE__); \ + ZB_ERROR_CHECK(RET_ERROR); \ + } \ + } while (0) +#else +#define ZB_ERROR_CHECK(ERR_CODE) \ + do { \ + const uint32_t LOCAL_ERR_CODE = (uint32_t) (-ERR_CODE); \ + if (LOCAL_ERR_CODE != RET_OK) { \ + zb_osif_abort(); \ + } \ + } while (0) + +#define ZB_COMM_STATUS_CHECK(COMM_STATUS) \ + do { \ + if (COMM_STATUS != ZB_TRUE) { \ + ZB_ERROR_CHECK(RET_ERROR); \ + } \ + } while (0) +#endif + +/** + * @} + * + */ + +#endif /* ZIGBEE_ERROR_HANDLER_H__ */ diff --git a/include/zigbee/zigbee_fota.h b/include/zigbee/zigbee_fota.h new file mode 100644 index 00000000..38eca09f --- /dev/null +++ b/include/zigbee/zigbee_fota.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** @file zigbee_fota.h + * + * @defgroup zigbee_fota Zigbee firmware over-the-air download library + * @{ + * @brief Library for downloading a firmware update through Zigbee network. + * + * @details Discovers Zigbee OTA server, queries it for new images and + * downloads the matching file to the secondary partition of MCUboot. + * After the file has been downloaded, the secondary slot is tagged as having + * valid firmware inside it. + */ + +#ifndef ZIGBEE_FOTA_H_ +#define ZIGBEE_FOTA_H_ + +#include +#include + +#define ZIGBEE_FOTA_EVT_DL_COMPLETE_VAL 100 + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Zigbee FOTA download event IDs. + */ +enum zigbee_fota_evt_id { + /** Zigbee FOTA download progress report. */ + ZIGBEE_FOTA_EVT_PROGRESS, + /** Zigbee FOTA download finished. */ + ZIGBEE_FOTA_EVT_FINISHED, + /** Zigbee FOTA download error. */ + ZIGBEE_FOTA_EVT_ERROR, +}; + +/** + * @brief Zigbee FOTA download progress event data. + */ +struct zigbee_fota_event_dl { + int progress; /* Download progress percent, 0-100 */ +}; + +/** + * @brief Zigbee FOTA download event data. + */ +struct zigbee_fota_evt { + enum zigbee_fota_evt_id id; + union { + struct zigbee_fota_event_dl dl; + }; +}; + +/** + * @brief Zigbee FOTA download asynchronous callback function. + * + * @param evt Event. + * + */ +typedef void (*zigbee_fota_callback_t)(const struct zigbee_fota_evt *evt); + +/** + * @brief Initialize the Zigbee firmware over-the-air download library. + * + * @param client_callback Callback for the generated events. + * + * @retval 0 If successfully initialized. + * Otherwise, a negative value is returned. + */ +int zigbee_fota_init(zigbee_fota_callback_t client_callback); + +/** + * @brief Abort all pending updates performed via Zigbee network. + */ +void zigbee_fota_abort(void); + +/** + * @brief Function for passing Zigbee stack signals to the Zigbee FOTA logic. + * + * @param[in] bufid Reference to the Zigbee stack buffer used to pass signal. + */ +void zigbee_fota_signal_handler(zb_bufid_t bufid); + +/** + * @brief Function for passing ZCL callback events to the Zigbee FOTA logic. + * + * @param[in] bufid Reference to the Zigbee stack buffer used to pass event. + */ +void zigbee_fota_zcl_cb(zb_bufid_t bufid); + +#ifdef __cplusplus +} +#endif + +#endif /* ZIGBEE_FOTA_H_ */ + +/**@} */ diff --git a/include/zigbee/zigbee_logger_eprxzcl.h b/include/zigbee/zigbee_logger_eprxzcl.h new file mode 100644 index 00000000..e981086d --- /dev/null +++ b/include/zigbee/zigbee_logger_eprxzcl.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef ZIGBEE_COMMON_ZIGBEE_LOGGER_EPRXZCL_H_ +#define ZIGBEE_COMMON_ZIGBEE_LOGGER_EPRXZCL_H_ + +#include + +/** @file zigbee_logger_eprxzcl.h + * + * @defgroup zigbee_logger_ep Zigbee endpoint logger library. + * @{ + * @brief Library for logging incoming ZCL packets. + * + * @details Provides Zigbee endpoint handler for parsing incoming ZCL packets + * and logging packet's fields and payload. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/**@brief Handler function which may be called to log an incoming frame + * onto Zigbee endpoint. + * + * @details When this function is called as a callback bound to endpoint + * via ZB_AF_SET_ENDPOINT_HANDLER(), + * (directly or indirectly) it produces a log line similar + * to the following: + * @code + * Received ZCL command (17): src_addr=0x0000(short) src_ep=64 dst_ep=64 + * cluster_id=0x0000 profile_id=0x0104 rssi=0 cmd_dir=0 common_cmd=1 cmd_id=0x00 + * cmd_seq=128 disable_def_resp=0 manuf_code=void payload=[0700] (17) + * @endcode + * + * @param bufid Reference to Zigbee buffer holding received + * zcl command to be logged + * + * @retval ZB_FALSE in all conditions. This enables possibility + * to use this function directly + * as Zigbee stack endpoint handler. + * + */ +zb_uint8_t zigbee_logger_eprxzcl_ep_handler(zb_bufid_t bufid); + +#ifdef __cplusplus +} +#endif + +/**@} */ + +#endif /* ZIGBEE_COMMON_ZIGBEE_LOGGER_EPRXZCL_H_ */ diff --git a/include/zigbee/zigbee_shell.h b/include/zigbee/zigbee_shell.h new file mode 100644 index 00000000..5b533ca0 --- /dev/null +++ b/include/zigbee/zigbee_shell.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef ZIGBEE_SHELL_H__ +#define ZIGBEE_SHELL_H__ + +#include + + +/** @file zigbee_shell.h + * + * @defgroup zigbee_shell Zigbee shell library. + * @{ + * @brief Library for enabling and configuring Zigbee shell library. + * + * @details Provides a set of functions for enabling and configuring Zigbee shell library + * and zigbee shell endpoint handler function. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/**@brief Function to get the Endpoint number used by the shell. + * + * @return Number of Endpoint used by the shell. + */ +zb_uint8_t zb_shell_get_endpoint(void); + +/**@brief Sets the Endpoint number used by the shell. + */ +void zb_shell_set_endpoint(zb_uint8_t ep); + +/**@brief Configures shell endpoint by setting number of endpoint to be used + * by the shell and registers shell endpoint handler for chosen endpoint. + */ +void zb_shell_configure_endpoint(void); + +/**@brief Function for intercepting every frame coming to the endpoint, + * so the frame may be processed before it is processed by the Zigbee + * stack. + * + * @param bufid Reference to the ZBOSS buffer. + * + * @retval ZB_TRUE Frame has already been processed and Zigbee stack can skip + * processing this frame. + * @retval ZB_FALSE Zigbee stack must process this frame. + */ +zb_uint8_t zb_shell_ep_handler(zb_bufid_t bufid); + +/**@brief Function for setting the state of the debug mode of the shell. + * + * @param debug Turns the debug mode on (ZB_TRUE) or off (ZB_FALSE). + */ +void zb_shell_debug_set(zb_bool_t debug); + +/**@brief Function for getting the state of the debug mode of the shell. + * + * @retval ZB_TRUE Debug mode is turned on. + * @retval ZB_FALSE Debug mode is turned off. + */ +zb_bool_t zb_shell_debug_get(void); + +#ifdef CONFIG_ZIGBEE_SHELL_DEBUG_CMD +/**@brief Function for getting the state of the Zigbee NVRAM. + * + * @retval ZB_TRUE Zigbee NVRAM is enabled. + * @retval ZB_FALSE Zigbee NVRAM is disabled. + */ +zb_bool_t zb_shell_nvram_enabled(void); +#endif /* CONFIG_ZIGBEE_SHELL_DEBUG_CMD */ + +#ifdef __cplusplus +} +#endif + +/**@} */ + +#endif /* ZIGBEE_SHELL_H__ */ diff --git a/include/zigbee/zigbee_zcl_scenes.h b/include/zigbee/zigbee_zcl_scenes.h new file mode 100644 index 00000000..a6dbf549 --- /dev/null +++ b/include/zigbee/zigbee_zcl_scenes.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef ZIGBEE_ZCL_SCENES_H__ +#define ZIGBEE_ZCL_SCENES_H__ + +#include + +/** @file zigbee_zcl_scenes.h + * + * @defgroup zigbee_scenes Zigbee ZCL scenes helper library. + * @{ + * @brief Library for handling ZCL scenes events for common clusters. + * + * @details Provides ZCL callback event processing function that handles ZCL scenes commands. + */ + +/** + * @brief Initialize the Zigbee ZCL scenes helper library. + * + * @details This function initializes the ZCL scene table as well as registers the + * settings observer for restoring scene table entries. + */ +void zcl_scenes_init(void); + +/** + * @brief Function for passing ZCL callback events to the scenes helper library logic. + * + * @param[in] bufid Reference to the Zigbee stack buffer used to pass event. + * + * @retval ZB_TRUE if the event was handled by the library + * @retval ZB_FALSE if the event was not handled by the library + */ +zb_bool_t zcl_scenes_cb(zb_bufid_t bufid); + +#endif /* ZIGBEE_ZCL_SCENES_H__ */ + +/**@} */ diff --git a/subsys/CMakeLists.txt b/subsys/CMakeLists.txt new file mode 100644 index 00000000..7e40e931 --- /dev/null +++ b/subsys/CMakeLists.txt @@ -0,0 +1,44 @@ +# +# Copyright (c) 2020 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +add_subdirectory(lib) + +zephyr_interface_library_named(zigbee) + +target_include_directories(zigbee INTERFACE osif) + +zephyr_library() + +zephyr_library_link_libraries(zigbee) + +zephyr_link_libraries_ifdef(CONFIG_ZIGBEE_USE_SOFTWARE_AES nrfxlib_crypto) + +# Source files +zephyr_library_sources(osif/zb_nrf_platform.c) +zephyr_library_sources(osif/zb_nrf_transceiver.c) +zephyr_library_sources(osif/zb_nrf_crypto.c) +zephyr_library_sources(osif/zb_nrf_pwr_mgmt.c) +zephyr_library_sources_ifdef(CONFIG_DK_LIBRARY osif/zb_nrf_led_button.c) +zephyr_library_sources_ifdef(CONFIG_ZIGBEE_HAVE_SERIAL osif/zb_nrf_serial.c) +zephyr_library_sources_ifdef(CONFIG_ZIGBEE_HAVE_ASYNC_SERIAL osif/zb_nrf_async_serial.c) +zephyr_library_sources_ifdef(CONFIG_ZBOSS_TRACE_HEXDUMP_LOGGING osif/zb_nrf_logger.c) +zephyr_library_sources_ifdef(CONFIG_ZBOSS_TRACE_BINARY_LOGGING osif/zb_nrf_serial_logger.c) + +zephyr_library_sources_ifdef(CONFIG_FLASH_MAP osif/zb_nrf_nvram.c) +zephyr_library_sources_ifndef(CONFIG_FLASH_MAP osif/zb_nrf_nvram_none.c) + +zephyr_library_sources_ifdef(CONFIG_ZIGBEE_TIME_COUNTER osif/zb_nrf_timer_counter.c) +zephyr_library_sources_ifdef(CONFIG_ZIGBEE_TIME_KTIMER osif/zb_nrf_timer_ktimer.c) + +zephyr_library_link_libraries(zboss) + +zephyr_compile_definitions(CUSTOM_IEEE802154_L2=ZIGBEE_L2) +zephyr_compile_definitions(ZIGBEE_L2=ZIGBEE_ADD_ON) + +if (CONFIG_ZIGBEE_ADD_ON) + include(${ZEPHYR_NRF_MODULE_DIR}/cmake/extensions.cmake) + ncs_add_partition_manager_config(partition_manager/pm.yml.zboss) +endif() diff --git a/subsys/Kconfig b/subsys/Kconfig new file mode 100644 index 00000000..477e6b45 --- /dev/null +++ b/subsys/Kconfig @@ -0,0 +1,419 @@ +# +# Copyright (c) 2020 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menuconfig ZIGBEE_ADD_ON + bool "Enable Zigbee stack [EXPERIMENTAL]" + select EXPERIMENTAL + imply FPU + select APP_LINK_WITH_ZBOSS + select PM_SINGLE_IMAGE + select NET_L2_ZIGBEE + select NETWORKING + select NET_PKT_TXTIME + select REBOOT + select PSA_WANT_ALG_ECB_NO_PADDING if NRF_SECURITY + select PSA_WANT_KEY_TYPE_AES if NRF_SECURITY + select MBEDTLS_ENABLE_HEAP if NRF_SECURITY + imply ENTROPY_GENERATOR + imply POLL + imply FLASH + imply FLASH_PAGE_LAYOUT + imply FLASH_MAP + imply MPU_ALLOW_FLASH_WRITE + depends on (SOC_NRF52840 || SOC_NRF54L15) + +if ZIGBEE_ADD_ON + +rsource "lib/Kconfig" + +# Override libc implementation +choice LIBC_IMPLEMENTATION + default MINIMAL_LIBC + help + Use minimal libc implementation with Zigbee. +endchoice + +config NET_L2_ZIGBEE + bool "Zigbee L2" + depends on NETWORKING + select NET_L2_CUSTOM_IEEE802154 + select NET_PKT_TIMESTAMP + select IEEE802154_NRF5_FCS_IN_LENGTH + +choice ZIGBEE_CHANNEL_SELECTION_MODE + prompt "Zigbee channel selection mode" + default ZIGBEE_CHANNEL_SELECTION_MODE_SINGLE + +config ZIGBEE_CHANNEL_SELECTION_MODE_SINGLE + bool "Single channel" + help + "Zigbee uses exactly one channel specified by its number" + +config ZIGBEE_CHANNEL_SELECTION_MODE_MULTI + bool "Group of channels" + help + "Zigbee uses a group of channels specified by bitwise mask" + +endchoice # "Zigbee channel selection mode" + +config ZIGBEE_CHANNEL + int "802.15.4 channel used by Zigbee <11-26>" + range 11 26 + default 16 + depends on ZIGBEE_CHANNEL_SELECTION_MODE_SINGLE + help + 802.15.4 channel used by Zigbee. Defaults to 16. + +config ZIGBEE_CHANNEL_MASK + hex "Mask of 802.15.4 channels used by Zigbee" + range 0x800 0x7FFF800 + default 0x7FFF800 + depends on ZIGBEE_CHANNEL_SELECTION_MODE_MULTI + help + Bitwise "OR" mask of channels used by Zigbee. + The default setting enables all channels. + + Available channels: + - bits 31..27: UNUSED, should be cleared + - bits 26..11: bit n enables its corresponding channel + - bits 10..0: UNUSED, should be cleared + +choice ZIGBEE_ROLE + prompt "Select Zigbee device role" + +config ZIGBEE_ROLE_END_DEVICE + bool "End Device role" + +config ZIGBEE_ROLE_ROUTER + bool "Router role" + +config ZIGBEE_ROLE_COORDINATOR + bool "Coordinator role" + +endchoice + +config ZBOSS_DEFAULT_THREAD_PRIORITY + int "Set default ZBOSS thread priority" + default 3 + +config ZBOSS_DEFAULT_THREAD_STACK_SIZE + int "Stack size of ZBOSS Zephyr task" + default 5120 + help + Stack size of ZBOSS Zephyr task. This is the task zboss_main_loop_iteration is called from. + +config ZIGBEE_APP_CB_QUEUE_LENGTH + int "Length of the application callback and alarm queue" + default 10 + help + This queue is used to pass application callbacks and alarms from other + threads/ISR to the ZBOSS main loop context. + Elements from this queue are flushed right after ZBOSS context awakes, + before the actual callback execution. + +config ZIGBEE_DEBUG_FUNCTIONS + bool "Include Zigbee debug functions" + help + Include functions to suspend/resume ZBOSS thread. + It may be helpful when debugging but using this functions can cause instability of the device. + +# Configure default behavior of zb_osif_abort() function called as ZBOSS assert handler +choice ZBOSS_ASSERT_HANDLER + prompt "Default behavior for ZBOSS stack internal assert handler" + default ZBOSS_RESET_ON_ASSERT + + config ZBOSS_RESET_ON_ASSERT + bool "Reset device when a ZBOSS assert occurs" + depends on REBOOT + + config ZBOSS_HALT_ON_ASSERT + bool "Halt device when a ZBOSS assert occurs. Use only for testing and debugging" + +endchoice + +menu "Zigbee Log configuration" + +config ZBOSS_ERROR_PRINT_TO_LOG + bool "Log ZBOSS errors" + default y + select ZIGBEE_ERROR_TO_STRING_ENABLED + help + Log ZBOSS errors. + +config ZIGBEE_ERROR_TO_STRING_ENABLED + bool "Include functions for mapping ZBOSS errors to string" + default y + help + Include functions for mapping ZBOSS errors to string. + +# Configure ZBOSS_TRACE_LOG_LEVEL +choice ZBOSS_TRACE_LOG_LEVEL_CHOICE + prompt "Max compiled-in log level for ZBOSS trace" + default ZBOSS_TRACE_LOG_LEVEL_OFF + +config ZBOSS_TRACE_LOG_LEVEL_OFF + bool "Off" + +config ZBOSS_TRACE_LOG_LEVEL_ERR + depends on LOG + bool "Error" + +config ZBOSS_TRACE_LOG_LEVEL_WRN + depends on LOG + bool "Warning" + +config ZBOSS_TRACE_LOG_LEVEL_INF + depends on LOG + bool "Info" + +config ZBOSS_TRACE_LOG_LEVEL_DBG + depends on LOG + bool "Debug" + +endchoice + +config ZBOSS_TRACE_LOG_LEVEL + int + default 0 if ZBOSS_TRACE_LOG_LEVEL_OFF + default 1 if ZBOSS_TRACE_LOG_LEVEL_ERR + default 2 if ZBOSS_TRACE_LOG_LEVEL_WRN + default 3 if ZBOSS_TRACE_LOG_LEVEL_INF + default 4 if ZBOSS_TRACE_LOG_LEVEL_DBG + +config ZBOSS_TRACE_MASK + hex "Trace mask of ZBOSS stack logs" + default 0x0000 + range 0x00000000 0xFFFFFFFF + help + Selectively enable Zigbee binary trace logs. + The mask value is expected to be a bitwise OR of values assigned to selected modules. + + Available modules: + + - 0x04000000 Reserved + - 0x02000000 Reserved + - 0x01000000 Reserved + - 0x00800000 Reserved + - 0x00400000 Reserved + - 0x00200000 Zigbee NCP: transport (LL) + - 0x00100000 Zigbee NCP: command adapters (HL) + - 0x00080000 ZCL: Firmware over-the-air upgrade + - 0x00040000 IAS zone device: battery monitoring + - 0x00020000 Reserved + - 0x00010000 MAC lower layer + - 0x00008000 MAC layer API calls + - 0x00004000 Zigbee Green Power + - 0x00002000 Custom components + - 0x00001000 Reserved + - 0x00000800 Application + - 0x00000400 Reserved + - 0x00000200 Zigbee Light Link + - 0x00000100 Zigbee Cluster Library + - 0x00000080 Security + - 0x00000040 Zigbee Device Object + - 0x00000020 Zigbee Smart Energy + - 0x00000010 Application support layer + - 0x00000008 Network layer + - 0x00000004 MAC layer + - 0x00000002 Memory management + - 0x00000001 Common + + For example, to enable traces related to OTA DFU, set this option to 0x00080100. + + Note: For general debugging purposes, use 0x00000C48. + +config ZBOSS_TRAF_DUMP + bool "Enable logging received 802.15.4 frames over ZBOSS traces" + depends on !ZBOSS_TRACE_LOG_LEVEL_OFF + help + Dumps all packets destined to the node over ZBOSS binary trace protocol + +if ZIGBEE_HAVE_SERIAL + +choice ZBOSS_TRACE_LOGGING_METHOD + prompt "Select ZBOSS trace logging method: hexdump, binary, NCP transport" + default ZBOSS_TRACE_HEXDUMP_LOGGING + +config ZBOSS_TRACE_HEXDUMP_LOGGING + bool "Enable logging hexdumped ZBOSS Traces" + select RING_BUFFER + +config ZBOSS_TRACE_BINARY_LOGGING + bool "Enable logging binary ZBOSS Traces" + select SERIAL + select RING_BUFFER + +config ZBOSS_TRACE_BINARY_NCP_TRANSPORT_LOGGING + bool "Enable logging binary ZBOSS Traces over the NCP transport channel" + depends on ZIGBEE_HAVE_ASYNC_SERIAL + +endchoice +endif # ZIGBEE_HAVE_SERIAL + +if (ZBOSS_TRACE_HEXDUMP_LOGGING || ZBOSS_TRACE_BINARY_LOGGING) + +config ZBOSS_TRACE_LOGGER_BUFFER_SIZE + int "Size of ring buffer to store ZBOSS Traces" + default 4096 + range 256 2147483648 + +endif # (ZBOSS_TRACE_HEXDUMP_LOGGING || ZBOSS_TRACE_BINARY_LOGGING) + +if ZBOSS_TRACE_BINARY_LOGGING + +choice ZBOSS_TRACE_LOGGING_BACKEND + prompt "Backend used to log ZBOSS Traces" + default ZBOSS_TRACE_UART_LOGGING + +config ZBOSS_TRACE_UART_LOGGING + bool "UART Serial" + select UART_INTERRUPT_DRIVEN + +config ZBOSS_TRACE_USB_CDC_LOGGING + bool "UART USB CDC (supported only for Zigbee NCP sample)" + select USB_DEVICE_STACK + select USB_CDC_ACM + select UART_LINE_CTRL + select UART_INTERRUPT_DRIVEN + +endchoice + +endif # ZBOSS_TRACE_BINARY_LOGGING + +# Configure ZBOSS_OSIF_LOG_LEVEL +module = ZBOSS_OSIF +module-str = ZBOSS OSIF layer +source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" + +endmenu #menu "Zigbee Log configuration" + +menu "ZBOSS OSIF configuration" + +config ZIGBEE_HAVE_SERIAL + bool "UART serial abstract for ZBOSS OSIF" + default y + +menuconfig ZIGBEE_HAVE_ASYNC_SERIAL + bool "Asynchronous UART serial abstract for ZBOSS OSIF" + select SERIAL + select UART_INTERRUPT_DRIVEN + select RING_BUFFER + depends on ZIGBEE_HAVE_SERIAL + +if ZIGBEE_HAVE_ASYNC_SERIAL + +config ZIGBEE_UART_SUPPORTS_FLOW_CONTROL + bool "Set to true to inform application that Zigbee UART device supports flow control" + default n + depends on UART_LINE_CTRL + +config ZIGBEE_UART_TX_TIMEOUT + int "Timeout value for starting asynchronous transmission, in milliseconds" + default 10 + +config ZIGBEE_UART_PARTIAL_TX_TIMEOUT + int "Timeout value between the last transmitted byte and the TX ready event if only a part of TX buffer was transmitted, in milliseconds" + default 10 + +config ZIGBEE_UART_RX_TIMEOUT + int "Timeout value for starting asynchronous reception, in milliseconds" + default 10 + +config ZIGBEE_UART_PARTIAL_RX_TIMEOUT + int "Timeout value between the last received byte and the RX event if only a part of RX buffer was received, in milliseconds" + default 10 + +config ZIGBEE_UART_RX_BUF_LEN + int "Size of the asynchronous receive buffer" + default 16 + +config ZIGBEE_UART_TX_BUF_LEN + int "Size of the synchronous transmit buffer" + default 128 + +endif #ZIGBEE_HAVE_ASYNC_SERIAL + +config ZIGBEE_USE_SOFTWARE_AES + bool "Use software based AES" + select NRF_OBERON + default n + +config NRF_SECURITY + default y + +if NRF_SECURITY + +config MBEDTLS_HEAP_SIZE + default 2048 + +config PSA_CRYPTO_DRIVER_OBERON + default y + +endif + +config ZIGBEE_USE_LEDS + bool "LEDs abstract for ZBOSS OSIF" + imply GPIO + imply DK_LIBRARY + +config ZIGBEE_USE_DIMMABLE_LED + bool "Dimmable LED (PWM) abstract for ZBOSS OSIF" + imply GPIO + imply PWM + +config ZIGBEE_USE_BUTTONS + bool "Buttons abstract for ZBOSS OSIF" + imply GPIO + imply DK_LIBRARY + +config ZIGBEE_NVRAM_PAGE_COUNT + int "The number of ZBOSS NVRAM pages" + default 2 + range 2 16 + +config ZIGBEE_NVRAM_PAGE_SIZE + depends on !FLASH_MAP + int "The size of a single ZBOSS NVRAM page" + default 512 + +config ZIGBEE_TC_REJOIN_ENABLED + bool "Enables Trust Center Rejoin" + default y + +DT_CHOSEN_NCS_ZIGBEE_TIMER := ncs,zigbee-timer + +choice ZIGBEE_TIME_SOURCE + prompt "ZBOSS time source" + default ZIGBEE_TIME_COUNTER if ZIGBEE_LIBRARY_PRODUCTION && \ + $(dt_chosen_enabled,$(DT_CHOSEN_NCS_ZIGBEE_TIMER)) + +config ZIGBEE_TIME_COUNTER + bool "Dedicated timer-based counter" + depends on $(dt_chosen_enabled,$(DT_CHOSEN_NCS_ZIGBEE_TIMER)) + imply COUNTER + +config ZIGBEE_TIME_KTIMER + bool "Kernel (system) time" + +endchoice + +endmenu #menu "ZBOSS osif configuration" + +config APP_LINK_WITH_ZIGBEE + bool + default y + help + Link application with Zigbee subsystem + +partition=ZBOSS_NVRAM +partition-size=0x8000 +source "${ZEPHYR_BASE}/../nrf/subsys/partition_manager/Kconfig.template.partition_config" + +partition=ZBOSS_PRODUCT_CONFIG +partition-size=0x1000 +source "${ZEPHYR_BASE}/../nrf/subsys/partition_manager/Kconfig.template.partition_config" + +endif #ZIGBEE_ADD_ON diff --git a/subsys/lib/CMakeLists.txt b/subsys/lib/CMakeLists.txt new file mode 100644 index 00000000..061ba8b2 --- /dev/null +++ b/subsys/lib/CMakeLists.txt @@ -0,0 +1,11 @@ +# +# Copyright (c) 2020 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +add_subdirectory_ifdef(CONFIG_ZIGBEE_FOTA zigbee_fota) +add_subdirectory_ifdef(CONFIG_ZIGBEE_APP_UTILS zigbee_app_utils) +add_subdirectory_ifdef(CONFIG_ZIGBEE_LOGGER_EP zigbee_logger_ep) +add_subdirectory_ifdef(CONFIG_ZIGBEE_SCENES zigbee_scenes) +add_subdirectory_ifdef(CONFIG_ZIGBEE_SHELL zigbee_shell) diff --git a/subsys/lib/Kconfig b/subsys/lib/Kconfig new file mode 100644 index 00000000..a9289503 --- /dev/null +++ b/subsys/lib/Kconfig @@ -0,0 +1,14 @@ +# Copyright (c) 2020 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menu "Zigbee libraries" + +rsource "zigbee_fota/Kconfig" +rsource "zigbee_app_utils/Kconfig" +rsource "zigbee_logger_ep/Kconfig" +rsource "zigbee_scenes/Kconfig" +rsource "zigbee_shell/Kconfig" + +endmenu diff --git a/subsys/lib/zigbee_app_utils/CMakeLists.txt b/subsys/lib/zigbee_app_utils/CMakeLists.txt new file mode 100644 index 00000000..4b75fd1b --- /dev/null +++ b/subsys/lib/zigbee_app_utils/CMakeLists.txt @@ -0,0 +1,11 @@ +# +# Copyright (c) 2020 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +zephyr_library() +zephyr_library_sources(zigbee_app_utils.c) + +zephyr_library_link_libraries(zboss) +zephyr_library_link_libraries(zigbee) diff --git a/subsys/lib/zigbee_app_utils/Kconfig b/subsys/lib/zigbee_app_utils/Kconfig new file mode 100644 index 00000000..2caa7b75 --- /dev/null +++ b/subsys/lib/zigbee_app_utils/Kconfig @@ -0,0 +1,38 @@ +# +# Copyright (c) 2020 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menuconfig ZIGBEE_APP_UTILS + bool "Enable API for Zigbee application utilities" + help + This allows Zigbee applications to call API: + + - for parsing and converting Zigbee data types + - for handling common ZBOSS signals, with behavior depending on the device role + - for indicating the device status at the network using onboard LEDs + +if ZIGBEE_APP_UTILS + +# Configure ZIGBEE_APP_UTILS_LOG_LEVEL +module = ZIGBEE_APP_UTILS +module-str = Zigbee application utilities +source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" + +menuconfig ZIGBEE_FACTORY_RESET + bool "Enable API for Zigbee factory reset" + default y + imply DK_LIBRARY + +if ZIGBEE_FACTORY_RESET + +config FACTORY_RESET_PRESS_TIME_SECONDS + int "Minimal Factory Reset button press time" + default 5 + help + How long a specified button has to be pressed to initiate Factory Reset procedure + +endif # ZIGBEE_FACTORY_RESET + +endif # ZIGBEE_APP_UTILS diff --git a/subsys/lib/zigbee_app_utils/zigbee_app_utils.c b/subsys/lib/zigbee_app_utils/zigbee_app_utils.c new file mode 100644 index 00000000..702092ae --- /dev/null +++ b/subsys/lib/zigbee_app_utils/zigbee_app_utils.c @@ -0,0 +1,1064 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +/* Number of retries until the pin value stabilizes. */ +#define READ_RETRIES 10 + +/* Timeout after which End Device stops to send beacons + * if can not join/rejoin a network. + */ +#ifndef ZB_DEV_REJOIN_TIMEOUT_MS +#define ZB_DEV_REJOIN_TIMEOUT_MS (1000 * 200) +#endif + +/* Maximum interval between join/rejoin attempts. */ +#define REJOIN_INTERVAL_MAX_S (15 * 60) + +/* Rejoin interval, after which the device will perform Trust Center Rejoin + * instead of a secure rejoin. + */ +#define TC_REJOIN_INTERVAL_THRESHOLD_S (2 * 60) +#define ZB_SECUR_PROVISIONAL_KEY 2 + +#define IEEE_ADDR_BUF_SIZE 17 + +#if defined CONFIG_ZIGBEE_FACTORY_RESET +#define FACTORY_RESET_PROBE_TIME K_SECONDS(1) +#endif /* CONFIG_ZIGBEE_FACTORY_RESET */ + +LOG_MODULE_REGISTER(zigbee_app_utils, CONFIG_ZIGBEE_APP_UTILS_LOG_LEVEL); + +/* Rejoin-procedure related variables. */ +static bool stack_initialised; +static bool is_rejoin_procedure_started; +static bool is_rejoin_stop_requested; +static bool is_rejoin_in_progress; +static uint8_t rejoin_attempt_cnt; +#if defined CONFIG_ZIGBEE_ROLE_END_DEVICE +static volatile bool wait_for_user_input; +static volatile bool is_rejoin_start_scheduled; +#endif + +/* Forward declarations. */ +static void rejoin_the_network(zb_uint8_t param); +static void start_network_rejoin(void); +static void stop_network_rejoin(zb_uint8_t was_scheduled); +#if defined ZB_COORDINATOR_ROLE +static void change_panid(zb_uint8_t param); +#endif + +/* A ZBOSS internal API needed for workaround for KRKNWK-14112 */ +struct zb_aps_device_key_pair_set_s ZB_PACKED_PRE +{ + zb_ieee_addr_t device_address; + zb_uint8_t link_key[ZB_CCM_KEY_SIZE]; +#ifndef ZB_LITE_NO_GLOBAL_VS_UNIQUE_KEYS + zb_bitfield_t aps_link_key_type:1; +#endif + zb_bitfield_t key_source:1; + zb_bitfield_t key_attributes:2; + zb_bitfield_t reserved:4; + zb_uint8_t align[3]; +} ZB_PACKED_STRUCT; +extern void zb_nwk_forget_device(zb_uint8_t addr_ref); +extern struct zb_aps_device_key_pair_set_s *zb_secur_get_link_key_by_address( + zb_ieee_addr_t address, + zb_uint8_t attr); + + +#if defined CONFIG_ZIGBEE_FACTORY_RESET +/* Factory Reset related variables. */ +struct factory_reset_context_t { + uint32_t button; + bool reset_done; + bool pibcache_pan_id_needs_reset; + struct k_timer timer; +}; +static struct factory_reset_context_t factory_reset_context; +#endif /* CONFIG_ZIGBEE_FACTORY_RESET */ + +/**@brief Function to set the Erase persistent storage + * depending on the erase pin + */ +void zigbee_erase_persistent_storage(zb_bool_t erase) +{ +#ifdef ZB_USE_NVRAM + zb_set_nvram_erase_at_start(erase); +#endif +} + +int to_hex_str(char *out, uint16_t out_size, const uint8_t *in, + uint8_t in_size, bool reverse) +{ + int bytes_written = 0; + int status; + int i = reverse ? in_size - 1 : 0; + + for (; in_size > 0; in_size--) { + status = snprintf(out + bytes_written, + out_size - bytes_written, + "%02x", + in[i]); + if (status < 0) { + return status; + } + + bytes_written += status; + i += reverse ? -1 : 1; + } + + return bytes_written; +} + +int ieee_addr_to_str(char *str_buf, uint16_t buf_len, + const zb_ieee_addr_t addr) +{ + return to_hex_str(str_buf, buf_len, (const uint8_t *)addr, + sizeof(zb_ieee_addr_t), true); +} + +bool parse_hex_str(char const *in_str, uint8_t in_str_len, uint8_t *out_buff, + uint8_t out_buff_size, bool reverse) +{ + uint8_t i = 0; + int8_t delta = 1; + + /* Skip 0x suffix if present. */ + if ((in_str_len > 2) && (in_str[0] == '0') && + (tolower(in_str[1]) == 'x')) { + in_str_len -= 2; + in_str += 2; + } + + if (reverse) { + in_str = in_str + in_str_len - 1; + delta = -1; + } + + /* Check if we have enough output space */ + if (in_str_len > 2 * out_buff_size) { + return false; + } + + memset(out_buff, 0, out_buff_size); + + while (i < in_str_len) { + uint8_t nibble = 0; + + if (char2hex(*in_str, &nibble)) { + break; + } + + if (i & 0x01) { + *out_buff |= reverse ? nibble << 4 : nibble; + out_buff++; + } else { + *out_buff = reverse ? nibble : nibble << 4; + } + + i += 1; + in_str += delta; + } + + return (i == in_str_len); +} + +addr_type_t parse_address(const char *input, zb_addr_u *addr, + addr_type_t addr_type) +{ + addr_type_t result = ADDR_INVALID; + size_t len; + + if (!input || !addr) { + return ADDR_INVALID; + } + + len = strlen(input); + if (!len) { + return ADDR_INVALID; + } + + /* Skip 0x suffix if present. */ + if ((input[0] == '0') && (tolower(input[1]) == 'x')) { + input += 2; + len -= 2; + } + + if ((len == 2 * sizeof(zb_ieee_addr_t)) && + (addr_type == ADDR_ANY || addr_type == ADDR_LONG)) { + result = ADDR_LONG; + } else if ((len == 2 * sizeof(uint16_t)) && + (addr_type == ADDR_ANY || addr_type == ADDR_SHORT)) { + result = ADDR_SHORT; + } else { + return ADDR_INVALID; + } + + return parse_hex_str(input, len, (uint8_t *)addr, len / 2, true) ? + result : + ADDR_INVALID; +} + +zb_ret_t zigbee_default_signal_handler(zb_bufid_t bufid) +{ + zb_zdo_app_signal_hdr_t *sig_hndler = NULL; + zb_zdo_app_signal_type_t sig = zb_get_app_signal(bufid, &sig_hndler); + zb_ret_t status = ZB_GET_APP_SIGNAL_STATUS(bufid); + zb_nwk_device_type_t role = zb_get_network_role(); + zb_ret_t ret_code = RET_OK; + zb_bool_t comm_status = ZB_TRUE; + + switch (sig) { + case ZB_ZDO_SIGNAL_PRODUCTION_CONFIG_READY: + /* At this point Zigbee stack attempted to load production + * configuration from NVRAM. + * This step is performed each time the stack is initialized. + * + * Note: if it is necessary for a device to have + * a valid production configuration to operate + * (e.g. due to legal reasons), the application should + * implement the customized logic for this signal + * (e.g. assert on the signal status code). + */ + if (status != RET_OK) { + LOG_INF("Production configuration is not present or invalid (status: %d)", + status); + } else { + LOG_INF("Production configuration successfully loaded"); + } + break; + + case ZB_ZDO_SIGNAL_SKIP_STARTUP: + /* At this point Zigbee stack: + * - Initialized the scheduler. + * - Initialized and read NVRAM configuration. + * - Initialized all stack-related global variables. + * + * Next step: perform BDB initialization procedure, + * (see BDB specification section 7.1). + */ + ZB_ERROR_CHECK(zb_zcl_set_backward_comp_mode(ZB_ZCL_AUTO_MODE)); + ZB_ERROR_CHECK( + zb_zcl_set_backward_compatible_statuses_mode(ZB_ZCL_STATUSES_ZCL8_MODE)); + stack_initialised = true; + LOG_INF("Zigbee stack initialized"); + comm_status = bdb_start_top_level_commissioning( + ZB_BDB_INITIALIZATION); + break; + + case ZB_BDB_SIGNAL_DEVICE_FIRST_START: + /* At this point Zigbee stack is ready to operate and the BDB + * initialization procedure has finished. + * There is no network configuration stored inside NVRAM. + * + * Next step: + * - If the device implements Zigbee router + * or Zigbee end device, perform network steering + * for a node not on a network, + * (see BDB specification section 8.3). + * - If the device implements Zigbee coordinator, + * perform network formation, + * (see BDB specification section 8.4). + */ + LOG_INF("Device started for the first time"); + if (status == RET_OK) { + if (role != ZB_NWK_DEVICE_TYPE_COORDINATOR) { + LOG_INF("Start network steering"); + start_network_rejoin(); + } else { + LOG_INF("Start network formation"); + comm_status = bdb_start_top_level_commissioning( + ZB_BDB_NETWORK_FORMATION); + } + } else { + LOG_ERR("Failed to initialize Zigbee stack (status: %d)", + status); + } + break; + + case ZB_BDB_SIGNAL_DEVICE_REBOOT: + /* At this point Zigbee stack is ready to operate and the BDB + * initialization procedure has finished. There is network + * configuration stored inside NVRAM, so the device + * will try to rejoin. + * + * Next step: if the device implement Zigbee router or + * end device, and the initialization has failed, + * perform network steering for a node on a network, + * (see BDB specification section 8.2). + */ + if (status == RET_OK) { + zb_ext_pan_id_t extended_pan_id; + char ieee_addr_buf[IEEE_ADDR_BUF_SIZE] = { 0 }; + int addr_len; + + zb_get_extended_pan_id(extended_pan_id); + addr_len = ieee_addr_to_str(ieee_addr_buf, + sizeof(ieee_addr_buf), + extended_pan_id); + if (addr_len < 0) { + strcpy(ieee_addr_buf, "unknown"); + } + + /* Device has joined the network so stop the network + * rejoin procedure. + */ + stop_network_rejoin(ZB_FALSE); + LOG_INF("Joined network successfully on reboot signal (Extended PAN ID: %s, PAN ID: 0x%04hx)", + ieee_addr_buf, + ZB_PIBCACHE_PAN_ID()); + } else { + if (role != ZB_NWK_DEVICE_TYPE_COORDINATOR) { + LOG_INF("Unable to join the network, start network steering"); + start_network_rejoin(); + } else { + LOG_ERR("Failed to initialize Zigbee stack using NVRAM data (status: %d)", + status); + } + } + break; + + case ZB_BDB_SIGNAL_STEERING: + /* At this point the Zigbee stack has finished network steering + * procedure. The device may have rejoined the network, + * which is indicated by signal's status code. + * + * Next step: + * - If the device implements Zigbee router and the steering + * is not successful, retry joining Zigbee network + * by starting network steering after 1 second. + * - It is not expected to finish network steering with error + * status if the device implements Zigbee coordinator, + * (see BDB specification section 8.2). + */ + if (status == RET_OK) { + zb_ext_pan_id_t extended_pan_id; + char ieee_addr_buf[IEEE_ADDR_BUF_SIZE] = { 0 }; + int addr_len; + + zb_get_extended_pan_id(extended_pan_id); + addr_len = ieee_addr_to_str(ieee_addr_buf, + sizeof(ieee_addr_buf), + extended_pan_id); + if (addr_len < 0) { + strcpy(ieee_addr_buf, "unknown"); + } + + LOG_INF("Joined network successfully (Extended PAN ID: %s, PAN ID: 0x%04hx)", + ieee_addr_buf, + ZB_PIBCACHE_PAN_ID()); + /* Device has joined the network so stop the network + * rejoin procedure. + */ + if (role != ZB_NWK_DEVICE_TYPE_COORDINATOR) { + stop_network_rejoin(ZB_FALSE); + } + } else { + if (role != ZB_NWK_DEVICE_TYPE_COORDINATOR) { + LOG_INF("Network steering was not successful (status: %d)", + status); + start_network_rejoin(); + } else { + LOG_INF("Network steering failed on Zigbee coordinator (status: %d)", + status); + } + } + break; + + case ZB_BDB_SIGNAL_FORMATION: + /* At this point the Zigbee stack has finished network formation + * procedure. The device may have created a new Zigbee network, + * which is indicated by signal's status code. + * + * Next step: + * - If the device implements Zigbee coordinator + * and the formation is not successful, try to form a new + * Zigbee network by performing network formation after + * 1 second (see BDB specification section 8.4). + * - If the network formation was successful, open the newly + * created network for other devices to join by starting + * network steering for a node on a network, + * (see BDB specification section 8.2). + * - If the device implements Zigbee router or end device, + * this signal is not expected. + */ + if (status == RET_OK) { + zb_ext_pan_id_t extended_pan_id; + char ieee_addr_buf[IEEE_ADDR_BUF_SIZE] = { 0 }; + int addr_len; + + zb_get_extended_pan_id(extended_pan_id); + addr_len = ieee_addr_to_str(ieee_addr_buf, + sizeof(ieee_addr_buf), + extended_pan_id); + if (addr_len < 0) { + strcpy(ieee_addr_buf, "unknown"); + } + + LOG_INF("Network formed successfully, start network steering (Extended PAN ID: %s, PAN ID: 0x%04hx)", + ieee_addr_buf, + ZB_PIBCACHE_PAN_ID()); + comm_status = bdb_start_top_level_commissioning( + ZB_BDB_NETWORK_STEERING); + } else { + LOG_INF("Restart network formation (status: %d)", + status); + ret_code = ZB_SCHEDULE_APP_ALARM( + (zb_callback_t) + bdb_start_top_level_commissioning, + ZB_BDB_NETWORK_FORMATION, + ZB_TIME_ONE_SECOND); + } + break; + + case ZB_ZDO_SIGNAL_LEAVE: + /* This signal is generated when the device itself has left + * the network by sending leave command. + * + * Note: this signal will be generated if the device tries + * to join legacy Zigbee network and the TCLK exchange + * cannot be completed. In such situation, + * the ZB_BDB_NETWORK_STEERING signal will be generated + * afterwards, so this case may be left unimplemented. + */ + if (status == RET_OK) { + zb_zdo_signal_leave_params_t *leave_params = + ZB_ZDO_SIGNAL_GET_PARAMS( + sig_hndler, + zb_zdo_signal_leave_params_t); + LOG_INF("Network left (leave type: %d)", + leave_params->leave_type); + + if (zb_get_network_role() == + ZB_NWK_DEVICE_TYPE_COORDINATOR) { + if (factory_reset_context.pibcache_pan_id_needs_reset) { + zigbee_pibcache_pan_id_clear(); + factory_reset_context.pibcache_pan_id_needs_reset = false; + } + /* For coordinator node, + * start network formation. + */ + comm_status = bdb_start_top_level_commissioning( + ZB_BDB_NETWORK_FORMATION); + } else { + /* Start network rejoin procedure. */ + start_network_rejoin(); + } + } else { + LOG_ERR("Unable to leave network (status: %d)", status); + } + break; + + case ZB_ZDO_SIGNAL_LEAVE_INDICATION: { + /* This signal is generated on the parent to indicate, that one + * of its child nodes left the network. + */ + zb_zdo_signal_leave_indication_params_t + *leave_ind_params = ZB_ZDO_SIGNAL_GET_PARAMS( + sig_hndler, + zb_zdo_signal_leave_indication_params_t); + char ieee_addr_buf[IEEE_ADDR_BUF_SIZE] = { 0 }; + int addr_len; + + addr_len = ieee_addr_to_str( + ieee_addr_buf, sizeof(ieee_addr_buf), + leave_ind_params->device_addr); + if (addr_len < 0) { + strcpy(ieee_addr_buf, "unknown"); + } + LOG_INF("Child left the network (long: %s, rejoin flag: %d)", + ieee_addr_buf, + leave_ind_params->rejoin); + break; + } + + case ZB_COMMON_SIGNAL_CAN_SLEEP: + /* Zigbee stack can enter sleep state. If the application wants + * to proceed, it should call zb_sleep_now() function. + * + * Note: if the application shares some resources between Zigbee + * stack and other tasks/contexts, device disabling should + * be overwritten by implementing one of the weak + * functions inside zb_nrf_pwr_mgmt.c. + */ + zb_sleep_now(); + break; + + case ZB_ZDO_SIGNAL_DEVICE_UPDATE: { + /* This signal notifies the Zigbee Trust center (usually + * implemented on the coordinator node) or parent router + * application once a device joined, rejoined, + * or left the network. + * + * For more information see table 4.14 + * of the Zigbee Specification (R21). + */ + zb_zdo_signal_device_update_params_t *update_params = + ZB_ZDO_SIGNAL_GET_PARAMS( + sig_hndler, + zb_zdo_signal_device_update_params_t); + char ieee_addr_buf[IEEE_ADDR_BUF_SIZE] = { 0 }; + int addr_len; + + addr_len = ieee_addr_to_str(ieee_addr_buf, + sizeof(ieee_addr_buf), + update_params->long_addr); + if (addr_len < 0) { + strcpy(ieee_addr_buf, "unknown"); + } + LOG_INF("Device update received (short: 0x%04hx, long: %s, status: %d)", + update_params->short_addr, + ieee_addr_buf, + update_params->status); + + if (IS_ENABLED(CONFIG_ZIGBEE_TC_REJOIN_ENABLED)) { + /* Workaround: KRKNWK-14112 */ + zb_address_ieee_ref_t addr_ref; + + if ((zb_address_by_ieee(update_params->long_addr, ZB_FALSE, ZB_FALSE, + &addr_ref) == RET_OK) + && zb_secur_get_link_key_by_address(update_params->long_addr, + ZB_SECUR_PROVISIONAL_KEY)) { + ZB_SCHEDULE_APP_ALARM_CANCEL(zb_nwk_forget_device, addr_ref); + } + } + break; + } + + case ZB_ZDO_SIGNAL_DEVICE_ANNCE: { + /* This signal is generated when a Device Announcement command + * is received by the device. Such packet is generated whenever + * a node joins or rejoins the network, so this signal may be + * used to track the number of devices. + * + * Note: since the Device Announcement command is sent to the + * broadcast address, this method may miss some devices. + * The complete knowledge about nodes has only + * the coordinator. + * + * Note: it may happen, that a device broadcasts the Device + * Announcement command and is removed by the coordinator + * afterwards, due to security policy + * (lack of TCLK exchange). + */ + zb_zdo_signal_device_annce_params_t *dev_annce_params = + ZB_ZDO_SIGNAL_GET_PARAMS( + sig_hndler, + zb_zdo_signal_device_annce_params_t); + LOG_INF("New device commissioned or rejoined (short: 0x%04hx)", + dev_annce_params->device_short_addr); + break; + } + +#ifndef CONFIG_ZIGBEE_ROLE_END_DEVICE + case ZB_ZDO_SIGNAL_DEVICE_AUTHORIZED: { + /* This signal notifies the Zigbee Trust center application + * (usually implemented on the coordinator node) about + * authorization of a new device in the network. + * + * For Zigbee 3.0 (and newer) devices this signal + * is generated if: + * - TCKL exchange procedure was successful + * - TCKL exchange procedure timed out + * + * If the coordinator allows for legacy devices to join + * the network (enabled by zb_bdb_set_legacy_device_support(1) + * API call), this signal is generated: + * - If the parent router generates Update Device command and + * the joining device does not perform TCLK exchange + * within timeout. + * - If the TCLK exchange is successful. + */ + zb_zdo_signal_device_authorized_params_t + *authorize_params = ZB_ZDO_SIGNAL_GET_PARAMS( + sig_hndler, + zb_zdo_signal_device_authorized_params_t); + char ieee_addr_buf[IEEE_ADDR_BUF_SIZE] = { 0 }; + int addr_len; + + addr_len = + ieee_addr_to_str(ieee_addr_buf, + sizeof(ieee_addr_buf), + authorize_params->long_addr); + if (addr_len < 0) { + strcpy(ieee_addr_buf, "unknown"); + } + LOG_INF("Device authorization event received" + " (short: 0x%04hx, long: %s, authorization type: %d," + " authorization status: %d)", + authorize_params->short_addr, ieee_addr_buf, + authorize_params->authorization_type, + authorize_params->authorization_status); + break; + } +#endif + + case ZB_NWK_SIGNAL_NO_ACTIVE_LINKS_LEFT: + /* This signal informs the application that all links to other + * routers has expired. In such situation, the node can + * communicate only with its children. + * + * Example reasons of signal generation: + * - The device was brought too far from the rest + * of the network. + * - There was a power cut and the whole network + * suddenly disappeared. + * + * Note: This signal is not generated for the coordinator node, + * since it may operate alone in the network. + */ + LOG_WRN("Parent is unreachable"); + break; + + case ZB_BDB_SIGNAL_FINDING_AND_BINDING_TARGET_FINISHED: + /* This signal informs the Finding & Binding target device that + * the procedure has finished and the other device has + * been bound or the procedure timed out. + */ + LOG_INF("Find and bind target finished (status: %d)", status); + break; + +#if defined ZB_COORDINATOR_ROLE + case ZB_NWK_SIGNAL_PANID_CONFLICT_DETECTED: { + /* This signal informs the Coordinator that conflict PAN ID + * has been detected and needs to be resolved. + */ + LOG_INF("PAN ID conflict detected, trying to resolve."); + + zb_bufid_t buf_copy = zb_buf_get_out(); + + if (buf_copy) { + zb_buf_copy(buf_copy, bufid); + ZVUNUSED(ZB_ZDO_SIGNAL_CUT_HEADER(buf_copy)); + + change_panid(buf_copy); + } else { + LOG_ERR("No free buffer available, skipping conflict resolving this time."); + } + break; + } +#endif /* ZB_COORDINATOR_ROLE */ + + case ZB_ZDO_SIGNAL_DEFAULT_START: + case ZB_NWK_SIGNAL_DEVICE_ASSOCIATED: + /* Obsolete signals, used for pre-R21 ZBOSS API. Ignore. */ + break; + + case ZB_BDB_SIGNAL_TC_REJOIN_DONE: + /* This signal informs that Trust Center Rejoin is completed. + * The signal status indicates if the device has successfully + * rejoined the network. + * + * Next step: if the device implement Zigbee router or + * end device, and the Trust Center Rejoin has failed, + * perform restart the generic rejoin procedure. + */ + if (IS_ENABLED(CONFIG_ZIGBEE_TC_REJOIN_ENABLED)) { + if (status == RET_OK) { + zb_ext_pan_id_t extended_pan_id; + char ieee_addr_buf[IEEE_ADDR_BUF_SIZE] = { 0 }; + int addr_len; + + zb_get_extended_pan_id(extended_pan_id); + addr_len = ieee_addr_to_str(ieee_addr_buf, + sizeof(ieee_addr_buf), + extended_pan_id); + if (addr_len < 0) { + strcpy(ieee_addr_buf, "unknown"); + } + + LOG_INF("Joined network successfully after TC rejoin" + " (Extended PAN ID: %s, PAN ID: 0x%04hx)", + ieee_addr_buf, + ZB_PIBCACHE_PAN_ID()); + /* Device has joined the network so stop the network + * rejoin procedure. + */ + if (role != ZB_NWK_DEVICE_TYPE_COORDINATOR) { + stop_network_rejoin(ZB_FALSE); + } + } else { + if (role != ZB_NWK_DEVICE_TYPE_COORDINATOR) { + LOG_INF("TC Rejoin was not successful (status: %d)", + status); + start_network_rejoin(); + } else { + LOG_INF("TC Rejoin failed on Zigbee coordinator" + " (status: %d)", status); + } + } + break; + } + + /* + * Fall-through to the default case if Trust Center Rejoin is disabled + */ + + default: + /* Unimplemented signal. For more information, + * see: zb_zdo_app_signal_type_e and zb_ret_e. + */ + LOG_INF("Unimplemented signal (signal: %d, status: %d)", + sig, status); + break; + } + + /* If configured, process network rejoin procedure. */ + rejoin_the_network(0); + + if ((ret_code == RET_OK) && (comm_status != ZB_TRUE)) { + ret_code = RET_ERROR; + } + + return ret_code; +} + +void zigbee_led_status_update(zb_bufid_t bufid, uint32_t led_idx) +{ + zb_zdo_app_signal_hdr_t *p_sg_p = NULL; + zb_zdo_app_signal_type_t sig = zb_get_app_signal(bufid, &p_sg_p); + zb_ret_t status = ZB_GET_APP_SIGNAL_STATUS(bufid); + + switch (sig) { + case ZB_BDB_SIGNAL_DEVICE_REBOOT: + /* fall-through */ + case ZB_BDB_SIGNAL_STEERING: + if (status == RET_OK) { + dk_set_led_on(led_idx); + } else { + dk_set_led_off(led_idx); + } + break; + + case ZB_ZDO_SIGNAL_LEAVE: + /* Update network status LED */ + dk_set_led_off(led_idx); + break; + + default: + break; + } +} + +/**@brief Start network steering. + */ +static void start_network_steering(zb_uint8_t param) +{ + ZVUNUSED(param); + ZVUNUSED(bdb_start_top_level_commissioning(ZB_BDB_NETWORK_STEERING)); +} + +/**@brief Process rejoin procedure. To be called in signal handler. + */ +static void rejoin_the_network(zb_uint8_t param) +{ + ZVUNUSED(param); + + if (stack_initialised && is_rejoin_procedure_started) { + if (is_rejoin_stop_requested) { + is_rejoin_procedure_started = false; + is_rejoin_stop_requested = false; + +#if defined CONFIG_ZIGBEE_ROLE_END_DEVICE + LOG_INF("Network rejoin procedure stopped as %sscheduled.", + (wait_for_user_input) ? "" : "NOT "); +#else + LOG_INF("Network rejoin procedure stopped."); +#endif + } else if (!is_rejoin_in_progress) { + /* Calculate new timeout */ + zb_time_t timeout_s; + zb_ret_t zb_err_code; + zb_callback_t alarm_cb = start_network_steering; + zb_uint8_t alarm_cb_param = ZB_FALSE; + + if ((1 << rejoin_attempt_cnt) > REJOIN_INTERVAL_MAX_S) { + timeout_s = REJOIN_INTERVAL_MAX_S; + } else { + timeout_s = (1 << rejoin_attempt_cnt); + rejoin_attempt_cnt++; + } + + if (IS_ENABLED(CONFIG_ZIGBEE_TC_REJOIN_ENABLED)) { + if ((timeout_s > TC_REJOIN_INTERVAL_THRESHOLD_S) + && !zb_bdb_is_factory_new()) { + alarm_cb = zb_bdb_initiate_tc_rejoin; + alarm_cb_param = ZB_UNDEFINED_BUFFER; + } + } + + zb_err_code = ZB_SCHEDULE_APP_ALARM( + alarm_cb, + alarm_cb_param, + ZB_MILLISECONDS_TO_BEACON_INTERVAL(timeout_s * 1000)); + + ZB_ERROR_CHECK(zb_err_code); + is_rejoin_in_progress = true; + } + } +} + +/**@brief Function for starting rejoin network procedure. + * + * @note For Router device if stack is initialised, device is not joined + * and rejoin procedure is not running, start rejoin procedure. + * + * @note For End Device if stack is initialised, rejoin procedure + * is not running, device is not joined and device is not waiting + * for the user input, start rejoin procedure. Additionally, + * schedule alarm to stop rejoin procedure after the timeout + * defined by ZB_DEV_REJOIN_TIMEOUT_MS. + */ +static void start_network_rejoin(void) +{ +#if defined CONFIG_ZIGBEE_ROLE_END_DEVICE + if (!ZB_JOINED() && stack_initialised && !wait_for_user_input) { +#else + if (!ZB_JOINED() && stack_initialised) { +#endif + is_rejoin_in_progress = false; + + if (!is_rejoin_procedure_started) { + is_rejoin_procedure_started = true; + is_rejoin_stop_requested = false; + is_rejoin_in_progress = false; + rejoin_attempt_cnt = 0; + +#if defined CONFIG_ZIGBEE_ROLE_END_DEVICE + wait_for_user_input = false; + is_rejoin_start_scheduled = false; + + zb_ret_t zb_err_code = ZB_SCHEDULE_APP_ALARM( + stop_network_rejoin, + ZB_TRUE, + ZB_MILLISECONDS_TO_BEACON_INTERVAL( + ZB_DEV_REJOIN_TIMEOUT_MS)); + ZB_ERROR_CHECK(zb_err_code); +#endif + + LOG_INF("Started network rejoin procedure."); + } + } +} + +/**@brief Function for stopping rejoin network procedure + * and related scheduled alarms. + * + * @param[in] was_scheduled Zigbee flag to indicate if the function + * was scheduled or called directly. + */ +static void stop_network_rejoin(zb_uint8_t was_scheduled) +{ + /* For Router and End Device: + * Try to stop scheduled network steering. Stop rejoin procedure + * or if no network steering was scheduled, request rejoin stop + * on next rejoin_the_network() call. + * For End Device only: + * If stop_network_rejoin() was called from scheduler, the rejoin + * procedure has reached timeout, set wait_for_user_input + * to true so the rejoin procedure can only be started by calling + * user_input_indicate(). If not, set wait_for_user_input to false. + */ + + zb_ret_t zb_err_code; + +#if defined CONFIG_ZIGBEE_ROLE_END_DEVICE + /* Set wait_for_user_input depending on if the device should retry + * joining on user_input_indication(). + */ + wait_for_user_input = was_scheduled; +#else + ZVUNUSED(was_scheduled); +#endif + + if (is_rejoin_procedure_started) { + zb_err_code = ZB_SCHEDULE_APP_ALARM_CANCEL( + start_network_steering, + ZB_ALARM_ANY_PARAM); + if (zb_err_code == RET_OK) { + /* Stop rejoin procedure */ + is_rejoin_procedure_started = false; + is_rejoin_stop_requested = false; +#if defined CONFIG_ZIGBEE_ROLE_END_DEVICE + LOG_INF("Network rejoin procedure stopped as %sscheduled.", + (wait_for_user_input) ? "" : "not "); +#else + LOG_INF("Network rejoin procedure stopped."); +#endif + } else { + /* Request rejoin procedure stop */ + is_rejoin_stop_requested = true; + } + } + + if (IS_ENABLED(CONFIG_ZIGBEE_ROLE_END_DEVICE)) { + /* Make sure scheduled stop alarm is canceled. */ + zb_err_code = ZB_SCHEDULE_APP_ALARM_CANCEL( + stop_network_rejoin, + ZB_ALARM_ANY_PARAM); + if (zb_err_code != RET_NOT_FOUND) { + ZB_ERROR_CHECK(zb_err_code); + } + } +} + +#if defined CONFIG_ZIGBEE_ROLE_END_DEVICE +/* Function to be scheduled when user_input_indicate() is called + * and wait_for_user_input is true. + */ +static void start_network_rejoin_ED(zb_uint8_t param) +{ + ZVUNUSED(param); + if (!ZB_JOINED() && wait_for_user_input) { + zb_ret_t zb_err_code; + + wait_for_user_input = false; + start_network_rejoin(); + + zb_err_code = ZB_SCHEDULE_APP_ALARM( + rejoin_the_network, + 0, + ZB_TIME_ONE_SECOND); + ZB_ERROR_CHECK(zb_err_code); + } + is_rejoin_start_scheduled = false; +} + +/* Function to be called by an application + * e.g. inside button handler function + */ +void user_input_indicate(void) +{ + if (wait_for_user_input && !(is_rejoin_start_scheduled)) { + zb_ret_t zb_err_code = RET_OK; + + zb_err_code = + zigbee_schedule_callback(start_network_rejoin_ED, 0); + ZB_ERROR_CHECK(zb_err_code); + + /* Prevent scheduling multiple rejoin starts */ + if (!zb_err_code) { + is_rejoin_start_scheduled = true; + } + } +} + +/* Function to enable sleepy behavior for End Device. */ +void zigbee_configure_sleepy_behavior(bool enable) +{ + if (enable) { + zb_set_rx_on_when_idle(ZB_FALSE); + LOG_INF("Enabled sleepy end device behavior."); + } else { + zb_set_rx_on_when_idle(ZB_TRUE); + LOG_INF("Disabling sleepy end device behavior."); + } +} +#endif /* CONFIG_ZIGBEE_ROLE_END_DEVICE */ + +#if defined CONFIG_ZIGBEE_FACTORY_RESET + +static void factory_reset_timer_expired(struct k_timer *timer_id) +{ + uint32_t button_state = 0; + uint32_t has_changed = 0; + + dk_read_buttons(&button_state, &has_changed); + if (button_state & factory_reset_context.button) { + LOG_DBG("FR button pressed for %d [s]", timer_id->status); + if (timer_id->status >= CONFIG_FACTORY_RESET_PRESS_TIME_SECONDS) { + /* Schedule a callback so that Factory Reset is started + * from ZBOSS scheduler context + */ + LOG_DBG("Schedule Factory Reset; stop timer; set factory_reset_done flag"); + factory_reset_context.pibcache_pan_id_needs_reset = true; + ZB_SCHEDULE_APP_CALLBACK(zb_bdb_reset_via_local_action, 0); + k_timer_stop(timer_id); + factory_reset_context.reset_done = true; + } + } else { + LOG_DBG("FR button released prematurely"); + k_timer_stop(timer_id); + } +} + +void register_factory_reset_button(uint32_t button) +{ + factory_reset_context.button = button; + factory_reset_context.reset_done = false; + factory_reset_context.pibcache_pan_id_needs_reset = false; + + k_timer_init(&factory_reset_context.timer, factory_reset_timer_expired, NULL); +} + +void check_factory_reset_button(uint32_t button_state, uint32_t has_changed) +{ + if (button_state & has_changed & factory_reset_context.button) { + LOG_DBG("Clear factory_reset_done flag; start Factory Reset timer"); + + /* Reset flag indicating that Factory Reset was initiated */ + factory_reset_context.reset_done = false; + factory_reset_context.pibcache_pan_id_needs_reset = false; + + /* Start timer checking button press time */ + k_timer_start(&factory_reset_context.timer, + FACTORY_RESET_PROBE_TIME, + FACTORY_RESET_PROBE_TIME); + } +} + +bool was_factory_reset_done(void) +{ + return factory_reset_context.reset_done; +} +#endif /* CONFIG_ZIGBEE_FACTORY_RESET */ + +#if defined ZB_COORDINATOR_ROLE +static void change_panid_cb(zb_uint8_t param) +{ + zb_channel_panid_change_preparation_t *params = ZB_BUF_GET_PARAM(param, zb_channel_panid_change_preparation_t); + if (params->error_cnt == 0) + { + LOG_DBG("Preparation for PAN ID change is successful"); + if (RET_OK == zb_start_panid_change(param)) + { + LOG_DBG("Change PAN ID procedure started successfully"); + } + } +} + +static void change_panid(zb_uint8_t param) +{ + if (param == 0u) + { + zb_buf_get_out_delayed(change_panid); + } + else + { + zb_panid_change_parameters_t *params = ZB_BUF_GET_PARAM(param, zb_panid_change_parameters_t); + params->next_panid_change = 0xffffu; /* The next panid value will be randomly generated */ + + /* Send set_configuration_req to all devices for allow PAN ID change */ + if (RET_OK == zb_prepare_network_for_panid_change(param, change_panid_cb)) + { + LOG_DBG("Prepare network for panid change"); + } + } +} +#endif /* ZB_COORDINATOR_ROLE */ diff --git a/subsys/lib/zigbee_fota/CMakeLists.txt b/subsys/lib/zigbee_fota/CMakeLists.txt new file mode 100644 index 00000000..8c6fe46c --- /dev/null +++ b/subsys/lib/zigbee_fota/CMakeLists.txt @@ -0,0 +1,13 @@ +# +# Copyright (c) 2020 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +zephyr_library() +zephyr_library_sources( + src/zigbee_fota.c + src/dfu_multi_target.c + ) + +zephyr_library_link_libraries(zboss) +zephyr_library_link_libraries(zigbee) diff --git a/subsys/lib/zigbee_fota/Kconfig b/subsys/lib/zigbee_fota/Kconfig new file mode 100644 index 00000000..9db271cb --- /dev/null +++ b/subsys/lib/zigbee_fota/Kconfig @@ -0,0 +1,104 @@ +# +# Copyright (c) 2020 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menuconfig ZIGBEE_FOTA + bool "Enable Zigbee FOTA" + imply BOOTLOADER_MCUBOOT + imply MCUMGR + imply DFU_TARGET + imply DFU_MULTI_IMAGE + imply DFU_MULTI_IMAGE_PACKAGE_BUILD if !ZIGBEE_FOTA_GENERATE_LEGACY_IMAGE_TYPE + +if ZIGBEE_FOTA + +config ZIGBEE_FOTA_GENERATE_LEGACY_IMAGE_TYPE + bool "Generate legacy .zigbee image" + help + Since NCS v2.0 the following changes were made to the Zigbee FOTA image format: + - The FW contains a multi-image binary, instead of the bare MCUboot image + - The Zigbee FW follows the subelement structure, instead of putting the + firmware blob right after the OTA header. + If this option is enabled, the old image format will be used for .zigbee image generation, + but the FOTA library will still understand only the new image structure. + The purpose of this option is to provide a possibility to upgrade old devices. + +config ZIGBEE_FOTA_HW_VERSION + int "Zigbee hardware version" + range 0 65535 + default 52 + +config ZIGBEE_FOTA_DATA_BLOCK_SIZE + int "Maximum data size of Query Block Image" + default 64 + range 4 64 + +config ZIGBEE_FOTA_ENDPOINT + int "Zigbee OTA endpoint" + default 10 + range 1 240 + +config ZIGBEE_FOTA_PROGRESS_EVT + bool "Emit progress event upon receiving a download fragment" + +config ZIGBEE_FOTA_MANUFACTURER_ID + hex "Manufacturer ID" + default 0x127F + range 0x0000 0xFFFF + help + This is the ZigBee assigned identifier for each member company. + 0x127F - Nordic Semiconductor + 0xFFFF - wild card value has a 'match all' effect. + +config ZIGBEE_FOTA_IMAGE_TYPE + hex "Image type" + default 0x0141 + range 0x0000 0xFFFF + help + 0x0000 - 0xFFBF Manufacturer Specific + 0xFFC0 - Client Security credentials + 0xFFC1 - Client Configuration + 0xFFC2 - Server Log + 0xFFC3 - Picture + 0xFFFF - Wild card value has a 'match all' effect. + +config ZIGBEE_FOTA_COMMENT + string "OTA header string" + default "" + help + Firmware comment to be used in Zigbee OTA header. + +config ENABLE_ZIGBEE_FOTA_MIN_HW_VERSION + bool "Enable Zigbee OTA minimum hw version" + +config ZIGBEE_FOTA_MIN_HW_VERSION + hex "Zigbee OTA minimum hw version" + default 0 + range 0x00 0xFF + depends on ENABLE_ZIGBEE_FOTA_MIN_HW_VERSION + +config ENABLE_ZIGBEE_FOTA_MAX_HW_VERSION + bool "Enable Zigbee OTA maximum hw version" + +config ZIGBEE_FOTA_MAX_HW_VERSION + hex "Zigbee OTA maximum hw version" + default 0 + range 0x00 0xFF + depends on ENABLE_ZIGBEE_FOTA_MAX_HW_VERSION + +config ZIGBEE_FOTA_SERVER_DISOVERY_INTERVAL_HRS + int "Time interval between queries for the Zigbee FOTA server" + default 24 + +config ZIGBEE_FOTA_IMAGE_QUERY_INTERVAL_MIN + int "Time interval between queries for the available Zigbee FOTA images" + default 60 + +module=ZIGBEE_FOTA +module-dep=LOG +module-str=Zigbee FOTA +source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config" + +endif #ZIGBEE_FOTA diff --git a/subsys/lib/zigbee_fota/src/dfu_multi_target.c b/subsys/lib/zigbee_fota/src/dfu_multi_target.c new file mode 100644 index 00000000..b492b4b0 --- /dev/null +++ b/subsys/lib/zigbee_fota/src/dfu_multi_target.c @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include "dfu_multi_target.h" + +#define DFU_TARGET_SCHEDULE_ALL_IMAGES -1 + +union dfu_multi_target_ver { + uint32_t uint32_ver; + struct { + uint16_t revision; + uint8_t minor; + uint8_t major; + }; +}; + +static int dfu_multi_target_open(int image_id, size_t image_size); +static int dfu_multi_target_write(const uint8_t *chunk, size_t chunk_size); +static int dfu_multi_target_done_0(bool success); +#if CONFIG_UPDATEABLE_IMAGE_NUMBER > 1 +static int dfu_multi_target_done_1(bool success); +#endif +#if CONFIG_UPDATEABLE_IMAGE_NUMBER > 2 +static int dfu_multi_target_done_2(bool success); +#endif + +LOG_MODULE_DECLARE(zigbee_fota); +static uint32_t images_to_download; + +/** Buffer for parsing the multi-image header as well as DFU target buffer. */ +static uint8_t image_buf[DFU_MULTI_TARGET_BUFFER_SIZE] __aligned(4); + +/** DFU target writer for image 0 (single-app image or APP core image put in internal flash). */ +static struct dfu_image_writer dfu_multi_target_writer_0 = { + .image_id = 0, + .open = dfu_multi_target_open, + .write = dfu_multi_target_write, + .close = dfu_multi_target_done_0 +}; + +#if CONFIG_UPDATEABLE_IMAGE_NUMBER > 1 +/** DFU target writer for image 1 (NET core). */ +static struct dfu_image_writer dfu_multi_target_writer_1 = { + .image_id = 1, + .open = dfu_multi_target_open, + .write = dfu_multi_target_write, + .close = dfu_multi_target_done_1 +}; +#endif + +#if CONFIG_UPDATEABLE_IMAGE_NUMBER > 2 +/** DFU target writer for image 2 (split from APP core and to be put in external memory). */ +static struct dfu_image_writer dfu_multi_target_writer_2 = { + .image_id = 2, + .open = dfu_multi_target_open, + .write = dfu_multi_target_write, + .close = dfu_multi_target_done_2 +}; +#endif + +static int dfu_multi_target_open(int image_id, size_t image_size) +{ + LOG_INF("Open image: %d size: %d", image_id, image_size); + + if (image_id >= CONFIG_UPDATEABLE_IMAGE_NUMBER) { + return -EINVAL; + } + + return dfu_target_init(DFU_TARGET_IMAGE_TYPE_MCUBOOT, image_id, image_size, NULL); +} + +static int dfu_multi_target_write(const uint8_t *chunk, size_t chunk_size) +{ + LOG_DBG("Write next %d bytes", chunk_size); + + return dfu_target_write(chunk, chunk_size); +} + +static int dfu_multi_target_done_0(bool success) +{ + LOG_INF("Close image 0 for writing success: %d", success); + + if (success) { + images_to_download &= ~(1 << 0); + return dfu_target_done(success); + } else { + return dfu_target_reset(); + } +} + +#if CONFIG_UPDATEABLE_IMAGE_NUMBER > 1 +static int dfu_multi_target_done_1(bool success) +{ + LOG_INF("Close image 1 for writing success: %d", success); + + if (success) { + images_to_download &= ~(1 << 1); + return dfu_target_done(success); + } else { + return dfu_target_reset(); + } +} +#endif + +#if CONFIG_UPDATEABLE_IMAGE_NUMBER > 2 +static int dfu_multi_target_done_2(bool success) +{ + LOG_INF("Close image 2 for writing success: %d", success); + + if (success) { + images_to_download &= ~(1 << 2); + return dfu_target_done(success); + } else { + return dfu_target_reset(); + } +} +#endif + +uint32_t dfu_multi_target_get_version(void) +{ + struct mcuboot_img_header mcuboot_header; + union dfu_multi_target_ver image_ver = {0}; + + int err = boot_read_bank_header(PM_MCUBOOT_PRIMARY_ID, + &mcuboot_header, + sizeof(mcuboot_header)); + if (err) { + LOG_WRN("Failed read app ver from image header (err %d)", err); + } else { + image_ver.major = mcuboot_header.h.v1.sem_ver.major; + image_ver.minor = mcuboot_header.h.v1.sem_ver.minor; + image_ver.revision = mcuboot_header.h.v1.sem_ver.revision; + } + + return image_ver.uint32_ver; +} + +int dfu_multi_target_init_default(void) +{ + int err; + + err = dfu_multi_image_init(image_buf, sizeof(image_buf)); + if (err) { + LOG_ERR("Failed to initialize DFU multi image library (err %d)", err); + return err; + } + + images_to_download = 0; + + err = dfu_multi_image_register_writer(&dfu_multi_target_writer_0); + if (err) { + LOG_ERR("Failed to register DFU target writer for image 0 (err %d)", err); + return err; + } + + images_to_download |= (1 << 0); + +#if CONFIG_UPDATEABLE_IMAGE_NUMBER > 1 + err = dfu_multi_image_register_writer(&dfu_multi_target_writer_1); + if (err) { + LOG_ERR("Failed to register DFU target writer for image 1 (err %d)", err); + return err; + } + + images_to_download |= (1 << 1); +#endif + +#if CONFIG_UPDATEABLE_IMAGE_NUMBER > 2 + err = dfu_multi_image_register_writer(&dfu_multi_target_writer_2); + if (err) { + LOG_ERR("Failed to register DFU target writer for image 2 (err %d)", err); + return err; + } + + images_to_download |= (1 << 2); +#endif + + err = dfu_target_mcuboot_set_buf(image_buf, sizeof(image_buf)); + if (err) { + LOG_ERR("Failed to set DFU target buffer (err %d)", err); + } + + return err; +} + +int dfu_multi_target_schedule_update(void) +{ + if (images_to_download != 0) { + LOG_ERR("Failed to set schedule update. Missing images: 0x%x", images_to_download); + return -ENOEXEC; + } + + return dfu_target_schedule_update(DFU_TARGET_SCHEDULE_ALL_IMAGES); +} diff --git a/subsys/lib/zigbee_fota/src/dfu_multi_target.h b/subsys/lib/zigbee_fota/src/dfu_multi_target.h new file mode 100644 index 00000000..68ed839a --- /dev/null +++ b/subsys/lib/zigbee_fota/src/dfu_multi_target.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef DFU_MULTI_TARGET_H__ +#define DFU_MULTI_TARGET_H__ + +#include + +#ifdef CONFIG_ZIGBEE_FOTA_DATA_BLOCK_SIZE +#define DFU_MULTI_TARGET_BUFFER_SIZE CONFIG_ZIGBEE_FOTA_DATA_BLOCK_SIZE +#endif + +/**@brief Return the 32-bit incremental identifier of the currently running images. */ +uint32_t dfu_multi_target_get_version(void); + +/**@brief Initialize the multi-image library with default dfu_target writers. */ +int dfu_multi_target_init_default(void); + +/**@brief Schedule update of the transferred images on the next boot. */ +int dfu_multi_target_schedule_update(void); + +#endif /* DFU_MULTI_TARGET_H__ */ diff --git a/subsys/lib/zigbee_fota/src/zigbee_fota.c b/subsys/lib/zigbee_fota/src/zigbee_fota.c new file mode 100644 index 00000000..c8b9bf30 --- /dev/null +++ b/subsys/lib/zigbee_fota/src/zigbee_fota.c @@ -0,0 +1,607 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include "dfu_multi_target.h" +#include "zigbee_ota.h" + +LOG_MODULE_REGISTER(zigbee_fota, CONFIG_ZIGBEE_FOTA_LOG_LEVEL); + +#define MANDATORY_HEADER_LEN sizeof(zb_zcl_ota_upgrade_file_header_t) +#define OPTIONAL_HEADER_LEN sizeof(zb_zcl_ota_upgrade_file_header_optional_t) +#define TOTAL_HEADER_LEN (MANDATORY_HEADER_LEN + OPTIONAL_HEADER_LEN) +#define SUBELEMENT_HEADER_SIZE sizeof(zb_zcl_ota_upgrade_sub_element_t) + +struct zb_ota_dfu_context { + uint32_t ota_header_size; + uint32_t bin_size; + uint32_t total_size; + uint8_t ota_header[TOTAL_HEADER_LEN]; + uint32_t ota_header_fill_level; + uint32_t ota_subelement_fill_level; + uint32_t ota_image_processed; + bool mandatory_header_finished; + bool process_optional_header; + bool process_subelement_header; + bool process_bin_image; +}; + +struct zb_ota_upgrade_attr { + zb_ieee_addr_t upgrade_server; + zb_uint32_t file_offset; + zb_uint32_t file_version; + zb_uint16_t stack_version; + zb_uint32_t downloaded_file_ver; + zb_uint32_t downloaded_stack_ver; + zb_uint8_t image_status; + zb_uint16_t manufacturer; + zb_uint16_t image_type; + zb_uint16_t min_block_reque; + zb_uint16_t image_stamp; + zb_uint16_t server_addr; + zb_uint8_t server_ep; +}; + +struct zb_ota_basic_attr { + zb_uint8_t zcl_version; + zb_uint8_t power_source; +}; + +struct zb_ota_client_ctx { + struct zb_ota_basic_attr basic_attr; + struct zb_ota_upgrade_attr ota_attr; + struct k_timer alarm; +}; + +static struct zb_ota_dfu_context ota_ctx; +static struct zb_ota_client_ctx dev_ctx; +static zigbee_fota_callback_t callback; + +/* Declare attribute list for Basic cluster. */ +ZB_ZCL_DECLARE_BASIC_ATTRIB_LIST(ota_basic_attr_list, + &dev_ctx.basic_attr.zcl_version, + &dev_ctx.basic_attr.power_source); + +/* OTA cluster attributes data. */ +ZB_ZCL_DECLARE_OTA_UPGRADE_ATTRIB_LIST(ota_upgrade_attr_list, + dev_ctx.ota_attr.upgrade_server, + &dev_ctx.ota_attr.file_offset, + &dev_ctx.ota_attr.file_version, + &dev_ctx.ota_attr.stack_version, + &dev_ctx.ota_attr.downloaded_file_ver, + &dev_ctx.ota_attr.downloaded_stack_ver, + &dev_ctx.ota_attr.image_status, + &dev_ctx.ota_attr.manufacturer, + &dev_ctx.ota_attr.image_type, + &dev_ctx.ota_attr.min_block_reque, + &dev_ctx.ota_attr.image_stamp, + &dev_ctx.ota_attr.server_addr, + &dev_ctx.ota_attr.server_ep, + (uint16_t)CONFIG_ZIGBEE_FOTA_HW_VERSION, + CONFIG_ZIGBEE_FOTA_DATA_BLOCK_SIZE, + CONFIG_ZIGBEE_FOTA_IMAGE_QUERY_INTERVAL_MIN); + +ZB_HA_DECLARE_OTA_UPGRADE_CLIENT_CLUSTER_LIST(ota_upgrade_client_clusters, + ota_basic_attr_list, ota_upgrade_attr_list); + +ZB_HA_DECLARE_OTA_UPGRADE_CLIENT_EP(zigbee_fota_client_ep, + CONFIG_ZIGBEE_FOTA_ENDPOINT, + ota_upgrade_client_clusters); + +static void send_evt(zb_uint8_t id) +{ + __ASSERT(id != ZIGBEE_FOTA_EVT_PROGRESS, "use send_progress"); + const struct zigbee_fota_evt evt = { + .id = (enum zigbee_fota_evt_id)id + }; + callback(&evt); +} + +static void send_progress(zb_uint8_t progress) +{ +#ifdef CONFIG_ZIGBEE_FOTA_PROGRESS_EVT + const struct zigbee_fota_evt evt = { .id = ZIGBEE_FOTA_EVT_PROGRESS, + .dl.progress = progress }; + callback(&evt); +#endif /* CONFIG_ZIGBEE_FOTA_PROGRESS_EVT */ +} + +/**@brief Function for initializing all OTA clusters attributes. + * + * @note This function shall be called after the dfu initialization. + */ +static void ota_client_attr_init(void) +{ + /* Basic cluster attributes data. */ + dev_ctx.basic_attr.zcl_version = ZB_ZCL_VERSION; + dev_ctx.basic_attr.power_source = ZB_ZCL_BASIC_POWER_SOURCE_UNKNOWN; + + /* OTA cluster attributes data. */ + zb_ieee_addr_t addr = ZB_ZCL_OTA_UPGRADE_SERVER_DEF_VALUE; + + ZB_MEMCPY(dev_ctx.ota_attr.upgrade_server, addr, + sizeof(zb_ieee_addr_t)); + dev_ctx.ota_attr.file_offset = ZB_ZCL_OTA_UPGRADE_FILE_OFFSET_DEF_VALUE; + dev_ctx.ota_attr.file_version = dfu_multi_target_get_version(); + dev_ctx.ota_attr.stack_version = + ZB_ZCL_OTA_UPGRADE_FILE_HEADER_STACK_PRO; + dev_ctx.ota_attr.downloaded_file_ver = + ZB_ZCL_OTA_UPGRADE_DOWNLOADED_FILE_VERSION_DEF_VALUE; + dev_ctx.ota_attr.downloaded_stack_ver = + ZB_ZCL_OTA_UPGRADE_DOWNLOADED_STACK_DEF_VALUE; + dev_ctx.ota_attr.image_status = + ZB_ZCL_OTA_UPGRADE_IMAGE_STATUS_DEF_VALUE; + dev_ctx.ota_attr.manufacturer = CONFIG_ZIGBEE_FOTA_MANUFACTURER_ID; + dev_ctx.ota_attr.image_type = CONFIG_ZIGBEE_FOTA_IMAGE_TYPE; + dev_ctx.ota_attr.min_block_reque = 0; + dev_ctx.ota_attr.image_stamp = ZB_ZCL_OTA_UPGRADE_IMAGE_STAMP_MIN_VALUE; +} + +static void ota_dfu_reset(void) +{ + memset(&ota_ctx, 0, sizeof(ota_ctx)); +} + +static zb_uint8_t ota_process_mandatory_header(zb_uint8_t *data, uint32_t len, + uint32_t *bytes_copied) +{ + uint32_t bytes_left = + MANDATORY_HEADER_LEN - ota_ctx.ota_header_fill_level; + *bytes_copied = MIN(bytes_left, len); + + LOG_DBG("Process mandatory header."); + LOG_DBG("Bytes left: %d copy: %d", bytes_left, *bytes_copied); + + memcpy(&ota_ctx.ota_header[ota_ctx.ota_header_fill_level], + data, *bytes_copied); + ota_ctx.ota_header_fill_level += *bytes_copied; + + if (ota_ctx.ota_header_fill_level == MANDATORY_HEADER_LEN) { + ota_ctx.mandatory_header_finished = true; + + /* All the mandatory header is copied. */ + zb_zcl_ota_upgrade_file_header_t *hdr = + (zb_zcl_ota_upgrade_file_header_t *)ota_ctx.ota_header; + ota_ctx.ota_header_size = hdr->header_length; + ota_ctx.total_size = hdr->total_image_size; + + if (hdr->header_length > MANDATORY_HEADER_LEN) { + ota_ctx.process_optional_header = true; + } else { + ota_ctx.ota_subelement_fill_level = 0; + ota_ctx.ota_image_processed = 0; + ota_ctx.process_subelement_header = true; + } + + LOG_INF("Mandatory header received."); + LOG_INF("\tHeader size: %d", ota_ctx.ota_header_size); + LOG_INF("\tOptional header: %s", + (ota_ctx.process_optional_header ? "true" : "false")); + } + + return ZB_ZCL_OTA_UPGRADE_STATUS_OK; +} + +static zb_uint8_t ota_process_optional_header(zb_uint8_t *data, uint32_t len, + uint32_t *bytes_copied) +{ + uint32_t bytes_left = + ota_ctx.ota_header_size - ota_ctx.ota_header_fill_level; + *bytes_copied = MIN(bytes_left, len); + + LOG_DBG("Process optional header."); + LOG_DBG("Bytes left: %d copy: %d", bytes_left, *bytes_copied); + + if (ota_ctx.ota_header_fill_level < TOTAL_HEADER_LEN) { + uint32_t bytes_to_copy = MIN(*bytes_copied, TOTAL_HEADER_LEN - + ota_ctx.ota_header_fill_level); + memcpy(&ota_ctx.ota_header[ota_ctx.ota_header_fill_level], + data, bytes_to_copy); + } + + /* Current software should skip unsupported header fields. */ + ota_ctx.ota_header_fill_level += *bytes_copied; + + if (ota_ctx.ota_header_fill_level == ota_ctx.ota_header_size) { + LOG_INF("Full header received. Continue downloading firmware."); + ota_ctx.process_optional_header = false; + ota_ctx.ota_subelement_fill_level = 0; + ota_ctx.ota_image_processed = 0; + ota_ctx.process_subelement_header = true; + } + + return ZB_ZCL_OTA_UPGRADE_STATUS_OK; +} + +static zb_uint8_t ota_process_subelement_header(zb_uint8_t *data, uint32_t len, + uint32_t *bytes_copied) +{ + static uint8_t header_buf[SUBELEMENT_HEADER_SIZE]; + uint32_t bytes_left = 0; + + if (ota_ctx.ota_subelement_fill_level < SUBELEMENT_HEADER_SIZE) { + bytes_left = SUBELEMENT_HEADER_SIZE - ota_ctx.ota_subelement_fill_level; + } + *bytes_copied = MIN(bytes_left, len); + + LOG_DBG("Process subelement header."); + LOG_DBG("Bytes left: %d copy: %d", bytes_left, *bytes_copied); + + memcpy(&header_buf[ota_ctx.ota_subelement_fill_level], data, *bytes_copied); + ota_ctx.ota_subelement_fill_level += *bytes_copied; + + if (ota_ctx.ota_subelement_fill_level == SUBELEMENT_HEADER_SIZE) { + zb_zcl_ota_upgrade_sub_element_t *hdr = + (zb_zcl_ota_upgrade_sub_element_t *)header_buf; + int err = 0; + + LOG_INF("Subelement header received."); + LOG_INF("\tType: %d", hdr->tag_id ); + LOG_INF("\tSize: %d", hdr->length); + + ota_ctx.ota_image_processed += ota_ctx.ota_subelement_fill_level; + ota_ctx.ota_subelement_fill_level = 0; + + if (hdr->tag_id == ZB_ZCL_OTA_UPGRADE_FILE_TAG_UPGRADE_IMAGE) { + ota_ctx.bin_size = hdr->length; + ota_ctx.process_subelement_header = false; + ota_ctx.process_bin_image = true; + + err = dfu_multi_target_init_default(); + if (err != 0) { + return ZB_ZCL_OTA_UPGRADE_STATUS_ERROR; + } + } else { + /* Skip unknown subelement payload. */ + ota_ctx.ota_image_processed += hdr->length; + *bytes_copied += MIN(len - *bytes_copied, hdr->length); + } + } + + return ZB_ZCL_OTA_UPGRADE_STATUS_OK; +} + +static zb_uint8_t ota_process_firmware(zb_uint32_t offset, zb_uint8_t *data, uint32_t len, + uint32_t *bytes_copied) +{ + int err = 0; + + LOG_DBG("Process firmware."); + LOG_DBG("Bytes left: %d copy: %d", ota_ctx.bin_size - offset, len); + + err = dfu_multi_image_write(offset, data, len); + if (err != 0) { + LOG_ERR("dfu_multi_image_write err %d offset: %d len: %d", err, offset, len); + + err = dfu_multi_image_done(false); + if (err != 0) { + LOG_ERR("Unable to cancel DFU image transfer"); + } + + *bytes_copied = 0; + return ZB_ZCL_OTA_UPGRADE_STATUS_ERROR; + } + + *bytes_copied = len; + offset += len; + + if (ota_ctx.bin_size == offset) { + LOG_INF("Firmware downloaded."); + ota_ctx.process_bin_image = false; + ota_ctx.ota_subelement_fill_level = 0; + ota_ctx.process_subelement_header = true; + ota_ctx.ota_image_processed += ota_ctx.bin_size; + } + + send_progress(offset * 100 / (ota_ctx.bin_size + 1)); + + return ZB_ZCL_OTA_UPGRADE_STATUS_OK; +} + +/** @brief Code to process the incoming Zigbee OTA frame + * + * @param ota Pointer to the zb_zcl_ota_upgrade_value_param_t structure, + * passed from the handler + * @param bufid ZBOSS buffer id + * + * @return ZB_ZCL_OTA_UPGRADE_STATUS_BUSY if OTA has to be suspended, + * ZB_ZCL_OTA_UPGRADE_STATUS_OK otherwise + */ +static zb_uint8_t ota_process_chunk( + const zb_zcl_ota_upgrade_value_param_t *ota, zb_bufid_t bufid) +{ + uint8_t ret = ZB_ZCL_OTA_UPGRADE_STATUS_OK; + uint32_t bytes_consumed = 0; + uint32_t bytes_copied = 0; + zb_uint32_t current_offset = ( + ota_ctx.ota_header_fill_level + + ota_ctx.ota_subelement_fill_level + + ota_ctx.ota_image_processed); + + /* Add the offset provided by the dfu_multi_image library. */ + if (ota_ctx.process_bin_image) { + current_offset += dfu_multi_image_offset(); + } + + if (ota->upgrade.receive.file_offset != current_offset) { + LOG_WRN("Unaligned OTA transfer. Expected: %d, received: %d", + current_offset, + ota->upgrade.receive.file_offset); + return ZB_ZCL_OTA_UPGRADE_STATUS_ERROR; + } + + /* Process image header and save it in the memory. */ + if (!ota_ctx.mandatory_header_finished) { + ret = ota_process_mandatory_header( + &ota->upgrade.receive.block_data[bytes_consumed], + ota->upgrade.receive.data_length - bytes_consumed, + &bytes_copied); + bytes_consumed += bytes_copied; + } + + /* Processing optional header is activated when downloaded header + * length is greater than MANDATORY_HEADER_LEN. + */ + if (ota_ctx.process_optional_header) { + ret = ota_process_optional_header( + &ota->upgrade.receive.block_data[bytes_consumed], + ota->upgrade.receive.data_length - bytes_consumed, + &bytes_copied); + bytes_consumed += bytes_copied; + } + + while ((ota->upgrade.receive.data_length - bytes_consumed) > 0) { + bytes_copied = 0; + + /* Parse the OTA image subelement header. */ + if (ota_ctx.process_subelement_header) { + ret = ota_process_subelement_header( + &ota->upgrade.receive.block_data[bytes_consumed], + ota->upgrade.receive.data_length - bytes_consumed, + &bytes_copied); + bytes_consumed += bytes_copied; + } + + /* Pass the image to the DFU multi image module. */ + if (ota_ctx.process_bin_image) { + ret = ota_process_firmware( + ota->upgrade.receive.file_offset + bytes_consumed + - (ota_ctx.ota_header_fill_level + ota_ctx.ota_image_processed), + &ota->upgrade.receive.block_data[bytes_consumed], + ota->upgrade.receive.data_length - bytes_consumed, + &bytes_copied); + bytes_consumed += bytes_copied; + } + + if ((bytes_copied == 0) || (ret != ZB_ZCL_OTA_UPGRADE_STATUS_OK)) { + break; + } + } + + if (ota_ctx.total_size == ota_ctx.ota_header_fill_level + ota_ctx.ota_image_processed) { + LOG_INF("Full image downloaded."); + ota_ctx.mandatory_header_finished = false; + ota_ctx.process_optional_header = false; + ota_ctx.process_subelement_header = false; + ota_ctx.process_bin_image = false; + } + + /* Update the current file offset. */ + if (bytes_consumed > 0) { + current_offset = ( + ota_ctx.ota_header_fill_level + + ota_ctx.ota_subelement_fill_level + + ota_ctx.ota_image_processed); + + /* Add the offset provided by the dfu_multi_image library. */ + if (ota_ctx.process_bin_image) { + current_offset += dfu_multi_image_offset(); + } + + ZB_ZCL_SET_ATTRIBUTE( + CONFIG_ZIGBEE_FOTA_ENDPOINT, + ZB_ZCL_CLUSTER_ID_OTA_UPGRADE, + ZB_ZCL_CLUSTER_CLIENT_ROLE, + ZB_ZCL_ATTR_OTA_UPGRADE_FILE_OFFSET_ID, + (zb_uint8_t *)¤t_offset, + ZB_FALSE); + } + + return ret; +} + +static void ota_server_discovery_handler(struct k_timer *work) +{ + /* The user callback function is not invoked upon OTA server discovery + * failure. This is the reason why an explicit attribute value check + * must be used. + */ + zb_zcl_attr_t *attr_desc = zb_zcl_get_attr_desc_a( + CONFIG_ZIGBEE_FOTA_ENDPOINT, + ZB_ZCL_CLUSTER_ID_OTA_UPGRADE, + ZB_ZCL_CLUSTER_CLIENT_ROLE, + ZB_ZCL_ATTR_OTA_UPGRADE_SERVER_ID); + + /* A periodical discovery was started on an endpoint that does + * not implement OTA client. Abort the application. + */ + ZB_ASSERT(attr_desc); + + if (ZB_IS_64BIT_ADDR_UNKNOWN(attr_desc->data_p)) { + /* Restart OTA server discovery. In case of OOM state, + * the discovery mechanism is restarted in the next interval. + */ + zigbee_get_out_buf_delayed(zb_zcl_ota_upgrade_init_client); + } else { + /* OTA server discovery is finished. Stop the timer. */ + k_timer_stop(&dev_ctx.alarm); + } +} + +void zigbee_fota_abort(void) +{ + LOG_INF("ABORT Zigbee DFU"); + /* Reset the context. */ + ota_dfu_reset(); + dfu_multi_image_done(false); +} + + + +int zigbee_fota_init(zigbee_fota_callback_t client_callback) +{ + if (client_callback == NULL) { + return -EINVAL; + } + + callback = client_callback; + ota_client_attr_init(); + + /* Initialize periodic OTA server discovery. */ + k_timer_init(&dev_ctx.alarm, ota_server_discovery_handler, NULL); + + return 0; +} + + +void zigbee_fota_signal_handler(zb_bufid_t bufid) +{ + zb_zdo_app_signal_hdr_t *sig_handler = NULL; + zb_zdo_app_signal_type_t sig = zb_get_app_signal(bufid, &sig_handler); + zb_ret_t status = ZB_GET_APP_SIGNAL_STATUS(bufid); + + switch (sig) { + case ZB_BDB_SIGNAL_DEVICE_REBOOT: + /* fall-through */ + case ZB_BDB_SIGNAL_STEERING: + if (status == RET_OK) { + k_timer_start(&dev_ctx.alarm, K_NO_WAIT, + K_HOURS(CONFIG_ZIGBEE_FOTA_SERVER_DISOVERY_INTERVAL_HRS)); + } + break; + default: + break; + } +} + +void zigbee_fota_zcl_cb(zb_bufid_t bufid) +{ + zb_zcl_device_callback_param_t *device_cb_param = + ZB_BUF_GET_PARAM(bufid, zb_zcl_device_callback_param_t); + + if (device_cb_param->device_cb_id != ZB_ZCL_OTA_UPGRADE_VALUE_CB_ID) { + return; + } + + device_cb_param->status = RET_OK; + zb_zcl_ota_upgrade_value_param_t *ota_upgrade_value = + &(device_cb_param->cb_param.ota_value_param); + + switch (ota_upgrade_value->upgrade_status) { + case ZB_ZCL_OTA_UPGRADE_STATUS_START: + LOG_INF("New OTA image available:"); + LOG_INF("\tManufacturer: 0x%04x", + ota_upgrade_value->upgrade.start.manufacturer); + LOG_INF("\tType: 0x%04x", + ota_upgrade_value->upgrade.start.image_type); + LOG_INF("\tVersion: 0x%08x", + ota_upgrade_value->upgrade.start.file_version); + + /* Check if OTA client is in the middle of image + * download. If so, silently ignore the second + * QueryNextImageResponse packet from OTA server. + */ + if (zb_zcl_ota_upgrade_get_ota_status( + device_cb_param->endpoint) != + ZB_ZCL_OTA_UPGRADE_IMAGE_STATUS_NORMAL) { + + ota_upgrade_value->upgrade_status = + ZB_ZCL_OTA_UPGRADE_STATUS_BUSY; + + /* Check if we're not downgrading. + * If we do, let's politely say no since we do not + * support that. + */ + } else if (ota_upgrade_value->upgrade.start.file_version + > dev_ctx.ota_attr.file_version) { + ota_dfu_reset(); + ota_upgrade_value->upgrade_status = + ZB_ZCL_OTA_UPGRADE_STATUS_OK; + } else { + LOG_DBG("ZB_ZCL_OTA_UPGRADE_STATUS_ABORT"); + ota_upgrade_value->upgrade_status = + ZB_ZCL_OTA_UPGRADE_STATUS_ABORT; + } + break; + + case ZB_ZCL_OTA_UPGRADE_STATUS_RECEIVE: + /* Process image block. */ + ota_upgrade_value->upgrade_status = + ota_process_chunk(ota_upgrade_value, bufid); + break; + + case ZB_ZCL_OTA_UPGRADE_STATUS_CHECK: + LOG_INF("New OTA image downloaded."); + if (dfu_multi_image_done(true)) { + LOG_ERR("Unable to verify the update"); + ota_upgrade_value->upgrade_status = + ZB_ZCL_OTA_UPGRADE_STATUS_ERROR; + } else { + ota_upgrade_value->upgrade_status = + ZB_ZCL_OTA_UPGRADE_STATUS_OK; + } + break; + + case ZB_ZCL_OTA_UPGRADE_STATUS_APPLY: + LOG_INF("Mark OTA image as downloaded."); + ota_upgrade_value->upgrade_status = ZB_ZCL_OTA_UPGRADE_STATUS_OK; + send_progress(ZIGBEE_FOTA_EVT_DL_COMPLETE_VAL); + break; + + case ZB_ZCL_OTA_UPGRADE_STATUS_FINISH: + LOG_INF("Mark OTA image as ready to be installed."); + if (dfu_multi_target_schedule_update()) { + LOG_ERR("Unable to schedule the update"); + ota_upgrade_value->upgrade_status = + ZB_ZCL_OTA_UPGRADE_STATUS_ERROR; + zigbee_fota_abort(); + } else { + LOG_INF("Zigbee DFU completed. Reboot the application."); + ota_upgrade_value->upgrade_status = + ZB_ZCL_OTA_UPGRADE_STATUS_OK; + /* It is time to upgrade FW. + * We use callback so the stack can have time to i.e. + * send response etc. + */ + ZB_SCHEDULE_APP_CALLBACK(send_evt, ZIGBEE_FOTA_EVT_FINISHED); + if (dfu_multi_image_done(false) != 0) { + LOG_ERR("Unable to reset DFU multi image transfer"); + } + } + break; + + case ZB_ZCL_OTA_UPGRADE_STATUS_ABORT: + LOG_INF("Zigbee DFU Aborted"); + ota_upgrade_value->upgrade_status = + ZB_ZCL_OTA_UPGRADE_STATUS_ABORT; + zigbee_fota_abort(); + send_evt(ZIGBEE_FOTA_EVT_ERROR); + break; + + default: + device_cb_param->status = RET_NOT_IMPLEMENTED; + break; + } + + /* No need to free the buffer - stack handles that if needed. */ + return; +} diff --git a/subsys/lib/zigbee_fota/src/zigbee_ota.h b/subsys/lib/zigbee_fota/src/zigbee_ota.h new file mode 100644 index 00000000..d26d6af7 --- /dev/null +++ b/subsys/lib/zigbee_fota/src/zigbee_ota.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef ZIGBEE_OTA_H__ +#define ZIGBEE_OTA_H__ + +#define ZB_HA_DEVICE_VER_OTA_UPGRADE_CLIENT 0 +#define ZB_HA_OTA_UPGRADE_CLIENT_DEVICE_ID 0xfff0 + +/* OTA Upgrade client test input clusters number. */ +#define ZB_HA_OTA_UPGRADE_CLIENT_IN_CLUSTER_NUM 1 +/* OTA Upgrade client test output clusters number. */ +#define ZB_HA_OTA_UPGRADE_CLIENT_OUT_CLUSTER_NUM 1 + + +/** + * @brief Declare cluster list for OTA Upgrade client device. + * @param cluster_list_name [IN] - cluster list variable name. + * @param basic_attr_list [IN] - attribute list for Basic cluster. + * @param ota_upgrade_attr_list [OUT] - attribute list for OTA Upgrade client + * cluster + */ +#define ZB_HA_DECLARE_OTA_UPGRADE_CLIENT_CLUSTER_LIST(cluster_list_name, \ + basic_attr_list, \ + ota_upgrade_attr_list) \ + zb_zcl_cluster_desc_t cluster_list_name[] = \ + { \ + ZB_ZCL_CLUSTER_DESC( \ + ZB_ZCL_CLUSTER_ID_BASIC, \ + ZB_ZCL_ARRAY_SIZE(basic_attr_list, zb_zcl_attr_t), \ + (basic_attr_list), \ + ZB_ZCL_CLUSTER_SERVER_ROLE, \ + ZB_ZCL_MANUF_CODE_INVALID \ + ), \ + ZB_ZCL_CLUSTER_DESC( \ + ZB_ZCL_CLUSTER_ID_OTA_UPGRADE, \ + ZB_ZCL_ARRAY_SIZE(ota_upgrade_attr_list, zb_zcl_attr_t), \ + (ota_upgrade_attr_list), \ + ZB_ZCL_CLUSTER_CLIENT_ROLE, \ + ZB_ZCL_MANUF_CODE_INVALID \ + ) \ + } + +/** + * @brief Declare simple descriptor for OTA Upgrade client device. + * @param ep_name - endpoint variable name. + * @param ep_id [IN] - endpoint ID. + * @param in_clust_num [IN] - number of supported input clusters. + * @param out_clust_num [IN] - number of supported output clusters. + * @note in_clust_num, out_clust_num should be defined by numeric constants, + * not variables or any definitions, because these values are used to + * form simple descriptor type name. + */ +#define ZB_ZCL_DECLARE_OTA_UPGRADE_CLIENT_SIMPLE_DESC( \ + ep_name, ep_id, in_clust_num, out_clust_num) \ +ZB_AF_SIMPLE_DESC_TYPE(in_clust_num, out_clust_num) simple_desc_##ep_name = { \ + ep_id, \ + ZB_AF_HA_PROFILE_ID, \ + ZB_HA_OTA_UPGRADE_CLIENT_DEVICE_ID, \ + ZB_HA_DEVICE_VER_OTA_UPGRADE_CLIENT, \ + 0, \ + in_clust_num, \ + out_clust_num, \ + { \ + ZB_ZCL_CLUSTER_ID_BASIC, \ + ZB_ZCL_CLUSTER_ID_OTA_UPGRADE \ + } \ + } + + +/** + * @brief Declare endpoint for OTA Upgrade client device. + * @param ep_name [IN] - endpoint variable name. + * @param ep_id [IN] - endpoint ID. + * @param cluster_list [IN] - endpoint cluster list. + */ +#define ZB_HA_DECLARE_OTA_UPGRADE_CLIENT_EP(ep_name, ep_id, cluster_list) \ + ZB_ZCL_DECLARE_OTA_UPGRADE_CLIENT_SIMPLE_DESC( \ + ep_name, \ + ep_id, \ + ZB_HA_OTA_UPGRADE_CLIENT_IN_CLUSTER_NUM, \ + ZB_HA_OTA_UPGRADE_CLIENT_OUT_CLUSTER_NUM); \ + 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, \ + 0, NULL, /* No reporting ctx */ \ + 0, NULL) + +#endif /* ZIGBEE_OTA_H__ */ diff --git a/subsys/lib/zigbee_logger_ep/CMakeLists.txt b/subsys/lib/zigbee_logger_ep/CMakeLists.txt new file mode 100644 index 00000000..f0a84c9f --- /dev/null +++ b/subsys/lib/zigbee_logger_ep/CMakeLists.txt @@ -0,0 +1,11 @@ +# +# Copyright (c) 2020 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +zephyr_library() +zephyr_library_sources(zigbee_logger_eprxzcl.c) + +zephyr_library_link_libraries(zboss) +zephyr_library_link_libraries(zigbee) diff --git a/subsys/lib/zigbee_logger_ep/Kconfig b/subsys/lib/zigbee_logger_ep/Kconfig new file mode 100644 index 00000000..20674293 --- /dev/null +++ b/subsys/lib/zigbee_logger_ep/Kconfig @@ -0,0 +1,19 @@ +# +# Copyright (c) 2020 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menuconfig ZIGBEE_LOGGER_EP + bool "Enable Zigbee endpoint handler for logging incoming ZCL frames" + select ZIGBEE_APP_UTILS + depends on LOG + +if ZIGBEE_LOGGER_EP + +# Configure ZIGBEE_LOGGER_EP_LOG_LEVEL +module = ZIGBEE_LOGGER_EP +module-str = Zigbee endpoint logger +source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" + +endif # ZIGBEE_LOGGER_EP diff --git a/subsys/lib/zigbee_logger_ep/zigbee_logger_eprxzcl.c b/subsys/lib/zigbee_logger_ep/zigbee_logger_eprxzcl.c new file mode 100644 index 00000000..33933f48 --- /dev/null +++ b/subsys/lib/zigbee_logger_ep/zigbee_logger_eprxzcl.c @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include + +#include + +#include +#include + +LOG_LEVEL_SET(CONFIG_ZIGBEE_LOGGER_EP_LOG_LEVEL); + +/**@brief Name of the log module related to Zigbee. */ +#define LOG_MODULE_NAME zigbee + +/** @brief Name of the submodule used for logger messaging. */ +#define LOG_SUBMODULE_NAME eprxzcl + +LOG_INSTANCE_REGISTER(LOG_MODULE_NAME, LOG_SUBMODULE_NAME, + CONFIG_ZIGBEE_LOGGER_EP_LOG_LEVEL); + +/* This structure keeps reference to the logger instance used by this module. */ +struct log_ctx { + LOG_INSTANCE_PTR_DECLARE(inst); +}; + +/* Logger instance used by this module. */ +static struct log_ctx logger = { LOG_INSTANCE_PTR_INIT( + inst, LOG_MODULE_NAME, LOG_SUBMODULE_NAME) }; + +/**@brief Number of logs that can be simultaneously buffered before processing + * by a backend + */ +#define PRV_LOG_CIRC_BUFFER_MAX_LOGS 8U + +/**@brief Maximum length of log line. */ +#define PRV_LOG_CIRC_BUFFER_MAX_MESSAGE_LENGTH 512U + +typedef struct { + size_t idx; + char buffers[PRV_LOG_CIRC_BUFFER_MAX_LOGS] + [PRV_LOG_CIRC_BUFFER_MAX_MESSAGE_LENGTH]; +} prv_log_circ_buffer_t; + +/**@brief ZCL received frames log counter. + * @details Successive calls to @ref zigbee_logger_eprxzcl_ep_handler + * increment this value. + * @ref zigbee_logger_eprxzcl_ep_handler puts this value in every log line + */ +static uint32_t log_counter; + +/**@brief Circular buffer of logs + * produced by @ref zigbee_logger_eprxzcl_ep_handler + * @details Successive calls to @ref prv_log_circ_buffer_get_next_buffer + * return successive buffers to be filled in and that may be put + * into LOG_INF with deferred processing. + */ +static prv_log_circ_buffer_t log_circ_buffer; + +/**@brief Function returning next log buffer to be filled in. + * + * This function returns a buffer, which may be filled up with log message + * and passed to LOG_INF. + * User must be aware that actually no 'free' operation happens anywhere. + * Buffer may get overwritten if the backend processes logs slower than + * they are produced. Keep PRV_LOG_CIRC_BUFFER_MAX_LOGS big enough. + * + * @return Pointer to the log message buffer capable of storing + * LOG_CIRC_BUFFER_MAX_MESSAGE_LENGTH bytes (including null terminator). + */ +static char *prv_log_circ_buffer_get_next_buffer(void) +{ + char *result; + size_t new_idx; + + new_idx = log_circ_buffer.idx; + result = log_circ_buffer.buffers[new_idx]; + + /* Increment modulo now */ + new_idx++; + if (new_idx >= PRV_LOG_CIRC_BUFFER_MAX_LOGS) { + new_idx = 0U; + } + log_circ_buffer.idx = new_idx; + + memset(result, 0, PRV_LOG_CIRC_BUFFER_MAX_MESSAGE_LENGTH); + + return result; +} + +/**@brief Macro used within zigbee_logger_eprxzcl_ep_handler + * to perform guarded log_message_curr advance + * @note To be used within @ref zigbee_logger_eprxzcl_ep_handler only + */ +#define PRV_ADVANCE_CURR_LOG_MESSAGE_PTR(s) \ + do { \ + if ((s) < 0) { \ + LOG_INST_ERR( \ + logger.inst, \ + "Received ZCL command but encoding error occurred during log producing"); \ + return ZB_FALSE; \ + } \ + log_message_curr += (s); \ + if (log_message_curr >= log_message_end) { \ + LOG_INST_ERR( \ + logger.inst, \ + "Received ZCL command but produced log is too long"); \ + return ZB_FALSE; \ + } \ + } while (0) + +zb_uint8_t zigbee_logger_eprxzcl_ep_handler(zb_bufid_t bufid) +{ + if (CONFIG_ZIGBEE_LOGGER_EP_LOG_LEVEL >= LOG_LEVEL_INF) { + int status; + char *log_message = prv_log_circ_buffer_get_next_buffer(); + char *log_message_curr = log_message; + char *log_message_end = + log_message_curr + + PRV_LOG_CIRC_BUFFER_MAX_MESSAGE_LENGTH - 1U; + zb_zcl_parsed_hdr_t *cmd_info = + ZB_BUF_GET_PARAM(bufid, zb_zcl_parsed_hdr_t); + size_t payload_length = zb_buf_len(bufid); + const zb_uint8_t *payload = zb_buf_begin(bufid); + uint32_t log_number = log_counter++; + + status = snprintf(log_message_curr, + log_message_end - log_message_curr, + "Received ZCL command (%" PRIu32 + "): src_addr=", + log_number); + PRV_ADVANCE_CURR_LOG_MESSAGE_PTR(status); + + switch (ZB_ZCL_PARSED_HDR_SHORT_DATA(cmd_info) + .source.addr_type) { + case ZB_ZCL_ADDR_TYPE_SHORT: + status = snprintf( + log_message_curr, + log_message_end - log_message_curr, + "0x%04x(short)", + ZB_ZCL_PARSED_HDR_SHORT_DATA(cmd_info) + .source.u.short_addr); + PRV_ADVANCE_CURR_LOG_MESSAGE_PTR(status); + break; + + case ZB_ZCL_ADDR_TYPE_IEEE_GPD: + status = ieee_addr_to_str( + log_message_curr, + log_message_end - log_message_curr, + ZB_ZCL_PARSED_HDR_SHORT_DATA(cmd_info) + .source.u.ieee_addr); + PRV_ADVANCE_CURR_LOG_MESSAGE_PTR(status); + + status = + snprintf(log_message_curr, + log_message_end - log_message_curr, + "(ieee_gpd)"); + PRV_ADVANCE_CURR_LOG_MESSAGE_PTR(status); + break; + + case ZB_ZCL_ADDR_TYPE_SRC_ID_GPD: + status = snprintf( + log_message_curr, + log_message_end - log_message_curr, + "0x%x(src_id_gpd)", + ZB_ZCL_PARSED_HDR_SHORT_DATA(cmd_info) + .source.u.src_id); + PRV_ADVANCE_CURR_LOG_MESSAGE_PTR(status); + break; + + case ZB_ZCL_ADDR_TYPE_IEEE: + status = ieee_addr_to_str( + log_message_curr, + log_message_end - log_message_curr, + ZB_ZCL_PARSED_HDR_SHORT_DATA(cmd_info) + .source.u.ieee_addr); + PRV_ADVANCE_CURR_LOG_MESSAGE_PTR(status); + status = + snprintf(log_message_curr, + log_message_end - log_message_curr, + "(ieee)"); + PRV_ADVANCE_CURR_LOG_MESSAGE_PTR(status); + break; + + default: + status = + snprintf(log_message_curr, + log_message_end - log_message_curr, + "0(unknown)"); + PRV_ADVANCE_CURR_LOG_MESSAGE_PTR(status); + break; + } + + status = snprintf( + log_message_curr, + log_message_end - log_message_curr, + " src_ep=%u dst_ep=%u cluster_id=0x%04x profile_id=0x%04x", + ZB_ZCL_PARSED_HDR_SHORT_DATA(cmd_info).src_endpoint, + ZB_ZCL_PARSED_HDR_SHORT_DATA(cmd_info).dst_endpoint, + cmd_info->cluster_id, cmd_info->profile_id); + PRV_ADVANCE_CURR_LOG_MESSAGE_PTR(status); + + status = snprintf( + log_message_curr, + log_message_end - log_message_curr, + " cmd_dir=%u common_cmd=%u cmd_id=0x%02x cmd_seq=%u disable_def_resp=%u", + cmd_info->cmd_direction, + cmd_info->is_common_command, cmd_info->cmd_id, + cmd_info->seq_number, + cmd_info->disable_default_response); + PRV_ADVANCE_CURR_LOG_MESSAGE_PTR(status); + + if (cmd_info->is_manuf_specific) { + status = + snprintf(log_message_curr, + log_message_end - log_message_curr, + " manuf_code=0x%04x", + cmd_info->manuf_specific); + } else { + status = + snprintf(log_message_curr, + log_message_end - log_message_curr, + " manuf_code=void"); + } + PRV_ADVANCE_CURR_LOG_MESSAGE_PTR(status); + + status = snprintf(log_message_curr, + log_message_end - log_message_curr, + " payload=["); + PRV_ADVANCE_CURR_LOG_MESSAGE_PTR(status); + + status = to_hex_str(log_message_curr, + log_message_end - log_message_curr, + payload, payload_length, false); + PRV_ADVANCE_CURR_LOG_MESSAGE_PTR(status); + + /* Put again log_counter to be able to simple check + * log consistency + */ + status = snprintf(log_message_curr, + log_message_end - log_message_curr, + "] (%" PRIu32 ")", log_number); + PRV_ADVANCE_CURR_LOG_MESSAGE_PTR(status); + + *log_message_curr = '\0'; + + LOG_INST_INF(logger.inst, "%s", log_message); + } + + return ZB_FALSE; +} diff --git a/subsys/lib/zigbee_scenes/CMakeLists.txt b/subsys/lib/zigbee_scenes/CMakeLists.txt new file mode 100644 index 00000000..35a08a81 --- /dev/null +++ b/subsys/lib/zigbee_scenes/CMakeLists.txt @@ -0,0 +1,11 @@ +# +# Copyright (c) 2021 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +zephyr_library() +zephyr_library_sources(zigbee_zcl_scenes.c) + +zephyr_library_link_libraries(zboss) +zephyr_library_link_libraries(zigbee) diff --git a/subsys/lib/zigbee_scenes/Kconfig b/subsys/lib/zigbee_scenes/Kconfig new file mode 100644 index 00000000..2c76f53f --- /dev/null +++ b/subsys/lib/zigbee_scenes/Kconfig @@ -0,0 +1,29 @@ +# +# Copyright (c) 2021 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menuconfig ZIGBEE_SCENES + bool "Enable Zigbee scenes extension" + select SETTINGS + imply NVS + +if ZIGBEE_SCENES + +config ZIGBEE_SCENES_ENDPOINT + int "Zigbee scenes endpoint" + default 10 + range 1 240 + +config ZIGBEE_SCENE_TABLE_SIZE + int "Zigbee scene table size" + default 3 + range 1 30 + +# Configure ZIGBEE_SCENES_LOG_LEVEL +module = ZIGBEE_SCENES +module-str = Zigbee scenes extension +source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" + +endif # ZIGBEE_SCENES diff --git a/subsys/lib/zigbee_scenes/zigbee_zcl_scenes.c b/subsys/lib/zigbee_scenes/zigbee_zcl_scenes.c new file mode 100644 index 00000000..169c5ad6 --- /dev/null +++ b/subsys/lib/zigbee_scenes/zigbee_zcl_scenes.c @@ -0,0 +1,939 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include + +LOG_MODULE_REGISTER(zigbee_zcl_scenes, CONFIG_ZIGBEE_SCENES_LOG_LEVEL); + +static zb_uint8_t scene_table_get_entry(zb_uint16_t group_id, zb_uint8_t scene_id); +static void scene_table_remove_entries_by_group(zb_uint16_t group_id); +static void scene_table_init(void); + +struct zb_zcl_scenes_fieldset_data_on_off { + zb_bool_t has_on_off; + zb_uint8_t on_off; +}; + +struct zb_zcl_scenes_fieldset_data_level_control { + zb_bool_t has_current_level; + zb_uint8_t current_level; +}; + +struct zb_zcl_scenes_fieldset_data_window_covering { + zb_bool_t has_current_position_lift_percentage; + zb_uint8_t current_position_lift_percentage; + zb_bool_t has_current_position_tilt_percentage; + zb_uint8_t current_position_tilt_percentage; +}; + +struct scene_table_on_off_entry { + zb_zcl_scene_table_record_fixed_t common; + struct zb_zcl_scenes_fieldset_data_on_off on_off; + struct zb_zcl_scenes_fieldset_data_level_control level_control; + struct zb_zcl_scenes_fieldset_data_window_covering window_covering; +}; + +static struct scene_table_on_off_entry scenes_table[CONFIG_ZIGBEE_SCENE_TABLE_SIZE]; + +struct response_info { + zb_zcl_parsed_hdr_t cmd_info; + zb_zcl_scenes_view_scene_req_t view_scene_req; + zb_zcl_scenes_get_scene_membership_req_t get_scene_membership_req; +}; + +static struct response_info resp_info; + +static int scenes_table_set(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg) +{ + const char *next; + int rc; + + if (settings_name_steq(name, "scenes_table", &next) && !next) { + if (len != sizeof(scenes_table)) { + return -EINVAL; + } + + rc = read_cb(cb_arg, scenes_table, sizeof(scenes_table)); + if (rc >= 0) { + return 0; + } + + return rc; + } + + return -ENOENT; +} + +static void scenes_table_save(void) +{ + settings_save_one("scenes/scenes_table", scenes_table, sizeof(scenes_table)); +} + +struct settings_handler scenes_conf = { + .name = "scenes", + .h_set = scenes_table_set +}; + +static zb_bool_t has_cluster(zb_uint16_t cluster_id) +{ + return (get_endpoint_by_cluster(cluster_id, ZB_ZCL_CLUSTER_SERVER_ROLE) + == CONFIG_ZIGBEE_SCENES_ENDPOINT) + ? ZB_TRUE : ZB_FALSE; +} + +static zb_bool_t add_fieldset(zb_zcl_scenes_fieldset_common_t *fieldset, + struct scene_table_on_off_entry *entry) +{ + zb_uint8_t *fs_data_ptr; + + if (fieldset->cluster_id == ZB_ZCL_CLUSTER_ID_ON_OFF && + fieldset->fieldset_length >= 1 && + has_cluster(ZB_ZCL_CLUSTER_ID_ON_OFF)) { + fs_data_ptr = (zb_uint8_t *)fieldset + sizeof(zb_zcl_scenes_fieldset_common_t); + + entry->on_off.has_on_off = ZB_TRUE; + entry->on_off.on_off = *fs_data_ptr; + + LOG_INF("Add fieldset: cluster_id=0x%x, length=%d, on/off=%d", + fieldset->cluster_id, fieldset->fieldset_length, entry->on_off.on_off); + + return ZB_TRUE; + } + if (fieldset->cluster_id == ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL && + fieldset->fieldset_length >= 1 && + has_cluster(ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL)) { + fs_data_ptr = (zb_uint8_t *)fieldset + sizeof(zb_zcl_scenes_fieldset_common_t); + + entry->level_control.has_current_level = ZB_TRUE; + entry->level_control.current_level = *fs_data_ptr; + + LOG_INF("Add fieldset: cluster_id=0x%x, length=%d, level=%d", + fieldset->cluster_id, + fieldset->fieldset_length, + entry->level_control.current_level); + + return ZB_TRUE; + } + if (fieldset->cluster_id == ZB_ZCL_CLUSTER_ID_WINDOW_COVERING && + fieldset->fieldset_length >= 1 && + has_cluster(ZB_ZCL_CLUSTER_ID_WINDOW_COVERING)) { + fs_data_ptr = (zb_uint8_t *)fieldset + sizeof(zb_zcl_scenes_fieldset_common_t); + + entry->window_covering.has_current_position_lift_percentage = ZB_TRUE; + entry->window_covering.current_position_lift_percentage = *fs_data_ptr; + + if (fieldset->fieldset_length >= 2) { + entry->window_covering.has_current_position_tilt_percentage = ZB_TRUE; + entry->window_covering.current_position_tilt_percentage = + *(fs_data_ptr + 1); + } + + LOG_INF("Add fieldset: cluster_id=0x%x, length=%d, lift=%d", + fieldset->cluster_id, fieldset->fieldset_length, + entry->window_covering.current_position_lift_percentage); + + return ZB_TRUE; + } + + LOG_INF("Ignore fieldset: cluster_id = 0x%x, length = %d", + fieldset->cluster_id, fieldset->fieldset_length); + + return ZB_FALSE; +} + +static zb_uint8_t *dump_fieldsets(struct scene_table_on_off_entry *entry, + zb_uint8_t *payload_ptr) +{ + if (entry->on_off.has_on_off == ZB_TRUE) { + LOG_INF("Append On/Off fieldset"); + + /* Extension set: Cluster ID = On/Off */ + ZB_ZCL_PACKET_PUT_DATA16_VAL(payload_ptr, ZB_ZCL_CLUSTER_ID_ON_OFF); + + /* Extension set: Fieldset length = 1 */ + ZB_ZCL_PACKET_PUT_DATA8(payload_ptr, 1); + + /* Extension set: On/Off state */ + ZB_ZCL_PACKET_PUT_DATA8(payload_ptr, entry->on_off.on_off); + } + + if (entry->level_control.has_current_level == ZB_TRUE) { + LOG_INF("Append level control fieldset"); + + /* Extension set: Cluster ID = Level Control */ + ZB_ZCL_PACKET_PUT_DATA16_VAL(payload_ptr, ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL); + + /* Extension set: Fieldset length = 1 */ + ZB_ZCL_PACKET_PUT_DATA8(payload_ptr, 1); + + /* Extension set: current_level state */ + ZB_ZCL_PACKET_PUT_DATA8(payload_ptr, entry->level_control.current_level); + } + + if (entry->window_covering.has_current_position_lift_percentage == ZB_TRUE) { + LOG_INF("Append window covering fieldset"); + + /* Extension set: Cluster ID = Window covering */ + ZB_ZCL_PACKET_PUT_DATA16_VAL(payload_ptr, ZB_ZCL_CLUSTER_ID_WINDOW_COVERING); + + /* Extension set: Fieldset length = 1 or 2*/ + if (entry->window_covering.has_current_position_tilt_percentage == ZB_TRUE) { + ZB_ZCL_PACKET_PUT_DATA8(payload_ptr, 2); + } else { + ZB_ZCL_PACKET_PUT_DATA8(payload_ptr, 1); + } + /* Extension set: current_position_lift_percentage state */ + ZB_ZCL_PACKET_PUT_DATA8(payload_ptr, + entry->window_covering.current_position_lift_percentage); + + if (entry->window_covering.has_current_position_tilt_percentage == ZB_TRUE) { + /* Extension set: current_position_tilt_percentage state */ + ZB_ZCL_PACKET_PUT_DATA8(payload_ptr, + entry->window_covering.current_position_tilt_percentage); + } + } + + /* Pass the updated data pointer. */ + return payload_ptr; +} + +static zb_ret_t get_on_off_value(zb_uint8_t *on_off) +{ + zb_zcl_attr_t *attr_desc = zb_zcl_get_attr_desc_a( + CONFIG_ZIGBEE_SCENES_ENDPOINT, + ZB_ZCL_CLUSTER_ID_ON_OFF, + ZB_ZCL_CLUSTER_SERVER_ROLE, + ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID); + + if (attr_desc != NULL) { + *on_off = (zb_bool_t)ZB_ZCL_GET_ATTRIBUTE_VAL_8(attr_desc); + return RET_OK; + } + + return RET_ERROR; +} + +static zb_ret_t get_current_level_value(zb_uint8_t *current_level) +{ + zb_zcl_attr_t *attr_desc = zb_zcl_get_attr_desc_a( + CONFIG_ZIGBEE_SCENES_ENDPOINT, + ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL, + ZB_ZCL_CLUSTER_SERVER_ROLE, + ZB_ZCL_ATTR_LEVEL_CONTROL_CURRENT_LEVEL_ID); + + if (attr_desc != NULL) { + *current_level = ZB_ZCL_GET_ATTRIBUTE_VAL_8(attr_desc); + return RET_OK; + } + + return RET_ERROR; +} + +static zb_ret_t get_current_lift_value(zb_uint8_t *percentage) +{ + zb_zcl_attr_t *attr_desc = zb_zcl_get_attr_desc_a( + CONFIG_ZIGBEE_SCENES_ENDPOINT, + ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, + ZB_ZCL_CLUSTER_SERVER_ROLE, + ZB_ZCL_ATTR_WINDOW_COVERING_CURRENT_POSITION_LIFT_PERCENTAGE_ID); + + if (attr_desc != NULL) { + *percentage = ZB_ZCL_GET_ATTRIBUTE_VAL_8(attr_desc); + return RET_OK; + } + + return RET_ERROR; +} + +static zb_ret_t get_current_tilt_value(zb_uint8_t *percentage) +{ + zb_zcl_attr_t *attr_desc = zb_zcl_get_attr_desc_a( + CONFIG_ZIGBEE_SCENES_ENDPOINT, + ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, + ZB_ZCL_CLUSTER_SERVER_ROLE, + ZB_ZCL_ATTR_WINDOW_COVERING_CURRENT_POSITION_TILT_PERCENTAGE_ID); + + if (attr_desc != NULL) { + *percentage = ZB_ZCL_GET_ATTRIBUTE_VAL_8(attr_desc); + return RET_OK; + } + + return RET_ERROR; +} + +static void save_state_as_scene(struct scene_table_on_off_entry *entry) +{ + if (has_cluster(ZB_ZCL_CLUSTER_ID_ON_OFF) && + get_on_off_value(&entry->on_off.on_off) == RET_OK) { + LOG_INF("Save On/Off state inside scene table"); + entry->on_off.has_on_off = ZB_TRUE; + } + if (has_cluster(ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL) && + get_current_level_value(&entry->level_control.current_level) == RET_OK) { + LOG_INF("Save level control state inside scene table"); + entry->level_control.has_current_level = ZB_TRUE; + } + if (has_cluster(ZB_ZCL_CLUSTER_ID_WINDOW_COVERING)) { + LOG_INF("Save window covering state inside scene table"); + if (get_current_lift_value( + &entry->window_covering.current_position_lift_percentage) == RET_OK) { + entry->window_covering.has_current_position_lift_percentage = ZB_TRUE; + } + if (get_current_tilt_value( + &entry->window_covering.current_position_tilt_percentage) == RET_OK) { + entry->window_covering.has_current_position_tilt_percentage = ZB_TRUE; + } + } +} + +static void recall_scene(struct scene_table_on_off_entry *entry) +{ + zb_bufid_t buf = zb_buf_get_any(); + zb_zcl_attr_t *attr_desc; + zb_ret_t result; + + if (entry->on_off.has_on_off) { + LOG_INF("Recall On/Off state"); + + attr_desc = zb_zcl_get_attr_desc_a( + CONFIG_ZIGBEE_SCENES_ENDPOINT, + ZB_ZCL_CLUSTER_ID_ON_OFF, + ZB_ZCL_CLUSTER_SERVER_ROLE, + ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID); + + ZB_ZCL_INVOKE_USER_APP_SET_ATTR_WITH_RESULT( + buf, + CONFIG_ZIGBEE_SCENES_ENDPOINT, + ZB_ZCL_CLUSTER_ID_ON_OFF, + attr_desc, + &entry->on_off.on_off, + result + ); + } + if (entry->level_control.has_current_level) { + LOG_INF("Recall level control state"); + + attr_desc = zb_zcl_get_attr_desc_a( + CONFIG_ZIGBEE_SCENES_ENDPOINT, + ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL, + ZB_ZCL_CLUSTER_SERVER_ROLE, + ZB_ZCL_ATTR_LEVEL_CONTROL_CURRENT_LEVEL_ID); + + ZB_ZCL_INVOKE_USER_APP_SET_ATTR_WITH_RESULT( + buf, + CONFIG_ZIGBEE_SCENES_ENDPOINT, + ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL, + attr_desc, + &entry->level_control.current_level, + result + ); + } + if (entry->window_covering.has_current_position_lift_percentage) { + LOG_INF("Recall window covering lift state"); + + attr_desc = zb_zcl_get_attr_desc_a( + CONFIG_ZIGBEE_SCENES_ENDPOINT, + ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, + ZB_ZCL_CLUSTER_SERVER_ROLE, + ZB_ZCL_ATTR_WINDOW_COVERING_CURRENT_POSITION_LIFT_PERCENTAGE_ID); + + ZB_ZCL_INVOKE_USER_APP_SET_ATTR_WITH_RESULT( + buf, + CONFIG_ZIGBEE_SCENES_ENDPOINT, + ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, + attr_desc, + &entry->window_covering.current_position_lift_percentage, + result + ); + } + if (entry->window_covering.has_current_position_tilt_percentage) { + LOG_INF("Recall window covering tilt state"); + + attr_desc = zb_zcl_get_attr_desc_a( + CONFIG_ZIGBEE_SCENES_ENDPOINT, + ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, + ZB_ZCL_CLUSTER_SERVER_ROLE, + ZB_ZCL_ATTR_WINDOW_COVERING_CURRENT_POSITION_TILT_PERCENTAGE_ID); + + ZB_ZCL_INVOKE_USER_APP_SET_ATTR_WITH_RESULT( + buf, + CONFIG_ZIGBEE_SCENES_ENDPOINT, + ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, + attr_desc, + &entry->window_covering.current_position_tilt_percentage, + result + ); + } + + zb_buf_free(buf); +} + +static void send_view_scene_resp(zb_bufid_t bufid, zb_uint16_t idx) +{ + zb_uint8_t *payload_ptr; + zb_uint8_t view_scene_status = ZB_ZCL_STATUS_NOT_FOUND; + + LOG_DBG(">> %s bufid %hd idx %d", __func__, bufid, idx); + + if (idx != 0xFF && + scenes_table[idx].common.group_id != ZB_ZCL_SCENES_FREE_SCENE_TABLE_RECORD) { + /* Scene found */ + view_scene_status = ZB_ZCL_STATUS_SUCCESS; + } else if (!zb_aps_is_endpoint_in_group(resp_info.view_scene_req.group_id, + ZB_ZCL_PARSED_HDR_SHORT_DATA(&resp_info.cmd_info).dst_endpoint)) { + /* Not in the group */ + view_scene_status = ZB_ZCL_STATUS_INVALID_FIELD; + } + + ZB_ZCL_SCENES_INIT_VIEW_SCENE_RES( + bufid, + payload_ptr, + resp_info.cmd_info.seq_number, + view_scene_status, + resp_info.view_scene_req.group_id, + resp_info.view_scene_req.scene_id); + + if (view_scene_status == ZB_ZCL_STATUS_SUCCESS) { + ZB_ZCL_SCENES_ADD_TRANSITION_TIME_VIEW_SCENE_RES( + payload_ptr, + scenes_table[idx].common.transition_time); + + ZB_ZCL_SCENES_ADD_SCENE_NAME_VIEW_SCENE_RES( + payload_ptr, + scenes_table[idx].common.scene_name); + + payload_ptr = dump_fieldsets(&scenes_table[idx], payload_ptr); + } + + ZB_ZCL_SCENES_SEND_VIEW_SCENE_RES( + bufid, + payload_ptr, + ZB_ZCL_PARSED_HDR_SHORT_DATA(&resp_info.cmd_info).source.u.short_addr, + ZB_ZCL_PARSED_HDR_SHORT_DATA(&resp_info.cmd_info).src_endpoint, + ZB_ZCL_PARSED_HDR_SHORT_DATA(&resp_info.cmd_info).dst_endpoint, + resp_info.cmd_info.profile_id, + NULL); + + LOG_DBG("<< %s", __func__); +} + +static void send_get_scene_membership_resp(zb_bufid_t bufid) +{ + zb_uint8_t *payload_ptr; + zb_uint8_t *capacity_ptr; + zb_uint8_t *scene_count_ptr; + + LOG_DBG(">> %s bufid %hd", __func__, bufid); + + if (!zb_aps_is_endpoint_in_group( + resp_info.get_scene_membership_req.group_id, + ZB_ZCL_PARSED_HDR_SHORT_DATA(&resp_info.cmd_info).dst_endpoint)) { + /* Not in the group */ + ZB_ZCL_SCENES_INIT_GET_SCENE_MEMBERSHIP_RES( + bufid, + payload_ptr, + resp_info.cmd_info.seq_number, + capacity_ptr, + ZB_ZCL_STATUS_INVALID_FIELD, + ZB_ZCL_SCENES_CAPACITY_UNKNOWN, + resp_info.get_scene_membership_req.group_id); + } else { + zb_uint8_t i = 0; + + ZB_ZCL_SCENES_INIT_GET_SCENE_MEMBERSHIP_RES( + bufid, + payload_ptr, + resp_info.cmd_info.seq_number, + capacity_ptr, + ZB_ZCL_STATUS_SUCCESS, + 0, + resp_info.get_scene_membership_req.group_id); + + scene_count_ptr = payload_ptr; + ZB_ZCL_SCENES_ADD_SCENE_COUNT_GET_SCENE_MEMBERSHIP_RES(payload_ptr, 0); + + while (i < CONFIG_ZIGBEE_SCENE_TABLE_SIZE) { + if (scenes_table[i].common.group_id == + resp_info.get_scene_membership_req.group_id) { + /* Add to payload */ + LOG_INF("add scene_id %hd", scenes_table[i].common.scene_id); + ++(*scene_count_ptr); + ZB_ZCL_SCENES_ADD_SCENE_ID_GET_SCENE_MEMBERSHIP_RES( + payload_ptr, + scenes_table[i].common.scene_id); + } else if (scenes_table[i].common.group_id == + ZB_ZCL_SCENES_FREE_SCENE_TABLE_RECORD) { + LOG_INF("add capacity num"); + ++(*capacity_ptr); + } + ++i; + } + } + + ZB_ZCL_SCENES_SEND_GET_SCENE_MEMBERSHIP_RES( + bufid, + payload_ptr, + ZB_ZCL_PARSED_HDR_SHORT_DATA(&resp_info.cmd_info).source.u.short_addr, + ZB_ZCL_PARSED_HDR_SHORT_DATA(&resp_info.cmd_info).src_endpoint, + ZB_ZCL_PARSED_HDR_SHORT_DATA(&resp_info.cmd_info).dst_endpoint, + resp_info.cmd_info.profile_id, + NULL); + + LOG_DBG("<< %s", __func__); +} + +static void scene_table_init(void) +{ + zb_uint8_t i = 0; + + memset(scenes_table, 0, sizeof(scenes_table)); + while (i < CONFIG_ZIGBEE_SCENE_TABLE_SIZE) { + scenes_table[i].common.group_id = ZB_ZCL_SCENES_FREE_SCENE_TABLE_RECORD; + ++i; + } +} + +static zb_uint8_t scene_table_get_entry(zb_uint16_t group_id, zb_uint8_t scene_id) +{ + zb_uint8_t i = 0; + zb_uint8_t idx = 0xFF, free_idx = 0xFF; + + while (i < CONFIG_ZIGBEE_SCENE_TABLE_SIZE) { + if (scenes_table[i].common.group_id == group_id && + scenes_table[i].common.scene_id == scene_id) { + idx = i; + break; + } else if ((free_idx == 0xFF) && + (scenes_table[i].common.group_id == + ZB_ZCL_SCENES_FREE_SCENE_TABLE_RECORD)) { + /* Remember free index */ + free_idx = i; + } + ++i; + } + + return ((idx != 0xFF) ? idx : free_idx); +} + +static void scene_table_remove_entries_by_group(zb_uint16_t group_id) +{ + zb_uint8_t i = 0; + + LOG_DBG(">> %s: group_id 0x%x", __func__, group_id); + while (i < CONFIG_ZIGBEE_SCENE_TABLE_SIZE) { + if (scenes_table[i].common.group_id == group_id) { + LOG_INF("removing scene: entry idx %hd", i); + memset(&scenes_table[i], 0, sizeof(scenes_table[i])); + scenes_table[i].common.group_id = ZB_ZCL_SCENES_FREE_SCENE_TABLE_RECORD; + } + ++i; + } + LOG_DBG("<< %s", __func__); +} + +static zb_ret_t get_scene_valid_value(zb_bool_t *scene_valid) +{ + zb_zcl_attr_t *attr_desc = zb_zcl_get_attr_desc_a( + CONFIG_ZIGBEE_SCENES_ENDPOINT, + ZB_ZCL_CLUSTER_ID_SCENES, + ZB_ZCL_CLUSTER_SERVER_ROLE, + ZB_ZCL_ATTR_SCENES_SCENE_VALID_ID); + + if (attr_desc != NULL) { + *scene_valid = (zb_bool_t)ZB_ZCL_GET_ATTRIBUTE_VAL_8(attr_desc); + return RET_OK; + } + + return RET_ERROR; +} + +static zb_ret_t set_scene_valid_value(zb_bool_t scene_valid) +{ + zb_zcl_attr_t *attr_desc = zb_zcl_get_attr_desc_a( + CONFIG_ZIGBEE_SCENES_ENDPOINT, + ZB_ZCL_CLUSTER_ID_SCENES, + ZB_ZCL_CLUSTER_SERVER_ROLE, + ZB_ZCL_ATTR_SCENES_SCENE_VALID_ID); + + if (attr_desc != NULL) { + ZB_ZCL_SET_DIRECTLY_ATTR_VAL8(attr_desc, scene_valid); + return RET_OK; + } + + return RET_ERROR; +} + +static zb_ret_t get_current_scene_scene_id_value(zb_uint8_t *scene_id) +{ + zb_zcl_attr_t *attr_desc = zb_zcl_get_attr_desc_a( + CONFIG_ZIGBEE_SCENES_ENDPOINT, + ZB_ZCL_CLUSTER_ID_SCENES, + ZB_ZCL_CLUSTER_SERVER_ROLE, + ZB_ZCL_ATTR_SCENES_CURRENT_SCENE_ID); + + if (attr_desc != NULL) { + *scene_id = (zb_bool_t)ZB_ZCL_GET_ATTRIBUTE_VAL_8(attr_desc); + return RET_OK; + } + + return RET_ERROR; +} + +static zb_ret_t get_current_scene_group_id_value(zb_uint16_t *group_id) +{ + zb_zcl_attr_t *attr_desc = zb_zcl_get_attr_desc_a( + CONFIG_ZIGBEE_SCENES_ENDPOINT, + ZB_ZCL_CLUSTER_ID_SCENES, + ZB_ZCL_CLUSTER_SERVER_ROLE, + ZB_ZCL_ATTR_SCENES_CURRENT_GROUP_ID); + + if (attr_desc != NULL) { + *group_id = (zb_bool_t)ZB_ZCL_GET_ATTRIBUTE_VAL_8(attr_desc); + return RET_OK; + } + + return RET_ERROR; +} + +static void update_scene_valid_value(void) +{ + /* ZBOSS stack automatically sets the scene_valid attribute to true after + * scene recall, but is unable to set it back to false if the device state + * has changed. + */ + zb_bool_t scene_valid = ZB_FALSE; + zb_uint8_t scene_id = 0xFF; + zb_uint16_t group_id = ZB_ZCL_SCENES_FREE_SCENE_TABLE_RECORD; + + if (get_scene_valid_value(&scene_valid) == RET_OK && + get_current_scene_scene_id_value(&scene_id) == RET_OK && + get_current_scene_group_id_value(&group_id) == RET_OK && + scene_valid == ZB_TRUE) { + /* Verify if scene_valid should be reset. */ + zb_uint8_t idx = scene_table_get_entry(group_id, scene_id); + + if (group_id == ZB_ZCL_SCENES_FREE_SCENE_TABLE_RECORD || + scene_id < CONFIG_ZIGBEE_SCENE_TABLE_SIZE || + idx == ZB_ZCL_SCENES_FREE_SCENE_TABLE_RECORD) { + (void)set_scene_valid_value(ZB_FALSE); + return; + } + + if (scenes_table[idx].on_off.has_on_off) { + zb_uint8_t on_off; + + (void)get_on_off_value(&on_off); + if (on_off != scenes_table[idx].on_off.on_off) { + (void)set_scene_valid_value(ZB_FALSE); + return; + } + } + + if (scenes_table[idx].level_control.has_current_level) { + zb_uint8_t current_level; + + (void)get_current_level_value(¤t_level); + if (current_level != scenes_table[idx].level_control.current_level) { + (void)set_scene_valid_value(ZB_FALSE); + return; + } + } + + if (scenes_table[idx].window_covering.has_current_position_lift_percentage) { + zb_uint8_t lift; + + (void)get_current_lift_value(&lift); + if (lift != + scenes_table[idx].window_covering.current_position_lift_percentage) { + (void)set_scene_valid_value(ZB_FALSE); + return; + } + } + if (scenes_table[idx].window_covering.has_current_position_tilt_percentage) { + zb_uint8_t tilt; + + (void)get_current_lift_value(&tilt); + if (tilt != + scenes_table[idx].window_covering.current_position_tilt_percentage) { + (void)set_scene_valid_value(ZB_FALSE); + return; + } + } + } +} + +void zcl_scenes_init(void) +{ + scene_table_init(); + settings_register(&scenes_conf); +} + +zb_bool_t zcl_scenes_cb(zb_bufid_t bufid) +{ + zb_zcl_device_callback_param_t *device_cb_param = + ZB_BUF_GET_PARAM(bufid, zb_zcl_device_callback_param_t); + LOG_DBG(">> %s bufid %hd id %hd", __func__, bufid, device_cb_param->device_cb_id); + + update_scene_valid_value(); + + switch (device_cb_param->device_cb_id) { + case ZB_ZCL_SCENES_ADD_SCENE_CB_ID: { + const zb_zcl_scenes_add_scene_req_t *add_scene_req = + ZB_ZCL_DEVICE_CMD_PARAM_IN_GET( + bufid, + zb_zcl_scenes_add_scene_req_t); + zb_uint8_t idx = 0xFF; + zb_uint8_t *add_scene_status = + ZB_ZCL_DEVICE_CMD_PARAM_OUT_GET(bufid, zb_uint8_t); + + LOG_INF("ZB_ZCL_SCENES_ADD_SCENE_CB_ID: " + "group_id 0x%x scene_id %hd transition_time %d", + add_scene_req->group_id, + add_scene_req->scene_id, + add_scene_req->transition_time); + + *add_scene_status = ZB_ZCL_STATUS_INVALID_FIELD; + idx = scene_table_get_entry(add_scene_req->group_id, + add_scene_req->scene_id); + + if (idx != 0xFF) { + zb_zcl_scenes_fieldset_common_t *fieldset; + zb_uint8_t fs_content_length; + + if (scenes_table[idx].common.group_id != + ZB_ZCL_SCENES_FREE_SCENE_TABLE_RECORD) { + /* Indicate that we overwriting existing record */ + device_cb_param->status = RET_ALREADY_EXISTS; + } + zb_bool_t empty_entry = ZB_TRUE; + + ZB_ZCL_SCENES_GET_ADD_SCENE_REQ_NEXT_FIELDSET_DESC( + bufid, + fieldset, + fs_content_length); + while (fieldset) { + if (add_fieldset(fieldset, &scenes_table[idx]) == ZB_TRUE) { + empty_entry = ZB_FALSE; + } + ZB_ZCL_SCENES_GET_ADD_SCENE_REQ_NEXT_FIELDSET_DESC( + bufid, + fieldset, + fs_content_length); + } + + if (empty_entry) { + LOG_WRN("Saving empty scene."); + } + /* Store this scene */ + scenes_table[idx].common.group_id = add_scene_req->group_id; + scenes_table[idx].common.scene_id = add_scene_req->scene_id; + scenes_table[idx].common.transition_time = add_scene_req->transition_time; + *add_scene_status = ZB_ZCL_STATUS_SUCCESS; + scenes_table_save(); + } else { + LOG_ERR("Unable to add scene: ZB_ZCL_STATUS_INSUFF_SPACE"); + *add_scene_status = ZB_ZCL_STATUS_INSUFF_SPACE; + } + } + break; + + case ZB_ZCL_SCENES_VIEW_SCENE_CB_ID: { + const zb_zcl_scenes_view_scene_req_t *view_scene_req = + ZB_ZCL_DEVICE_CMD_PARAM_IN_GET(bufid, zb_zcl_scenes_view_scene_req_t); + const zb_zcl_parsed_hdr_t *in_cmd_info = ZB_ZCL_DEVICE_CMD_PARAM_CMD_INFO(bufid); + zb_uint8_t idx = 0xFF; + + LOG_INF("ZB_ZCL_SCENES_VIEW_SCENE_CB_ID: group_id 0x%x scene_id %hd", + view_scene_req->group_id, + view_scene_req->scene_id); + + idx = scene_table_get_entry(view_scene_req->group_id, view_scene_req->scene_id); + LOG_INF("Scene table entry index: %d", idx); + + /* Send View Scene Response */ + ZB_MEMCPY(&resp_info.cmd_info, in_cmd_info, sizeof(zb_zcl_parsed_hdr_t)); + ZB_MEMCPY(&resp_info.view_scene_req, view_scene_req, + sizeof(zb_zcl_scenes_view_scene_req_t)); + zb_buf_get_out_delayed_ext(send_view_scene_resp, idx, 0); + } + break; + + case ZB_ZCL_SCENES_REMOVE_SCENE_CB_ID: { + const zb_zcl_scenes_remove_scene_req_t *remove_scene_req = + ZB_ZCL_DEVICE_CMD_PARAM_IN_GET(bufid, zb_zcl_scenes_remove_scene_req_t); + zb_uint8_t idx = 0xFF; + zb_uint8_t *remove_scene_status = + ZB_ZCL_DEVICE_CMD_PARAM_OUT_GET(bufid, zb_uint8_t); + const zb_zcl_parsed_hdr_t *in_cmd_info = ZB_ZCL_DEVICE_CMD_PARAM_CMD_INFO(bufid); + + LOG_INF("ZB_ZCL_SCENES_REMOVE_SCENE_CB_ID: group_id 0x%x scene_id %hd", + remove_scene_req->group_id, + remove_scene_req->scene_id); + + *remove_scene_status = ZB_ZCL_STATUS_NOT_FOUND; + idx = scene_table_get_entry(remove_scene_req->group_id, + remove_scene_req->scene_id); + + if (idx != 0xFF && + scenes_table[idx].common.group_id != ZB_ZCL_SCENES_FREE_SCENE_TABLE_RECORD) { + /* Remove this entry */ + memset(&scenes_table[idx], 0, sizeof(scenes_table[idx])); + scenes_table[idx].common.group_id = ZB_ZCL_SCENES_FREE_SCENE_TABLE_RECORD; + LOG_INF("removing scene: entry idx %hd", idx); + *remove_scene_status = ZB_ZCL_STATUS_SUCCESS; + scenes_table_save(); + } else if (!zb_aps_is_endpoint_in_group( + remove_scene_req->group_id, + ZB_ZCL_PARSED_HDR_SHORT_DATA(in_cmd_info).dst_endpoint)) { + *remove_scene_status = ZB_ZCL_STATUS_INVALID_FIELD; + } + } + break; + + case ZB_ZCL_SCENES_REMOVE_ALL_SCENES_CB_ID: { + const zb_zcl_scenes_remove_all_scenes_req_t *remove_all_scenes_req = + ZB_ZCL_DEVICE_CMD_PARAM_IN_GET( + bufid, zb_zcl_scenes_remove_all_scenes_req_t); + zb_uint8_t *remove_all_scenes_status = + ZB_ZCL_DEVICE_CMD_PARAM_OUT_GET(bufid, zb_uint8_t); + const zb_zcl_parsed_hdr_t *in_cmd_info = ZB_ZCL_DEVICE_CMD_PARAM_CMD_INFO(bufid); + + LOG_INF("ZB_ZCL_SCENES_REMOVE_ALL_SCENES_CB_ID: group_id 0x%x", + remove_all_scenes_req->group_id); + + if (!zb_aps_is_endpoint_in_group( + remove_all_scenes_req->group_id, + ZB_ZCL_PARSED_HDR_SHORT_DATA(in_cmd_info).dst_endpoint)) { + *remove_all_scenes_status = ZB_ZCL_STATUS_INVALID_FIELD; + } else { + scene_table_remove_entries_by_group(remove_all_scenes_req->group_id); + *remove_all_scenes_status = ZB_ZCL_STATUS_SUCCESS; + scenes_table_save(); + } + } + break; + + case ZB_ZCL_SCENES_STORE_SCENE_CB_ID: { + const zb_zcl_scenes_store_scene_req_t *store_scene_req = + ZB_ZCL_DEVICE_CMD_PARAM_IN_GET(bufid, zb_zcl_scenes_store_scene_req_t); + zb_uint8_t idx = 0xFF; + zb_uint8_t *store_scene_status = + ZB_ZCL_DEVICE_CMD_PARAM_OUT_GET(bufid, zb_uint8_t); + const zb_zcl_parsed_hdr_t *in_cmd_info = ZB_ZCL_DEVICE_CMD_PARAM_CMD_INFO(bufid); + + LOG_INF("ZB_ZCL_SCENES_STORE_SCENE_CB_ID: group_id 0x%x scene_id %hd", + store_scene_req->group_id, + store_scene_req->scene_id); + + if (!zb_aps_is_endpoint_in_group( + store_scene_req->group_id, + ZB_ZCL_PARSED_HDR_SHORT_DATA(in_cmd_info).dst_endpoint)) { + *store_scene_status = ZB_ZCL_STATUS_INVALID_FIELD; + } else { + idx = scene_table_get_entry(store_scene_req->group_id, + store_scene_req->scene_id); + + if (idx != 0xFF) { + if (scenes_table[idx].common.group_id != + ZB_ZCL_SCENES_FREE_SCENE_TABLE_RECORD) { + /* Update existing entry with current On/Off state */ + device_cb_param->status = RET_ALREADY_EXISTS; + LOG_INF("update existing scene: entry idx %hd", idx); + } else { + /* Create new entry with empty name + * and 0 transition time + */ + scenes_table[idx].common.group_id = + store_scene_req->group_id; + scenes_table[idx].common.scene_id = + store_scene_req->scene_id; + scenes_table[idx].common.transition_time = 0; + LOG_INF("create new scene: entry idx %hd", idx); + } + save_state_as_scene(&scenes_table[idx]); + *store_scene_status = ZB_ZCL_STATUS_SUCCESS; + scenes_table_save(); + } else { + *store_scene_status = ZB_ZCL_STATUS_INSUFF_SPACE; + } + } + } + break; + + case ZB_ZCL_SCENES_RECALL_SCENE_CB_ID: { + const zb_zcl_scenes_recall_scene_req_t *recall_scene_req = + ZB_ZCL_DEVICE_CMD_PARAM_IN_GET(bufid, zb_zcl_scenes_recall_scene_req_t); + zb_uint8_t idx = 0xFF; + zb_uint8_t *recall_scene_status = + ZB_ZCL_DEVICE_CMD_PARAM_OUT_GET(bufid, zb_uint8_t); + + LOG_INF("ZB_ZCL_SCENES_RECALL_SCENE_CB_ID: group_id 0x%x scene_id %hd", + recall_scene_req->group_id, + recall_scene_req->scene_id); + + idx = scene_table_get_entry(recall_scene_req->group_id, + recall_scene_req->scene_id); + + if (idx != 0xFF && + scenes_table[idx].common.group_id != ZB_ZCL_SCENES_FREE_SCENE_TABLE_RECORD) { + /* Recall this entry */ + recall_scene(&scenes_table[idx]); + *recall_scene_status = ZB_ZCL_STATUS_SUCCESS; + } else { + *recall_scene_status = ZB_ZCL_STATUS_NOT_FOUND; + } + } + break; + + case ZB_ZCL_SCENES_GET_SCENE_MEMBERSHIP_CB_ID: { + const zb_zcl_scenes_get_scene_membership_req_t *get_scene_membership_req = + ZB_ZCL_DEVICE_CMD_PARAM_IN_GET(bufid, + zb_zcl_scenes_get_scene_membership_req_t); + const zb_zcl_parsed_hdr_t *in_cmd_info = ZB_ZCL_DEVICE_CMD_PARAM_CMD_INFO(bufid); + + LOG_INF("ZB_ZCL_SCENES_GET_SCENE_MEMBERSHIP_CB_ID: group_id 0x%x", + get_scene_membership_req->group_id); + + /* Send View Scene Response */ + ZB_MEMCPY(&resp_info.cmd_info, in_cmd_info, sizeof(zb_zcl_parsed_hdr_t)); + ZB_MEMCPY(&resp_info.get_scene_membership_req, + get_scene_membership_req, + sizeof(zb_zcl_scenes_get_scene_membership_req_t)); + zb_buf_get_out_delayed(send_get_scene_membership_resp); + } + break; + + case ZB_ZCL_SCENES_INTERNAL_REMOVE_ALL_SCENES_ALL_ENDPOINTS_CB_ID: { + const zb_zcl_scenes_remove_all_scenes_req_t *remove_all_scenes_req = + ZB_ZCL_DEVICE_CMD_PARAM_IN_GET(bufid, + zb_zcl_scenes_remove_all_scenes_req_t); + + LOG_INF("ZB_ZCL_SCENES_INTERNAL_REMOVE_ALL_SCENES_ALL_ENDPOINTS_CB_ID: " + "group_id 0x%x", remove_all_scenes_req->group_id); + + /* Have only one endpoint */ + scene_table_remove_entries_by_group(remove_all_scenes_req->group_id); + scenes_table_save(); + } + break; + + case ZB_ZCL_SCENES_INTERNAL_REMOVE_ALL_SCENES_ALL_ENDPOINTS_ALL_GROUPS_CB_ID: { + scene_table_init(); + scenes_table_save(); + } + break; + + default: + /* Return false if the packet was not processed. */ + return ZB_FALSE; + } + + LOG_DBG("<< %s %hd", __func__, device_cb_param->status); + return ZB_TRUE; +} diff --git a/subsys/lib/zigbee_shell/CMakeLists.txt b/subsys/lib/zigbee_shell/CMakeLists.txt new file mode 100644 index 00000000..ec598725 --- /dev/null +++ b/subsys/lib/zigbee_shell/CMakeLists.txt @@ -0,0 +1,28 @@ +# +# Copyright (c) 2022 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +zephyr_library() +zephyr_include_directories(include) +zephyr_library_sources( + src/zigbee_shell.c + src/zigbee_shell_utils.c + src/zigbee_shell_cmd.c + src/zigbee_shell_cmd_attr.c + src/zigbee_shell_cmd_zcl.c + src/zigbee_shell_cmd_attr_report.c + src/zigbee_shell_cmd_ping.c + src/zigbee_shell_cmd_bdb.c + src/zigbee_shell_cmd_generic_cmd.c + src/zigbee_shell_cmd_zdo.c + src/zigbee_shell_cmd_zscheduler.c + src/zigbee_shell_cmd_nvram.c + src/zigbee_shell_cmd_zcl_groups.c + src/zigbee_shell_ctx_mgr.c + src/zigbee_shell_cmd_nbr.c +) + +zephyr_library_link_libraries(zboss) +zephyr_library_link_libraries(zigbee) diff --git a/subsys/lib/zigbee_shell/Kconfig b/subsys/lib/zigbee_shell/Kconfig new file mode 100644 index 00000000..801c739c --- /dev/null +++ b/subsys/lib/zigbee_shell/Kconfig @@ -0,0 +1,61 @@ +# +# Copyright (c) 2022 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menuconfig ZIGBEE_SHELL + bool "Enable Zigbee Shell" + depends on ZIGBEE_ADD_ON + imply SHELL + +if ZIGBEE_SHELL + +config ZIGBEE_SHELL_ENDPOINT + int "Number of endpoint to be used by the Zigbee Shell instance" + range 1 240 + help + This option sets the endpoint which Zigbee shell uses for sending commands. + Zigbee shell library can utilize only already existing endpoint. + + Note: If chosen endpoint is not registered, some functionalities will not work correctly. + +config ZIGBEE_SHELL_CTX_MGR_ENTRIES_NBR + int "Number of entries in context manager" + default 24 + range 3 254 + help + Number of entries in context manager of Zigbee Shell. + Entries are shared by ZDO commands, ZCL commands and PING commands. + +config ZIGBEE_SHELL_DEBUG_CMD + bool "Enable debug shell commands" + select ZIGBEE_DEBUG_FUNCTIONS + imply ZIGBEE_LOGGER_EP + help + This option enables: + + - Logging incoming ZCL frames. + - Command to send custom ZCL frames. + - Command to suspend/resume Zigbee Stack execution. + + Note: These commands may be useful when debugging but can cause instability of the device. + +config ZIGBEE_SHELL_ZCL_CMD_TIMEOUT + int "Amount of time in seconds after which the ZCL cmd commands time out if no response is received" + default 10 + range 5 60 + +config ZIGBEE_SHELL_MONITOR_CACHE_SIZE + int "Size of the Zigbee active neighbor monitor cache" + default 10 + range 3 254 + help + The maximum number of neighbor table entries, logged by the Zigbee active neighbor monitor. + +# Configure ZIGBEE_SHELL_LOG_LEVEL +module = ZIGBEE_SHELL +module-str = Zigbee Shell +source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" + +endif #ZIGBEE_SHELL diff --git a/subsys/lib/zigbee_shell/include/zigbee_shell_cmd_zcl.h b/subsys/lib/zigbee_shell/include/zigbee_shell_cmd_zcl.h new file mode 100644 index 00000000..ed4f9841 --- /dev/null +++ b/subsys/lib/zigbee_shell/include/zigbee_shell_cmd_zcl.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef ZIGBEE_SHELL_CMD_ZCL_H__ +#define ZIGBEE_SHELL_CMD_ZCL_H__ + +#include + + +int cmd_zb_ping(const struct shell *shell, size_t argc, char **argv); +int cmd_zb_readattr(const struct shell *shell, size_t argc, char **argv); +int cmd_zb_writeattr(const struct shell *shell, size_t argc, char **argv); +int cmd_zb_subscribe_on(const struct shell *shell, size_t argc, char **argv); +int cmd_zb_subscribe_off(const struct shell *shell, size_t argc, char **argv); +int cmd_zb_generic_cmd(const struct shell *shell, size_t argc, char **argv); +int cmd_zb_zcl_raw(const struct shell *shell, size_t argc, char **argv); +int cmd_zb_get_group_mem(const struct shell *shell, size_t argc, char **argv); +int cmd_zb_add_remove_group(const struct shell *shell, size_t argc, char **argv); +int cmd_zb_remove_all_groups(const struct shell *shell, size_t argc, char **argv); +int cmd_zb_add_group_if_identifying(const struct shell *shell, size_t argc, char **argv); + +#endif /* ZIGBEE_SHELL_CMD_ZCL_H__ */ diff --git a/subsys/lib/zigbee_shell/include/zigbee_shell_ctx_mgr.h b/subsys/lib/zigbee_shell/include/zigbee_shell_ctx_mgr.h new file mode 100644 index 00000000..e15a5ad4 --- /dev/null +++ b/subsys/lib/zigbee_shell/include/zigbee_shell_ctx_mgr.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef ZIGBEE_SHELL_CTX_MGR_H__ +#define ZIGBEE_SHELL_CTX_MGR_H__ + +#include + +#include "zigbee_shell_zdo_types.h" +#include "zigbee_shell_zcl_types.h" + +#define CTX_MGR_ENTRY_IVALID_INDEX 0xFF + + +/* Type of a frame a context manager entry is associated with. */ +enum ctx_entry_type { + CTX_MGR_INVALID_ENTRY_TYPE, + CTX_MGR_ZDO_ENTRY_TYPE, + CTX_MGR_PING_REQ_ENTRY_TYPE, + CTX_MGR_PING_REPLY_ENTRY_TYPE, + CTX_MGR_ATTR_REQ_ENTRY_TYPE, + CTX_MGR_CFG_REPORT_REQ_ENTRY_TYPE, + CTX_MGR_GENERIC_CMD_ENTRY_TYPE, + CTX_MGR_GROUPS_CMD_ENTRY_TYPE +}; + +/* A context manager entry structure associated with a given frame, + * used to store a frame related data and a shell to be used for logging a data. + * Frame is identified by the type and its unique identifier number: + * - ZDO frames are using the transaction number (TSN) as identifier number. + * - ZCL frames are identified using the ZCL Sequence number. + * - PING frames are using the custom counter, implemented along + * the PING commands. + */ +struct ctx_entry { + bool taken; + /* Unique identifier of a frame. Type of used identifier depends on + * the type of frame the entry is associated with. + */ + uint8_t id; + enum ctx_entry_type type; + /* Pointer to the shell to be used for printing logs. */ + const struct shell *shell; + /* Union of structures used to store a frame related data, + * shared between the different frame types. + */ + union { + struct zdo_data zdo_data; + struct zcl_data zcl_data; + }; +}; + +/**@brief Delete context manager entry to mark it free and prepare to be + * reused later. + * + * @param ctx_entry Pointer to the context manager entry to be deleted. + */ +void ctx_mgr_delete_entry(struct ctx_entry *ctx_entry); + +/**@brief Get a pointer to a unused context manager entry. + * + * @param type Type of a data to be stored in an entry. + * + * @return Pointer to the context manager entry if an unused entry was found, + * NULL otherwise. + */ +struct ctx_entry *ctx_mgr_new_entry(enum ctx_entry_type type); + +/**@brief Return a pointer to the context manager entry which is associated with + * the given frame type and the given identifier. + * + * @param id Unique identifier of the frame the context manager + * entry is associated with. Type of the identifier used + * in the entry depends on the entry type. + * + * @param type Type of the frame the entry is associated with. + * + * @return Pointer to the context manager entry if the entry was found, + * NULL otherwise. + */ +struct ctx_entry *ctx_mgr_find_ctx_entry(uint8_t id, enum ctx_entry_type type); + +/**@brief Return an index of the given context manager entry. + * + * @param ctx_entry Pointer to the context manager entry. + * + * @return Index of given context manager entry. + */ +uint8_t ctx_mgr_get_index_by_entry(struct ctx_entry *ctx_entry); + +/**@brief Return a pointer to the context manager entry by given index. + * + * @param index Index of the context manager entry. + * + * @return Pointer to the context manager entry, NULL if not found. + */ +struct ctx_entry *ctx_mgr_get_entry_by_index(uint8_t index); + +/**@brief Return a pointer to the context manager entry of the given type + * and associated with the given buffer ID. + * + * @param bufid Buffer ID associated with the context manager entry. + * + * @param type Type of a data to be stored in an entry. + * + * @return Pointer to the context manager entry if the entry was found, NULL otherwise. + */ +struct ctx_entry *ctx_mgr_find_zcl_entry_by_bufid(zb_bufid_t bufid, enum ctx_entry_type type); + +#endif /* ZIGBEE_SHELL_CTX_MGR_H__ */ diff --git a/subsys/lib/zigbee_shell/include/zigbee_shell_ping_types.h b/subsys/lib/zigbee_shell/include/zigbee_shell_ping_types.h new file mode 100644 index 00000000..a1cd31aa --- /dev/null +++ b/subsys/lib/zigbee_shell/include/zigbee_shell_ping_types.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef ZIGBEE_SHELL_PING_TYPES_H__ +#define ZIGBEE_SHELL_PING_TYPES_H__ + +#include +#include + +#define PING_CUSTOM_CLUSTER 0xBEEF +#define PING_MAX_LENGTH 79 +#define PING_ECHO_REQUEST 0x00 +#define PING_ECHO_REPLY 0x01 +#define PING_ECHO_NO_ACK_REQUEST 0x02 +#define PING_NO_ECHO_REQUEST 0x03 +#define PING_ECHO_REQUEST_BYTE 0xAB +#define PING_ECHO_REPLY_BYTE 0xCD + + +/**@brief Ping event type. Informs what kind of acknowledgment was received. + */ +enum ping_time_evt { + /* APS acknowledgment of PING request received. */ + PING_EVT_ACK_RECEIVED, + /* PING reply received. */ + PING_EVT_ECHO_RECEIVED, + /* PING reply or APS acknowledgment was not received + * within timeout time. + */ + PING_EVT_FRAME_TIMEOUT, + /* PING request was successfully scheduled for sending by the stack. */ + PING_EVT_FRAME_SCHEDULED, + /* PING request was successfully sent. This event occurs only + * if both APS ACK was not requested. + */ + PING_EVT_FRAME_SENT, + /* PING received was received. This event is passed only + * to the callback registered via @p zb_ping_set_ping_indication_cb. + */ + PING_EVT_REQUEST_RECEIVED, + /* An error occurred during sending PING request. */ + PING_EVT_ERROR, +}; + +/**@brief Ping event callback definition. + * + * @param[in] evt Type of received ping acknowledgment + * @param[in] delay_ms Time, in milliseconds, between ping request + * and the event. + * @param[in] entry Pointer to context manager entry in which the ping + * request data is stored. + */ +typedef void (*ping_time_cb_t)(enum ping_time_evt evt, zb_uint32_t delay_ms, + struct ctx_entry *entry); + +/* Structure used to store PING request data in the context manager entry. */ +struct ping_req { + zb_uint8_t ping_seq; + zb_uint8_t request_ack; + zb_uint8_t request_echo; + zb_uint16_t count; + zb_uint16_t timeout_ms; + ping_time_cb_t cb; + volatile int64_t sent_time; +}; + +/* Structure used to store PING reply data in the context manager entry. */ +struct ping_reply { + zb_uint8_t ping_seq; + zb_uint8_t count; + zb_uint8_t send_ack; +}; + +/**@brief Set ping request indication callback. + * + * @note The @p cb argument delay_ms will reflect current time + * in milliseconds. + */ +void zb_ping_set_ping_indication_cb(ping_time_cb_t cb); + +#endif /* ZIGBEE_SHELL_PING_TYPES_H__ */ diff --git a/subsys/lib/zigbee_shell/include/zigbee_shell_utils.h b/subsys/lib/zigbee_shell/include/zigbee_shell_utils.h new file mode 100644 index 00000000..7559c682 --- /dev/null +++ b/subsys/lib/zigbee_shell/include/zigbee_shell_utils.h @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef ZIGBEE_SHELL_UTILS_H__ +#define ZIGBEE_SHELL_UTILS_H__ + +#include +#include +#include +#include +#include + +#include +#include +#include "zigbee_shell_ctx_mgr.h" + + +/**@brief Finish the command by dumping 'Done'. + * + * @param prepend_newline Whether to prepend a newline. + */ +static inline void zb_shell_print_done(const struct shell *shell, bool prepend_newline) +{ + shell_fprintf(shell, SHELL_NORMAL, + (prepend_newline ? "\nDone\n" : "Done\n")); +} + +/**@brief Print error message to the console. + * + * @param message Pointer to the message which should be printed + * as an error. + * @param prepend_newline Whether to prepend a newline. + */ +static inline void zb_shell_print_error(const struct shell *shell, const char *message, + bool prepend_newline) +{ + if (message) { + shell_error(shell, prepend_newline ? + "\nError: %s" : "Error: %s", message); + } else { + shell_error(shell, prepend_newline ? + "\nError: Unknown error occurred" : + "Error: Unknown error occurred"); + } +} + +/**@brief Print a list of items. + * + * Individual items in the list are delimited by comma. + * + * @param text_buffer A pointer to text buffer. + * @param hdr The list header string. + * @param fmt A printf like format of an individual list item. + * @param type Type of the list item. + * @param ptr A pointer to the first item in the list. + * @param size The list size (in items). + */ +#define PRINT_LIST(text_buffer, hdr, fmt, type, ptr, size) \ +{ \ + sprintf((text_buffer + strlen(text_buffer)), hdr); \ + for (type * item = (ptr); item < (ptr) + size - 1; item++) { \ + type local_item = UNALIGNED_GET(item); \ + sprintf((text_buffer + \ + strlen(text_buffer)), fmt ",", local_item); \ + } \ + if (size > 0) { \ + sprintf((text_buffer + \ + strlen(text_buffer)), fmt " ", *((ptr) + size - 1)); \ + } \ +} + +/**@brief Convert ZCL attribute value to string. + * + * @param[out] str_buf Pointer to a string buffer which will be filled. + * @param[in] buf_len String buffer length. + * @param[in] attr_type ZCL attribute type value. + * @param[in] attr Pointer to ZCL attribute value. + * + * @return Number of bytes written into string buffer or negative value + * on error. + */ +int zb_shell_zcl_attr_to_str(char *str_buf, uint16_t buf_len, zb_uint16_t attr_type, + zb_uint8_t *attr); + +/**@brief Check if the ZCL frame we received is the response to our command in the table entry. + * + * @param zcl_hdr Pointer to the parsed header of the frame. + * @param entry Pointer to the entry in the context manager to check against. + * + * @return ZB_TRUE if is response to command, ZB_FALSE otherwise. + */ +zb_bool_t zb_shell_is_zcl_cmd_response(zb_zcl_parsed_hdr_t *zcl_hdr, struct ctx_entry *entry); + +/**@brief Function to invalidate an entry with ZCL cmd request data, to be called after the timeout. + * This function is called as the ZBOSS callback. + * + * @param index Index of context entry to invalidate. + */ +void zb_shell_zcl_cmd_timeout_cb(zb_uint8_t index); + +/**@brief Parse uint8_t from input string. + * + * The reason for this explicit function is because newlib-nano sscanf() + * does not support 1-byte target. + * + * @param[in] bp Pointer to input string. + * @param[out] value Pointer to output value. + * + * @return 1 on success, 0 otherwise. + */ +int zb_shell_sscan_uint8(const char *bp, uint8_t *value); + +/**@brief Parse unsigned integers from input string. + * + * The reason for this explicit function is because of lack + * of sscanf() function. This function is to be used to parse number + * up to (UINT32_MAX). + * + * @param[in] bp Pointer to input string. + * @param[out] value Pointer to variable to store result of the function. + * @param[in] size Size, in bytes, that determines expected maximum value + * of converted number. + * @param[in] base Numerical base (radix) that determines the valid + * characters and their interpretation. + * + * @return 1 on success, 0 otherwise. + */ +int zb_shell_sscan_uint(const char *bp, uint8_t *value, uint8_t size, uint8_t base); + +/**@brief Print buffer as hex string. + * + * @param shell Pointer to the shell instance. + * @param data Pointer to data to be printed out. + * @param size Data size in bytes + * @param reverse If True then data is printed out in reverse order. + */ +void zb_shell_print_hexdump(const struct shell *shell, const uint8_t *data, uint8_t size, + bool reverse); + +/**@brief Print 64bit value (address, extpan) as hex string. + * + * The value is expected to be low endian. + * + * @param shell Pointer to the shell instance. + * @param addr 64 data to be printed out. + */ +static inline void zb_shell_print_eui64(const struct shell *shell, const zb_64bit_addr_t addr) +{ + zb_shell_print_hexdump(shell, (const uint8_t *)addr, sizeof(zb_64bit_addr_t), true); +} + +#endif /* ZIGBEE_SHELL_UTILS_H__ */ diff --git a/subsys/lib/zigbee_shell/include/zigbee_shell_zcl_types.h b/subsys/lib/zigbee_shell/include/zigbee_shell_zcl_types.h new file mode 100644 index 00000000..2d717261 --- /dev/null +++ b/subsys/lib/zigbee_shell/include/zigbee_shell_zcl_types.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef ZIGBEE_SHELL_ZCL_TYPES_H__ +#define ZIGBEE_SHELL_ZCL_TYPES_H__ + +#include +#include "zigbee_shell_ping_types.h" + +/* Payload size in bytes, payload read from string is twice the size. */ +#define CMD_PAYLOAD_SIZE 25 + + +/* Structure used to pass information required to send ZCL frame. */ +struct zcl_packet_info { + zb_bufid_t buffer; + zb_uint8_t *ptr; + zb_addr_u dst_addr; + zb_uint8_t dst_addr_mode; + zb_uint8_t dst_ep; + zb_uint8_t ep; + zb_uint16_t prof_id; + zb_uint16_t cluster_id; + zb_callback_t cb; + zb_bool_t disable_aps_ack; +}; + +/* Enum used to determine which command Write or Read Attribute to send. */ +enum attr_req_type { + ATTR_READ_REQUEST, + ATTR_WRITE_REQUEST +}; + +/* Structure used to store Read/Write Attributes request data in the context manager entry. */ +struct read_write_attr_req { + zb_zcl_frame_direction_t direction; + zb_uint8_t attr_type; + zb_uint16_t attr_id; + zb_uint8_t attr_value[32]; + enum attr_req_type req_type; +}; + +/* Structure used to store Configure Reporting request data in the context manager entry. */ +struct configure_reporting_req { + zb_uint8_t attr_type; + zb_uint16_t attr_id; + zb_uint16_t interval_min; + zb_uint16_t interval_max; +}; + +/* Structure used to store generic ZCL command data in the context manager entry. */ +struct generic_cmd { + zb_zcl_disable_default_response_t def_resp; + zb_uint8_t payload_length; + zb_uint16_t cmd_id; + zb_uint8_t payload[CMD_PAYLOAD_SIZE]; +}; + +/* Structure used to store Groups commands data in the context manager entry. */ +struct groups_cmd { + zb_uint8_t group_list_cnt; + zb_uint16_t group_id; + zb_callback_t send_fn; + zb_uint16_t group_list[7]; +}; + +/* Structure used to store ZCL data in the context manager entry. */ +struct zcl_data { + /* Common ZCL packet information. */ + struct zcl_packet_info pkt_info; + /* Union of structures used to store a ZCL commands related data, + * shared between the different commands. + */ + union { + struct ping_req ping_req; + struct ping_reply ping_reply; + struct read_write_attr_req read_write_attr_req; + struct configure_reporting_req configure_reporting_req; + struct generic_cmd generic_cmd; + struct groups_cmd groups_cmd; + }; +}; + +#endif /* ZIGBEE_SHELL_ZCL_TYPES_H__ */ diff --git a/subsys/lib/zigbee_shell/include/zigbee_shell_zdo_types.h b/subsys/lib/zigbee_shell/include/zigbee_shell_zdo_types.h new file mode 100644 index 00000000..79867773 --- /dev/null +++ b/subsys/lib/zigbee_shell/include/zigbee_shell_zdo_types.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef ZIGBEE_SHELL_ZDO_TYPES_H__ +#define ZIGBEE_SHELL_ZDO_TYPES_H__ + +#include + + +/* Forward declaration of context manager entry structure. */ +struct ctx_entry; + +/* Additional context for commands which request tables. */ +struct zdo_req_seq { + /* Starting index for the requested elements. */ + zb_uint8_t start_index; + /* Destination address. */ + zb_uint16_t dst_addr; +}; + +/* Structure to store an information required for sending zdo requests. */ +struct zdo_req_info { + zb_bufid_t buffer_id; + zb_uint8_t ctx_timeout; + zb_uint8_t (*req_fn)(zb_uint8_t param, zb_callback_t cb_fn); + void (*timeout_cb_fn)(zb_bufid_t bufid); + void (*req_cb_fn)(zb_bufid_t bufid); +}; + +/* Structure used to store ZDO data in the context manager entry. */ +struct zdo_data { + bool is_broadcast; + bool (*app_cb_fn)(struct ctx_entry *ctx_entry, uint8_t param); + struct zdo_req_seq req_seq; + struct zdo_req_info zdo_req; +}; + +#endif /* ZIGBEE_SHELL_ZDO_TYPES_H__ */ diff --git a/subsys/lib/zigbee_shell/src/zigbee_shell.c b/subsys/lib/zigbee_shell/src/zigbee_shell.c new file mode 100644 index 00000000..0e146b2a --- /dev/null +++ b/subsys/lib/zigbee_shell/src/zigbee_shell.c @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include + +#include +#include + + +/* Endpoint used by the Zigbee shell. */ +static zb_uint8_t shell_ep; + +/* Zigbee shell debug mode indicator. */ +static zb_bool_t debug_mode = ZB_FALSE; + +LOG_MODULE_REGISTER(zigbee_shell, CONFIG_ZIGBEE_SHELL_LOG_LEVEL); + +/**@brief Returns the Endpoint number used by the shell. + */ +zb_uint8_t zb_shell_get_endpoint(void) +{ + return shell_ep; +} + +/**@brief Sets the Endpoint number used by the shell. + */ +void zb_shell_set_endpoint(zb_uint8_t ep) +{ + shell_ep = ep; +} + +/**@brief Configures shell endpoint by setting number of endpoint to be used + * by the shell and for chosen endpoint registers shell endpoint handler. + */ +void zb_shell_configure_endpoint(void) +{ + zb_zcl_globals_t *zcl_ctx = zb_zcl_get_ctx(); + zb_af_endpoint_desc_t *shell_ep_desc; + + /* Verify that ZCL device ctx is present at the device as for example, + * network coordinator sample doesn't have one by default. + */ + if (zcl_ctx->device_ctx == NULL) { + LOG_ERR("Register ZCL ctx at Zigbee device to use Zigbee shell ZCL commands."); + return; + } + + /* Set endpoint number to use from KConfig. */ +#ifdef CONFIG_ZIGBEE_SHELL_ENDPOINT + zb_shell_set_endpoint(CONFIG_ZIGBEE_SHELL_ENDPOINT); +#endif + /* Verify that endpoint used by shell is registered. */ + shell_ep_desc = zb_af_get_endpoint_desc(zb_shell_get_endpoint()); + if (!shell_ep_desc) { + LOG_ERR("Zigbee shell endpoint: %d is not registered.", + zb_shell_get_endpoint()); + return; + } + + /* Check that no endpoint device handler is set for shell endpoint + * and set handler used by shell. + */ + if (shell_ep_desc->device_handler) { + LOG_ERR("Can not set device handler for shell endpoint - handler already set."); + } else { + ZB_AF_SET_ENDPOINT_HANDLER(zb_shell_get_endpoint(), + zb_shell_ep_handler); + } +} + +/**@brief Sets the debug mode. + */ +void zb_shell_debug_set(zb_bool_t debug) +{ + debug_mode = debug; +} + +/**@brief Gets the debug mode. + */ +zb_bool_t zb_shell_debug_get(void) +{ + return debug_mode; +} diff --git a/subsys/lib/zigbee_shell/src/zigbee_shell_cmd.c b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd.c new file mode 100644 index 00000000..04f87c40 --- /dev/null +++ b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd.c @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include "zigbee_shell_utils.h" + +#define DEBUG_HELP \ + "Return state of debug mode." + +#define DEBUG_ON_HELP \ + "Turn on debug mode." + +#define DEBUG_OFF_HELP \ + "Turn off debug mode." + +#define DEBUG_WARN_MSG \ + "You are about to turn the debug mode on. This unblocks several\n" \ + "additional commands in the Shell. They can render the device unstable.\n" \ + "It is implied that you know what you are doing." + + +/**@brief Print Shell, ZBOSS and Zephyr kernel version + * + * @code + * version + * @endcode + * + * @code + * > version + * Shell: Jul 2 2020 16:14:18 + * ZBOSS: 3.1.0.59 + * Done + * @endcode + */ +static int cmd_version(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + shell_print(shell, "Shell: " __DATE__ " " __TIME__); + shell_print(shell, "ZBOSS: %d.%d.0.%d", ZBOSS_MAJOR, ZBOSS_MINOR, + ZBOSS_SDK_REVISION); + shell_print(shell, "Zephyr kernel version: %s", KERNEL_VERSION_STRING); + + zb_shell_print_done(shell, false); + return 0; +} + +/**@brief Get state of debug mode in the shell. + * + * @code + * debug + * @endcode + */ +static int cmd_debug(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + shell_print(shell, "Debug mode is %s.", + zb_shell_debug_get() ? "on" : "off"); + + zb_shell_print_done(shell, false); + return 0; +} + +/**@brief Enable debug mode in the shell. + * + * @code + * debug on + * @endcode + * + * This command unblocks several additional commands in the shell. + * They can render the device unstable. It is implied that you know + * what you are doing. + */ +static int cmd_debug_on(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + shell_warn(shell, DEBUG_WARN_MSG); + zb_shell_debug_set(ZB_TRUE); + shell_print(shell, "Debug mode is on."); + + zb_shell_print_done(shell, false); + return 0; +} + +/**@brief Disable debug mode in the shell. + * + * @code + * debug off + * @endcode + * + * This command blocks several additional commands in the shell. + * They can render the device unstable. It is implied that you know + * what you are doing. + */ +static int cmd_debug_off(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + zb_shell_debug_set(ZB_FALSE); + shell_print(shell, "Debug mode is off."); + + zb_shell_print_done(shell, false); + return 0; +} + +SHELL_CMD_REGISTER(version, NULL, "Print firmware version", cmd_version); + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_debug, + SHELL_COND_CMD(CONFIG_ZIGBEE_SHELL_DEBUG_CMD, off, NULL, + DEBUG_OFF_HELP, cmd_debug_off), + SHELL_COND_CMD(CONFIG_ZIGBEE_SHELL_DEBUG_CMD, on, NULL, + DEBUG_ON_HELP, cmd_debug_on), + SHELL_SUBCMD_SET_END); + +SHELL_COND_CMD_REGISTER(CONFIG_ZIGBEE_SHELL_DEBUG_CMD, debug, &sub_debug, + DEBUG_HELP, cmd_debug); diff --git a/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_attr.c b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_attr.c new file mode 100644 index 00000000..37f49c27 --- /dev/null +++ b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_attr.c @@ -0,0 +1,491 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include + +#include +#include +#include +#include "zigbee_shell_utils.h" + + +LOG_MODULE_DECLARE(zigbee_shell, CONFIG_ZIGBEE_SHELL_LOG_LEVEL); + +/**@brief Print the Read Attribute Response. + * + * @param bufid Zigbee buffer ID with Read Attribute Response packet. + * @param entry Pointer to the entry in the context manager. + */ +static void print_read_attr_response(zb_bufid_t bufid, struct ctx_entry *entry) +{ + zb_zcl_read_attr_res_t *attr_resp; + + /* Get the contents of Read Attribute Response frame. */ + ZB_ZCL_GENERAL_GET_NEXT_READ_ATTR_RES(bufid, attr_resp); + if (attr_resp->status == ZB_ZCL_STATUS_SUCCESS) { + char attr_buf[40]; + int bytes_written = + zb_shell_zcl_attr_to_str( + attr_buf, + sizeof(attr_buf), + attr_resp->attr_type, + attr_resp->attr_value); + + if (bytes_written < 0) { + zb_shell_print_error( + entry->shell, + "Unable to print attribute value", + ZB_TRUE); + } else { + shell_print(entry->shell, + "ID: %d Type: %x Value: %s", + attr_resp->attr_id, + attr_resp->attr_type, + attr_buf); + zb_shell_print_done(entry->shell, ZB_FALSE); + } + } else { + shell_print(entry->shell, "Error: Status %d", attr_resp->status); + } +} + +/**@brief Print the Write Attribute Response. + * + * @param bufid Zigbee buffer ID with Write Attribute Response packet. + * @param entry Pointer to the entyr in the context manager. + */ +static void print_write_attr_response(zb_bufid_t bufid, struct ctx_entry *entry) +{ + zb_zcl_write_attr_res_t *attr_resp; + + /* Get the contents of Write Attribute Response frame. */ + ZB_ZCL_GET_NEXT_WRITE_ATTR_RES(bufid, attr_resp); + + if (!attr_resp) { + zb_shell_print_error(entry->shell, "No attribute could be retrieved", ZB_TRUE); + return; + } + + if (attr_resp->status != ZB_ZCL_STATUS_SUCCESS) { + shell_print(entry->shell, "Error: Status %d", attr_resp->status); + return; + } + + zb_shell_print_done(entry->shell, ZB_FALSE); +} + +/**@brief The Handler to 'intercept' every frame coming to the endpoint. + * + * @param bufid ZBOSS buffer id. + */ +zb_uint8_t zb_shell_ep_handler_attr(zb_bufid_t bufid) +{ + zb_ret_t zb_err_code; + struct ctx_entry *entry; + zb_zcl_parsed_hdr_t *zcl_hdr; + zb_zcl_default_resp_payload_t *def_resp; + + zcl_hdr = ZB_BUF_GET_PARAM(bufid, zb_zcl_parsed_hdr_t); + + /* Get the entry in the context manager according by the seq number. */ + entry = ctx_mgr_find_ctx_entry(zcl_hdr->seq_number, CTX_MGR_ATTR_REQ_ENTRY_TYPE); + + if (entry == NULL) { + return ZB_FALSE; + } + + if (!zb_shell_is_zcl_cmd_response(zcl_hdr, entry)) { + return ZB_FALSE; + } + + switch (zcl_hdr->cmd_id) { + case ZB_ZCL_CMD_DEFAULT_RESP: + def_resp = ZB_ZCL_READ_DEFAULT_RESP(bufid); + shell_error(entry->shell, "Error: Default Response received; "); + shell_error(entry->shell, + "Command: %d, Status: %d ", + def_resp->command_id, + def_resp->status); + break; + case ZB_ZCL_CMD_READ_ATTRIB_RESP: + print_read_attr_response(bufid, entry); + break; + case ZB_ZCL_CMD_WRITE_ATTRIB_RESP: + print_write_attr_response(bufid, entry); + break; + default: + /* Unknown response. */ + return ZB_FALSE; + } + + /* Cancel the ongoing alarm which was to delete entry with frame data ... */ + zb_err_code = ZB_SCHEDULE_APP_ALARM_CANCEL(zb_shell_zcl_cmd_timeout_cb, + ctx_mgr_get_index_by_entry(entry)); + ZB_ERROR_CHECK(zb_err_code); + + /* ... and delete it manually. */ + ctx_mgr_delete_entry(entry); + + zb_buf_free(bufid); + + return ZB_TRUE; +} + +/**@brief Function to construct Read/Write attribute command in the buffer. + * Function to be called in ZBOSS thread context to prevent non thread-safe operations. + * + * @param entry Pointer to the entry with frame to send in the context manager. + * + */ +static void construct_read_write_attr_frame(struct ctx_entry *entry) +{ + struct zcl_packet_info *packet_info = &(entry->zcl_data.pkt_info); + struct read_write_attr_req *req_data = &(entry->zcl_data.read_write_attr_req); + + /* Get the ZCL packet sequence number. */ + entry->id = ZCL_CTX().seq_number; + + /* Fill the buffer with data depending on the request type. */ + if (req_data->req_type == ATTR_READ_REQUEST) { + ZB_ZCL_GENERAL_INIT_READ_ATTR_REQ_A(packet_info->buffer, + packet_info->ptr, + req_data->direction, + ZB_ZCL_ENABLE_DEFAULT_RESPONSE); + ZB_ZCL_GENERAL_ADD_ID_READ_ATTR_REQ(packet_info->ptr, + req_data->attr_id); + } else { + ZB_ZCL_GENERAL_INIT_WRITE_ATTR_REQ_A(packet_info->buffer, + packet_info->ptr, + req_data->direction, + ZB_ZCL_ENABLE_DEFAULT_RESPONSE); + ZB_ZCL_GENERAL_ADD_VALUE_WRITE_ATTR_REQ(packet_info->ptr, + req_data->attr_id, + req_data->attr_type, + req_data->attr_value); + } +} + +/**@brief Actually send the Read or Write Attribute frame. + * + * @param index Index of the entry with frame to send in the context manager. + */ +static void zb_zcl_send_read_write_attr_frame(zb_uint8_t index) +{ + zb_ret_t zb_err_code; + struct zcl_packet_info *packet_info; + struct ctx_entry *entry = ctx_mgr_get_entry_by_index(index); + + if (entry == NULL) { + LOG_ERR("Couldn't send ping frame - context entry %d not found", index); + return; + } + + construct_read_write_attr_frame(entry); + + packet_info = &(entry->zcl_data.pkt_info); + + /* Send the actual frame. */ + zb_err_code = zb_zcl_finish_and_send_packet_new( + packet_info->buffer, + packet_info->ptr, + &(packet_info->dst_addr), + packet_info->dst_addr_mode, + packet_info->dst_ep, + packet_info->ep, + packet_info->prof_id, + packet_info->cluster_id, + packet_info->cb, + 0, + packet_info->disable_aps_ack, + 0); + + if (zb_err_code != RET_OK) { + zb_shell_print_error(entry->shell, "Can not send ZCL frame", ZB_FALSE); + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(packet_info->buffer); + zb_osif_enable_all_inter(); + + /* Invalidate an entry with frame data in the context manager. */ + ctx_mgr_delete_entry(entry); + } +} + +/**@brief Actually construct the Read or Write Attribute frame. + * + * @param entry Pointer to the entry with frame to send in the context manager. + * + * @return Error code with function execution status. + */ +static int read_write_attr_send(struct ctx_entry *entry) +{ + zb_ret_t zb_err_code; + uint8_t entry_index = ctx_mgr_get_index_by_entry(entry); + + zb_err_code = + ZB_SCHEDULE_APP_ALARM( + zb_shell_zcl_cmd_timeout_cb, + entry_index, + (CONFIG_ZIGBEE_SHELL_ZCL_CMD_TIMEOUT * ZB_TIME_ONE_SECOND)); + + if (zb_err_code != RET_OK) { + zb_shell_print_error(entry->shell, "Couldn't schedule timeout cb.", ZB_FALSE); + goto error; + } + + zb_err_code = ZB_SCHEDULE_APP_CALLBACK(zb_zcl_send_read_write_attr_frame, entry_index); + if (zb_err_code != RET_OK) { + zb_shell_print_error(entry->shell, "Can not schedule zcl frame.", ZB_FALSE); + + zb_err_code = + ZB_SCHEDULE_APP_ALARM_CANCEL(zb_shell_zcl_cmd_timeout_cb, entry_index); + ZB_ERROR_CHECK(zb_err_code); + goto error; + } + + return 0; + +error: + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(entry->zcl_data.pkt_info.buffer); + zb_osif_enable_all_inter(); + + /* Invalidate an entry with frame data in the context manager. */ + ctx_mgr_delete_entry(entry); + + return -ENOEXEC; +} + +/**@brief Retrieve the attribute value of the remote node. + * + * @code + * zcl attr read [-c] + * @endcode + * + * Read the value of the attribute `attr_id` in the cluster `cluster`. + * The cluster belongs to the profile `profile`, which resides on the endpoint + * `ep` of the remote node `dst_addr`. If the attribute is on the client role + * side of the cluster, use the `-c` switch. + */ +int cmd_zb_readattr(const struct shell *shell, size_t argc, char **argv) +{ + bool is_direction_present; + zb_bufid_t bufid; + struct zcl_packet_info *packet_info; + struct read_write_attr_req *req_data; + struct ctx_entry *entry = ctx_mgr_new_entry(CTX_MGR_ATTR_REQ_ENTRY_TYPE); + + is_direction_present = ((argc == 7) && !strcmp(argv[4], "-c")); + + if (entry == NULL) { + zb_shell_print_error( + shell, + "Request pool empty - wait for ongoing command to finish", + ZB_FALSE); + return -ENOEXEC; + } + + if (argc != 6 && !is_direction_present) { + zb_shell_print_error(shell, "Wrong number of arguments", ZB_FALSE); + goto readattr_error; + } + + req_data = &(entry->zcl_data.read_write_attr_req); + packet_info = &(entry->zcl_data.pkt_info); + + packet_info->dst_addr_mode = parse_address(*(++argv), &(packet_info->dst_addr), ADDR_ANY); + + if (packet_info->dst_addr_mode == ADDR_INVALID) { + zb_shell_print_error(shell, "Invalid address", ZB_FALSE); + goto readattr_error; + } + + if (!zb_shell_sscan_uint8(*(++argv), &(packet_info->dst_ep))) { + zb_shell_print_error(shell, "Incorrect remote endpoint", ZB_FALSE); + goto readattr_error; + } + + if (!parse_hex_u16(*(++argv), &(packet_info->cluster_id))) { + zb_shell_print_error(shell, "Invalid cluster id", ZB_FALSE); + goto readattr_error; + } + + if (is_direction_present) { + req_data->direction = ZB_ZCL_FRAME_DIRECTION_TO_CLI; + ++argv; + } else { + req_data->direction = ZB_ZCL_FRAME_DIRECTION_TO_SRV; + } + + if (!parse_hex_u16(*(++argv), &(packet_info->prof_id))) { + zb_shell_print_error(shell, "Invalid profile id", ZB_FALSE); + goto readattr_error; + } + + if (!parse_hex_u16(*(++argv), &(req_data->attr_id))) { + zb_shell_print_error(shell, "Invalid attribute id", ZB_FALSE); + goto readattr_error; + } + + req_data->req_type = ATTR_READ_REQUEST; + + /* Put the shell instance to be used later. */ + entry->shell = shell; + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + bufid = zb_buf_get_out(); + zb_osif_enable_all_inter(); + + if (!bufid) { + zb_shell_print_error( + shell, + "Failed to execute command (buf alloc failed)", + ZB_FALSE); + ctx_mgr_delete_entry(entry); + return -ENOEXEC; + } + + /* Fill the structure for sending ZCL frame. */ + packet_info->buffer = bufid; + /* DstAddr, DstAddr Mode, Destination endpoint are already set. */ + packet_info->ep = zb_shell_get_endpoint(); + packet_info->disable_aps_ack = ZB_FALSE; + + return read_write_attr_send(entry); + +readattr_error: + /* Mark data structure as free. */ + ctx_mgr_delete_entry(entry); + return -EINVAL; +} + +/**@brief Write the attribute value to the remote node. + * + * @code + * zcl attr write + * + * @endcode + * + * Write the `attr_value` value of the attribute `attr_id` of the type + * `attr_type` in the cluster `cluster`. The cluster belongs to the profile + * `profile`, which resides on the endpoint `ep` of the remote node `dst_addr`. + * + * @note The `attr_value` value must be in hexadecimal format, unless it is a + * string (`attr_type == 42`), then it must be a string. + * + */ +int cmd_zb_writeattr(const struct shell *shell, size_t argc, char **argv) +{ + zb_bufid_t bufid; + struct zcl_packet_info *packet_info; + struct read_write_attr_req *req_data; + struct ctx_entry *entry = ctx_mgr_new_entry(CTX_MGR_ATTR_REQ_ENTRY_TYPE); + + bool is_direction_present = ((argc == 9) && !strcmp(argv[4], "-c")); + + if (argc != 8 && !is_direction_present) { + zb_shell_print_error(shell, "Wrong number of arguments", ZB_FALSE); + return -EINVAL; + } + + if (entry == NULL) { + zb_shell_print_error( + shell, + "Request pool empty - wait for ongoing command to finish", + ZB_FALSE); + return -ENOEXEC; + } + + req_data = &(entry->zcl_data.read_write_attr_req); + packet_info = &(entry->zcl_data.pkt_info); + + packet_info->dst_addr_mode = parse_address(*(++argv), &(packet_info->dst_addr), ADDR_ANY); + + if (packet_info->dst_addr_mode == ADDR_INVALID) { + zb_shell_print_error(shell, "Invalid address", ZB_FALSE); + goto writeattr_error; + } + + if (!zb_shell_sscan_uint8(*(++argv), &(packet_info->dst_ep))) { + zb_shell_print_error(shell, "Incorrect remote endpoint", ZB_FALSE); + goto writeattr_error; + } + + if (!parse_hex_u16(*(++argv), &(packet_info->cluster_id))) { + zb_shell_print_error(shell, "Invalid cluster id", ZB_FALSE); + goto writeattr_error; + } + + if (is_direction_present) { + req_data->direction = ZB_ZCL_FRAME_DIRECTION_TO_CLI; + ++argv; + } else { + req_data->direction = ZB_ZCL_FRAME_DIRECTION_TO_SRV; + } + + if (!parse_hex_u16(*(++argv), &(packet_info->prof_id))) { + zb_shell_print_error(shell, "Invalid profile id", ZB_FALSE); + goto writeattr_error; + } + + if (!parse_hex_u16(*(++argv), &(req_data->attr_id))) { + zb_shell_print_error(shell, "Invalid attribute id", ZB_FALSE); + goto writeattr_error; + } + + if (!parse_hex_u8(*(++argv), &(req_data->attr_type))) { + zb_shell_print_error(shell, "Invalid attribute type", ZB_FALSE); + goto writeattr_error; + } + + uint8_t len = strlen(*(++argv)); + + if (req_data->attr_type == ZB_ZCL_ATTR_TYPE_CHAR_STRING) { + req_data->attr_value[0] = len; + strncpy((zb_char_t *)(req_data->attr_value + 1), + *argv, + sizeof(req_data->attr_value) - 1); + } else if (!parse_hex_str(*argv, len, req_data->attr_value, + sizeof(req_data->attr_value), true)) { + + zb_shell_print_error(shell, "Invalid attribute value", ZB_FALSE); + goto writeattr_error; + } + + req_data->req_type = ATTR_WRITE_REQUEST; + /* Put the shell instance to be used later. */ + entry->shell = shell; + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + bufid = zb_buf_get_out(); + zb_osif_enable_all_inter(); + + if (!bufid) { + zb_shell_print_error( + shell, + "Failed to execute command (buf alloc failed)", + ZB_FALSE); + ctx_mgr_delete_entry(entry); + return -ENOEXEC; + } + + /* Fill the structure for sending ZCL frame. */ + packet_info->buffer = bufid; + /* DstAddr, DstAddr Mode, Destination endpoint are already set. */ + packet_info->ep = zb_shell_get_endpoint(); + packet_info->disable_aps_ack = ZB_FALSE; + + return read_write_attr_send(entry); + +writeattr_error: + /* Mark data structure as free. */ + ctx_mgr_delete_entry(entry); + return -EINVAL; +} diff --git a/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_attr_report.c b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_attr_report.c new file mode 100644 index 00000000..9528abfa --- /dev/null +++ b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_attr_report.c @@ -0,0 +1,525 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include + +#include +#include +#include +#include "zigbee_shell_utils.h" + + +/* Defines default value for minimum interval inside configure reporting request. */ +#define ZIGBEE_SHELL_CONFIGURE_REPORT_DEFAULT_MIN_INTERVAL 1 + +/* Defines default value for maximum interval inside configure reporting request. */ +#define ZIGBEE_SHELL_CONFIGURE_REPORT_DEFAULT_MAX_INTERVAL 60 + +/* Defines default value for minimum value change inside configure reporting request. */ +#define ZIGBEE_SHELL_CONFIGURE_REPORT_DEFAULT_VALUE_CHANGE NULL + +/* Defines default value for minimum interval configured in order to turn off reporting. + * See ZCL specification, sec. 2.5.7.1.5. + * This can be any value, only max_interval parameters is relevant. + */ +#define ZIGBEE_SHELL_CONFIGURE_REPORT_OFF_MIN_INTERVAL 0x000F + +/* Defines default value for maximum interval inside configure reporting request. + * See ZCL specification, sec. 2.5.7.1.6. + */ +#define ZIGBEE_SHELL_CONFIGURE_REPORT_OFF_MAX_INTERVAL 0xFFFF + + +LOG_MODULE_REGISTER(zigbee_shell_report, CONFIG_ZIGBEE_SHELL_LOG_LEVEL); + +/**@brief Prints the Configure Reporting Response. + * + * @param[in] entry Pointer to the entry in the context manager. + * @param[in] bufid ZBOSS buffer id + */ +static void cmd_zb_subscribe_unsubscribe_cb(struct ctx_entry *entry, zb_bufid_t bufid) +{ + zb_ret_t zb_err_code; + zb_bool_t failed = ZB_FALSE; + zb_zcl_configure_reporting_res_t *resp = NULL; + + zb_err_code = ZB_SCHEDULE_APP_ALARM_CANCEL(zb_shell_zcl_cmd_timeout_cb, ZB_ALARM_ANY_PARAM); + ZB_ERROR_CHECK(zb_err_code); + + /* Check if response contains only status code. */ + if (sizeof(zb_zcl_configure_reporting_res_t) > zb_buf_len(bufid)) { + resp = (zb_zcl_configure_reporting_res_t *)zb_buf_begin(bufid); + + if (resp->status == ZB_ZCL_STATUS_SUCCESS) { + zb_shell_print_done(entry->shell, ZB_FALSE); + } else { + shell_error(entry->shell, + "Error: Unable to configure reporting. Status: %d", + resp->status); + } + goto delete_ctx_entry; + } + + /* Received a full Configure Reporting Response frame. */ + ZB_ZCL_GENERAL_GET_NEXT_CONFIGURE_REPORTING_RES(bufid, resp); + if (resp == NULL) { + zb_shell_print_error( + entry->shell, + "Unable to parse configure reporting response", + ZB_TRUE); + goto delete_ctx_entry; + } + + while (resp != NULL) { + if (resp->status == ZB_ZCL_STATUS_SUCCESS) { + switch (resp->direction) { + case ZB_ZCL_CONFIGURE_REPORTING_SEND_REPORT: + shell_print(entry->shell, + "Local subscription to attribute ID %hx updated", + resp->attr_id); + break; + + case ZB_ZCL_CONFIGURE_REPORTING_RECV_REPORT: + shell_print(entry->shell, + "Remote node subscription to receive attribute ID %hx updated", + resp->attr_id); + break; + + default: + shell_error(entry->shell, + "Error: Unknown reporting configuration direction for attribute %hx", + resp->attr_id); + failed = ZB_TRUE; + break; + } + } else { + shell_error( + entry->shell, + "Error: Unable to configure attribute %hx reporting. Status: %hd", + resp->attr_id, + resp->status); + failed = ZB_TRUE; + } + ZB_ZCL_GENERAL_GET_NEXT_CONFIGURE_REPORTING_RES(bufid, resp); + } + + if (failed == ZB_TRUE) { + zb_shell_print_error( + entry->shell, + "One or more attributes reporting were not configured successfully", + ZB_TRUE); + } else { + zb_shell_print_done(entry->shell, ZB_FALSE); + } + +delete_ctx_entry: + zb_buf_free(bufid); + ctx_mgr_delete_entry(entry); +} + +/**@brief Print the Report Attribute Command + * + * @param[in] zcl_hdr Pointer to parsed ZCL header + * @param[in] bufid ZBOSS buffer id + */ +static void print_attr_update(zb_zcl_parsed_hdr_t *zcl_hdr, zb_bufid_t bufid) +{ + char print_buf[255]; + int bytes_written = 0; + zb_zcl_report_attr_req_t *attr_resp = NULL; + zb_zcl_addr_t remote_node_data = zcl_hdr->addr_data.common_data.source; + + if (remote_node_data.addr_type == ZB_ZCL_ADDR_TYPE_SHORT) { + LOG_INF("Received value updates from the remote node 0x%04x", + remote_node_data.u.short_addr); + } else { + bytes_written = ieee_addr_to_str(print_buf, + sizeof(print_buf), + remote_node_data.u.ieee_addr); + if (bytes_written < 0) { + LOG_INF("Received value updates from the remote node (unknown address)"); + } else { + LOG_INF("Received value updates from the remote node 0x%s", + print_buf); + } + } + + /* Get the contents of Read Attribute Response frame. */ + ZB_ZCL_GENERAL_GET_NEXT_REPORT_ATTR_REQ(bufid, attr_resp); + while (attr_resp != NULL) { + bytes_written = 0; + bytes_written = + zb_shell_zcl_attr_to_str( + print_buf, + sizeof(print_buf), + attr_resp->attr_type, + attr_resp->attr_value); + + if (bytes_written < 0) { + LOG_ERR(" Unable to print updated attribute value"); + } else { + LOG_INF(" Profile: 0x%04x Cluster: 0x%04x Attribute: 0x%04x" + " Type: %hu Value: %s", + zcl_hdr->profile_id, + zcl_hdr->cluster_id, + attr_resp->attr_id, + attr_resp->attr_type, + print_buf); + } + + ZB_ZCL_GENERAL_GET_NEXT_REPORT_ATTR_REQ(bufid, attr_resp); + } +} + +/**@brief The Handler to 'intercept' every frame coming to the endpoint + * + * @param[in] bufid ZBOSS buffer id. + * + * @returns ZB_TRUE if ZCL command was processed. + */ +zb_uint8_t zb_shell_ep_handler_report(zb_bufid_t bufid) +{ + struct ctx_entry *entry; + zb_zcl_parsed_hdr_t *cmd_info = ZB_BUF_GET_PARAM(bufid, zb_zcl_parsed_hdr_t); + + if (cmd_info->cmd_id == ZB_ZCL_CMD_REPORT_ATTRIB) { + print_attr_update(cmd_info, bufid); + zb_buf_free(bufid); + return ZB_TRUE; + + } else if (cmd_info->cmd_id == ZB_ZCL_CMD_CONFIG_REPORT_RESP) { + /* Find command context by ZCL sequence number. */ + entry = ctx_mgr_find_ctx_entry(cmd_info->seq_number, + CTX_MGR_CFG_REPORT_REQ_ENTRY_TYPE); + + if (entry != NULL) { + cmd_zb_subscribe_unsubscribe_cb(entry, bufid); + return ZB_TRUE; + } + } + + return ZB_FALSE; +} + +/**@brief Function to construct Configure Reporting command in the buffer. + * Function to be called in ZBOSS thread context to prevent non thread-safe operations. + * + * @param entry Pointer to the entry with frame to send in the context manager. + * + */ +static void construct_reporting_frame(struct ctx_entry *entry) +{ + struct configure_reporting_req *req_data = &(entry->zcl_data.configure_reporting_req); + + /* Get the ZCL packet sequence number. */ + entry->id = ZCL_CTX().seq_number; + + /* Construct and send request. */ + ZB_ZCL_GENERAL_INIT_CONFIGURE_REPORTING_SRV_REQ( + entry->zcl_data.pkt_info.buffer, + entry->zcl_data.pkt_info.ptr, + ZB_ZCL_ENABLE_DEFAULT_RESPONSE); + ZB_ZCL_GENERAL_ADD_SEND_REPORT_CONFIGURE_REPORTING_REQ( + entry->zcl_data.pkt_info.ptr, + req_data->attr_id, + req_data->attr_type, + req_data->interval_min, + req_data->interval_max, + ZIGBEE_SHELL_CONFIGURE_REPORT_DEFAULT_VALUE_CHANGE); +} + +/**@brief Actually send the Configure Reporting frame. + * + * @param index Index of the entry with frame to send in the context manager. + */ +static void zb_zcl_send_attr_report_frame(zb_uint8_t index) +{ + zb_ret_t zb_err_code; + struct zcl_packet_info *packet_info; + struct ctx_entry *entry = ctx_mgr_get_entry_by_index(index); + + if (entry == NULL) { + LOG_ERR("Couldn't send Configure Reporting frame - context entry %d not found", + index); + return; + } + + construct_reporting_frame(entry); + + packet_info = &(entry->zcl_data.pkt_info); + + /* Send the actual frame. */ + zb_err_code = zb_zcl_finish_and_send_packet_new( + packet_info->buffer, + packet_info->ptr, + &(packet_info->dst_addr), + packet_info->dst_addr_mode, + packet_info->dst_ep, + packet_info->ep, + packet_info->prof_id, + packet_info->cluster_id, + packet_info->cb, + 0, + packet_info->disable_aps_ack, + 0); + + if (zb_err_code != RET_OK) { + zb_shell_print_error(entry->shell, "Can not send ZCL frame", ZB_FALSE); + zb_buf_free(packet_info->buffer); + + /* Invalidate an entry with frame data in the context manager. */ + ctx_mgr_delete_entry(entry); + } +} + +/**@brief Function to send Configure Reporting command. + * + * @param entry Pointer to the entry with frame to send in the context manager. + * + * @return Error code with function execution status. + */ +static int send_reporting_frame(struct ctx_entry *entry) +{ + zb_ret_t zb_err_code; + uint8_t entry_index = ctx_mgr_get_index_by_entry(entry); + + zb_err_code = ZB_SCHEDULE_APP_ALARM( + zb_shell_zcl_cmd_timeout_cb, + entry_index, + (CONFIG_ZIGBEE_SHELL_ZCL_CMD_TIMEOUT * ZB_TIME_ONE_SECOND)); + + if (zb_err_code != RET_OK) { + zb_shell_print_error(entry->shell, "Couldn't schedule timeout cb.", ZB_FALSE); + goto error; + } + + zb_err_code = ZB_SCHEDULE_APP_CALLBACK(zb_zcl_send_attr_report_frame, + entry_index); + if (zb_err_code != RET_OK) { + zb_shell_print_error(entry->shell, "Can not schedule zcl frame.", ZB_FALSE); + + zb_err_code = + ZB_SCHEDULE_APP_ALARM_CANCEL(zb_shell_zcl_cmd_timeout_cb, entry_index); + ZB_ERROR_CHECK(zb_err_code); + goto error; + } + + return 0; + +error: + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(entry->zcl_data.pkt_info.buffer); + zb_osif_enable_all_inter(); + + /* Invalidate an entry with frame data in the context manager. */ + ctx_mgr_delete_entry(entry); + + return -ENOEXEC; +} + +/**@brief Validate and parse input arguments `subscribe` command is called with. + * Takes the same input as command handler and if parsed correctly, + * fills the context manager `entry` with request data. + */ +static int cmd_zb_subscribe_parse_input(size_t argc, char **argv, struct ctx_entry *entry) +{ + struct zcl_packet_info *packet_info = &(entry->zcl_data.pkt_info); + struct configure_reporting_req *req_data = &(entry->zcl_data.configure_reporting_req); + + packet_info->dst_addr_mode = parse_address(argv[1], &packet_info->dst_addr, ADDR_ANY); + + if (packet_info->dst_addr_mode == ADDR_INVALID) { + zb_shell_print_error(entry->shell, "Invalid remote address", ZB_FALSE); + return -EINVAL; + } + + if (!zb_shell_sscan_uint8(argv[2], &(packet_info->dst_ep))) { + zb_shell_print_error(entry->shell, "Incorrect remote endpoint", ZB_FALSE); + return -EINVAL; + } + + if (!parse_hex_u16(argv[3], &(packet_info->cluster_id))) { + zb_shell_print_error(entry->shell, "Incorrect cluster ID", ZB_FALSE); + return -EINVAL; + } + + if (!parse_hex_u16(argv[4], &(packet_info->prof_id))) { + zb_shell_print_error(entry->shell, "Incorrect profile ID", ZB_FALSE); + return -EINVAL; + } + + if (!parse_hex_u16(argv[5], &(req_data->attr_id))) { + zb_shell_print_error(entry->shell, "Incorrect attribute ID", ZB_FALSE); + return -EINVAL; + } + + if (!zb_shell_sscan_uint8(argv[6], &(req_data->attr_type))) { + zb_shell_print_error(entry->shell, "Incorrect attribute type", ZB_FALSE); + return -EINVAL; + } + + /* Optional parameters parsing. */ + if (argc > 7) { + if (!zb_shell_sscan_uint(argv[7], (uint8_t *)&req_data->interval_min, 2, 10)) { + zb_shell_print_error(entry->shell, "Incorrect minimum interval", ZB_FALSE); + return -EINVAL; + } + } + + if (argc > 8) { + if (!zb_shell_sscan_uint(argv[8], (uint8_t *)&req_data->interval_max, 2, 10)) { + zb_shell_print_error(entry->shell, "Incorrect maximum interval", ZB_FALSE); + return -EINVAL; + } + } + + return 0; +} + +/**@brief Subscribe to the attribute changes on the remote node. + * + * @code + * zcl subscribe on + * [] [] + * @endcode + * + * Enable reporting on the node identified by `addr`, with the endpoint `ep` + * that uses the profile `profile` of the attribute `attr_id` with the type + * `attr_type` in the cluster `cluster`. + * + * Reports must be generated in intervals not shorter than `min_interval` + * (1 second by default) and not longer + * than `max_interval` (60 seconds by default). + */ +int cmd_zb_subscribe_on(const struct shell *shell, size_t argc, char **argv) +{ + int ret_val; + zb_bufid_t bufid; + struct configure_reporting_req *req_data; + struct ctx_entry *entry = ctx_mgr_new_entry(CTX_MGR_CFG_REPORT_REQ_ENTRY_TYPE); + + if (entry == NULL) { + zb_shell_print_error( + shell, + "Request pool empty - wait for ongoing command to finish", + ZB_FALSE); + return -ENOEXEC; + } + + req_data = &(entry->zcl_data.configure_reporting_req); + + /* Set default interval values. */ + req_data->interval_min = ZIGBEE_SHELL_CONFIGURE_REPORT_DEFAULT_MIN_INTERVAL; + req_data->interval_max = ZIGBEE_SHELL_CONFIGURE_REPORT_DEFAULT_MAX_INTERVAL; + + /* Set pointer to the shell to be used by the command handler. */ + entry->shell = shell; + + ret_val = cmd_zb_subscribe_parse_input(argc, argv, entry); + if (ret_val) { + /* Parsing input arguments failed, error message has been already printed + * in the `cmd_zb_subscribe_parse_input`, so free the ctx entry. + */ + ctx_mgr_delete_entry(entry); + return ret_val; + } + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + bufid = zb_buf_get_out(); + zb_osif_enable_all_inter(); + + if (!bufid) { + zb_shell_print_error( + shell, + "Failed to execute command (buf alloc failed)", + ZB_FALSE); + ctx_mgr_delete_entry(entry); + return -ENOEXEC; + } + + /* Fill the structure for sending ZCL frame. */ + entry->zcl_data.pkt_info.buffer = bufid; + /* DstAddr, DstAddr Mode and dst endpoint are already set. */ + entry->zcl_data.pkt_info.ep = zb_shell_get_endpoint(); + /* Profile ID and Cluster ID are already set. */ + entry->zcl_data.pkt_info.cb = NULL; + entry->zcl_data.pkt_info.disable_aps_ack = ZB_FALSE; + + return send_reporting_frame(entry); +} + +/**@brief Unsubscribe from the attribute changes on the remote node. + * + * @code + * zcl subscribe off + * [] [] + * @endcode + * + * Disable reporting on the node identified by `addr`, with the endpoint `ep` + * that uses the profile `profile` of the attribute `attr_id` with the type + * `attr_type` in the cluster `cluster`. + */ +int cmd_zb_subscribe_off(const struct shell *shell, size_t argc, char **argv) +{ + int ret_val; + zb_bufid_t bufid; + struct configure_reporting_req *req_data; + struct ctx_entry *entry = ctx_mgr_new_entry(CTX_MGR_CFG_REPORT_REQ_ENTRY_TYPE); + + if (entry == NULL) { + zb_shell_print_error( + shell, + "Request pool empty - wait for ongoing command to finish", + ZB_FALSE); + return -ENOEXEC; + } + + req_data = &(entry->zcl_data.configure_reporting_req); + + /* Set default interval values. */ + req_data->interval_min = ZIGBEE_SHELL_CONFIGURE_REPORT_OFF_MIN_INTERVAL; + req_data->interval_max = ZIGBEE_SHELL_CONFIGURE_REPORT_OFF_MAX_INTERVAL; + + /* Set pointer to the shell to be used by the command handler. */ + entry->shell = shell; + + ret_val = cmd_zb_subscribe_parse_input(argc, argv, entry); + if (ret_val) { + /* Parsing input arguments failed, error message has been already printed + * in the `cmd_zb_subscribe_parse_input`, so free the ctx entry. + */ + ctx_mgr_delete_entry(entry); + return ret_val; + } + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + bufid = zb_buf_get_out(); + zb_osif_enable_all_inter(); + + if (!bufid) { + zb_shell_print_error( + shell, + "Failed to execute command (buf alloc failed)", + ZB_FALSE); + ctx_mgr_delete_entry(entry); + return -ENOEXEC; + } + + /* Fill the structure for sending ZCL frame. */ + entry->zcl_data.pkt_info.buffer = bufid; + /* DstAddr, DstAddr Mode and dst endpoint are already set. */ + entry->zcl_data.pkt_info.ep = zb_shell_get_endpoint(); + /* Profile ID and Cluster ID are already set. */ + entry->zcl_data.pkt_info.cb = NULL; + entry->zcl_data.pkt_info.disable_aps_ack = ZB_FALSE; + + return send_reporting_frame(entry); +} diff --git a/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_bdb.c b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_bdb.c new file mode 100644 index 00000000..9acec50e --- /dev/null +++ b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_bdb.c @@ -0,0 +1,1225 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include + +#include +#include +#include +#include +#include "zigbee_shell_utils.h" + +#define IC_ADD_HELP \ + ("Add install code for device with given eui64.\n" \ + "Usage: add ") + +#define IC_LIST_HELP \ + ("Read install codes stored at the device.\n" \ + "Usage: list") + +#define IC_POLICY_HELP \ + ("Set Trust Center install code policy.\n" \ + "Usage: policy ") + +#define IC_SET_HELP \ + ("Add install code for the device.\n" \ + "Usage: set ") + +#define CHANNEL_HELP \ + ("Set/get channel.\n" \ + "Usage: channel []\n" \ + "If n is [11:26], set to that channel. Otherwise, treat n as bitmask.") + +#define CHILD_MAX_HELP \ + ("Set max_child number.\n" \ + "Usage: child_max ") + +#define EXTPANID_HELP \ + ("Set/get extpanid.\n" \ + "Usage: extpanid []") + +#define NWKKEY_HELP \ + ("Set network key.\n" \ + "Usage: nwkkey ") + +#define PANID_HELP \ + ("Set/get panid.\n" \ + "Usage: panid []") + +/* Forward declarations. */ +#ifndef ZB_ED_ROLE +static void zb_install_code_list_read(zb_uint8_t buffer, zb_uint16_t start_index); +#endif + +/* Install code list entry structure. */ +struct __packed zb_secur_ic_list_entry { + /* Partner address */ + zb_ieee_addr_t device_address; + zb_uint8_t options; + zb_uint8_t reserved; + /* 16b installcode +2b crc. */ + zb_uint8_t installcode[ZB_CCM_KEY_SIZE + ZB_CCM_KEY_CRC_SIZE]; +}; + +struct ic_cmd_ctx { + volatile bool taken; + zb_ieee_addr_t addr; + zb_uint8_t ic[ZB_CCM_KEY_SIZE + ZB_CCM_KEY_CRC_SIZE]; + const struct shell *shell; +}; + +struct ic_cmd_list_ctx { + volatile bool taken; + const struct shell *shell; +}; + +static zb_bool_t legacy_mode = ZB_FALSE; +#ifndef ZB_ED_ROLE +static struct ic_cmd_ctx ic_add_ctx = {0}; +static struct ic_cmd_list_ctx ic_list_ctx = {0}; +static zb_nwk_device_type_t default_role = ZB_NWK_DEVICE_TYPE_ROUTER; +#else +static zb_nwk_device_type_t default_role = ZB_NWK_DEVICE_TYPE_ED; +#endif + +/**@brief Get Zigbee role set for the device. + * + * @return Zigbee role already set or role planned to set if stack is not yet started. + */ +static zb_nwk_device_type_t zb_shell_get_network_role(void) +{ + if (zigbee_is_stack_started()) { + return zb_get_network_role(); + } else { + return default_role; + } +} + +/**@brief Get Zigbee role of the device. + * + * @code + * bdb role + * @endcode + * + * @pre Reading only after @ref start "bdb start". + * + */ +static int cmd_zb_role(const struct shell *shell, size_t argc, char **argv) +{ + zb_nwk_device_type_t role; + + if (!zigbee_is_stack_started() + && zigbee_is_nvram_initialised() +#ifdef CONFIG_ZIGBEE_SHELL_DEBUG_CMD + && zb_shell_nvram_enabled() +#endif /* CONFIG_ZIGBEE_SHELL_DEBUG_CMD */ + ) { + shell_warn( + shell, + "Zigbee stack has been configured in the past.\r\n" + "Please start the Zigbee stack to check the configured role."); + + zb_shell_print_error( + shell, + "Can't get role before stack is started - NVRAM not empty", + ZB_FALSE); + return -ENOEXEC; + } + + role = zb_shell_get_network_role(); + + if (role == ZB_NWK_DEVICE_TYPE_COORDINATOR) { + shell_print(shell, "zc"); + } else if (role == ZB_NWK_DEVICE_TYPE_ROUTER) { + shell_print(shell, "zr"); + } else if (role == ZB_NWK_DEVICE_TYPE_ED) { + shell_print(shell, "zed"); + } + + zb_shell_print_done(shell, ZB_FALSE); + return 0; +} + +/**@brief Set Zigbee coordinator role of the device. + * + * @code + * bdb role zc + * @endcode + * + * @pre Setting only before @ref start "bdb start". + * + */ +static int cmd_zb_role_zc(const struct shell *shell, size_t argc, char **argv) +{ + if (zigbee_is_stack_started()) { + zb_shell_print_error(shell, "Stack already started", ZB_FALSE); + return -ENOEXEC; + } + +#ifdef ZB_ED_ROLE + zb_shell_print_error(shell, "Role unsupported", ZB_FALSE); + return -ENOEXEC; +#else + if (zigbee_is_nvram_initialised() +#ifdef CONFIG_ZIGBEE_SHELL_DEBUG_CMD + && zb_shell_nvram_enabled() +#endif /* CONFIG_ZIGBEE_SHELL_DEBUG_CMD */ + ) { + shell_warn( + shell, + "Zigbee stack has been configured in the past.\r\n" + "Please use the same role or disable NVRAM to change the Zigbee role."); + } else { + shell_info( + shell, + "Zigbee shell does not erase the NVRAM between reboots, but is not aware of the previously configured role.\r\n" + "Remember to set the coordinator role after rebooting the device."); + } + + default_role = ZB_NWK_DEVICE_TYPE_COORDINATOR; + shell_print(shell, "Coordinator set"); + zb_shell_print_done(shell, ZB_FALSE); + return 0; +#endif +} + +/**@brief Set Zigbee End Device role of the device. + * + * @code + * bdb role zed + * @endcode + * + * @pre Setting only before @ref start "bdb start". + * + * @note Zigbee End Device role is not currently supported to be switched to + * from Router or Coordinator role. + * + */ +static int cmd_zb_role_zed(const struct shell *shell, size_t argc, char **argv) +{ + if (zigbee_is_stack_started()) { + zb_shell_print_error(shell, "Stack already started", ZB_FALSE); + return -ENOEXEC; + } + +#ifdef ZB_ED_ROLE + default_role = ZB_NWK_DEVICE_TYPE_ED; + shell_print(shell, "End Device role set"); + zb_shell_print_done(shell, ZB_FALSE); + return 0; +#else + zb_shell_print_error(shell, "Role unsupported", ZB_FALSE); + return -ENOEXEC; +#endif +} + +/**@brief Set Zigbee Router role of the device. + * + * @code + * bdb role zr + * @endcode + * + * @pre Setting only before @ref start "bdb start". + * + */ +static int cmd_zb_role_zr(const struct shell *shell, size_t argc, char **argv) +{ + if (zigbee_is_stack_started()) { + zb_shell_print_error(shell, "Stack already started", ZB_FALSE); + return -ENOEXEC; + } + +#ifdef ZB_ED_ROLE + zb_shell_print_error(shell, "Role unsupported", ZB_FALSE); + return -ENOEXEC; +#else + if (zigbee_is_nvram_initialised() +#ifdef CONFIG_ZIGBEE_SHELL_DEBUG_CMD + && zb_shell_nvram_enabled() +#endif /* CONFIG_ZIGBEE_SHELL_DEBUG_CMD */ + ) { + shell_warn( + shell, + "Zigbee stack has been configured in the past.\r\n" + "Please use the same role or disable NVRAM to change the Zigbee role."); + } + + default_role = ZB_NWK_DEVICE_TYPE_ROUTER; + shell_print(shell, "Router role set"); + zb_shell_print_done(shell, ZB_FALSE); + return 0; +#endif +} + +/**@brief Start bdb top level commissioning process. + * + * This command can be called multiple times. + * + * If the ZBOSS thread has not been created yet, `zigbee_enable` is called to + * create the ZBOSS thread and the Zigbee stack is started. The subsequent + * behavior of the device depends on the implementation of the + * `zboss_signal_handler` function. If `zigbee_default_signal_handler` is used + * to handle a ZBOSS signals, the device will attempt to join or form a network + * (depending on the role) after the Zigbee stack is started. + * + * If the device is not on a network but the ZBOSS thread has been created + * (so the Zigbee stack is started), then it will attempt to join + * or form a network (depending on the role). + * + * If the device is on the network then the command will open the network for + * new devices to join. + * + * See Base Device Behaviour specification for details. + * + * @code + * > bdb start + * Started coordinator + * Done + * @endcode + */ +static int cmd_zb_start(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + uint32_t channel; + zb_bool_t ret = ZB_TRUE; + zb_uint8_t mode_mask = ZB_BDB_NETWORK_STEERING; + + if ((!zigbee_is_stack_started())) { + channel = zb_get_bdb_primary_channel_set(); + + switch (default_role) { +#ifndef ZB_ED_ROLE + case ZB_NWK_DEVICE_TYPE_ROUTER: + zb_set_network_router_role(channel); + shell_print(shell, "Started router"); + break; + + case ZB_NWK_DEVICE_TYPE_COORDINATOR: + zb_set_network_coordinator_role(channel); + shell_print(shell, "Started coordinator"); + break; +#else + case ZB_NWK_DEVICE_TYPE_ED: + zb_set_network_ed_role(channel); + shell_print(shell, "Started End Device"); + break; +#endif + default: + zb_shell_print_error(shell, "Role unsupported", ZB_FALSE); + return -ENOEXEC; + } + + zigbee_enable(); + } else { + /* Handle case where Zigbee coordinator has left the network + * and a new network needs to be formed. + */ + if ((default_role == ZB_NWK_DEVICE_TYPE_COORDINATOR) && !ZB_JOINED()) { + mode_mask = ZB_BDB_NETWORK_FORMATION; + } + ret = bdb_start_top_level_commissioning(mode_mask); + } + + if (ret) { + zb_shell_print_done(shell, ZB_FALSE); + return 0; + } else { + zb_shell_print_error(shell, "Could not start top level commissioning", ZB_FALSE); + return -ENOEXEC; + } +} + +/**@brief Set or get the Zigbee Extended Pan ID value. + * + * @code + * bdb extpanid [] + * @endcode + * + * @pre Setting only before @ref start "bdb start". Reading only after + * @ref start "bdb start". + * + * If the optional argument is not provided, gets the extended PAN ID + * of the joined network. + * + * If the optional argument is provided, gets the extended PAN ID to `id`. + * + * + */ +static int cmd_zb_extpanid(const struct shell *shell, size_t argc, char **argv) +{ + zb_ext_pan_id_t extpanid; + + if (argc == 1) { + zb_get_extended_pan_id(extpanid); + zb_shell_print_eui64(shell, extpanid); + /* Prepend newline because `zb_shell_print_eui64` does not + * print LF. + */ + zb_shell_print_done(shell, ZB_TRUE); + } else if (argc == 2) { + if (zigbee_is_stack_started()) { + zb_shell_print_error(shell, "Stack already started", ZB_FALSE); + return -ENOEXEC; + } + + if (zigbee_is_nvram_initialised() +#ifdef CONFIG_ZIGBEE_SHELL_DEBUG_CMD + && zb_shell_nvram_enabled() +#endif /* CONFIG_ZIGBEE_SHELL_DEBUG_CMD */ + ) { + shell_warn( + shell, + "Zigbee stack has been configured in the past.\r\n" + "Please disable NVRAM to change the Extended PAN ID."); + + zb_shell_print_error( + shell, + "Can't change extpanid - NVRAM not empty", + ZB_FALSE); + return -ENOEXEC; + } + + if (parse_long_address(argv[1], extpanid)) { + zb_set_extended_pan_id(extpanid); + zb_shell_print_done(shell, ZB_FALSE); + } else { + zb_shell_print_error(shell, "Failed to parse extpanid", ZB_FALSE); + return -EINVAL; + } + } + + return 0; +} + +/**@brief Set or get the Zigbee Pan ID value. + * + * @code + * bdb panid [] + * @endcode + * + * @pre Setting only before @ref start "bdb start". Reading only + * after @ref start "bdb start". + * + * If the optional argument is not provided, gets the PAN ID + * of the joined network. + * If the optional argument is provided, sets the PAN ID to `id`. + * + */ +static int cmd_zb_panid(const struct shell *shell, size_t argc, char **argv) +{ + zb_uint16_t pan_id; + + if (argc == 1) { + shell_print(shell, "%0X", ZB_PIBCACHE_PAN_ID()); + zb_shell_print_done(shell, ZB_FALSE); + } else if (argc == 2) { + if (zigbee_is_stack_started()) { + zb_shell_print_error(shell, "Stack already started", ZB_FALSE); + return -ENOEXEC; + } + + if (zigbee_is_nvram_initialised() +#ifdef CONFIG_ZIGBEE_SHELL_DEBUG_CMD + && zb_shell_nvram_enabled() +#endif /* CONFIG_ZIGBEE_SHELL_DEBUG_CMD */ + ) { + shell_warn( + shell, + "Zigbee stack has been configured in the past.\r\n" + "Please disable NVRAM to change the PAN ID."); + + zb_shell_print_error( + shell, + "Can't change PAN ID - NVRAM not empty", + ZB_FALSE); + return -ENOEXEC; + } + + if (parse_hex_u16(argv[1], &pan_id)) { + ZB_PIBCACHE_PAN_ID() = pan_id; + zb_shell_print_done(shell, ZB_FALSE); + } else { + zb_shell_print_error(shell, "Failed to parse PAN ID", ZB_FALSE); + return -EINVAL; + } + } else { + zb_shell_print_error(shell, "Unsupported format. Expected panid ", ZB_FALSE); + return -EINVAL; + } + + return 0; +} + +/**@brief Set or get 802.15.4 channel. + * + * @code + * bdb channel [] + * @endcode + * + * @pre Setting only before @ref start "bdb start". + * + * If the optional argument is not provided, get the current number + * and bitmask of the channel. + * + * If the optional argument is provided: + * - If `n` is in [11:26] range, set to that channel. + * - Otherwise, treat `n` as bitmask (logical or of a single bit shifted + * by channel number). + * + * + * Example: + * @code + * > bdb channel 0x110000 + * Setting channel bitmask to 110000 + * Done + * @endcode + */ +static int cmd_zb_channel(const struct shell *shell, size_t argc, char **argv) +{ + uint32_t chan[2]; + uint32_t channel_number = 0; + uint32_t channel_mask = 0; + + if (argc == 1) { + int i; + int c; + + chan[0] = zb_get_bdb_primary_channel_set(); + chan[1] = zb_get_bdb_secondary_channel_set(); + + /* Check for case in which channel mask can not be read. */ + if (zigbee_is_nvram_initialised() && !zigbee_is_stack_started() +#ifdef CONFIG_ZIGBEE_SHELL_DEBUG_CMD + && zb_shell_nvram_enabled() +#endif /* CONFIG_ZIGBEE_SHELL_DEBUG_CMD */ + ) { + shell_warn( + shell, + "Zigbee stack has been configured in the past.\r\n" + "Please start the Zigbee stack to check the configured channels."); + + zb_shell_print_error( + shell, + "Can't get channel mask - NVRAM not empty", + ZB_FALSE); + return -ENOEXEC; + } + + /* If channel lists are empty, the default channel will be used. + * Set it to correct value so it can be printed correctly. + */ + if (chan[0] == 0) { +#if defined CONFIG_ZIGBEE_CHANNEL_SELECTION_MODE_SINGLE + chan[0] = (1U << CONFIG_ZIGBEE_CHANNEL); + chan[1] = (1U << CONFIG_ZIGBEE_CHANNEL); +#elif defined CONFIG_ZIGBEE_CHANNEL_SELECTION_MODE_MULTI + chan[0] = CONFIG_ZIGBEE_CHANNEL_MASK; + chan[1] = CONFIG_ZIGBEE_CHANNEL_MASK; +#endif + } + + /* Print for both channels. */ + for (c = 0; c < 2; c++) { + shell_fprintf(shell, SHELL_NORMAL, "%s channel(s):", + c == 0 ? "Primary" : "Secondary"); + for (i = 11; i <= 26; i++) { + if ((1 << i) & chan[c]) { + shell_fprintf(shell, SHELL_NORMAL, + " %d", i); + } + } + shell_print(shell, ""); + } + + zb_shell_print_done(shell, ZB_FALSE); + return 0; + } else if (argc == 2) { + if (zigbee_is_stack_started()) { + zb_shell_print_error(shell, "Stack already started", ZB_FALSE); + return -ENOEXEC; + } + + if (zigbee_is_nvram_initialised() +#ifdef CONFIG_ZIGBEE_SHELL_DEBUG_CMD + && zb_shell_nvram_enabled() +#endif /* CONFIG_ZIGBEE_SHELL_DEBUG_CMD */ + ) { + shell_warn( + shell, + "Zigbee stack has been configured in the past.\r\n" + "Please disable NVRAM to change the channel mask."); + + zb_shell_print_error( + shell, + "Can't change channel mask - NVRAM not empty", + ZB_FALSE); + return -ENOEXEC; + } + + zb_shell_sscan_uint(argv[1], (uint8_t *)&channel_number, 4, 10); + if (channel_number < 11 || channel_number > 26) { + /* Treat as a bitmask. */ + channel_number = 0; + zb_shell_sscan_uint(argv[1], (uint8_t *)&channel_mask, 4, 16); + + if ((!(channel_mask & 0x7FFF800)) || (channel_mask & (~0x7FFF800))) { + zb_shell_print_error(shell, "Bitmask invalid", ZB_FALSE); + return -EINVAL; + } + } else { + /* Treat as number. */ + channel_mask = 1 << channel_number; + } + + if (zb_get_bdb_primary_channel_set() != channel_mask) { + if (channel_number) { + shell_print(shell, "Setting channel to %d", + channel_number); + } else { + shell_print(shell, + "Setting channel bitmask to %x", + channel_mask); + } + + zb_set_bdb_primary_channel_set(channel_mask); + zb_set_bdb_secondary_channel_set(channel_mask); + zb_set_channel_mask(channel_mask); + } + + zb_shell_print_done(shell, ZB_FALSE); + } + return 0; +} + +#if !defined(ZB_ED_ROLE) + +static void zb_secur_ic_add_cb(zb_ret_t status) +{ + if (status != RET_OK) { + zb_shell_print_error(ic_add_ctx.shell, "Failed to add IC", ZB_FALSE); + } else { + zb_shell_print_done(ic_add_ctx.shell, ZB_FALSE); + } + + ic_add_ctx.taken = false; +} + +/**@brief Function adding install code, to be executed in Zigbee thread context. + * + * @param[in] param Unused param. + */ +void zb_install_code_add(zb_uint8_t param) +{ + ARG_UNUSED(param); + + if (zb_get_network_role() != ZB_NWK_DEVICE_TYPE_COORDINATOR) { + zb_shell_print_error( + ic_add_ctx.shell, + "Failed to add IC. Device must be a coordinator", + ZB_FALSE); + return; + } + + zb_secur_ic_add(ic_add_ctx.addr, ZB_IC_TYPE_128, ic_add_ctx.ic, zb_secur_ic_add_cb); +} + +/**@brief Function to print install codes list table header. + */ +static inline void zb_install_code_list_print_header(void) +{ + shell_print(ic_list_ctx.shell, + "[idx] EUI64: IC: options:"); +} + +/**@brief Function to print number of entries in the install codes table. + * + * @param[in] total_rows Number of rows present in the install code table. + */ +static inline void zb_install_code_list_print_summary(zb_uint8_t total_entries) +{ + shell_print(ic_list_ctx.shell, + "Total entries for the install codes table: %d", + total_entries); +} + +/**@brief Function to print install codes list table row. + * + * @param[in] ic Pointer to zb_secur_ic_list_entry structure to read install codes from. + * @param[in] index Index of install code in the install code table. + */ +static void zb_install_code_list_print_row(struct zb_secur_ic_list_entry *ic, zb_uint8_t index) +{ + /* Print row index. */ + shell_fprintf(ic_list_ctx.shell, SHELL_NORMAL, "[%3d] ", index); + /* Print device long address. */ + zb_shell_print_eui64(ic_list_ctx.shell, ic->device_address); + /* Print space to separate device address and install code fields. */ + shell_fprintf(ic_list_ctx.shell, SHELL_NORMAL, " "); + /* Print the install code. */ + zb_shell_print_hexdump( + ic_list_ctx.shell, + ic->installcode, + (ZB_CCM_KEY_SIZE + ZB_CCM_KEY_CRC_SIZE), + false); + /* Print space to separate install code and options fields. */ + shell_fprintf(ic_list_ctx.shell, SHELL_NORMAL, " "); + /* Print options with additional newline character. */ + shell_print(ic_list_ctx.shell, "%#02x", ic->options); +} + +/**@brief Callback function called when install codes are read and ready to be printed. + * To be executed in Zigbee thread context. + * + * @param[in] buffer Buffer ID. + */ +static void zb_install_code_list_read_cb(zb_uint8_t buffer) +{ + zb_uint8_t ic_entries_left_to_read = 0; + struct zb_secur_ic_list_entry *ic = NULL; + zb_secur_ic_get_list_resp_t *ic_list_resp = NULL; + + if (ic_list_ctx.taken == false) { + goto exit; + } + + /* Check if buffer id is correct. */ + if (buffer == ZB_BUF_INVALID) { + zb_shell_print_error( + ic_list_ctx.shell, + "Invalid buffer ID received in cb", + ZB_FALSE); + goto exit; + } + + ic_list_resp = ZB_BUF_GET_PARAM(buffer, zb_secur_ic_get_list_resp_t); + + if (ic_list_resp->status != RET_OK) { + zb_shell_print_error( + ic_list_ctx.shell, + "Error status when reading IC list.", + ZB_FALSE); + goto exit; + } + + /* Set pointer to first ic struct in the buffer. */ + ic = zb_buf_begin(buffer); + + /* If zero index row is to be printed, print table header first. */ + if (ic_list_resp->start_index == 0) { + zb_install_code_list_print_header(); + } + + /* Print every ic in the separate row. */ + for (zb_uint8_t id = 0; id < ic_list_resp->ic_table_list_count; id++) { + zb_install_code_list_print_row((ic + id), (ic_list_resp->start_index + id)); + } + + /* Calculate how many entries left to be read. */ + ic_entries_left_to_read = ic_list_resp->ic_table_entries; + ic_entries_left_to_read -= (ic_list_resp->start_index + ic_list_resp->ic_table_list_count); + + if (ic_entries_left_to_read > 0) { + zb_uint8_t new_start_index = (ic_list_resp->ic_table_entries + - ic_entries_left_to_read); + + /* Reuse the same buffer to read remaining install codes. */ + zb_buf_reuse(buffer); + zb_install_code_list_read(buffer, new_start_index); + return; + } + + /* All install codes have been read, print summary and done msg and clean up. */ + zb_install_code_list_print_summary(ic_list_resp->ic_table_entries); + zb_shell_print_done(ic_list_ctx.shell, ZB_FALSE); + +exit: + ic_list_ctx.taken = false; + if (buffer != ZB_BUF_INVALID) { + zb_buf_free(buffer); + } +} + +/**@brief Function to start reading install codes stored at the device. + * To be executed in Zigbee thread context. + * + * @param[in] buffer Buffer ID. + * @param[in] start_index Index of IC list to start reading install codes from. + */ +static void zb_install_code_list_read(zb_uint8_t buffer, zb_uint16_t start_index) +{ + zb_secur_ic_get_list_req_t *ic_list_req = NULL; + + /* Check if buffer id is correct. */ + if (buffer == ZB_BUF_INVALID) { + zb_shell_print_error(ic_list_ctx.shell, "Invalid buffer ID received", ZB_FALSE); + ic_list_ctx.taken = false; + return; + } + + /* Fill request data in the param. */ + ic_list_req = ZB_BUF_GET_PARAM(buffer, zb_secur_ic_get_list_req_t); + ic_list_req->start_index = start_index; + ic_list_req->response_cb = zb_install_code_list_read_cb; + + /* Call API to request IC list. */ + zb_secur_ic_get_list_req(buffer); +} +#endif /* !defined ZB_ED_ROLE */ + +/**@brief Set install code on the device, add information about the install code + * on the trust center, set the trust center install code policy. + * + * @code + * bdb ic add + * bdb ic set + * bdb ic policy + * bdb ic list + * @endcode + * + * @pre Adding and reading install codes only after @ref start "bdb start". + * + * bdb ic set must only be used on a joining device. + * + * bdb ic add must only be used on a coordinator. + * For , use the address + * of the joining device. + * + * bdb ic policy must only be used on a coordinator. + * + * bdb ic list must only be used on a coordinator. + * + * Provide the install code as an ASCII-encoded hex including CRC16. + * + * For production devices, an install code must be installed by the production + * configuration present in flash. + * + * + * Example: + * @code + * > bdb ic add 83FED3407A939723A5C639B26916D505C3B5 0B010E2F79E9DBFA + * Done + * @endcode + */ +static int cmd_zb_install_code(const struct shell *shell, size_t argc, + char **argv) +{ + const char *err_msg = NULL; + + if ((argc == 2) && (strcmp(argv[0], "set") == 0)) { + zb_uint8_t ic[ZB_CCM_KEY_SIZE + ZB_CCM_KEY_CRC_SIZE]; + + if (zb_shell_get_network_role() == ZB_NWK_DEVICE_TYPE_COORDINATOR) { + zb_shell_print_error(shell, "Device can't be a coordinator", ZB_FALSE); + return -ENOEXEC; + } + + if (zigbee_is_stack_started()) { + zb_shell_print_error(shell, "Stack already started", ZB_FALSE); + return -ENOEXEC; + } + + if (!parse_hex_str(argv[1], strlen(argv[1]), ic, sizeof(ic), + false)) { + err_msg = "Failed to parse IC"; + goto exit; + } + + if (zb_secur_ic_set(ZB_IC_TYPE_128, ic) != RET_OK) { + err_msg = "Failed to set IC"; + goto exit; + } +#ifndef ZB_ED_ROLE + } else if ((argc == 3) && (strcmp(argv[0], "add") == 0)) { + /* Check if stack is initialized as Install Code can not + * be added until production config is initialised. + */ + if (zb_shell_get_network_role() != ZB_NWK_DEVICE_TYPE_COORDINATOR) { + zb_shell_print_error(shell, "Device must be a coordinator", ZB_FALSE); + return -ENOEXEC; + } + + if (!zigbee_is_stack_started()) { + zb_shell_print_error(shell, "Stack not started", ZB_FALSE); + return -ENOEXEC; + } + + ic_add_ctx.shell = shell; + + if (ic_add_ctx.taken == true) { + err_msg = "Can not get ctx to store install code"; + goto exit; + } + + ic_add_ctx.taken = true; + + if (!parse_hex_str(argv[1], strlen(argv[1]), ic_add_ctx.ic, + sizeof(ic_add_ctx.ic), false)) { + err_msg = "Failed to parse IC"; + ic_add_ctx.taken = false; + goto exit; + } + + if (!parse_long_address(argv[2], ic_add_ctx.addr)) { + err_msg = "Failed to parse eui64"; + ic_add_ctx.taken = false; + goto exit; + } + + if (ZB_SCHEDULE_APP_CALLBACK(zb_install_code_add, 0) != + RET_OK) { + err_msg = "Can not add install code"; + ic_add_ctx.taken = false; + goto exit; + } + return 0; + + } else if ((argc == 2) && (strcmp(argv[0], "policy") == 0)) { + if (zb_shell_get_network_role() != ZB_NWK_DEVICE_TYPE_COORDINATOR) { + zb_shell_print_error(shell, "Device must be a coordinator", ZB_FALSE); + return -ENOEXEC; + } + + if (strcmp(argv[1], "enable") == 0) { + zb_set_installcode_policy(ZB_TRUE); + } else if (strcmp(argv[1], "disable") == 0) { + zb_set_installcode_policy(ZB_FALSE); + } else { + err_msg = "Syntax error"; + goto exit; + } + } else if ((argc == 1) && (strcmp(argv[0], "list") == 0)) { + zb_ret_t ret_val = RET_OK; + + if (zb_shell_get_network_role() != ZB_NWK_DEVICE_TYPE_COORDINATOR) { + zb_shell_print_error(shell, "Device must be a coordinator", ZB_FALSE); + return -ENOEXEC; + } + + if (!zigbee_is_stack_started()) { + zb_shell_print_error(shell, "Stack not started", ZB_FALSE); + return -ENOEXEC; + } + + if (ic_list_ctx.taken == true) { + zb_shell_print_error( + shell, + "Can't start reading IC list - already in progress.", + ZB_FALSE); + return -ENOEXEC; + } + + ic_list_ctx.taken = true; + ic_list_ctx.shell = shell; + + /* Schedule reading IC list immediately, start from index 0. */ + ret_val = zb_buf_get_out_delayed_ext(zb_install_code_list_read, 0, 0); + + if (ret_val != RET_OK) { + ic_list_ctx.taken = false; + zb_shell_print_error( + shell, + "Couldn't get buffer for reading IC list.", + ZB_FALSE); + return -ENOEXEC; + } + + return 0; +#endif + } else { + err_msg = "Syntax error"; + } + +exit: + if (err_msg) { + zb_shell_print_error(shell, err_msg, ZB_FALSE); + return -EINVAL; + } else { + zb_shell_print_done(shell, ZB_FALSE); + return 0; + } +} + +/**@brief Get the state of the legacy device support. + * + * @code + * bdb legacy + * @endcode + * + * Example: + * @code + * > bdb legacy + * on + * Done + * @endcode + */ +static int cmd_zb_legacy(const struct shell *shell, size_t argc, char **argv) +{ + if (zb_shell_get_network_role() != ZB_NWK_DEVICE_TYPE_COORDINATOR) { + zb_shell_print_error(shell, "Device must be a coordinator", ZB_FALSE); + return -ENOEXEC; + } + + shell_print(shell, "%s", legacy_mode ? "on" : "off"); + zb_shell_print_done(shell, ZB_FALSE); + return 0; +} + +/**@brief Enable the legacy device support. + * + * @code + * bdb legacy enable + * @endcode + * + * Allow pre-r21 devices on the Zigbee network. + * + * @pre Use only after @ref start "bdb start". + * + * Example: + * @code + * > bdb legacy enable + * Done + * @endcode + */ +static int cmd_zb_legacy_enable(const struct shell *shell, size_t argc, + char **argv) +{ + if (!zigbee_is_stack_started()) { + zb_shell_print_error(shell, "Stack not started", ZB_FALSE); + return -ENOEXEC; + } + + if (zb_get_network_role() != ZB_NWK_DEVICE_TYPE_COORDINATOR) { + zb_shell_print_error(shell, "Device must be a coordinator", ZB_FALSE); + return -ENOEXEC; + } + + zb_bdb_set_legacy_device_support(1); + legacy_mode = ZB_TRUE; + + if (ZB_SCHEDULE_APP_CALLBACK(zb_bdb_set_legacy_device_support, legacy_mode)) { + zb_shell_print_error(shell, "Can not execute command", ZB_FALSE); + return -ENOEXEC; + } + + zb_shell_print_done(shell, ZB_FALSE); + return 0; +} + +/**@brief Disable the legacy device support. + * + * @code + * bdb legacy disable + * @endcode + * + * Disallow legacy pre-r21 devices on the Zigbee network. + * + * @pre Use only after @ref start "bdb start". + * + * Example: + * @code + * > bdb legacy disable + * Done + * @endcode + */ +static int cmd_zb_legacy_disable(const struct shell *shell, size_t argc, + char **argv) +{ + if (!zigbee_is_stack_started()) { + zb_shell_print_error(shell, "Stack not started", ZB_FALSE); + return -ENOEXEC; + } + + if (zb_get_network_role() != ZB_NWK_DEVICE_TYPE_COORDINATOR) { + zb_shell_print_error(shell, "Device must be a coordinator", ZB_FALSE); + return -ENOEXEC; + } + + zb_bdb_set_legacy_device_support(0); + legacy_mode = ZB_FALSE; + + if (ZB_SCHEDULE_APP_CALLBACK(zb_bdb_set_legacy_device_support, legacy_mode)) { + zb_shell_print_error(shell, "Can not execute command", ZB_FALSE); + return -ENOEXEC; + } + + zb_shell_print_done(shell, ZB_FALSE); + return 0; +} + +/**@brief Set network key. + * + * @code + * bdb nwkkey + * @endcode + * + * Set a pre-defined network key instead of a random one. + * + * @pre Setting only before @ref start "bdb start". + * + * Example: + * @code + * > bdb nwkkey 00112233445566778899aabbccddeeff + * Done + * @endcode + */ +static int cmd_zb_nwkkey(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + + if (zigbee_is_stack_started()) { + zb_shell_print_error(shell, "Stack already started", ZB_FALSE); + return -ENOEXEC; + } + + if (zigbee_is_nvram_initialised() +#ifdef CONFIG_ZIGBEE_SHELL_DEBUG_CMD + && zb_shell_nvram_enabled() +#endif /* CONFIG_ZIGBEE_SHELL_DEBUG_CMD */ + ) { + shell_warn( + shell, + "Zigbee stack has been configured in the past.\r\n" + "Please disable NVRAM to change the preconfigured network key."); + + zb_shell_print_error(shell, "Can't change NWK key - NVRAM not empty", ZB_FALSE); + return -ENOEXEC; + } + + zb_uint8_t key[ZB_CCM_KEY_SIZE]; + + if (parse_hex_str(argv[1], strlen(argv[1]), key, sizeof(key), false)) { + zb_secur_setup_nwk_key(key, 0); + } else { + zb_shell_print_error(shell, "Failed to parse key", ZB_FALSE); + return -EINVAL; + } + + zb_shell_print_done(shell, ZB_FALSE); + return 0; +} + +/**@brief Perform a factory reset via local action + * + * See Base Device Behavior specification chapter 9.5 for details. + * + * @code + * > bdb factory_reset + * Done + * @endcode + */ +static int cmd_zb_factory_reset(const struct shell *shell, size_t argc, + char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + /* Do not allow to call factory_reset before the ZBOSS stack is started. */ + if (!zigbee_is_stack_started()) { + zb_shell_print_error(shell, "Stack not started", ZB_FALSE); + return -ENOEXEC; + } + + ZB_SCHEDULE_APP_CALLBACK(zb_bdb_reset_via_local_action, 0); + zb_shell_print_done(shell, ZB_FALSE); + return 0; +} + +/**@brief Set amount of the child devices which can be connected to the device. + * + * @code + * bdb child_max + * @endcode + * + * @pre Setting only before @ref start "bdb start". + * + * If the argument is provided and `children_nbr` is in [0:32] range, + * set to that number. Otherwise, return error. + * + * + * Example: + * @code + * > bdb child_max 16 + * Setting max children to: 16 + * Done + * @endcode + */ +#ifndef ZB_ED_ROLE +static int cmd_child_max(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + + uint32_t child_max = 0xFFFFFFFF; + + /* Two argc - set the amount of the max_children. */ + if (zigbee_is_stack_started()) { + zb_shell_print_error(shell, "Stack already started", ZB_FALSE); + return -ENOEXEC; + } + + zb_shell_sscan_uint(argv[1], (uint8_t *)&child_max, 4, 10); + if (child_max > 32) { + zb_shell_print_error( + shell, + "Children device number must be within [0:32]", + ZB_FALSE); + return -EINVAL; + } else { + /* Set the value by calling ZBOSS API. */ + zb_set_max_children(child_max); + shell_print(shell, "Setting max children to: %d", child_max); + } + + zb_shell_print_done(shell, ZB_FALSE); + return 0; +} +#endif + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_ic, +#ifndef ZB_ED_ROLE + SHELL_CMD_ARG(add, NULL, IC_ADD_HELP, cmd_zb_install_code, 3, 0), + SHELL_CMD_ARG(list, NULL, IC_LIST_HELP, cmd_zb_install_code, 1, 0), + SHELL_CMD_ARG(policy, NULL, IC_POLICY_HELP, cmd_zb_install_code, 2, 0), +#endif + SHELL_CMD_ARG(set, NULL, IC_SET_HELP, cmd_zb_install_code, 2, 0), + SHELL_SUBCMD_SET_END); + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_legacy, + SHELL_CMD_ARG(disable, NULL, "Disable legacy mode.", + cmd_zb_legacy_disable, 1, 0), + SHELL_CMD_ARG(enable, NULL, "Enable legacy mode.", + cmd_zb_legacy_enable, 1, 0), + SHELL_SUBCMD_SET_END); + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_role, + SHELL_CMD_ARG(zc, NULL, "Set Coordinator role.", + cmd_zb_role_zc, 1, 0), + SHELL_CMD_ARG(zed, NULL, "Set End Device role.", + cmd_zb_role_zed, 1, 0), + SHELL_CMD_ARG(zr, NULL, "Set Router role.", + cmd_zb_role_zr, 1, 0), + SHELL_SUBCMD_SET_END); + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_bdb, + SHELL_CMD_ARG(channel, NULL, CHANNEL_HELP, cmd_zb_channel, 1, 1), +#ifndef ZB_ED_ROLE + SHELL_CMD_ARG(child_max, NULL, CHILD_MAX_HELP, cmd_child_max, 2, 0), +#endif + SHELL_CMD_ARG(extpanid, NULL, EXTPANID_HELP, cmd_zb_extpanid, 1, 1), + SHELL_CMD_ARG(factory_reset, NULL, "Perform factory reset.", + cmd_zb_factory_reset, 1, 0), + SHELL_CMD(ic, &sub_ic, "Install code manipulation.", NULL), + SHELL_CMD_ARG(legacy, &sub_legacy, "Get legacy mode.", cmd_zb_legacy, + 1, 0), + SHELL_CMD_ARG(nwkkey, NULL, NWKKEY_HELP, cmd_zb_nwkkey, 2, 0), + SHELL_CMD_ARG(panid, NULL, PANID_HELP, cmd_zb_panid, 1, 1), + SHELL_CMD_ARG(role, &sub_role, ("Set/get role."), cmd_zb_role, 1, 1), + SHELL_CMD_ARG(start, NULL, "Start commissionning.", cmd_zb_start, 1, 0), + SHELL_SUBCMD_SET_END); + +SHELL_CMD_REGISTER(bdb, &sub_bdb, "Base device behaviour manipulation.", NULL); diff --git a/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_generic_cmd.c b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_generic_cmd.c new file mode 100644 index 00000000..22f60065 --- /dev/null +++ b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_generic_cmd.c @@ -0,0 +1,583 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include + +#include +#include +#include +#include "zigbee_shell_utils.h" + + +/* CMD ID used to mark that frame is to be constructed as `zcl raw`. */ +#define ZCL_CMD_ID_RAW 0xFFFF + +LOG_MODULE_DECLARE(zigbee_shell, CONFIG_ZIGBEE_SHELL_LOG_LEVEL); + +/**@brief Function called when command is APS ACKed - prints done and invalidates ZCL command entry + * in context manager. + * + * @param[in] bufid Reference to a ZBOSS buffer containing zb_zcl_command_send_status_t data. + */ +static void zb_zcl_cmd_acked(zb_uint8_t bufid) +{ + uint8_t index = 0; + zb_ret_t zb_err_code = RET_OK; + zb_zcl_command_send_status_t *cmd_status = NULL; + struct ctx_entry *entry = NULL; + + if (bufid == ZB_BUF_INVALID) { + return; + } + + cmd_status = ZB_BUF_GET_PARAM(bufid, zb_zcl_command_send_status_t); + + /* Handle only success status. */ + if (cmd_status->status != RET_OK) { + goto exit; + } + + /* Find entry of CTX_MGR_GENERIC_CMD_ENTRY_TYPE type with matching buffer id. */ + entry = ctx_mgr_find_zcl_entry_by_bufid(bufid, CTX_MGR_GENERIC_CMD_ENTRY_TYPE); + + if (entry == NULL) { + LOG_ERR("Couldn't find matching entry for ZCL generic cmd"); + goto exit; + } + + /* Get index of the entry to cancel the timeout callback. */ + index = ctx_mgr_get_index_by_entry(entry); + + zb_shell_print_done(entry->shell, ZB_FALSE); + + zb_err_code = ZB_SCHEDULE_APP_ALARM_CANCEL(zb_shell_zcl_cmd_timeout_cb, index); + ZB_ERROR_CHECK(zb_err_code); + + ctx_mgr_delete_entry(entry); +exit: + zb_buf_free(bufid); +} + +/**@brief Function to construct ZCL command in the buffer. + * Function to be called in ZBOSS thread context to prevent non thread-safe operations. + * + * @param entry Pointer to the entry with frame to send in the context manager. + * + */ +static void construct_zcl_cmd_frame(struct ctx_entry *entry) +{ + struct generic_cmd *req_data = &(entry->zcl_data.generic_cmd); + + /* Get the ZCL packet sequence number. */ + entry->id = ZCL_CTX().seq_number; + + if (req_data->cmd_id != ZCL_CMD_ID_RAW) { + /* Start filling buffer with packet data. */ + entry->zcl_data.pkt_info.ptr = + ZB_ZCL_START_PACKET_REQ(entry->zcl_data.pkt_info.buffer) + + ZB_ZCL_CONSTRUCT_SPECIFIC_COMMAND_REQ_FRAME_CONTROL( + entry->zcl_data.pkt_info.ptr, + (req_data->def_resp)) + ZB_ZCL_CONSTRUCT_COMMAND_HEADER_REQ( + entry->zcl_data.pkt_info.ptr, + ZB_ZCL_GET_SEQ_NUM(), + req_data->cmd_id); + } else { + /* Start filling buffer with packet data. */ + entry->zcl_data.pkt_info.ptr = ZB_ZCL_START_PACKET(entry->zcl_data.pkt_info.buffer); + } + + /* Put payload in buffer with command to send. */ + for (zb_uint8_t i = 0; i < req_data->payload_length; i++) { + ZB_ZCL_PACKET_PUT_DATA8(entry->zcl_data.pkt_info.ptr, (req_data->payload[i])); + } +} + +/**@brief Actually send the ZCL command frame. + * + * @param index Index of the entry with frame to send in the context manager. + */ +static void zb_zcl_send_cmd_frame(zb_uint8_t index) +{ + zb_ret_t zb_err_code; + struct zcl_packet_info *packet_info; + struct ctx_entry *entry = ctx_mgr_get_entry_by_index(index); + + if (entry == NULL) { + LOG_ERR("Couldn't send the ZCL command frame - context entry %d not found", index); + return; + } + + construct_zcl_cmd_frame(entry); + + packet_info = &(entry->zcl_data.pkt_info); + + /* Send the actual frame. */ + zb_err_code = zb_zcl_finish_and_send_packet_new( + packet_info->buffer, + packet_info->ptr, + &(packet_info->dst_addr), + packet_info->dst_addr_mode, + packet_info->dst_ep, + packet_info->ep, + packet_info->prof_id, + packet_info->cluster_id, + packet_info->cb, + 0, + packet_info->disable_aps_ack, + 0); + + if (zb_err_code != RET_OK) { + zb_shell_print_error(entry->shell, "Can not send ZCL frame", ZB_FALSE); + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(packet_info->buffer); + zb_osif_enable_all_inter(); + + /* Invalidate an entry with frame data in the context manager. */ + ctx_mgr_delete_entry(entry); + } +} + +/**@brief Function to send the ZCL command frame. + * + * @param entry Pointer to the entry with frame to send in the context manager. + * + * @return Error code with function execution status. + */ +static int zcl_cmd_send(struct ctx_entry *entry) +{ + zb_ret_t zb_err_code; + uint8_t entry_index = ctx_mgr_get_index_by_entry(entry); + + zb_err_code = + ZB_SCHEDULE_APP_ALARM( + zb_shell_zcl_cmd_timeout_cb, + entry_index, + (CONFIG_ZIGBEE_SHELL_ZCL_CMD_TIMEOUT * ZB_TIME_ONE_SECOND)); + + if (zb_err_code != RET_OK) { + zb_shell_print_error(entry->shell, "Couldn't schedule timeout cb.", ZB_FALSE); + goto error; + } + + zb_err_code = ZB_SCHEDULE_APP_CALLBACK(zb_zcl_send_cmd_frame, entry_index); + if (zb_err_code != RET_OK) { + zb_shell_print_error(entry->shell, "Can not schedule zcl frame.", ZB_FALSE); + + zb_err_code = + ZB_SCHEDULE_APP_ALARM_CANCEL(zb_shell_zcl_cmd_timeout_cb, entry_index); + ZB_ERROR_CHECK(zb_err_code); + goto error; + } + + return 0; + +error: + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(entry->zcl_data.pkt_info.buffer); + zb_osif_enable_all_inter(); + + /* Invalidate an entry with frame data in the context manager. */ + ctx_mgr_delete_entry(entry); + + return -ENOEXEC; +} + +/**@brief Send generic command to the remote node. + * + * @code + * zcl cmd [-d] [-p h:profile] + * [-l h:payload] + * @endcode + * + * Send generic command with ID `cmd_ID` with payload `payload` to the cluster + * `cluster`. The cluster belongs to the profile `profile`, which resides + * on the endpoint `ep` of the remote node `dst_addr`. Optional default + * response can be set with `-d`. + * + * @note By default profile is set to Home Automation Profile + * @note By default payload is empty + * @note To send via binding table, set `dst_addr` and `ep` to 0. + * + */ +int cmd_zb_generic_cmd(const struct shell *shell, size_t argc, char **argv) +{ + size_t len; + int ret_val; + zb_bufid_t bufid; + struct generic_cmd *req_data; + struct zcl_packet_info *packet_info; + char **arg = &argv[1]; + struct ctx_entry *entry = ctx_mgr_new_entry(CTX_MGR_GENERIC_CMD_ENTRY_TYPE); + + ARG_UNUSED(argc); + + if (entry == NULL) { + zb_shell_print_error( + shell, + "Request pool empty - wait for ongoing command to finish", + ZB_FALSE); + return -ENOEXEC; + } + + req_data = &(entry->zcl_data.generic_cmd); + packet_info = &(entry->zcl_data.pkt_info); + + /* Set default command values. */ + req_data->def_resp = ZB_ZCL_DISABLE_DEFAULT_RESPONSE; + req_data->payload_length = 0; + packet_info->prof_id = ZB_AF_HA_PROFILE_ID; + + /* Check if default response should be requested. */ + if (strcmp(*arg, "-d") == 0) { + req_data->def_resp = ZB_ZCL_ENABLE_DEFAULT_RESPONSE; + arg++; + } + + /* Parse and check remote node address. */ + packet_info->dst_addr_mode = parse_address(*arg, &(packet_info->dst_addr), ADDR_ANY); + + if (packet_info->dst_addr_mode == ADDR_INVALID) { + /* Handle the Binding table case (address == '0'). */ + if (!strcmp(*arg, "0")) { + packet_info->dst_addr_mode = ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + packet_info->dst_addr.addr_short = 0; + } else { + zb_shell_print_error(shell, "Wrong address format", ZB_FALSE); + goto error; + } + } + arg++; + + /* Read endpoint and cluster id. */ + ret_val = zb_shell_sscan_uint8(*(arg++), &(packet_info->dst_ep)); + if (ret_val == 0) { + zb_shell_print_error(shell, "Remote ep value", ZB_FALSE); + goto error; + } + /* Handle sending to group address. */ + if ((packet_info->dst_addr_mode == ZB_APS_ADDR_MODE_16_ENDP_PRESENT) && + (packet_info->dst_ep == 0)) { + zb_uint16_t group_addr = packet_info->dst_addr.addr_short; + + /* Verify group address. */ + if ((group_addr < ZB_ZCL_ATTR_SCENES_CURRENT_GROUP_MIN_VALUE) + || (group_addr > ZB_ZCL_ATTR_SCENES_CURRENT_GROUP_MAX_VALUE)) { + zb_shell_print_error(shell, "Incorrect group address", ZB_FALSE); + goto error; + } + + packet_info->dst_addr_mode = ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT; + } + + ret_val = parse_hex_u16(*(arg++), &(packet_info->cluster_id)); + if (ret_val == 0) { + zb_shell_print_error(shell, "Cluster id value", ZB_FALSE); + goto error; + } + + /* Check if different from HA profile should be used. */ + if (strcmp(*arg, "-p") == 0) { + ret_val = parse_hex_u16(*(++arg), &(packet_info->prof_id)); + if (ret_val == 0) { + zb_shell_print_error(shell, "Profile id value", ZB_FALSE); + goto error; + } + arg++; + } + + /* Read command id. */ + ret_val = parse_hex_u16(*(arg++), &(req_data->cmd_id)); + if (ret_val == 0) { + zb_shell_print_error(shell, "Cmd id value", ZB_FALSE); + goto error; + } + + /* Check if payload should be sent. */ + if (strcmp(*(arg++), "-l") == 0) { + /* In case payload starts with 0x, skip it and warn user. */ + if (((*arg)[0] == '0') && (tolower((*arg)[1]) == 'x')) { + *arg += 2; + shell_warn(shell, "Trimming \"0x\" from the payload"); + } + + len = strlen(*arg); + if (len > (2 * CMD_PAYLOAD_SIZE)) { + shell_warn(shell, + "Payload length is too big, trimming it to first %d bytes", + CMD_PAYLOAD_SIZE); + len = (2 * CMD_PAYLOAD_SIZE); + } + if ((len != 0) && (len % 2) != 0) { + zb_shell_print_error(shell, "Payload length is not even", ZB_FALSE); + goto error; + } + ret_val = parse_hex_str(*arg, len, req_data->payload, CMD_PAYLOAD_SIZE, false); + if (ret_val == 0) { + zb_shell_print_error(shell, "Payload value", ZB_FALSE); + goto error; + } + req_data->payload_length = len / 2; + } + + /* Set shell to print logs to. */ + entry->shell = shell; + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + bufid = zb_buf_get_out(); + zb_osif_enable_all_inter(); + + if (!bufid) { + zb_shell_print_error(entry->shell, "Can not get buffer.", ZB_FALSE); + /* Mark data structure as free. */ + ctx_mgr_delete_entry(entry); + return -ENOEXEC; + } + + packet_info->buffer = bufid; + /* DstAddr, Dst Addr Mode and Dst endpoint are already set. */ + packet_info->ep = zb_shell_get_endpoint(); + /* Profile ID and Cluster ID are already set. */ + /* If no default response requested, set cb function to call when cmd is APS ACKed. */ + if (req_data->def_resp == ZB_ZCL_DISABLE_DEFAULT_RESPONSE) { + packet_info->cb = zb_zcl_cmd_acked; + } + packet_info->disable_aps_ack = ZB_FALSE; + + return zcl_cmd_send(entry); +error: + /* Mark data structure as free. */ + ctx_mgr_delete_entry(entry); + return -EINVAL; +} + +/**@brief Send raw ZCL frame. + * + * @code + * zcl raw + * @endcode + * + * Send raw ZCL frame with payload `payload` to the cluster `cluster`. + * The cluster belongs to the profile `profile`, which resides + * on the endpoint `ep` of the remote node `dst_addr`. The payload represents + * the ZCL Header plus ZCL Payload. + * + * @note To send via binding table, set `dst_addr` and `ep` to 0. + * + */ +int cmd_zb_zcl_raw(const struct shell *shell, size_t argc, char **argv) +{ + size_t len; + int ret_val; + zb_bufid_t bufid; + struct generic_cmd *req_data; + struct zcl_packet_info *packet_info; + char **arg = &argv[1]; + struct ctx_entry *entry; + + ARG_UNUSED(argc); + + /* Debug mode quick return. */ + if (!zb_shell_debug_get()) { + zb_shell_print_error( + shell, + "This command is available only in debug mode. Run 'debug on' to enable it", + ZB_FALSE); + return -ENOEXEC; + } + + entry = ctx_mgr_new_entry(CTX_MGR_GENERIC_CMD_ENTRY_TYPE); + + if (entry == NULL) { + zb_shell_print_error( + shell, + "Request pool empty - wait for ongoing command to finish", + ZB_FALSE); + return -ENOEXEC; + } + + req_data = &(entry->zcl_data.generic_cmd); + packet_info = &(entry->zcl_data.pkt_info); + + /* Reset the counter. */ + req_data->payload_length = 0; + + /* Parse and check remote node address. */ + packet_info->dst_addr_mode = parse_address(*arg, &(packet_info->dst_addr), ADDR_ANY); + + if (packet_info->dst_addr_mode == ADDR_INVALID) { + /* Handle the Binding table case (address == '0'). */ + if (!strcmp(*arg, "0")) { + packet_info->dst_addr_mode = ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + packet_info->dst_addr.addr_short = 0; + } else { + zb_shell_print_error(shell, "Wrong address format", ZB_FALSE); + goto error; + } + } + arg++; + + /* Read endpoint and cluster id. */ + ret_val = zb_shell_sscan_uint8(*(arg++), &(packet_info->dst_ep)); + if (ret_val == 0) { + zb_shell_print_error(shell, "Remote ep value", ZB_FALSE); + goto error; + } + /* Handle sending to group address. */ + if ((packet_info->dst_addr_mode == ZB_APS_ADDR_MODE_16_ENDP_PRESENT) && + (packet_info->dst_ep == 0)) { + zb_uint16_t group_addr = packet_info->dst_addr.addr_short; + + /* Verify group address. */ + if ((group_addr < ZB_ZCL_ATTR_SCENES_CURRENT_GROUP_MIN_VALUE) + || (group_addr > ZB_ZCL_ATTR_SCENES_CURRENT_GROUP_MAX_VALUE)) { + zb_shell_print_error(shell, "Incorrect group address", ZB_FALSE); + goto error; + } + + packet_info->dst_addr_mode = ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT; + } + + ret_val = parse_hex_u16(*(arg++), &(packet_info->cluster_id)); + if (ret_val == 0) { + zb_shell_print_error(shell, "Cluster id value", ZB_FALSE); + goto error; + } + + ret_val = parse_hex_u16(*(arg++), &(packet_info->prof_id)); + if (ret_val == 0) { + zb_shell_print_error(shell, "Profile id value", ZB_FALSE); + goto error; + } + + /* Reuse the payload field from the context. + * In case payload starts with 0x, skip it and warn user. + */ + if (((*arg)[0] == '0') && (tolower((*arg)[1]) == 'x')) { + *arg += 2; + shell_warn(shell, "Trimming \"0x\" from the payload"); + } + + len = strlen(*arg); + if (len > (2 * CMD_PAYLOAD_SIZE)) { + shell_warn(shell, + "Raw data length is too big, trimming it to first %d bytes", + CMD_PAYLOAD_SIZE); + len = (2 * CMD_PAYLOAD_SIZE); + } + + if ((len != 0) && (len % 2) != 0) { + zb_shell_print_error(shell, "Payload length is not even", ZB_FALSE); + goto error; + } + + ret_val = parse_hex_str(*arg, len, req_data->payload, CMD_PAYLOAD_SIZE, false); + if (ret_val == 0) { + zb_shell_print_error(shell, "Payload value", ZB_FALSE); + goto error; + } + req_data->payload_length = len / 2; + + /* Disable default response for zcl raw command. */ + req_data->def_resp = ZB_ZCL_DISABLE_DEFAULT_RESPONSE; + + /* Set CMD ID to ZCL_CMD_ID_RAW to mark this frame as `zcl raw`. */ + req_data->cmd_id = ZCL_CMD_ID_RAW; + + /* Set shell to print logs to. */ + entry->shell = shell; + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + bufid = zb_buf_get_out(); + zb_osif_enable_all_inter(); + + if (!bufid) { + zb_shell_print_error(entry->shell, "Can not get buffer.", ZB_FALSE); + /* Mark data structure as free - delete entry. */ + ctx_mgr_delete_entry(entry); + return -ENOEXEC; + } + + packet_info->buffer = bufid; + /* DstAddr, Dst Addr Mode and Dst endpoint are already set. */ + packet_info->ep = zb_shell_get_endpoint(); + /* Profile ID and Cluster ID are already set. */ + packet_info->cb = zb_zcl_cmd_acked; + packet_info->disable_aps_ack = ZB_FALSE; + + return zcl_cmd_send(entry); +error: + /* Mark data structure as free. */ + ctx_mgr_delete_entry(entry); + return -EINVAL; +} + +/**@brief The Handler to 'intercept' every frame coming to the endpoint. + * + * @param bufid Reference to a ZBOSS buffer. + */ +zb_uint8_t zb_shell_ep_handler_generic_cmd(zb_bufid_t bufid) +{ + zb_ret_t zb_err_code; + struct ctx_entry *entry; + zb_zcl_parsed_hdr_t *cmd_info = ZB_BUF_GET_PARAM(bufid, zb_zcl_parsed_hdr_t); + + /* Find command context by ZCL sequence number. */ + entry = ctx_mgr_find_ctx_entry(cmd_info->seq_number, CTX_MGR_GENERIC_CMD_ENTRY_TYPE); + + if (entry == NULL) { + return ZB_FALSE; + } + + if (!zb_shell_is_zcl_cmd_response(cmd_info, entry)) { + return ZB_FALSE; + } + + if (cmd_info->cmd_id == ZB_ZCL_CMD_DEFAULT_RESP) { + zb_zcl_default_resp_payload_t *def_resp = ZB_ZCL_READ_DEFAULT_RESP(bufid); + + /* Print info received from default response. */ + shell_fprintf(entry->shell, + (def_resp->status == ZB_ZCL_STATUS_SUCCESS) ? + SHELL_INFO : SHELL_ERROR, + "Default Response received: "); + shell_fprintf(entry->shell, + (def_resp->status == ZB_ZCL_STATUS_SUCCESS) ? + SHELL_INFO : SHELL_ERROR, + "Command: %d, Status: %d\n", + def_resp->command_id, + def_resp->status); + + if (def_resp->status != ZB_ZCL_STATUS_SUCCESS) { + zb_shell_print_error(entry->shell, "Command not successful", ZB_FALSE); + } else { + zb_shell_print_done(entry->shell, ZB_FALSE); + } + } else { + /* In case of unknown response. */ + return ZB_FALSE; + } + /* Cancel the ongoing alarm which was to delete entry in the context manager ... */ + if (entry->zcl_data.generic_cmd.def_resp == ZB_ZCL_ENABLE_DEFAULT_RESPONSE) { + zb_err_code = ZB_SCHEDULE_APP_ALARM_CANCEL(zb_shell_zcl_cmd_timeout_cb, + ctx_mgr_get_index_by_entry(entry)); + ZB_ERROR_CHECK(zb_err_code); + + /* ...and erase it manually. */ + ctx_mgr_delete_entry(entry); + } + zb_buf_free(bufid); + + return ZB_TRUE; +} diff --git a/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_nbr.c b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_nbr.c new file mode 100644 index 00000000..0bfb24bf --- /dev/null +++ b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_nbr.c @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include + +#include +#include +#include "zigbee_shell_utils.h" + +#define MONITOR_ON_HELP \ + ("Start monitoring the list of active Zigbee neighbors.\n" \ + "Usage: on") +#define MONITOR_OFF_HELP \ + ("Stop monitoring the list of active Zigbee neighbors.\n" \ + "Usage: off") +#define MONITOR_TRIGGER_HELP \ + ("Trigger logging the list of active Zigbee neighbors.\n" \ + "Usage: trigger") + +#define IEEE_ADDR_BUF_SIZE 17 +#define UPDATE_COUNT_RELOAD 0xFFFFFFFF + + +struct active_nbr_cache_entry { + zb_ieee_addr_t ieee_addr; + zb_uint8_t device_type; + zb_uint8_t relationship; + zb_uint8_t outgoing_cost; /*!< The cost of an outgoing link. Got from link status. */ + zb_uint8_t age; /*!< Counter value for router aging. */ + zb_uint32_t timeout_counter; /*!< Timeout value ED aging, in milliseconds. */ +}; + +struct active_nbr_cache { + struct active_nbr_cache_entry entries[CONFIG_ZIGBEE_SHELL_MONITOR_CACHE_SIZE]; + zb_uint32_t n_entries; +}; + + +/* Use constant logging level. + * There is no point in eabling neighbor table monitor and suppressing its logs. + */ +LOG_MODULE_REGISTER(zigbee_shell_nbr, LOG_LEVEL_INF); + +static struct active_nbr_cache nbr_cache; +static zb_bool_t monitor_active; +static zb_bufid_t monitor_buf; +static zb_uint32_t last_update_count = UPDATE_COUNT_RELOAD; +static const char * const device_type_name[] = { + "coordinator", + "router ", + "end device ", + "unknown " +}; +static const char * const relationship_name[] = { + "parent ", + "child ", + "sibling ", + "unknown ", + "former child ", + "unauthenticated child" +}; + +static void refresh_active_nbt_table(zb_bufid_t bufid); + +static void active_nbr_clear(void) +{ + nbr_cache.n_entries = 0; +} + +static void active_nbr_store(zb_nwk_nbr_iterator_entry_t *entry) +{ + if (nbr_cache.n_entries < CONFIG_ZIGBEE_SHELL_MONITOR_CACHE_SIZE) { + ZB_IEEE_ADDR_COPY( + nbr_cache.entries[nbr_cache.n_entries].ieee_addr, + entry->ieee_addr); + nbr_cache.entries[nbr_cache.n_entries].device_type = entry->device_type; + nbr_cache.entries[nbr_cache.n_entries].relationship = entry->relationship; + nbr_cache.entries[nbr_cache.n_entries].outgoing_cost = entry->outgoing_cost; + nbr_cache.entries[nbr_cache.n_entries].age = entry->age; + nbr_cache.entries[nbr_cache.n_entries].timeout_counter = entry->timeout_counter; + + nbr_cache.n_entries++; + } +} + +static void active_nbr_log(void) +{ + zb_uint32_t i; + char ieee_addr_buf[IEEE_ADDR_BUF_SIZE] = { 0 }; + int addr_len; + int age; + int timeout; + + LOG_INF("Active neighbor table (%d entries):", nbr_cache.n_entries); + LOG_INF("[idx] ext_addr device_type relationship cost age timeout"); + + for (i = 0; i < nbr_cache.n_entries; i++) { + addr_len = + ieee_addr_to_str(ieee_addr_buf, + sizeof(ieee_addr_buf), + nbr_cache.entries[i].ieee_addr); + if (addr_len < 0) { + strcpy(ieee_addr_buf, "unknown"); + } + + ZB_ASSERT(nbr_cache.entries[i].device_type < 4); + ZB_ASSERT(nbr_cache.entries[i].relationship < 6); + + if (nbr_cache.entries[i].device_type == 2) { + timeout = nbr_cache.entries[i].timeout_counter; + age = -1; + } else { + timeout = -1; + age = nbr_cache.entries[i].age; + } + + LOG_INF("[%3u] %s %s %s %4u %3d %d", + i, + ieee_addr_buf, + device_type_name[nbr_cache.entries[i].device_type], + relationship_name[nbr_cache.entries[i].relationship], + nbr_cache.entries[i].outgoing_cost, + age, + timeout); + } +} + +static void refresh_active_nbt_table_delayed(zb_bufid_t bufid) +{ + zb_ret_t error_code; + + error_code = zb_nwk_nbr_iterator_next(bufid, refresh_active_nbt_table); + ZB_ERROR_CHECK(error_code); +} + +static void refresh_active_nbt_table(zb_bufid_t bufid) +{ + static zb_bool_t cache_updated = ZB_FALSE; + zb_ret_t error_code; + zb_nwk_nbr_iterator_params_t *args = ZB_BUF_GET_PARAM(bufid, zb_nwk_nbr_iterator_params_t); + zb_nwk_nbr_iterator_entry_t *entry = (zb_nwk_nbr_iterator_entry_t *)zb_buf_begin(bufid); + + /* Check if the function processes the newest active request. */ + if ((bufid != monitor_buf) || (monitor_active == ZB_FALSE)) { + zb_buf_free(bufid); + return; + } + + /* Initialize the params structure if this is the first API call. */ + if (last_update_count == UPDATE_COUNT_RELOAD) { + args->update_count = 0; + args->index = ZB_NWK_NBR_ITERATOR_INDEX_EOT; + zb_buf_set_status(bufid, RET_OK); + } + + error_code = zb_buf_get_status(bufid); + ZB_ERROR_CHECK(error_code); + + if (args->update_count != last_update_count) { + /* Counter skew - restart the read process. */ + active_nbr_clear(); + args->index = 0; + last_update_count = args->update_count; + cache_updated = ZB_TRUE; + } else if (args->index != ZB_NWK_NBR_ITERATOR_INDEX_EOT) { + /* Counter correct, regular entry - add it to the cache. */ + active_nbr_store(entry); + args->index++; + cache_updated = ZB_TRUE; + } else if (cache_updated) { + /* Counter correct, last entry received. + * Previous call contained a regular entry - log the table. + */ + active_nbr_log(); + cache_updated = ZB_FALSE; + } + + if (args->index != ZB_NWK_NBR_ITERATOR_INDEX_EOT) { + /* Regular entry received - call the API immediately. */ + error_code = zb_nwk_nbr_iterator_next(bufid, refresh_active_nbt_table); + ZB_ERROR_CHECK(error_code); + } else { + /* Regular entry not present - call the API after 1 second. */ + ZB_SCHEDULE_APP_ALARM( + refresh_active_nbt_table_delayed, + bufid, + ZB_TIME_ONE_SECOND); + } +} + +/**@brief Enable, disable or refresh neighbor table monitor. + * + * @code + * nbr monitor {on,off,trigger} + * @endcode + * + * Register a neighbor table monitoring routine and log all entries once + * a new entry is created or an existing one is removed. + * The trigger command can be used to trigger logging the current state + * of the neighbor table. + * + * Example: + * @code + * nbr monitor on + * @endcode + * + */ +static int cmd_zb_nbr_monitor(const struct shell *shell, size_t argc, char **argv) +{ + zb_bool_t start; + + ARG_UNUSED(argc); + if (strcmp(argv[0], "trigger") == 0) { + /* This will trigger logging (if monitor is active). */ + last_update_count = UPDATE_COUNT_RELOAD; + zb_shell_print_done(shell, ZB_FALSE); + return 0; + } + + if (strcmp(argv[0], "on") == 0) { + start = ZB_TRUE; + } else { + start = ZB_FALSE; + } + + if (monitor_active == start) { + /* Make sure that the monitor is not started twice. */ + zb_shell_print_done(shell, ZB_FALSE); + return 0; + } + + if (start == ZB_TRUE) { + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + monitor_buf = zb_buf_get_out(); + zb_osif_enable_all_inter(); + + if (!monitor_buf) { + zb_shell_print_error( + shell, + "Failed to execute command (buf alloc failed)", + ZB_FALSE); + return -ENOEXEC; + } + + last_update_count = UPDATE_COUNT_RELOAD; + monitor_active = ZB_TRUE; + ZB_SCHEDULE_APP_CALLBACK(refresh_active_nbt_table, monitor_buf); + } else if (monitor_active) { + monitor_active = ZB_FALSE; + /* monitor_buf will be freed from refresh_active_nbt_table. */ + } + + zb_shell_print_done(shell, ZB_FALSE); + return 0; +} + + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_monitor, + SHELL_CMD_ARG(on, NULL, MONITOR_ON_HELP, cmd_zb_nbr_monitor, 1, 0), + SHELL_CMD_ARG(off, NULL, MONITOR_OFF_HELP, cmd_zb_nbr_monitor, 1, 0), + SHELL_CMD_ARG(trigger, NULL, MONITOR_TRIGGER_HELP, cmd_zb_nbr_monitor, 1, 0), + SHELL_SUBCMD_SET_END); + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_nbr, + SHELL_CMD(monitor, &sub_monitor, + "Enable/disable neighbor table monitoring.", NULL), + SHELL_SUBCMD_SET_END); + +SHELL_CMD_REGISTER(nbr, &sub_nbr, "Zigbee neighbor table.", NULL); diff --git a/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_nvram.c b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_nvram.c new file mode 100644 index 00000000..4d9a3ac9 --- /dev/null +++ b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_nvram.c @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include + +#include +#include +#include +#include "zigbee_shell_utils.h" + + +#ifdef CONFIG_ZIGBEE_SHELL_DEBUG_CMD +static zb_bool_t nvram_enabled = ZB_TRUE; + +/**@brief Enable Zigbee NVRAM + * + * @code + * nvram enable + * @endcode + * + */ +static int cmd_zb_nvram_enable(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + if (zigbee_is_stack_started()) { + zb_shell_print_error(shell, "Stack already started", ZB_FALSE); + return -ENOEXEC; + } + + /* Keep Zigbee NVRAM after device reboot or power-off. */ + zb_set_nvram_erase_at_start(ZB_FALSE); + nvram_enabled = ZB_TRUE; + zb_shell_print_done(shell, ZB_FALSE); + + return 0; +} + +/**@brief Disable Zigbee NVRAM + * + * @note If disabled, the Zigbee NVRAM pages are used, but they will be erased + * during the stack startup sequence, so the device will behave as if + * it has stored the NVRAM data in volatile memory. + * + * @code + * nvram disable + * @endcode + * + */ +static int cmd_zb_nvram_disable(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + if (zigbee_is_stack_started()) { + zb_shell_print_error(shell, "Stack already started", ZB_FALSE); + return -ENOEXEC; + } + + /* Erase Zigbee NVRAM after device reboot or power-off. */ + zb_set_nvram_erase_at_start(ZB_TRUE); + nvram_enabled = ZB_FALSE; + zb_shell_print_done(shell, ZB_FALSE); + + return 0; +} + +zb_bool_t zb_shell_nvram_enabled(void) +{ + return nvram_enabled; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_nvram, + SHELL_CMD_ARG(enable, NULL, "Enable Zigbee NVRAM.", + cmd_zb_nvram_enable, 1, 0), + SHELL_CMD_ARG(disable, NULL, "Disable Zigbee NVRAM.", + cmd_zb_nvram_disable, 1, 0), + SHELL_SUBCMD_SET_END); + +SHELL_CMD_REGISTER(nvram, &sub_nvram, "Zigbee NVRAM manipulation.", + NULL); +#endif diff --git a/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_ping.c b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_ping.c new file mode 100644 index 00000000..6462eb32 --- /dev/null +++ b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_ping.c @@ -0,0 +1,783 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include + +#include +#include +#include "zigbee_shell_utils.h" + + +/** @brief ZCL Frame control field of Zigbee PING commands. + */ + +#define ZIGBEE_PING_FRAME_CONTROL_FIELD 0x11 + +LOG_MODULE_REGISTER(zigbee_shell_ping, CONFIG_ZIGBEE_SHELL_LOG_LEVEL); + +static uint8_t ping_seq_num; +static ping_time_cb_t ping_ind_cb; + +static zb_uint32_t get_request_duration(struct ctx_entry *req_data); + +/**@brief Invalidate an entry with ping request data after the timeout. + * This function is called as the ZBOSS callback. + * + * @param index Index to context entry with ping data to invalidate. + */ +static void invalidate_ping_entry_cb(zb_uint8_t index) +{ + uint32_t delay_ms; + struct ctx_entry *ping_entry = ctx_mgr_get_entry_by_index(index); + + if (ping_entry) { + delay_ms = get_request_duration(ping_entry); + /* Inform user about timeout event. */ + if (ping_entry->zcl_data.ping_req.cb) { + ping_entry->zcl_data.ping_req.cb(PING_EVT_FRAME_TIMEOUT, + delay_ms, + ping_entry); + } + + ctx_mgr_delete_entry(ping_entry); + } else { + LOG_ERR("Couldn't get ping entry %d- entry not found", index); + } +} + +/**@brief Get the first entry with ping request sent to addr_short. + * + * @param addr_short Short network address to look for. + * + * @return Pointer to the ping request entry, NULL if none. + */ +static struct ctx_entry *find_ping_entry_by_short(zb_uint16_t addr_short) +{ + int i; + zb_addr_u req_remote_addr; + + for (i = 0; i < CONFIG_ZIGBEE_SHELL_CTX_MGR_ENTRIES_NBR; i++) { + struct ctx_entry *ping_entry = ctx_mgr_get_entry_by_index(i); + + if (!ping_entry) { + continue; + } + + if ((ping_entry->type == CTX_MGR_PING_REQ_ENTRY_TYPE) && ping_entry->taken) { + req_remote_addr = ping_entry->zcl_data.pkt_info.dst_addr; + } else { + continue; + } + + if (ping_entry->zcl_data.pkt_info.dst_addr_mode == + ZB_APS_ADDR_MODE_16_ENDP_PRESENT) { + if (req_remote_addr.addr_short == addr_short) { + return ping_entry; + } + } else { + if (zb_address_short_by_ieee(req_remote_addr.addr_long) == addr_short) { + return ping_entry; + } + } + } + + return NULL; +} + +/**@brief Function to actually send a ping frame. + * + * @param index Index of context manager entry with ping data to send. + */ +static void zb_zcl_send_ping_frame(zb_uint8_t index) +{ + zb_ret_t zb_err_code; + struct zcl_packet_info *packet_info; + struct ctx_entry *ping_entry = ctx_mgr_get_entry_by_index(index); + + if (!ping_entry) { + LOG_ERR("Couldn't send ping frame - context entry not found"); + return; + } + + if (ping_entry->type == CTX_MGR_PING_REQ_ENTRY_TYPE) { + packet_info = &(ping_entry->zcl_data.pkt_info); + + /* Capture the sending time. */ + ping_entry->zcl_data.ping_req.sent_time = k_uptime_ticks(); + } else { + packet_info = &(ping_entry->zcl_data.pkt_info); + } + + /* Send the actual frame. */ + zb_err_code = zb_zcl_finish_and_send_packet_new( + packet_info->buffer, + packet_info->ptr, + &(packet_info->dst_addr), + packet_info->dst_addr_mode, + packet_info->dst_ep, + packet_info->ep, + packet_info->prof_id, + packet_info->cluster_id, + packet_info->cb, + 0, + packet_info->disable_aps_ack, + 0); + + if (ping_entry->type == CTX_MGR_PING_REQ_ENTRY_TYPE) { + if (zb_err_code != RET_OK) { + zb_shell_print_error(ping_entry->shell, "Can not send zcl frame", ZB_FALSE); + zb_buf_free(packet_info->buffer); + ctx_mgr_delete_entry(ping_entry); + return; + } + + zb_err_code = ZB_SCHEDULE_APP_ALARM(invalidate_ping_entry_cb, + index, + ZB_MILLISECONDS_TO_BEACON_INTERVAL( + ping_entry->zcl_data.ping_req.timeout_ms)); + + if (zb_err_code != RET_OK) { + zb_shell_print_error( + ping_entry->shell, + "Can not schedule timeout alarm.", + ZB_FALSE); + ctx_mgr_delete_entry(ping_entry); + return; + } + + if (ping_entry->zcl_data.ping_req.cb) { + uint32_t time_diff = get_request_duration(ping_entry); + + ping_entry->zcl_data.ping_req.cb(PING_EVT_FRAME_SCHEDULED, + time_diff, + ping_entry); + } + } else { + if (zb_err_code != RET_OK) { + zb_shell_print_error(ping_entry->shell, "Can not send zcl frame", ZB_FALSE); + zb_buf_free(packet_info->buffer); + } + /* We don't need the entry anymore, + * since we're not expecting any reply to a Ping Reply. + */ + ctx_mgr_delete_entry(ping_entry); + } +} + +/**@brief Get time difference, in milliseconds between ping request time + * and current time. + * + * @param[in] req_data Pointer to the ping request structure, + * from which the time difference should be calculated. + * + * @return Time difference in milliseconds. + */ +static zb_uint32_t get_request_duration(struct ctx_entry *req_data) +{ + uint32_t time_diff_ms; + int32_t time_diff_ticks; + + /* Calculate the time difference between request being sent + * and reply being received. + */ + time_diff_ticks = k_uptime_ticks() - req_data->zcl_data.ping_req.sent_time; + time_diff_ms = k_ticks_to_ms_near32(time_diff_ticks); + + return time_diff_ms; +} + +static void frame_acked_cb(zb_bufid_t bufid) +{ + if (bufid) { + zb_buf_free(bufid); + } +} + +/**@brief Default handler for incoming ping request APS acknowledgments. + * + * @details If there is a user callback defined for the acknowledged request, + * the callback with PING_EVT_ACK_RECEIVED event will be called. + * + * @param[in] bufid Reference to a ZBOSS buffer containing APC ACK data. + */ +static void dispatch_user_callback(zb_bufid_t bufid) +{ + zb_uint16_t short_addr; + zb_ret_t zb_err_code = RET_OK; + struct ctx_entry *req_data = NULL; + zb_zcl_command_send_status_t *ping_cmd_status; + + if (bufid == 0) { + return; + } + + ping_cmd_status = ZB_BUF_GET_PARAM(bufid, zb_zcl_command_send_status_t); + + if (ping_cmd_status->dst_addr.addr_type == ZB_ZCL_ADDR_TYPE_SHORT) { + short_addr = ping_cmd_status->dst_addr.u.short_addr; + } else if (ping_cmd_status->dst_addr.addr_type == ZB_ZCL_ADDR_TYPE_IEEE) { + short_addr = zb_address_short_by_ieee(ping_cmd_status->dst_addr.u.ieee_addr); + } else { + LOG_ERR("Ping request acked with an unknown dest address type: %d", + ping_cmd_status->dst_addr.addr_type); + zb_buf_free(bufid); + return; + } + + req_data = find_ping_entry_by_short(short_addr); + + if (req_data != NULL) { + uint32_t delay_ms = get_request_duration(req_data); + + if (ping_cmd_status->status == RET_OK) { + /* Inform user about ACK reception. */ + if (req_data->zcl_data.ping_req.cb) { + if (req_data->zcl_data.ping_req.request_ack == 0) { + req_data->zcl_data.ping_req.cb(PING_EVT_FRAME_SENT, + delay_ms, + req_data); + } else { + req_data->zcl_data.ping_req.cb(PING_EVT_ACK_RECEIVED, + delay_ms, + req_data); + } + } + + /* If only ACK was requested, cancel ongoing alarm. */ + if (req_data->zcl_data.ping_req.request_echo == 0) { + zb_err_code = ZB_SCHEDULE_APP_ALARM_CANCEL( + invalidate_ping_entry_cb, + ctx_mgr_get_index_by_entry(req_data)); + ZB_ERROR_CHECK(zb_err_code); + + ctx_mgr_delete_entry(req_data); + } + } else { + LOG_ERR("Ping request returned error status: %d", + ping_cmd_status->status); + } + } else { + LOG_ERR("Couldn't find entry with ping request sent to %#.4x", + short_addr); + } + + zb_buf_free(bufid); +} + +/**@brief Default ping event handler. Prints out measured time on the shell and exits. + * + * @param[in] evt Type of received ping acknowledgment + * @param[in] delay_ms Time, in milliseconds, between ping request + * and the event. + * @param[in] req_data Pointer to the context manager entry with ongoing + * ping request data. + */ +static void ping_shell_evt_handler(enum ping_time_evt evt, zb_uint32_t delay_ms, + struct ctx_entry *req_data) +{ + switch (evt) { + case PING_EVT_FRAME_SCHEDULED: + break; + + case PING_EVT_FRAME_TIMEOUT: + shell_error(req_data->shell, "Error: Request timed out after %u ms.", delay_ms); + break; + + case PING_EVT_ECHO_RECEIVED: + shell_print(req_data->shell, "Ping time: %u ms", delay_ms); + zb_shell_print_done(req_data->shell, ZB_FALSE); + break; + + case PING_EVT_ACK_RECEIVED: + if (req_data->zcl_data.ping_req.request_echo == 0) { + shell_print(req_data->shell, "Ping time: %u ms", delay_ms); + zb_shell_print_done(req_data->shell, ZB_FALSE); + } + break; + + case PING_EVT_FRAME_SENT: + if ((req_data->zcl_data.ping_req.request_echo == 0) && + (req_data->zcl_data.ping_req.request_ack == 0)) { + zb_shell_print_done(req_data->shell, ZB_FALSE); + } + break; + + case PING_EVT_ERROR: + zb_shell_print_error(req_data->shell, "Unable to send ping request", ZB_FALSE); + break; + + default: + LOG_ERR("Unknown ping event received: %d", evt); + break; + } +} + +void zb_ping_set_ping_indication_cb(ping_time_cb_t cb) +{ + ping_ind_cb = cb; +} + +/**@brief Actually construct the Ping Request frame and send it. + * + * @param ping_entry Pointer to the context manager entry with ping + * request data. + */ +static void ping_request_send(struct ctx_entry *ping_entry) +{ + zb_ret_t zb_err_code; + zb_bufid_t bufid; + zb_uint8_t *cmd_buf_ptr; + zb_uint8_t shell_ep = zb_shell_get_endpoint(); + struct zcl_packet_info *packet_info = &(ping_entry->zcl_data.pkt_info); + + if (ping_entry->zcl_data.ping_req.count > PING_MAX_LENGTH) { + if (ping_entry->zcl_data.ping_req.cb) { + ping_entry->zcl_data.ping_req.cb(PING_EVT_ERROR, 0, ping_entry); + } + return; + } + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + bufid = zb_buf_get_out(); + zb_osif_enable_all_inter(); + + if (!bufid) { + if (ping_entry->zcl_data.ping_req.cb) { + ping_entry->zcl_data.ping_req.cb(PING_EVT_ERROR, 0, ping_entry); + } + return; + } + + /* Ping Frame is constructed by 'overloading' the common ZCL frame. + * Basically every frame which comes addressed to + * the PING_CUSTOM_CLUSTER is considered a Ping Frame. The FCF is being + * set to 0x00, the sequence number field is being used as + * a Ping Sequence Number, while the Command field is used to + * distinguish request/reply. The farther payload of the ping is filled + * with bytes PING_ECHO_REQUEST_BYTE/PING_ECHO_REPLY_BYTE. + */ + cmd_buf_ptr = ZB_ZCL_START_PACKET(bufid); + *(cmd_buf_ptr++) = ZIGBEE_PING_FRAME_CONTROL_FIELD; + /* Sequence Number Field. */ + *(cmd_buf_ptr++) = ping_seq_num; + + /* Fill Command Field. */ + if ((ping_entry->zcl_data.ping_req.request_echo) && + (ping_entry->zcl_data.ping_req.request_ack)) { + *(cmd_buf_ptr++) = PING_ECHO_REQUEST; + } else if ((ping_entry->zcl_data.ping_req.request_echo) && + (!ping_entry->zcl_data.ping_req.request_ack)) { + *(cmd_buf_ptr++) = PING_ECHO_NO_ACK_REQUEST; + } else { + *(cmd_buf_ptr++) = PING_NO_ECHO_REQUEST; + } + + memset(cmd_buf_ptr, PING_ECHO_REQUEST_BYTE, ping_entry->zcl_data.ping_req.count); + cmd_buf_ptr += ping_entry->zcl_data.ping_req.count; + ping_entry->zcl_data.ping_req.ping_seq = ping_seq_num; + ping_entry->id = ping_seq_num; + ping_seq_num++; + + /* Schedule frame to send. */ + packet_info->buffer = bufid; + packet_info->ptr = cmd_buf_ptr; + /* DstAddr and Addr mode already set. */ + packet_info->dst_ep = shell_ep; + packet_info->ep = shell_ep; + packet_info->prof_id = ZB_AF_HA_PROFILE_ID; + packet_info->cluster_id = PING_CUSTOM_CLUSTER; + packet_info->cb = dispatch_user_callback; + packet_info->disable_aps_ack = + (ping_entry->zcl_data.ping_req.request_ack ? ZB_FALSE : ZB_TRUE); + + zb_err_code = ZB_SCHEDULE_APP_CALLBACK(zb_zcl_send_ping_frame, + ctx_mgr_get_index_by_entry(ping_entry)); + if (zb_err_code != RET_OK) { + zb_shell_print_error(ping_entry->shell, "Can not schedule zcl frame.", ZB_FALSE); + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(packet_info->buffer); + zb_osif_enable_all_inter(); + + ctx_mgr_delete_entry(ping_entry); + return; + } +} + +/**@brief Actually construct the Ping Reply frame and send it. + * + * @param ping_entry Pointer to the context manager entry with ping reply data. + */ +static void ping_reply_send(struct ctx_entry *ping_entry) +{ + zb_bufid_t bufid; + zb_uint8_t *cmd_buf_ptr; + zb_uint8_t shell_ep = zb_shell_get_endpoint(); + zb_ret_t zb_err_code; + struct zcl_packet_info *packet_info = &(ping_entry->zcl_data.pkt_info); + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + bufid = zb_buf_get_out(); + zb_osif_enable_all_inter(); + + if (!bufid) { + LOG_WRN("Drop ping request due to the lack of output buffers"); + ctx_mgr_delete_entry(ping_entry); + return; + } + LOG_DBG("Send ping reply"); + + cmd_buf_ptr = ZB_ZCL_START_PACKET(bufid); + *(cmd_buf_ptr++) = ZIGBEE_PING_FRAME_CONTROL_FIELD; + *(cmd_buf_ptr++) = ping_entry->zcl_data.ping_reply.ping_seq; + *(cmd_buf_ptr++) = PING_ECHO_REPLY; + memset(cmd_buf_ptr, PING_ECHO_REPLY_BYTE, ping_entry->zcl_data.ping_reply.count); + cmd_buf_ptr += ping_entry->zcl_data.ping_reply.count; + + /* Schedule frame to send. */ + packet_info->buffer = bufid; + packet_info->ptr = cmd_buf_ptr; + /* DstAddr is already set. */ + packet_info->dst_addr_mode = ZB_APS_ADDR_MODE_16_ENDP_PRESENT; + packet_info->dst_ep = shell_ep; + packet_info->ep = shell_ep; + packet_info->prof_id = ZB_AF_HA_PROFILE_ID; + packet_info->cluster_id = PING_CUSTOM_CLUSTER; + packet_info->cb = frame_acked_cb; + packet_info->disable_aps_ack = + (ping_entry->zcl_data.ping_reply.send_ack ? ZB_FALSE : ZB_TRUE); + + zb_err_code = ZB_SCHEDULE_APP_CALLBACK(zb_zcl_send_ping_frame, + ctx_mgr_get_index_by_entry(ping_entry)); + if (zb_err_code != RET_OK) { + zb_shell_print_error(ping_entry->shell, "Can not schedule zcl frame.", ZB_FALSE); + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(packet_info->buffer); + zb_osif_enable_all_inter(); + + ctx_mgr_delete_entry(ping_entry); + } +} + +/**@brief Indicate ping request reception. + * + * @param zcl_cmd_bufid Zigbee buffer id with the received ZCL packet. + */ +static void ping_req_indicate(zb_bufid_t zcl_cmd_bufid) +{ + struct ctx_entry tmp_request; + zb_zcl_addr_t remote_node_addr; + zb_zcl_parsed_hdr_t *zcl_hdr = ZB_BUF_GET_PARAM(zcl_cmd_bufid, zb_zcl_parsed_hdr_t); + + remote_node_addr = zcl_hdr->addr_data.common_data.source; + + if (ping_ind_cb == NULL) { + return; + } + + memset(&tmp_request, 0, sizeof(struct ctx_entry)); + + switch (zcl_hdr->cmd_id) { + case PING_ECHO_REQUEST: + tmp_request.zcl_data.ping_req.request_echo = 1; + tmp_request.zcl_data.ping_req.request_ack = 1; + break; + + case PING_ECHO_NO_ACK_REQUEST: + tmp_request.zcl_data.ping_req.request_echo = 1; + break; + + case PING_NO_ECHO_REQUEST: + break; + + default: + /* Received frame is not a ping request. */ + return; + } + + tmp_request.taken = true; + tmp_request.zcl_data.ping_req.ping_seq = zcl_hdr->seq_number; + tmp_request.zcl_data.ping_req.count = zb_buf_len(zcl_cmd_bufid); + tmp_request.zcl_data.ping_req.sent_time = k_uptime_ticks(); + + if (remote_node_addr.addr_type != ZB_ZCL_ADDR_TYPE_SHORT) { + LOG_INF("Ping request received, but indication will not be generated" + " due to the unsupported address type."); + /* Not supported. */ + return; + } + tmp_request.zcl_data.pkt_info.dst_addr_mode = ZB_APS_ADDR_MODE_16_ENDP_PRESENT; + tmp_request.zcl_data.pkt_info.dst_addr.addr_short = remote_node_addr.u.short_addr; + + ping_ind_cb(PING_EVT_REQUEST_RECEIVED, + k_ticks_to_us_near32(tmp_request.zcl_data.ping_req.sent_time), + &tmp_request); +} + +/**@brief The Handler to 'intercept' every frame coming to the endpoint. + * + * @param bufid Reference to a ZBOSS buffer + */ +zb_uint8_t zb_shell_ep_handler_ping(zb_bufid_t bufid) +{ + zb_uint32_t time_diff; + zb_zcl_addr_t remote_node_addr; + zb_zcl_parsed_hdr_t *zcl_hdr = ZB_BUF_GET_PARAM(bufid, zb_zcl_parsed_hdr_t); + + remote_node_addr = zcl_hdr->addr_data.common_data.source; + + if ((zcl_hdr->cluster_id != PING_CUSTOM_CLUSTER) || + (zcl_hdr->profile_id != ZB_AF_HA_PROFILE_ID)) { + return ZB_FALSE; + } + + LOG_DBG("New ping frame received, bufid: %d", bufid); + ping_req_indicate(bufid); + + if (zcl_hdr->cmd_id == PING_ECHO_REPLY) { + struct zcl_packet_info *packet_info; + zb_uint16_t remote_short_addr = 0x0000; + + /* We have our ping reply. */ + struct ctx_entry *req_data = + ctx_mgr_find_ctx_entry(zcl_hdr->seq_number, CTX_MGR_PING_REQ_ENTRY_TYPE); + + if (req_data == NULL) { + return ZB_FALSE; + } + + packet_info = &(req_data->zcl_data.pkt_info); + + if (packet_info->dst_addr_mode == ZB_APS_ADDR_MODE_16_ENDP_PRESENT) { + remote_short_addr = packet_info->dst_addr.addr_short; + } else { + remote_short_addr = + zb_address_short_by_ieee(packet_info->dst_addr.addr_long); + } + + if (remote_node_addr.addr_type != ZB_ZCL_ADDR_TYPE_SHORT) { + return ZB_FALSE; + } + if (remote_short_addr != remote_node_addr.u.short_addr) { + return ZB_FALSE; + } + + /* Catch the timer value. */ + time_diff = get_request_duration(req_data); + + /* Cancel the ongoing alarm which was to erase the row ... */ + zb_ret_t zb_err_code = + ZB_SCHEDULE_APP_ALARM_CANCEL( + invalidate_ping_entry_cb, + ctx_mgr_get_index_by_entry(req_data)); + ZB_ERROR_CHECK(zb_err_code); + + /* Call callback function in order to indicate + * echo response reception. + */ + if (req_data->zcl_data.ping_req.cb) { + req_data->zcl_data.ping_req.cb(PING_EVT_ECHO_RECEIVED, time_diff, req_data); + } + + /* ... and erase it manually. */ + ctx_mgr_delete_entry(req_data); + + } else if ((zcl_hdr->cmd_id == PING_ECHO_REQUEST) || + (zcl_hdr->cmd_id == PING_ECHO_NO_ACK_REQUEST)) { + + zb_uint8_t len = zb_buf_len(bufid); + struct zcl_packet_info *packet_info; + struct ctx_entry *ping_entry = ctx_mgr_new_entry(CTX_MGR_PING_REPLY_ENTRY_TYPE); + + if (ping_entry == NULL) { + LOG_WRN("Request pool empty - wait for ongoing command to finish"); + return ZB_FALSE; + } + packet_info = &(ping_entry->zcl_data.pkt_info); + + /* Save the Ping Reply information in the table and schedule + * a sending function. + */ + ping_entry->zcl_data.ping_reply.count = len; + ping_entry->zcl_data.ping_reply.ping_seq = zcl_hdr->seq_number; + + if (zcl_hdr->cmd_id == PING_ECHO_REQUEST) { + LOG_DBG("PING echo request with APS ACK received"); + ping_entry->zcl_data.ping_reply.send_ack = 1; + } else { + LOG_DBG("PING echo request without APS ACK received"); + ping_entry->zcl_data.ping_reply.send_ack = 0; + } + + if (remote_node_addr.addr_type == ZB_ZCL_ADDR_TYPE_SHORT) { + packet_info->dst_addr.addr_short = remote_node_addr.u.short_addr; + } else { + LOG_WRN("Drop ping request due to incorrect address type"); + ctx_mgr_delete_entry(ping_entry); + zb_buf_free(bufid); + return ZB_TRUE; + } + + /* Send the Ping Reply, invalidate the row if not possible. */ + ping_reply_send(ping_entry); + } else if (zcl_hdr->cmd_id == PING_NO_ECHO_REQUEST) { + LOG_DBG("PING request without ECHO received"); + } else { + LOG_WRN("Unsupported Ping message received, cmd_id %d", zcl_hdr->cmd_id); + } + + zb_buf_free(bufid); + return ZB_TRUE; +} + +/** @brief ping over ZCL + * + * @code + * zcl ping [--no-echo] [--aps-ack] + * @endcode + * + * Example: + * @code + * zcl ping 0b010eaafd745dfa 32 + * @endcode + * + * @pre Ping only after starting @ref zigbee. + * + * Issue a ping-style command to another Shell device of the address `dst_addr` + * by using `payload_size` bytes of payload.
+ * + * Optionally, the device can request an APS acknowledgment (`--aps-ack`) or + * ask destination not to sent ping reply (`--no-echo`).
+ * + * To implement the ping-like functionality, a new custom cluster has been + * defined with ID 64. There are four custom commands defined inside it, + * each with its own ID. + * + * See the following flow graphs for details. + * + * - Case 1: Ping with echo, without the APS ack (default mode): + * @code + * App 1 Node 1 Node 2 + * | -- ping -> | -- ping request -> | (command ID: 0x02 - ping + * | | | request without the APS + * | | | acknowledgment) + * | | <- MAC ACK -- | + * | | <- ping reply -- | (command ID: 0x01 - ping + * | | | reply) + * | | -- MAC ACK -> | + * | <- Done -- | | + * @endcode + * + * In this default mode, the `ping` command measures the time needed + * for a Zigbee frame to travel between two nodes in the network + * (there and back again). The command uses a custom "overloaded" ZCL frame, + * which is constructed as a ZCL frame of the new custom ping + * ZCL cluster (ID 64). + * + * - Case 2: Ping with echo, with the APS acknowledgment: + * @code + * App 1 Node 1 Node 2 + * | -- ping -> | -- ping request -> | (command ID: 0x00 - ping + * | | | request with the APS + * | | | acknowledgment) + * | | <- MAC ACK -- | + * | | <- APS ACK -- | + * | | -- MAC ACK -> | + * | | <- ping reply -- | (command ID: 0x01 - ping + * | | | reply) + * | | -- MAC ACK -> | + * | | -- APS ACK -> | + * | | <- MAC ACK -- | + * | <- Done -- | | + * @endcode + * + * - Case 3: Ping without echo, with the APS acknowledgment: + * @code + * App 1 Node 1 Node 2 + * | -- ping -> | -- ping request -> | (command ID: 0x03 - ping + * | | | request without echo) + * | | <- MAC ACK -- | + * | | <- APS ACK -- | + * | | -- MAC ACK -> | + * | <- Done -- | | + * @endcode + * + * - Case 4: Ping without echo, without the APS acknowledgment: + * @code + * App 1 Node 1 Node 2 + * | -- ping -> | -- ping request -> | (command ID: 0x03 - ping + * | | | request without echo) + * | <- Done -- | | + * | | <- MAC ACK -- | + * @endcode + */ +int cmd_zb_ping(const struct shell *shell, size_t argc, char **argv) +{ + uint8_t i; + struct ctx_entry *ping_entry = ctx_mgr_new_entry(CTX_MGR_PING_REQ_ENTRY_TYPE); + + if (!ping_entry) { + zb_shell_print_error( + shell, + "Request pool empty - wait for ongoing command to finish", + ZB_FALSE); + return -ENOEXEC; + } + + ping_entry->zcl_data.ping_req.cb = ping_shell_evt_handler; + ping_entry->zcl_data.ping_req.request_ack = 0; + ping_entry->zcl_data.ping_req.request_echo = 1; + ping_entry->zcl_data.ping_req.timeout_ms = + (CONFIG_ZIGBEE_SHELL_ZCL_CMD_TIMEOUT * MSEC_PER_SEC); + + for (i = 1; i < (argc - 2); i++) { + if (strcmp(argv[i], "--aps-ack") == 0) { + ping_entry->zcl_data.ping_req.request_ack = 1; + } else if (strcmp(argv[i], "--no-echo") == 0) { + ping_entry->zcl_data.ping_req.request_echo = 0; + } + } + + ping_entry->zcl_data.pkt_info.dst_addr_mode = + parse_address(argv[argc - 2], + &(ping_entry->zcl_data.pkt_info.dst_addr), + ADDR_ANY); + + if (ping_entry->zcl_data.pkt_info.dst_addr_mode == ADDR_INVALID) { + zb_shell_print_error(shell, "Wrong address format", ZB_FALSE); + ctx_mgr_delete_entry(ping_entry); + return -EINVAL; + } + + if (!zb_shell_sscan_uint(argv[argc - 1], + (uint8_t *)&ping_entry->zcl_data.ping_req.count, 2, + 10)) { + + zb_shell_print_error(shell, "Incorrect ping payload size", ZB_FALSE); + ctx_mgr_delete_entry(ping_entry); + return -EINVAL; + } + + if (ping_entry->zcl_data.ping_req.count > PING_MAX_LENGTH) { + shell_print(shell, + "Note: Ping payload size exceeds maximum possible, assuming maximum"); + ping_entry->zcl_data.ping_req.count = PING_MAX_LENGTH; + } + + /* Put the shell instance to be used later. */ + ping_entry->shell = shell; + + ping_request_send(ping_entry); + return 0; +} diff --git a/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_zcl.c b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_zcl.c new file mode 100644 index 00000000..8646efc2 --- /dev/null +++ b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_zcl.c @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "zigbee_shell_cmd_zcl.h" + +#define ATTR_HELP "Read/write an attribute." + +#define ATTR_READ_HELP \ + ("Send Read Attribute Zigbee command.\n" \ + "Usage: read " \ + " [-c] \n" \ + "-c switches the server-to-client direction.\n" \ + "h: is for hex, d: is for decimal.") + +#define ATTR_WRITE_HELP \ + ("Send Write Attribute Zigbee command.\n" \ + "Usage: write [-c] " \ + " \n" \ + "-c switches the server-to-client direction.\n" \ + "h: is for hex, d: is for decimal.") + +#define SUBSCRIBE_HELP "(un)subscribes to an attribute." + +#define SUBSCRIBE_ON_HELP \ + ("Subscribe to an attribute.\n" \ + "Usage: on " \ + " " \ + " [] []") + +#define SUBSCRIBE_OFF_HELP \ + ("Unubscribe from an attribute.\n" \ + "Usage: off " \ + " ") + +#define PING_HELP \ + ("Send ping command over ZCL.\n" \ + "Usage: ping [--no-echo] [--aps-ack] ") + +#define CMD_HELP \ + ("Send generic ZCL command.\n" \ + "Usage: cmd [-d] [-p h:profile]" \ + " [-l h:payload]\n" \ + "-d - Require default response.\n" \ + "-p - Set profile ID, HA profile by default.\n" \ + "-l - Send payload in command, Little Endian bytes order.") + +#define RAW_HELP \ + ("Send raw ZCL frame.\n" \ + "Usage: raw ") + +#define ZCL_HELP "ZCL subsystem commands." + +#define GROUPS_ADD_HELP \ + ("Add the endpoint on the remote node to the group.\n" \ + "Usage: add [-p h:profile] \n" \ + "-p - Set profile ID, HA profile by default.") + +#define GROUPS_ADD_IF_IDENTIFY_HELP \ + ("Add the endpoint on the remote nodes to the group if they are in the Identify mode.\n" \ + "Usage: add_if_identify [-p h:profile] \n" \ + "-p - Set profile ID, HA profile by default.") + +#define GROUPS_GET_MEMBER \ + ("Get group membership from the endpoint on the remote node.\n" \ + "Usage: get_member [-p h:profile] [h:group IDs ...]\n" \ + "-p - Set profile ID, HA profile by default.") + +#define GROUPS_REMOVE_HELP \ + ("Remove the endpoint on the remote node from the group.\n" \ + "Usage: remove [-p h:profile] \n" \ + "-p - Set profile ID, HA profile by default.") + +#define GROUPS_REMOVE_ALL_HELP \ + ("Remove the endpoint on the remote node from all groups.\n" \ + "Usage: remove_all [-p h:profile]\n" \ + "-p - Set profile ID, HA profile by default.") + +#define GROUPS_HELP "Manage Zigbee groups." + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_attr, + SHELL_CMD_ARG(read, NULL, ATTR_READ_HELP, cmd_zb_readattr, 6, 1), + SHELL_CMD_ARG(write, NULL, ATTR_WRITE_HELP, cmd_zb_writeattr, 8, 1), + SHELL_SUBCMD_SET_END); + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_subsbcribe, + SHELL_CMD_ARG(on, NULL, SUBSCRIBE_ON_HELP, cmd_zb_subscribe_on, + 7, 2), + SHELL_CMD_ARG(off, NULL, SUBSCRIBE_OFF_HELP, cmd_zb_subscribe_off, + 7, 0), + SHELL_SUBCMD_SET_END); + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_groups, + SHELL_CMD_ARG(add, NULL, GROUPS_ADD_HELP, cmd_zb_add_remove_group, 4, 2), + SHELL_CMD_ARG(add_if_identify, NULL, GROUPS_ADD_IF_IDENTIFY_HELP, + cmd_zb_add_group_if_identifying, 4, 2), + SHELL_CMD_ARG(get_member, NULL, GROUPS_GET_MEMBER, cmd_zb_get_group_mem, 3, 7), + SHELL_CMD_ARG(remove, NULL, GROUPS_REMOVE_HELP, cmd_zb_add_remove_group, 4, 2), + SHELL_CMD_ARG(remove_all, NULL, GROUPS_REMOVE_ALL_HELP, cmd_zb_remove_all_groups, 3, 2), + SHELL_SUBCMD_SET_END); + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_zcl, + SHELL_CMD(attr, &sub_attr, ATTR_HELP, NULL), + SHELL_CMD_ARG(cmd, NULL, CMD_HELP, cmd_zb_generic_cmd, 5, 10), + SHELL_CMD(groups, &sub_groups, GROUPS_HELP, NULL), + SHELL_CMD_ARG(ping, NULL, PING_HELP, cmd_zb_ping, 3, 2), + SHELL_COND_CMD_ARG(CONFIG_ZIGBEE_SHELL_DEBUG_CMD, raw, NULL, + RAW_HELP, cmd_zb_zcl_raw, 6, 0), + SHELL_CMD(subscribe, &sub_subsbcribe, SUBSCRIBE_HELP, NULL), + SHELL_SUBCMD_SET_END); + +SHELL_CMD_REGISTER(zcl, &sub_zcl, ZCL_HELP, NULL); diff --git a/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_zcl_groups.c b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_zcl_groups.c new file mode 100644 index 00000000..75523d85 --- /dev/null +++ b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_zcl_groups.c @@ -0,0 +1,946 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include + +#include +#include +#include +#include "zigbee_shell_utils.h" + +LOG_MODULE_DECLARE(zigbee_shell, CONFIG_ZIGBEE_SHELL_LOG_LEVEL); + + +/**@brief Function to construct and send Add Group command. + * + * @param index Index of the entry with frame to send in the context manager. + */ +static void zb_zcl_send_add_group_cmd(zb_uint8_t index) +{ + struct zcl_packet_info *packet_info; + struct ctx_entry *entry = ctx_mgr_get_entry_by_index(index); + + if (entry == NULL) { + LOG_ERR("Couldn't send the ZCL command frame - context entry %d not found", index); + return; + } + + /* Get the ZCL packet sequence number. */ + entry->id = ZCL_CTX().seq_number; + + packet_info = &(entry->zcl_data.pkt_info); + + ZB_ZCL_GROUPS_SEND_ADD_GROUP_REQ( + packet_info->buffer, + packet_info->dst_addr, + packet_info->dst_addr_mode, + packet_info->dst_ep, + packet_info->ep, + packet_info->prof_id, + ZB_ZCL_DISABLE_DEFAULT_RESPONSE, + packet_info->cb, + entry->zcl_data.groups_cmd.group_id); +} + +/**@brief Function to construct and send Remove Group command. + * + * @param index Index of the entry with frame to send in the context manager. + */ +static void zb_zcl_send_remove_group_cmd(zb_uint8_t index) +{ + struct zcl_packet_info *packet_info; + struct ctx_entry *entry = ctx_mgr_get_entry_by_index(index); + + if (entry == NULL) { + LOG_ERR("Couldn't send the ZCL command frame - context entry %d not found", index); + return; + } + + /* Get the ZCL packet sequence number. */ + entry->id = ZCL_CTX().seq_number; + + packet_info = &(entry->zcl_data.pkt_info); + + ZB_ZCL_GROUPS_SEND_REMOVE_GROUP_REQ( + packet_info->buffer, + packet_info->dst_addr, + packet_info->dst_addr_mode, + packet_info->dst_ep, + packet_info->ep, + packet_info->prof_id, + ZB_ZCL_DISABLE_DEFAULT_RESPONSE, + packet_info->cb, + entry->zcl_data.groups_cmd.group_id); +} + +/**@brief Function to construct and send Get Group Membership command. + * + * @param index Index of the entry with frame to send in the context manager. + */ +static void zb_zcl_send_get_group_mem_cmd(zb_uint8_t index) +{ + zb_uint8_t *data_ptr; + struct zcl_packet_info *packet_info; + struct ctx_entry *entry = ctx_mgr_get_entry_by_index(index); + + if (entry == NULL) { + LOG_ERR("Couldn't send the ZCL command frame - context entry %d not found", index); + return; + } + + /* Get the ZCL packet sequence number. */ + entry->id = ZCL_CTX().seq_number; + + packet_info = &(entry->zcl_data.pkt_info); + + ZB_ZCL_GROUPS_INIT_GET_GROUP_MEMBERSHIP_REQ( + packet_info->buffer, + data_ptr, + ZB_ZCL_DISABLE_DEFAULT_RESPONSE, + entry->zcl_data.groups_cmd.group_list_cnt); + + for (uint8_t i = 0; i < entry->zcl_data.groups_cmd.group_list_cnt; i++) { + ZB_ZCL_GROUPS_ADD_ID_GET_GROUP_MEMBERSHIP_REQ( + data_ptr, + entry->zcl_data.groups_cmd.group_list[i]); + } + + ZB_ZCL_GROUPS_SEND_GET_GROUP_MEMBERSHIP_REQ( + packet_info->buffer, + data_ptr, + packet_info->dst_addr, + packet_info->dst_addr_mode, + packet_info->dst_ep, + packet_info->ep, + packet_info->prof_id, + packet_info->cb); +} + +/**@brief Function to construct and send Add Group If Identifying command. + * + * @param index Index of the entry with frame to send in the context manager. + */ +static void zb_zcl_send_add_group_if_identifying_cmd(zb_uint8_t index) +{ + struct zcl_packet_info *packet_info; + struct ctx_entry *entry = ctx_mgr_get_entry_by_index(index); + + if (entry == NULL) { + LOG_ERR("Couldn't send the ZCL command frame - context entry %d not found", index); + return; + } + + /* Get the ZCL packet sequence number. */ + entry->id = ZCL_CTX().seq_number; + + packet_info = &(entry->zcl_data.pkt_info); + + ZB_ZCL_GROUPS_SEND_ADD_GROUP_IF_IDENT_REQ( + packet_info->buffer, + packet_info->dst_addr, + packet_info->dst_addr_mode, + packet_info->dst_ep, + packet_info->ep, + packet_info->prof_id, + ZB_ZCL_DISABLE_DEFAULT_RESPONSE, + packet_info->cb, + entry->zcl_data.groups_cmd.group_id); +} + +/**@brief Function to construct and send Remove All Groups command. + * + * @param index Index of the entry with frame to send in the context manager. + */ +static void zb_zcl_send_remove_all_groups_cmd(zb_uint8_t index) +{ + struct zcl_packet_info *packet_info; + struct ctx_entry *entry = ctx_mgr_get_entry_by_index(index); + + if (entry == NULL) { + LOG_ERR("Couldn't send the ZCL command frame - context entry %d not found", index); + return; + } + + /* Get the ZCL packet sequence number. */ + entry->id = ZCL_CTX().seq_number; + + packet_info = &(entry->zcl_data.pkt_info); + + ZB_ZCL_GROUPS_SEND_REMOVE_ALL_GROUPS_REQ( + packet_info->buffer, + packet_info->dst_addr, + packet_info->dst_addr_mode, + packet_info->dst_ep, + packet_info->ep, + packet_info->prof_id, + ZB_ZCL_DISABLE_DEFAULT_RESPONSE, + packet_info->cb); +} + +/**@brief Function to send the ZCL groups cmds. + * + * @param entry Pointer to the entry with frame to send in the context manager. + * + * @return Error code with function execution status. + */ +static int zcl_groups_cmd_send(struct ctx_entry *entry) +{ + zb_ret_t zb_err_code; + uint8_t entry_index = ctx_mgr_get_index_by_entry(entry); + + zb_err_code = + ZB_SCHEDULE_APP_ALARM( + zb_shell_zcl_cmd_timeout_cb, + entry_index, + (CONFIG_ZIGBEE_SHELL_ZCL_CMD_TIMEOUT * ZB_TIME_ONE_SECOND)); + + if (zb_err_code != RET_OK) { + zb_shell_print_error(entry->shell, "Couldn't schedule timeout cb.", ZB_FALSE); + goto cmd_send_error; + } + + zb_err_code = ZB_SCHEDULE_APP_CALLBACK(entry->zcl_data.groups_cmd.send_fn, entry_index); + + if (zb_err_code != RET_OK) { + zb_shell_print_error(entry->shell, "Can not schedule zcl frame.", ZB_FALSE); + + zb_err_code = + ZB_SCHEDULE_APP_ALARM_CANCEL(zb_shell_zcl_cmd_timeout_cb, entry_index); + ZB_ERROR_CHECK(zb_err_code); + goto cmd_send_error; + } + + return 0; + +cmd_send_error: + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(entry->zcl_data.pkt_info.buffer); + zb_osif_enable_all_inter(); + + /* Invalidate an entry with frame data in the context manager. */ + ctx_mgr_delete_entry(entry); + + return -ENOEXEC; +} + +/**@brief Function called when command is APS ACKed - prints done and invalidates groups command + * entry in context manager. + * + * @param[in] bufid Reference to a ZBOSS buffer containing zb_zcl_command_send_status_t data. + */ +static void zb_zcl_cmd_acked(zb_uint8_t bufid) +{ + uint8_t index = 0; + zb_ret_t zb_err_code = RET_OK; + zb_zcl_command_send_status_t *cmd_status = NULL; + struct ctx_entry *entry = ctx_mgr_get_entry_by_index(index); + + if (bufid == ZB_BUF_INVALID) { + return; + } + + cmd_status = ZB_BUF_GET_PARAM(bufid, zb_zcl_command_send_status_t); + + /* Handle only success status. */ + if (cmd_status->status != RET_OK) { + goto exit; + } + + /* Find entry of CTX_MGR_GROUPS_CMD_ENTRY_TYPE type with matching buffer id. */ + entry = ctx_mgr_find_zcl_entry_by_bufid(bufid, CTX_MGR_GROUPS_CMD_ENTRY_TYPE); + + if (entry == NULL) { + LOG_ERR("Couldn't find matching entry for ZCL groups cmd"); + goto exit; + } + + zb_shell_print_done(entry->shell, ZB_FALSE); + + zb_err_code = ZB_SCHEDULE_APP_ALARM_CANCEL(zb_shell_zcl_cmd_timeout_cb, index); + ZB_ERROR_CHECK(zb_err_code); + + ctx_mgr_delete_entry(entry); +exit: + zb_buf_free(bufid); +} + +/**@brief Function to check if command is to be sent as unicast. + * + * @param[in] packet_info Pointer to structure with packet data. + * + * @return ZB_TRUE if command is sent as unicast, ZB_FALSE otherwise. + */ +static zb_bool_t is_sent_unicast(struct zcl_packet_info *packet_info) +{ + switch (packet_info->dst_addr_mode) { + case ADDR_LONG: + return ZB_TRUE; + case ADDR_SHORT: + if (ZB_NWK_IS_ADDRESS_BROADCAST(packet_info->dst_addr.addr_short)) { + return ZB_FALSE; + } else if ((packet_info->dst_ep < ZB_MIN_ENDPOINT_NUMBER) || + (packet_info->dst_ep == ZB_APS_BROADCAST_ENDPOINT_NUMBER)) { + return ZB_FALSE; + } else { + return ZB_TRUE; + } + default: + return ZB_FALSE; + } +} + +/**@brief Add remote node to the group or remove from the group. + * + * @code + * zcl group add [-p h:profile] + * @endcode + * + * Send Add Group command to the endpoint `ep` of the remote node `dst_addr` + * and add it to the group `group_id`. + * Providing profile ID is optional, Home Automation Profile is used by default. + * + * @code + * zcl group remove [-p h:profile] + * @endcode + * + * Send Remove Group command to the endpoint `ep` of the remote node `dst_addr` + * and remove it from the group `group_id`. + * Providing profile ID is optional, Home Automation Profile is used by default. + * + * @note These commands can be sent as groupcast. To send command as groupcast, + * set `dst_addr` to group address and `ep` to 0. + * + * @note Group name is not supported. + */ +int cmd_zb_add_remove_group(const struct shell *shell, size_t argc, char **argv) +{ + zb_bufid_t bufid; + struct groups_cmd *req_data; + struct zcl_packet_info *packet_info; + struct ctx_entry *entry = ctx_mgr_new_entry(CTX_MGR_GROUPS_CMD_ENTRY_TYPE); + + if (entry == NULL) { + zb_shell_print_error( + shell, + "Request pool empty - wait for ongoing command to finish", + ZB_FALSE); + return -ENOEXEC; + } + + req_data = &(entry->zcl_data.groups_cmd); + packet_info = &(entry->zcl_data.pkt_info); + + /* Set default profile ID to Home Automation. */ + packet_info->prof_id = ZB_AF_HA_PROFILE_ID; + /* Set function to be used to send Add Group or Remove Group command. */ + if (strcmp(*(argv), "add") == 0) { + req_data->send_fn = zb_zcl_send_add_group_cmd; + } else if (strcmp(*(argv), "remove") == 0) { + req_data->send_fn = zb_zcl_send_remove_group_cmd; + } else { + zb_shell_print_error(entry->shell, "Failed to parse command name", ZB_FALSE); + goto add_remove_group_error; + } + + packet_info->dst_addr_mode = parse_address(*(++argv), &(packet_info->dst_addr), ADDR_ANY); + + if (packet_info->dst_addr_mode == ADDR_INVALID) { + zb_shell_print_error(shell, "Invalid address", ZB_FALSE); + goto add_remove_group_error; + } + + if (!zb_shell_sscan_uint8(*(++argv), &(packet_info->dst_ep))) { + zb_shell_print_error(shell, "Incorrect remote endpoint", ZB_FALSE); + goto add_remove_group_error; + } + + /* Handle sending to group address. */ + if ((packet_info->dst_addr_mode == ZB_APS_ADDR_MODE_16_ENDP_PRESENT) + && (packet_info->dst_ep == 0)) { + zb_uint16_t group_addr = packet_info->dst_addr.addr_short; + + /* Verify group address. */ + if ((group_addr < ZB_ZCL_ATTR_SCENES_CURRENT_GROUP_MIN_VALUE) || + (group_addr > ZB_ZCL_ATTR_SCENES_CURRENT_GROUP_MAX_VALUE)) { + zb_shell_print_error(shell, "Incorrect group address", ZB_FALSE); + goto add_remove_group_error; + } + + packet_info->dst_addr_mode = ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT; + } + + /* Check if different from HA profile should be used. */ + if (strcmp(*(++argv), "-p") == 0) { + /* Check if argument with group id is present. */ + if (argc < 6) { + zb_shell_print_error(shell, "Invalid number of arguments", ZB_FALSE); + goto add_remove_group_error; + } + + if (!parse_hex_u16(*(++argv), &(packet_info->prof_id))) { + zb_shell_print_error(shell, "Invalid profile id", ZB_FALSE); + goto add_remove_group_error; + } + argv++; + } + + if (!parse_hex_u16(*argv, &(req_data->group_id))) { + zb_shell_print_error(shell, "Invalid group id", ZB_FALSE); + goto add_remove_group_error; + } + + /* Set shell to print logs to. */ + entry->shell = shell; + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + bufid = zb_buf_get_out(); + zb_osif_enable_all_inter(); + + if (!bufid) { + zb_shell_print_error(entry->shell, "Can not get buffer.", ZB_FALSE); + /* Mark data structure as free. */ + ctx_mgr_delete_entry(entry); + return -ENOEXEC; + } + + packet_info->buffer = bufid; + /* DstAddr, Dst Addr Mode and Dst endpoint are already set. */ + packet_info->ep = zb_shell_get_endpoint(); + /* Profile ID is already set, Cluster ID does not have to be. */ + if (is_sent_unicast(packet_info) == ZB_TRUE) { + packet_info->cb = NULL; + packet_info->disable_aps_ack = ZB_FALSE; + } else { + packet_info->cb = zb_zcl_cmd_acked; + packet_info->disable_aps_ack = ZB_TRUE; + } + + return zcl_groups_cmd_send(entry); + +add_remove_group_error: + /* Mark data structure as free. */ + ctx_mgr_delete_entry(entry); + return -EINVAL; +} + +/**@brief Get group membership from the remote node. + * + * @code + * zcl group get_member [-p h:profile] [h:group IDs ...] + * @endcode + * + * Send Get Group Membership command to the endpoint `ep` of the remote node `dst_addr` + * to get which groups from provided group list the remote node is part of. + * If no group ID is provided, the remote node is requested to response with information about + * all groups it is part of. + * Providing profile ID is optional, Home Automation Profile is used by default. + * + * @note Response from the remote node is parsed and printed only if the command was sent + * as unicast. + * + * @note This command can be sent as groupcast. To send command as groupcast, + * set `dst_addr` to group address and `ep` to 0. + */ +int cmd_zb_get_group_mem(const struct shell *shell, size_t argc, char **argv) +{ + zb_bufid_t bufid; + struct groups_cmd *req_data; + struct zcl_packet_info *packet_info; + struct ctx_entry *entry = ctx_mgr_new_entry(CTX_MGR_GROUPS_CMD_ENTRY_TYPE); + + if (entry == NULL) { + zb_shell_print_error( + shell, + "Request pool empty - wait for ongoing command to finish", + ZB_FALSE); + return -ENOEXEC; + } + + req_data = &(entry->zcl_data.groups_cmd); + packet_info = &(entry->zcl_data.pkt_info); + + /* Set default profile ID to Home Automation. */ + packet_info->prof_id = ZB_AF_HA_PROFILE_ID; + /* Set function to be used to send Get Group Membership command. */ + req_data->send_fn = zb_zcl_send_get_group_mem_cmd; + + packet_info->dst_addr_mode = parse_address(*(++argv), &(packet_info->dst_addr), ADDR_ANY); + argc--; + + if (packet_info->dst_addr_mode == ADDR_INVALID) { + zb_shell_print_error(shell, "Invalid address", ZB_FALSE); + goto get_group_mem_error; + } + + if (!zb_shell_sscan_uint8(*(++argv), &(packet_info->dst_ep))) { + zb_shell_print_error(shell, "Incorrect remote endpoint", ZB_FALSE); + goto get_group_mem_error; + } + argc--; + + /* Handle sending to group address. */ + if ((packet_info->dst_addr_mode == ZB_APS_ADDR_MODE_16_ENDP_PRESENT) + && (packet_info->dst_ep == 0)) { + zb_uint16_t group_addr = packet_info->dst_addr.addr_short; + + /* Verify group address. */ + if ((group_addr < ZB_ZCL_ATTR_SCENES_CURRENT_GROUP_MIN_VALUE) || + (group_addr > ZB_ZCL_ATTR_SCENES_CURRENT_GROUP_MAX_VALUE)) { + zb_shell_print_error(shell, "Incorrect group address", ZB_FALSE); + goto get_group_mem_error; + } + + packet_info->dst_addr_mode = ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT; + } + + /* Check if different from HA profile should be used. */ + if ((argc > 1) && (strcmp(*(++argv), "-p") == 0)) { + /* Check if argument with profile ID is present. */ + if (argc < 3) { + zb_shell_print_error(shell, "Invalid number of arguments", ZB_FALSE); + goto get_group_mem_error; + } + + if (!parse_hex_u16(*(++argv), &(packet_info->prof_id))) { + zb_shell_print_error(shell, "Invalid profile id", ZB_FALSE); + goto get_group_mem_error; + } + argv++; + argc -= 2; + } + + /* Check if any group ID was provided and if so, parse list group ID. */ + if (argc == 1) { + req_data->group_list_cnt = 0; + } else { + req_data->group_list_cnt = (argc - 1); + } + + for (uint8_t i = 0; i < req_data->group_list_cnt; i++) { + if (!parse_hex_u16(*argv, &(req_data->group_list[i]))) { + zb_shell_print_error(shell, "Invalid group id", ZB_FALSE); + goto get_group_mem_error; + } + argv++; + } + + /* Set shell to print logs to. */ + entry->shell = shell; + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + bufid = zb_buf_get_out(); + zb_osif_enable_all_inter(); + + if (!bufid) { + zb_shell_print_error(entry->shell, "Can not get buffer.", ZB_FALSE); + /* Mark data structure as free. */ + ctx_mgr_delete_entry(entry); + return -ENOEXEC; + } + + packet_info->buffer = bufid; + /* DstAddr, Dst Addr Mode and Dst endpoint are already set. */ + packet_info->ep = zb_shell_get_endpoint(); + /* Profile ID is already set, Cluster ID does not have to be. */ + if (is_sent_unicast(packet_info) == ZB_TRUE) { + packet_info->cb = NULL; + packet_info->disable_aps_ack = ZB_FALSE; + } else { + packet_info->cb = zb_zcl_cmd_acked; + packet_info->disable_aps_ack = ZB_TRUE; + } + + return zcl_groups_cmd_send(entry); + +get_group_mem_error: + /* Mark data structure as free. */ + ctx_mgr_delete_entry(entry); + return -EINVAL; +} + +/**@brief Add remote nodes to the group if the remote nodes are in the Identify mode. + * + * @code + * zcl group add_if_identify [-p h:profile] + * @endcode + * + * Send Add Group If Identifying command to the endpoint `ep` of the remote node `dst_addr` + * and add it to the group `group_id` if the remote node is in the Identify mode. + * Providing profile ID is optional, Home Automation Profile is used by default. + * + * @note This command can be sent as groupcast. To send command as groupcast, + * set `dst_addr` to group address and `ep` to 0. + */ +int cmd_zb_add_group_if_identifying(const struct shell *shell, size_t argc, char **argv) +{ + zb_bufid_t bufid; + struct groups_cmd *req_data; + struct zcl_packet_info *packet_info; + struct ctx_entry *entry = ctx_mgr_new_entry(CTX_MGR_GROUPS_CMD_ENTRY_TYPE); + + if (entry == NULL) { + zb_shell_print_error( + shell, + "Request pool empty - wait for ongoing command to finish", + ZB_FALSE); + return -ENOEXEC; + } + + req_data = &(entry->zcl_data.groups_cmd); + packet_info = &(entry->zcl_data.pkt_info); + + /* Set default profile ID to Home Automation. */ + packet_info->prof_id = ZB_AF_HA_PROFILE_ID; + /* Set function to be used to send Add Group If Identifying command. */ + req_data->send_fn = zb_zcl_send_add_group_if_identifying_cmd; + + packet_info->dst_addr_mode = parse_address(*(++argv), &(packet_info->dst_addr), ADDR_ANY); + + if (packet_info->dst_addr_mode == ADDR_INVALID) { + zb_shell_print_error(shell, "Invalid address", ZB_FALSE); + goto add_group_if_identifying_error; + } + + if (!zb_shell_sscan_uint8(*(++argv), &(packet_info->dst_ep))) { + zb_shell_print_error(shell, "Incorrect remote endpoint", ZB_FALSE); + goto add_group_if_identifying_error; + } + + /* Handle sending to group address. */ + if ((packet_info->dst_addr_mode == ZB_APS_ADDR_MODE_16_ENDP_PRESENT) + && (packet_info->dst_ep == 0)) { + zb_uint16_t group_addr = packet_info->dst_addr.addr_short; + + /* Verify group address. */ + if ((group_addr < ZB_ZCL_ATTR_SCENES_CURRENT_GROUP_MIN_VALUE) || + (group_addr > ZB_ZCL_ATTR_SCENES_CURRENT_GROUP_MAX_VALUE)) { + zb_shell_print_error(shell, "Incorrect group address", ZB_FALSE); + goto add_group_if_identifying_error; + } + + packet_info->dst_addr_mode = ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT; + } + + /* Check if different from HA profile should be used. */ + if (strcmp(*(++argv), "-p") == 0) { + /* Check if argument with group id is present. */ + if (argc < 6) { + zb_shell_print_error(shell, "Invalid number of arguments", ZB_FALSE); + goto add_group_if_identifying_error; + } + + if (!parse_hex_u16(*(++argv), &(packet_info->prof_id))) { + zb_shell_print_error(shell, "Invalid profile id", ZB_FALSE); + goto add_group_if_identifying_error; + } + argv++; + } + + if (!parse_hex_u16(*argv, &(req_data->group_id))) { + zb_shell_print_error(shell, "Invalid group id", ZB_FALSE); + goto add_group_if_identifying_error; + } + + /* Set shell to print logs to. */ + entry->shell = shell; + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + bufid = zb_buf_get_out(); + zb_osif_enable_all_inter(); + + if (!bufid) { + zb_shell_print_error(entry->shell, "Can not get buffer.", ZB_FALSE); + /* Mark data structure as free. */ + ctx_mgr_delete_entry(entry); + return -ENOEXEC; + } + + packet_info->buffer = bufid; + /* DstAddr, Dst Addr Mode and Dst endpoint are already set. */ + packet_info->ep = zb_shell_get_endpoint(); + /* Profile ID is already set, Cluster ID does not have to be. */ + packet_info->cb = zb_zcl_cmd_acked; + if (is_sent_unicast(packet_info) == ZB_TRUE) { + packet_info->disable_aps_ack = ZB_FALSE; + } else { + packet_info->disable_aps_ack = ZB_TRUE; + } + + return zcl_groups_cmd_send(entry); + +add_group_if_identifying_error: + /* Mark data structure as free. */ + ctx_mgr_delete_entry(entry); + return -EINVAL; +} + +/**@brief Remove the remote node from all groups. + * + * @code + * zcl group remove_all [-p h:profile] + * @endcode + * + * Send Remove All Groups command to the endpoint `ep` of the remote node `dst_addr`. + * Providing profile ID is optional, Home Automation Profile is used by default. + * + * @note This command can be sent as groupcast. To send command as groupcast, + * set `dst_addr` to group address and `ep` to 0. + */ +int cmd_zb_remove_all_groups(const struct shell *shell, size_t argc, char **argv) +{ + zb_bufid_t bufid; + struct groups_cmd *req_data; + struct zcl_packet_info *packet_info; + struct ctx_entry *entry = ctx_mgr_new_entry(CTX_MGR_GROUPS_CMD_ENTRY_TYPE); + + if (entry == NULL) { + zb_shell_print_error( + shell, + "Request pool empty - wait for ongoing command to finish", + ZB_FALSE); + return -ENOEXEC; + } + + req_data = &(entry->zcl_data.groups_cmd); + packet_info = &(entry->zcl_data.pkt_info); + + /* Set default profile ID to Home Automation. */ + packet_info->prof_id = ZB_AF_HA_PROFILE_ID; + /* Set function to be used to send Remove All groups command. */ + req_data->send_fn = zb_zcl_send_remove_all_groups_cmd; + + packet_info->dst_addr_mode = parse_address(*(++argv), &(packet_info->dst_addr), ADDR_ANY); + + if (packet_info->dst_addr_mode == ADDR_INVALID) { + zb_shell_print_error(shell, "Invalid address", ZB_FALSE); + goto remove_all_groups_error; + } + + if (!zb_shell_sscan_uint8(*(++argv), &(packet_info->dst_ep))) { + zb_shell_print_error(shell, "Incorrect remote endpoint", ZB_FALSE); + goto remove_all_groups_error; + } + + /* Handle sending to group address. */ + if ((packet_info->dst_addr_mode == ZB_APS_ADDR_MODE_16_ENDP_PRESENT) + && (packet_info->dst_ep == 0)) { + zb_uint16_t group_addr = packet_info->dst_addr.addr_short; + + /* Verify group address. */ + if ((group_addr < ZB_ZCL_ATTR_SCENES_CURRENT_GROUP_MIN_VALUE) || + (group_addr > ZB_ZCL_ATTR_SCENES_CURRENT_GROUP_MAX_VALUE)) { + zb_shell_print_error(shell, "Incorrect group address", ZB_FALSE); + goto remove_all_groups_error; + } + + packet_info->dst_addr_mode = ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT; + } + + /* Check if different from HA profile should be used. */ + if (strcmp(*(++argv), "-p") == 0) { + /* Check if argument with group id is present. */ + if (argc < 5) { + zb_shell_print_error(shell, "Invalid number of arguments", ZB_FALSE); + goto remove_all_groups_error; + } + + if (!parse_hex_u16(*(++argv), &(packet_info->prof_id))) { + zb_shell_print_error(shell, "Invalid profile id", ZB_FALSE); + goto remove_all_groups_error; + } + argv++; + } + + /* Set shell to print logs to. */ + entry->shell = shell; + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + bufid = zb_buf_get_out(); + zb_osif_enable_all_inter(); + + if (!bufid) { + zb_shell_print_error(entry->shell, "Can not get buffer.", ZB_FALSE); + /* Mark data structure as free. */ + ctx_mgr_delete_entry(entry); + return -ENOEXEC; + } + + packet_info->buffer = bufid; + /* DstAddr, Dst Addr Mode and Dst endpoint are already set. */ + packet_info->ep = zb_shell_get_endpoint(); + /* Profile ID is already set, Cluster ID does not have to be. */ + packet_info->cb = zb_zcl_cmd_acked; + if (is_sent_unicast(packet_info) == ZB_TRUE) { + packet_info->disable_aps_ack = ZB_FALSE; + } else { + packet_info->disable_aps_ack = ZB_TRUE; + } + + return zcl_groups_cmd_send(entry); + +remove_all_groups_error: + /* Mark data structure as free. */ + ctx_mgr_delete_entry(entry); + return -EINVAL; +} + +/**@brief The Handler to 'intercept' every frame coming to the endpoint. + * + * @param bufid Reference to a ZBOSS buffer. + * + * @return ZB_TRUE if command was handled by the function, ZB_FALSE otherwise. + */ +zb_uint8_t zb_shell_ep_handler_groups_cmd(zb_bufid_t bufid) +{ + zb_ret_t zb_err_code; + struct ctx_entry *entry; + zb_uint16_t remote_node_short_add; + struct zcl_packet_info *packet_info; + zb_zcl_parsed_hdr_t *cmd_info = ZB_BUF_GET_PARAM(bufid, zb_zcl_parsed_hdr_t); + + /* Find command context by ZCL sequence number. */ + entry = ctx_mgr_find_ctx_entry(cmd_info->seq_number, CTX_MGR_GROUPS_CMD_ENTRY_TYPE); + + if (entry == NULL) { + return ZB_FALSE; + } + + if (!zb_shell_is_zcl_cmd_response(cmd_info, entry)) { + return ZB_FALSE; + } + + packet_info = &entry->zcl_data.pkt_info; + + /* Get sender device short address for printing the response command. */ + if (packet_info->dst_addr_mode == ZB_APS_ADDR_MODE_64_ENDP_PRESENT) { + remote_node_short_add = zb_address_short_by_ieee(packet_info->dst_addr.addr_long); + } else { + remote_node_short_add = packet_info->dst_addr.addr_short; + } + + if (cmd_info->cmd_id == ZB_ZCL_CMD_DEFAULT_RESP) { + zb_zcl_default_resp_payload_t *def_resp = ZB_ZCL_READ_DEFAULT_RESP(bufid); + + /* Print info received from default response. */ + shell_fprintf(entry->shell, + (def_resp->status == ZB_ZCL_STATUS_SUCCESS) ? + SHELL_INFO : SHELL_ERROR, + "Default Response received: "); + shell_fprintf(entry->shell, + (def_resp->status == ZB_ZCL_STATUS_SUCCESS) ? + SHELL_INFO : SHELL_ERROR, + "Command: %d, Status: %d\n", + def_resp->command_id, + def_resp->status); + + if (def_resp->status != ZB_ZCL_STATUS_SUCCESS) { + zb_shell_print_error(entry->shell, "Command not successful", ZB_FALSE); + } + } else if (cmd_info->cmd_id == ZB_ZCL_CMD_GROUPS_ADD_GROUP_RES) { + zb_zcl_groups_add_group_res_t *add_group_resp; + + ZB_ZCL_GROUPS_GET_ADD_GROUP_RES(bufid, add_group_resp); + + if (add_group_resp == NULL) { + zb_shell_print_error( + entry->shell, + "Failed to parse response command", + ZB_FALSE); + } else { + shell_fprintf(entry->shell, + (add_group_resp->status == ZB_ZCL_STATUS_SUCCESS) ? + SHELL_INFO : SHELL_ERROR, + "Response command received, group: %#.4hx, status: %d\n", + add_group_resp->group_id, + add_group_resp->status); + + if (add_group_resp->status != ZB_ZCL_STATUS_SUCCESS) { + zb_shell_print_error( + entry->shell, + "Command not successful", + ZB_FALSE); + } else { + zb_shell_print_done(entry->shell, ZB_FALSE); + } + } + } else if (cmd_info->cmd_id == ZB_ZCL_CMD_GROUPS_REMOVE_GROUP_RES) { + zb_zcl_groups_remove_group_res_t *remove_group_resp; + + ZB_ZCL_GROUPS_GET_REMOVE_GROUP_RES(bufid, remove_group_resp); + + if (remove_group_resp == NULL) { + zb_shell_print_error( + entry->shell, + "Failed to parse response command", + ZB_FALSE); + } else { + shell_fprintf(entry->shell, + (remove_group_resp->status == ZB_ZCL_STATUS_SUCCESS) ? + SHELL_INFO : SHELL_ERROR, + "Response command received, group: %#.4hx, status: %d\n", + remove_group_resp->group_id, + remove_group_resp->status); + + if (remove_group_resp->status != ZB_ZCL_STATUS_SUCCESS) { + zb_shell_print_error( + entry->shell, + "Command not successful", + ZB_FALSE); + } else { + zb_shell_print_done(entry->shell, ZB_FALSE); + } + } + } else if (cmd_info->cmd_id == ZB_ZCL_CMD_GROUPS_GET_GROUP_MEMBERSHIP_RES) { + zb_zcl_groups_get_group_membership_res_t *get_group_mem_resp; + /* Handle up to 20 group IDs from a single device. */ + char text_buffer[141] = {0}; + + ZB_ZCL_GROUPS_GET_GROUP_MEMBERSHIP_RES(bufid, get_group_mem_resp); + + if (get_group_mem_resp == NULL) { + zb_shell_print_error( + entry->shell, + "Failed to parse response command", + ZB_FALSE); + } else { + for (uint8_t i = 0; i < get_group_mem_resp->group_count; i++) { + sprintf((text_buffer + 7 * i), + "%#.4hx,", + get_group_mem_resp->group_id[i]); + } + shell_fprintf(entry->shell, + SHELL_NORMAL, + "short_addr=%#.4hx ep=%hu capacity=%hu group_cnt=%hu group_list=%s\n", + remote_node_short_add, + packet_info->dst_ep, + get_group_mem_resp->capacity, + get_group_mem_resp->group_count, + text_buffer); + + zb_shell_print_done(entry->shell, ZB_FALSE); + } + } else { + /* In case of unknown response. */ + return ZB_FALSE; + } + + /* Cancel the ongoing alarm which was to delete entry in the context manager ... */ + zb_err_code = ZB_SCHEDULE_APP_ALARM_CANCEL(zb_shell_zcl_cmd_timeout_cb, + ctx_mgr_get_index_by_entry(entry)); + ZB_ERROR_CHECK(zb_err_code); + + /* ...and erase it manually. */ + ctx_mgr_delete_entry(entry); + zb_buf_free(bufid); + + return ZB_TRUE; +} diff --git a/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_zdo.c b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_zdo.c new file mode 100644 index 00000000..667f3915 --- /dev/null +++ b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_zdo.c @@ -0,0 +1,2326 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include + +#include +#include +#include +#include +#include "zigbee_shell_utils.h" + +#define BIND_ON_HELP \ + ("Create bind entry.\n" \ + "Usage: on " \ + " ") + +#define BIND_OFF_HELP \ + ("Remove bind entry.\n" \ + "Usage: off " \ + " ") + +#define ACTIVE_EP_HELP \ + ("Send active endpoint request.\n" \ + "Usage: active_ep ") + +#define SIMPLE_DESC_HELP \ + ("Send simple descriptor request.\n" \ + "Usage: simple_desc_req ") + +#define MATCH_DESC_HELP \ + ("Send match descriptor request.\n" \ + "Usage: match_desc " \ + " " \ + " [ ...] " \ + " [ ...] " \ + "[-t | --timeout d:number of seconds to wait for answers]") + +#define NWK_ADDR_HELP \ + ("Resolve EUI64 address to short network address.\n" \ + "Usage: nwk_addr ") + +#define IEEE_ADDR_HELP \ + ("Resolve network short address to EUI64 address.\n" \ + "Usage: ieee_addr ") + +#define EUI64_HELP \ + ("Get/set the eui64 address of the node.\n" \ + "Usage: eui64 []") + +#define MGMT_BIND_HELP \ + ("Get binding table (see spec. 2.4.3.3.4)\n" \ + "Usage: [d:start_index]") + +#define MGMT_LEAVE_HELP \ + ("Perform mgmt_leave_req (see spec. 2.4.3.3.5)\n" \ + "Usage: mgmt_leave [h:device_address eui64] " \ + "[--children] [--rejoin]\n" \ + "--children - Device should also remove its children when leaving.\n" \ + "--rejoin - Device should rejoin network after leave.") + +#define MGMT_LQI_HELP \ + ("Perform mgmt_lqi request.\n" \ + "Usage: mgmt_lqi [d:start index]") + +/* Defines how long to wait, in seconds, for Match Descriptor Response. */ +#define ZIGBEE_SHELL_MATCH_DESC_RESP_TIMEOUT 5 +/* Defines how long to wait, in seconds, for Bind Response. */ +#define ZIGBEE_SHELL_BIND_RESP_TIMEOUT 5 +/* Defines how long to wait, in seconds, for Network Address Response. */ +#define ZIGBEE_SHELL_NWK_ADDR_RESP_TIMEOUT 5 +/* Defines how long to wait, in seconds, for IEEE (EUI64) Address Response. */ +#define ZIGBEE_SHELL_IEEE_ADDR_RESP_TIMEOUT 5 +/* Defines how long to wait, in seconds, for mgmt_leave response. */ +#define ZIGBEE_SHELL_MGMT_LEAVE_RESP_TIMEOUT 5 +/* In case of group binding, the addressing field contains a short address but the endpoint number + * is not included. + */ +#define GROUP_BIND_TABLE_RECORD_SIZE \ + (ZB_OFFSETOF(zb_zdo_binding_table_record_t, dst_address) + sizeof(zb_uint16_t)) + +LOG_MODULE_DECLARE(zigbee_shell, CONFIG_ZIGBEE_SHELL_LOG_LEVEL); + + +/* Forward declarations. */ +static void ctx_timeout_cb(zb_uint8_t ctx_timeout_cb); + +/**@brief Parse a list of cluster IDs. + * + * @param[in] argv Pointer to argument table. + * @param[in] num Number of cluster IDs to scan. + * @param[out] ids Pointer to an array (uint16_t) to store cluster IDs. + * The array may be unaligned. + * + * @retval 1 Parsing succeeded. + * @retval 0 Parsing failed. + */ +static int sscan_cluster_list(char **argv, uint8_t num, void *ids) +{ + uint16_t len = 0; + uint16_t id; + uint16_t *ptr = ids; + + while ((len < num) && parse_hex_u16(argv[len], &id)) { + UNALIGNED_PUT(id, ptr); + len += 1; + ptr += 1; + } + + return (len == num); +} + +/**@brief Function to be executed in Zigbee thread to ensure that + * non-thread-safe ZDO API is safely called. + * + * @param[in] idx Index of context manager entry in which zdo request + * information is stored. + */ +static void zb_zdo_req(uint8_t idx) +{ + struct ctx_entry *ctx_entry = ctx_mgr_get_entry_by_index(idx); + struct zdo_req_info *req_ctx = &(ctx_entry->zdo_data.zdo_req); + zb_ret_t zb_err_code; + + /* Call the actual request function. */ + ctx_entry->id = req_ctx->req_fn(req_ctx->buffer_id, req_ctx->req_cb_fn); + + if (ctx_entry->id == ZB_ZDO_INVALID_TSN) { + zb_shell_print_error(ctx_entry->shell, "Failed to send request", ZB_FALSE); + zb_buf_free(req_ctx->buffer_id); + ctx_mgr_delete_entry(ctx_entry); + + } else if (req_ctx->ctx_timeout && req_ctx->timeout_cb_fn) { + zb_err_code = ZB_SCHEDULE_APP_ALARM(req_ctx->timeout_cb_fn, + ctx_entry->id, + req_ctx->ctx_timeout * + ZB_TIME_ONE_SECOND); + if (zb_err_code != RET_OK) { + zb_shell_print_error( + ctx_entry->shell, + "Unable to schedule timeout callback", + ZB_FALSE); + ctx_mgr_delete_entry(ctx_entry); + } + } +} + +/**@brief Handles timeout error and invalidates match descriptor request + * transaction. + * + * @param[in] tsn ZBOSS transaction sequence number. + */ +static void cmd_zb_match_desc_timeout(zb_uint8_t tsn) +{ + struct ctx_entry *ctx_entry = + ctx_mgr_find_ctx_entry(tsn, CTX_MGR_ZDO_ENTRY_TYPE); + + if (!ctx_entry) { + return; + } + + zb_shell_print_done(ctx_entry->shell, ZB_FALSE); + ctx_mgr_delete_entry(ctx_entry); +} + +/**@brief A callback called on match descriptor response. + * + * @param[in] bufid Reference number to ZBOSS memory buffer. + */ +static void cmd_zb_match_desc_cb(zb_bufid_t bufid) +{ + zb_zdo_match_desc_resp_t *match_desc_resp; + zb_apsde_data_indication_t *data_ind; + struct ctx_entry *ctx_entry; + + match_desc_resp = (zb_zdo_match_desc_resp_t *)zb_buf_begin(bufid); + data_ind = ZB_BUF_GET_PARAM(bufid, zb_apsde_data_indication_t); + ctx_entry = ctx_mgr_find_ctx_entry(match_desc_resp->tsn, + CTX_MGR_ZDO_ENTRY_TYPE); + + if (ctx_entry) { + if (match_desc_resp->status == ZB_ZDP_STATUS_SUCCESS) { + zb_uint8_t *matched_ep = + (zb_uint8_t *)(match_desc_resp + 1); + + shell_print(ctx_entry->shell, ""); + while (match_desc_resp->match_len > 0) { + /* Match EP list follows right after + * response header. + */ + shell_print(ctx_entry->shell, + "src_addr=%04hx ep=%d", + data_ind->src_addr, *matched_ep); + + matched_ep += 1; + match_desc_resp->match_len -= 1; + } + + if (!ctx_entry->zdo_data.is_broadcast) { + zb_shell_print_done(ctx_entry->shell, ZB_FALSE); + ctx_mgr_delete_entry(ctx_entry); + } + } else if (match_desc_resp->status == ZB_ZDP_STATUS_TIMEOUT) { + zb_shell_print_done(ctx_entry->shell, ZB_FALSE); + ctx_mgr_delete_entry(ctx_entry); + } + } + + zb_buf_free(bufid); +} + +static void cmd_zb_active_ep_cb(zb_bufid_t bufid) +{ + struct ctx_entry *ctx_entry; + zb_zdo_ep_resp_t *active_ep_resp = + (zb_zdo_ep_resp_t *)zb_buf_begin(bufid); + + ctx_entry = ctx_mgr_find_ctx_entry(active_ep_resp->tsn, + CTX_MGR_ZDO_ENTRY_TYPE); + if (!ctx_entry) { + zb_buf_free(bufid); + return; + } + + if (active_ep_resp->status == ZB_ZDP_STATUS_SUCCESS) { + char text_buffer[150] = ""; + + sprintf(text_buffer, "src_addr=%04hx ", + active_ep_resp->nwk_addr); + + PRINT_LIST(text_buffer, "ep=", "%d", zb_uint8_t, + ((zb_uint8_t *)active_ep_resp + + sizeof(zb_zdo_ep_resp_t)), + active_ep_resp->ep_count); + + shell_print(ctx_entry->shell, "%s", text_buffer); + + zb_shell_print_done(ctx_entry->shell, ZB_FALSE); + } else { + zb_shell_print_error(ctx_entry->shell, "Active ep request failed", ZB_FALSE); + } + + ctx_mgr_delete_entry(ctx_entry); + zb_buf_free(bufid); +} + +static void cmd_zb_simple_desc_req_cb(zb_bufid_t bufid) +{ + struct ctx_entry *ctx_entry; + zb_zdo_simple_desc_resp_t *simple_desc_resp; + zb_uint8_t in_cluster_cnt; + zb_uint8_t out_cluster_cnt; + void *cluster_list; + + simple_desc_resp = (zb_zdo_simple_desc_resp_t *)zb_buf_begin(bufid); + in_cluster_cnt = simple_desc_resp->simple_desc.app_input_cluster_count; + out_cluster_cnt = + simple_desc_resp->simple_desc.app_output_cluster_count; + cluster_list = + (zb_uint16_t *)simple_desc_resp->simple_desc.app_cluster_list; + + ctx_entry = ctx_mgr_find_ctx_entry(simple_desc_resp->hdr.tsn, + CTX_MGR_ZDO_ENTRY_TYPE); + if (!ctx_entry) { + zb_buf_free(bufid); + return; + } + + if (simple_desc_resp->hdr.status == ZB_ZDP_STATUS_SUCCESS) { + /* Buffer size set to 256, should be sufficient up to 20 clusters (in + out). */ + char text_buffer[256] = ""; + + sprintf(text_buffer, + "src_addr=0x%04hx ep=%d profile_id=0x%04hx" + " app_dev_id=0x%0hx app_dev_ver=0x%0hx ", + simple_desc_resp->hdr.nwk_addr, + simple_desc_resp->simple_desc.endpoint, + simple_desc_resp->simple_desc.app_profile_id, + simple_desc_resp->simple_desc.app_device_id, + simple_desc_resp->simple_desc.app_device_version); + + PRINT_LIST(text_buffer, "in_clusters=", "0x%04hx", zb_uint16_t, + (zb_uint16_t *)cluster_list, in_cluster_cnt); + + PRINT_LIST(text_buffer, "out_clusters=", "0x%04hx", zb_uint16_t, + (zb_uint16_t *)cluster_list + in_cluster_cnt, out_cluster_cnt); + + shell_print(ctx_entry->shell, "%s", text_buffer); + + zb_shell_print_done(ctx_entry->shell, ZB_FALSE); + } else { + zb_shell_print_error( + ctx_entry->shell, + "Simple descriptor request failed", + ZB_FALSE); + } + + ctx_mgr_delete_entry(ctx_entry); + zb_buf_free(bufid); +} + +/**@brief Handles timeout error and invalidates binding transaction. + * + * @param[in] tsn ZBOSS transaction sequence number. + */ +static void cmd_zb_bind_unbind_timeout(zb_uint8_t tsn) +{ + struct ctx_entry *ctx_entry = + ctx_mgr_find_ctx_entry(tsn, CTX_MGR_ZDO_ENTRY_TYPE); + + if (!ctx_entry) { + return; + } + + zb_shell_print_error(ctx_entry->shell, "Bind/unbind request timed out", ZB_FALSE); + ctx_mgr_delete_entry(ctx_entry); +} + +/**@brief A callback called on bind/unbind response. + * + * @param[in] bufid Reference number to ZBOSS memory buffer. + */ +void cmd_zb_bind_unbind_cb(zb_bufid_t bufid) +{ + zb_ret_t zb_err_code; + struct ctx_entry *ctx_entry; + zb_zdo_bind_resp_t *bind_resp = + (zb_zdo_bind_resp_t *)zb_buf_begin(bufid); + + ctx_entry = ctx_mgr_find_ctx_entry(bind_resp->tsn, + CTX_MGR_ZDO_ENTRY_TYPE); + if (!ctx_entry) { + zb_buf_free(bufid); + return; + } + + zb_err_code = ZB_SCHEDULE_APP_ALARM_CANCEL(cmd_zb_bind_unbind_timeout, + ZB_ALARM_ANY_PARAM); + ZB_ERROR_CHECK(zb_err_code); + + if (bind_resp->status == ZB_ZDP_STATUS_SUCCESS) { + zb_shell_print_done(ctx_entry->shell, ZB_FALSE); + } else { + shell_error(ctx_entry->shell, + "Error: Unable to modify binding. Status: %d", + bind_resp->status); + } + + ctx_mgr_delete_entry(ctx_entry); + zb_buf_free(bufid); +} + +/**@brief Handles timeout error and invalidates network address + * request transaction. + * + * @param[in] tsn ZBOSS transaction sequence number. + */ +static void cmd_zb_nwk_addr_timeout(zb_uint8_t tsn) +{ + struct ctx_entry *ctx_entry = + ctx_mgr_find_ctx_entry(tsn, CTX_MGR_ZDO_ENTRY_TYPE); + + if (!ctx_entry) { + return; + } + + zb_shell_print_error(ctx_entry->shell, "Network address request timed out", ZB_FALSE); + ctx_mgr_delete_entry(ctx_entry); +} + +/**@brief A callback called on network address response. + * + * @param[in] bufid Reference number to ZBOSS memory buffer. + */ +void cmd_zb_nwk_addr_cb(zb_bufid_t bufid) +{ + zb_zdo_nwk_addr_resp_head_t *nwk_addr_resp; + struct ctx_entry *ctx_entry; + zb_ret_t zb_err_code; + + nwk_addr_resp = (zb_zdo_nwk_addr_resp_head_t *)zb_buf_begin(bufid); + ctx_entry = ctx_mgr_find_ctx_entry(nwk_addr_resp->tsn, + CTX_MGR_ZDO_ENTRY_TYPE); + if (!ctx_entry) { + zb_buf_free(bufid); + return; + } + + zb_err_code = ZB_SCHEDULE_APP_ALARM_CANCEL(cmd_zb_nwk_addr_timeout, + ZB_ALARM_ANY_PARAM); + ZB_ERROR_CHECK(zb_err_code); + + if (nwk_addr_resp->status == ZB_ZDP_STATUS_SUCCESS) { + zb_uint16_t nwk_addr; + + ZB_LETOH16(&nwk_addr, &(nwk_addr_resp->nwk_addr)); + shell_print(ctx_entry->shell, "%04hx", nwk_addr); + zb_shell_print_done(ctx_entry->shell, ZB_FALSE); + } else { + shell_error(ctx_entry->shell, "Error: Unable to resolve EUI64 source address. Status: %d", + nwk_addr_resp->status); + } + + ctx_mgr_delete_entry(ctx_entry); + zb_buf_free(bufid); +} + +/**@brief Handles timeout error and invalidates IEEE (EUI64) address + * request transaction. + * + * @param[in] tsn ZBOSS transaction sequence number. + */ +static void cmd_zb_ieee_addr_timeout(zb_uint8_t tsn) +{ + struct ctx_entry *ctx_entry = + ctx_mgr_find_ctx_entry(tsn, CTX_MGR_ZDO_ENTRY_TYPE); + + if (ctx_entry) { + zb_shell_print_error(ctx_entry->shell, "IEEE address request timed out", ZB_FALSE); + ctx_mgr_delete_entry(ctx_entry); + } +} + +/**@brief A callback called on IEEE (EUI64) address response. + * + * @param[in] bufid Reference number to ZBOSS memory buffer. + */ +void cmd_zb_ieee_addr_cb(zb_bufid_t bufid) +{ + zb_zdo_ieee_addr_resp_t *ieee_addr_resp; + struct ctx_entry *ctx_entry; + zb_ret_t zb_err_code; + + ieee_addr_resp = (zb_zdo_ieee_addr_resp_t *)zb_buf_begin(bufid); + ctx_entry = ctx_mgr_find_ctx_entry(ieee_addr_resp->tsn, + CTX_MGR_ZDO_ENTRY_TYPE); + if (!ctx_entry) { + zb_buf_free(bufid); + return; + } + + zb_err_code = ZB_SCHEDULE_APP_ALARM_CANCEL(cmd_zb_ieee_addr_timeout, + ZB_ALARM_ANY_PARAM); + ZB_ERROR_CHECK(zb_err_code); + + if (ieee_addr_resp->status == ZB_ZDP_STATUS_SUCCESS) { + zb_address_ieee_ref_t addr_ref; + zb_ieee_addr_t ieee_addr; + zb_uint16_t nwk_addr; + zb_ret_t ret; + + ZB_LETOH64(ieee_addr, ieee_addr_resp->ieee_addr_remote_dev); + ZB_LETOH16(&nwk_addr, &(ieee_addr_resp->nwk_addr_remote_dev)); + + /* Update local IEEE address resolution table. */ + ret = zb_address_update(ieee_addr, nwk_addr, ZB_TRUE, + &addr_ref); + if (ret == RET_OK) { + zb_shell_print_eui64(ctx_entry->shell, ieee_addr); + /* Prepend newline because `zb_shell_print_eui64` + * does not print LF. + */ + zb_shell_print_done(ctx_entry->shell, ZB_TRUE); + } else { + shell_error(ctx_entry->shell, "Error: Failed to updated address table. Status: %d", + ret); + } + } else { + shell_error(ctx_entry->shell, + "Error: Unable to resolve IEEE address. Status: %d", + ieee_addr_resp->status); + } + + ctx_mgr_delete_entry(ctx_entry); + zb_buf_free(bufid); +} + +/**@brief Send Active Endpoint Request. + * + * @code + * zdo active_ep + * @endcode + * + * Send Active Endpoint Request to the node addressed by the short address. + * + * Example: + * @code + * > zdo active_ep 0xb4fc + * > src_addr=B4FC ep=10,11,12 + * Done + * @endcode + * + */ +static int cmd_zb_active_ep(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + + zb_zdo_active_ep_req_t *active_ep_req; + struct ctx_entry *ctx_entry; + zb_bufid_t bufid; + uint16_t addr; + zb_ret_t zb_err_code; + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + bufid = zb_buf_get_out(); + zb_osif_enable_all_inter(); + + if (!bufid) { + zb_shell_print_error( + shell, + "Failed to execute command (buf alloc failed)", + ZB_FALSE); + return -ENOEXEC; + } + + active_ep_req = zb_buf_initial_alloc(bufid, sizeof(*active_ep_req)); + + if (!parse_hex_u16(argv[1], &addr)) { + zb_shell_print_error(shell, "Incorrect network address", ZB_FALSE); + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + + return -EINVAL; + } + active_ep_req->nwk_addr = addr; + + ctx_entry = ctx_mgr_new_entry(CTX_MGR_ZDO_ENTRY_TYPE); + if (!ctx_entry) { + zb_shell_print_error( + shell, + "Request pool empty - wait for ongoing command to finish", + ZB_FALSE); + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + + return -ENOEXEC; + } + + /* Initialize context and send a request. */ + ctx_entry->shell = shell; + ctx_entry->zdo_data.zdo_req.buffer_id = bufid; + ctx_entry->zdo_data.zdo_req.req_fn = zb_zdo_active_ep_req; + ctx_entry->zdo_data.zdo_req.req_cb_fn = cmd_zb_active_ep_cb; + ctx_entry->zdo_data.zdo_req.ctx_timeout = 0; + ctx_entry->zdo_data.zdo_req.timeout_cb_fn = NULL; + + uint8_t ctx_entry_index = ctx_mgr_get_index_by_entry(ctx_entry); + + if (ctx_entry_index == CTX_MGR_ENTRY_IVALID_INDEX) { + zb_shell_print_error(shell, "Invalid index of entry", ZB_FALSE); + ctx_mgr_delete_entry(ctx_entry); + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + + return -ENOEXEC; + } + zb_err_code = ZB_SCHEDULE_APP_CALLBACK(zb_zdo_req, ctx_entry_index); + + if (zb_err_code != RET_OK) { + zb_shell_print_error(shell, "Unable to schedule zdo request", ZB_FALSE); + ctx_mgr_delete_entry(ctx_entry); + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + + return -ENOEXEC; + } + + return 0; +} + +/**@brief Send Simple Descriptor Request. + * + * @code + * zdo simple_desc_req + * @endcode + * + * Send Simple Descriptor Request to the given node and endpoint. + * + * Example: + * @code + * > zdo simple_desc_req 0xefba 10 + * > src_addr=0xEFBA ep=260 profile_id=0x0102 app_dev_id=0x0 app_dev_ver=0x5 + * in_clusters=0x0000,0x0003,0x0004,0x0005,0x0006,0x0008,0x0300 + * out_clusters=0x0300 + * Done + * @endcode + * + */ +static int cmd_zb_simple_desc(const struct shell *shell, size_t argc, + char **argv) +{ + ARG_UNUSED(argc); + + zb_zdo_simple_desc_req_t *simple_desc_req; + struct ctx_entry *ctx_entry; + zb_ret_t zb_err_code; + zb_bufid_t bufid; + zb_uint16_t addr; + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + bufid = zb_buf_get_out(); + zb_osif_enable_all_inter(); + + if (!bufid) { + zb_shell_print_error( + shell, + "Failed to execute command (buf alloc failed)", + ZB_FALSE); + return -ENOEXEC; + } + + simple_desc_req = zb_buf_initial_alloc(bufid, sizeof(*simple_desc_req)); + + if (!parse_hex_u16(argv[1], &addr)) { + zb_shell_print_error(shell, "Invalid network address", ZB_FALSE); + goto error; + } + simple_desc_req->nwk_addr = addr; + + if (!zb_shell_sscan_uint8(argv[2], &(simple_desc_req->endpoint))) { + zb_shell_print_error(shell, "Invalid endpoint", ZB_FALSE); + goto error; + } + + if ((simple_desc_req->endpoint < 1) || (simple_desc_req->endpoint > 254)) { + zb_shell_print_error(shell, "Invalid endpoint value, should be <1-254>", ZB_FALSE); + goto error; + } + + ctx_entry = ctx_mgr_new_entry(CTX_MGR_ZDO_ENTRY_TYPE); + if (!ctx_entry) { + zb_shell_print_error( + shell, + "Request pool empty - wait for ongoing command to finish", + ZB_FALSE); + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + + return -ENOEXEC; + } + + /* Initialize context and send a request. */ + ctx_entry->shell = shell; + ctx_entry->zdo_data.zdo_req.buffer_id = bufid; + ctx_entry->zdo_data.zdo_req.req_fn = zb_zdo_simple_desc_req; + ctx_entry->zdo_data.zdo_req.req_cb_fn = cmd_zb_simple_desc_req_cb; + ctx_entry->zdo_data.zdo_req.ctx_timeout = 0; + ctx_entry->zdo_data.zdo_req.timeout_cb_fn = NULL; + + uint8_t ctx_entry_index = ctx_mgr_get_index_by_entry(ctx_entry); + + if (ctx_entry_index == CTX_MGR_ENTRY_IVALID_INDEX) { + zb_shell_print_error(shell, "Invalid index of entry", ZB_FALSE); + ctx_mgr_delete_entry(ctx_entry); + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + + return -ENOEXEC; + } + zb_err_code = ZB_SCHEDULE_APP_CALLBACK(zb_zdo_req, ctx_entry_index); + + if (zb_err_code != RET_OK) { + zb_shell_print_error(shell, "Unable to schedule zdo request", ZB_FALSE); + ctx_mgr_delete_entry(ctx_entry); + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + + return -ENOEXEC; + } + + return 0; + +error: + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + + return -EINVAL; +} + +/**@brief Send match descriptor request. + * + * @code + * zdo match_desc + + [ ...] + [ ...] + [-t | --timeout ] + * + * @endcode + * + * Send Match Descriptor Request to the `dst_addr` node that is a + * query about the `req_addr` node of the `prof_id` profile ID, + * which must have at least one of `n_input_clusters`(whose IDs are listed + * in `{...}`) or `n_output_clusters` (whose IDs are listed in `{...}`). + * The IDs can be either decimal values or hexadecimal strings. + * Set the timeout of request with `-t` of `--timeout` optional parameter. + * + * Example: + * @code + * zdo match_desc 0xfffd 0xfffd 0x0104 1 6 0 + * @endcode + * + * In this example, the command sends a Match Descriptor Request to all + * non-sleeping nodes regarding all non-sleeping nodes that have + * 1 input cluster ON/OFF (ID 6) and 0 output clusters. + * + */ +static int cmd_zb_match_desc(const struct shell *shell, size_t argc, + char **argv) +{ + zb_zdo_match_desc_param_t *match_desc_req; + struct ctx_entry *ctx_entry; + zb_ret_t zb_err_code; + int timeout_offset; + zb_uint16_t temp; + zb_bufid_t bufid; + int ret_err = 0; + uint16_t *cluster_list = NULL; + zb_bool_t use_timeout = ZB_FALSE; + uint8_t len = sizeof(match_desc_req->cluster_list); + zb_uint16_t timeout = ZIGBEE_SHELL_MATCH_DESC_RESP_TIMEOUT; + + /* We use cluster_list for calls to ZBOSS API but we're not using + * cluster_list value in any way. + */ + (void)(cluster_list); + + if (!strcmp(argv[1], "-t") || !strcmp(argv[1], "--timeout")) { + zb_shell_print_error( + shell, + "Place option 'timeout' at the end of input parameters", + ZB_FALSE); + return -EINVAL; + } + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + bufid = zb_buf_get_out(); + zb_osif_enable_all_inter(); + + if (!bufid) { + zb_shell_print_error( + shell, + "Failed to execute command (buf alloc failed)", + ZB_FALSE); + return -ENOEXEC; + } + + match_desc_req = zb_buf_initial_alloc(bufid, sizeof(*match_desc_req)); + + if (!parse_hex_u16(argv[1], &temp)) { + zb_shell_print_error(shell, "Incorrect network address", ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + match_desc_req->nwk_addr = temp; + + if (!parse_hex_u16(argv[2], &temp)) { + zb_shell_print_error(shell, "Incorrect address of interest", ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + match_desc_req->addr_of_interest = temp; + + if (!parse_hex_u16(argv[3], &temp)) { + zb_shell_print_error(shell, "Incorrect profile id", ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + match_desc_req->profile_id = temp; + + /* The following functions don't perform any checks on the cluster list + * assuming that the Shell isn't abused. In practice the list length + * is limited by @p SHELL_ARGC_MAX which defaults to 12 arguments. + */ + + if (!zb_shell_sscan_uint8(argv[4], &(match_desc_req->num_in_clusters))) { + zb_shell_print_error(shell, "Incorrect number of input clusters", ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + + /* Check that number of output clusters is present in argv. */ + if ((6 + match_desc_req->num_in_clusters) > argc) { + zb_shell_print_error(shell, "Incorrect number of input clusters", ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + + if (match_desc_req->num_in_clusters) { + /* Allocate additional space for cluster IDs. Space for one + * cluster ID is already in the structure, + * hence we subtract len. + */ + cluster_list = zb_buf_alloc_right( + bufid, + match_desc_req->num_in_clusters * + sizeof(uint16_t) - len); + + /* We have used the space, set to 0 so that space for output + * clusters is calculated correctly. + */ + len = 0; + + /* Use match_desc_req->cluster_list as destination rather than + * cluster_list which points to the second element. + */ + uint8_t result = + sscan_cluster_list( + argv + 5, + match_desc_req->num_in_clusters, + match_desc_req->cluster_list); + + if (!result) { + zb_shell_print_error(shell, "Failed to parse input cluster list", ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + } + + if (!zb_shell_sscan_uint8(argv[5 + match_desc_req->num_in_clusters], + &(match_desc_req->num_out_clusters))) { + + zb_shell_print_error(shell, "Incorrect number of output clusters", ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + + /* Check if enough output clusters IDs are provided. */ + if ((6 + match_desc_req->num_in_clusters + + match_desc_req->num_out_clusters) > argc) { + zb_shell_print_error(shell, "Incorrect number of output clusters", ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + + if (match_desc_req->num_out_clusters) { + cluster_list = zb_buf_alloc_right( + bufid, + (match_desc_req->num_out_clusters * + sizeof(uint16_t) - len)); + + uint8_t result = + sscan_cluster_list( + (argv + 5 + match_desc_req->num_in_clusters + + 1), + match_desc_req->num_out_clusters, + ((uint16_t *)match_desc_req->cluster_list + + match_desc_req->num_in_clusters)); + + if (!result) { + zb_shell_print_error( + shell, + "Failed to parse output cluster list", + ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + } + + /* Now let's check for timeout option. */ + timeout_offset = (6 + match_desc_req->num_in_clusters + + match_desc_req->num_out_clusters); + + if (argc == timeout_offset + 2) { + if (!strcmp(argv[timeout_offset], "-t") || + !strcmp(argv[timeout_offset], "--timeout")) { + + use_timeout = ZB_TRUE; + if (zb_shell_sscan_uint(argv[timeout_offset + 1], + (uint8_t *)&timeout, + 2, 10) != 1) { + + /* Let's set the timeout to default. */ + timeout = ZIGBEE_SHELL_MATCH_DESC_RESP_TIMEOUT; + shell_warn(shell, "Could not parse the timeout value, setting to default."); + } + shell_print(shell, "Timeout set to %d.", timeout); + } + } + + ctx_entry = ctx_mgr_new_entry(CTX_MGR_ZDO_ENTRY_TYPE); + if (!ctx_entry) { + zb_shell_print_error( + shell, + "Request pool empty - wait for ongoing command to finish", + ZB_FALSE); + ret_err = -ENOEXEC; + goto error; + } + + /* Initialize context and send a request. */ + ctx_entry->shell = shell; + ctx_entry->zdo_data.zdo_req.buffer_id = bufid; + ctx_entry->zdo_data.is_broadcast = + ZB_NWK_IS_ADDRESS_BROADCAST(match_desc_req->nwk_addr); + ctx_entry->zdo_data.zdo_req.req_fn = zb_zdo_match_desc_req; + ctx_entry->zdo_data.zdo_req.req_cb_fn = cmd_zb_match_desc_cb; + + if (use_timeout || !ctx_entry->zdo_data.is_broadcast) { + ctx_entry->zdo_data.zdo_req.ctx_timeout = timeout; + ctx_entry->zdo_data.zdo_req.timeout_cb_fn = + cmd_zb_match_desc_timeout; + } else { + ctx_entry->zdo_data.zdo_req.ctx_timeout = 0; + ctx_entry->zdo_data.zdo_req.timeout_cb_fn = NULL; + } + + shell_print(shell, "Sending %s request.", + (ctx_entry->zdo_data.is_broadcast ? "broadcast" : "unicast")); + uint8_t ctx_entry_index = ctx_mgr_get_index_by_entry(ctx_entry); + + if (ctx_entry_index == CTX_MGR_ENTRY_IVALID_INDEX) { + zb_shell_print_error(shell, "Invalid index of entry", ZB_FALSE); + ctx_mgr_delete_entry(ctx_entry); + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + + return -ENOEXEC; + } + zb_err_code = ZB_SCHEDULE_APP_CALLBACK(zb_zdo_req, ctx_entry_index); + + if (zb_err_code != RET_OK) { + zb_shell_print_error(shell, "Unable to schedule zdo request", ZB_FALSE); + ctx_mgr_delete_entry(ctx_entry); + ret_err = -ENOEXEC; + goto error; + } + + return 0; + +error: + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + + return ret_err; +} + +/**@brief Create or remove a binding between two endpoints on two nodes. + * + * @code + * zdo bind {on,off} + * + * ` + * @endcode + * + * Create bound connection between a device identified by `source_eui64` and + * endpoint `source_ep`, and a device identified by `destination_addr` and + * endpoint `destination_ep`. The connection is created for ZCL commands and + * attributes assigned to the ZCL cluster `source_cluster_id` on the + * `request_dst_addr` node (usually short address corresponding to + * `source_eui64` argument). + * + * Example: + * @code + * zdo bind on 0B010E0405060708 1 0B010E4050607080 2 8 + * @endcode + * + */ +static int cmd_zb_bind(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + + zb_zdo_bind_req_param_t *bind_req; + zb_ret_t zb_err_code; + zb_bufid_t bufid; + zb_bool_t bind; + int ret_err = 0; + struct ctx_entry *ctx_entry = NULL; + + if (strcmp(argv[0], "on") == 0) { + bind = ZB_TRUE; + } else { + bind = ZB_FALSE; + } + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + bufid = zb_buf_get_out(); + zb_osif_enable_all_inter(); + + if (!bufid) { + zb_shell_print_error( + shell, + "Failed to execute command (buf alloc failed)", + ZB_FALSE); + return -ENOEXEC; + } + + bind_req = ZB_BUF_GET_PARAM(bufid, zb_zdo_bind_req_param_t); + + if (!parse_long_address(argv[1], bind_req->src_address)) { + zb_shell_print_error(shell, "Incorrect EUI64 source address format", ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + + if (!zb_shell_sscan_uint8(argv[2], &(bind_req->src_endp))) { + zb_shell_print_error(shell, "Incorrect source endpoint", ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + + bind_req->dst_addr_mode = parse_address(argv[3], + &(bind_req->dst_address), + ADDR_ANY); + if (bind_req->dst_addr_mode == ADDR_INVALID) { + zb_shell_print_error(shell, "Incorrect destination address format", ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + + if (!zb_shell_sscan_uint8(argv[4], &(bind_req->dst_endp))) { + zb_shell_print_error(shell, "Incorrect destination endpoint", ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + /* Handle binding to group - short addr and endpoint = 0. */ + if (bind_req->dst_addr_mode == ADDR_SHORT) { + zb_uint16_t group_addr = bind_req->dst_address.addr_short; + + /* Verify that dst_endp == 0, prevent binding to device by short address. */ + if (bind_req->dst_endp != 0) { + zb_shell_print_error( + shell, + "Set destination_ep to zero to bind to group", + ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + /* Verify group address. */ + if ((group_addr < ZB_ZCL_ATTR_SCENES_CURRENT_GROUP_MIN_VALUE) + || (group_addr > ZB_ZCL_ATTR_SCENES_CURRENT_GROUP_MAX_VALUE)) { + zb_shell_print_error(shell, "Incorrect group address", ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + + bind_req->dst_addr_mode = ZB_BIND_DST_ADDR_MODE_16_BIT_GROUP; + } + + if (!parse_hex_u16(argv[5], &(bind_req->cluster_id))) { + zb_shell_print_error(shell, "Incorrect cluster ID", ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + + if (!parse_short_address(argv[6], &(bind_req->req_dst_addr))) { + zb_shell_print_error( + shell, + "Incorrect destination network address for the request", + ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + + ctx_entry = ctx_mgr_new_entry(CTX_MGR_ZDO_ENTRY_TYPE); + if (!ctx_entry) { + zb_shell_print_error( + shell, + "Request pool empty - wait for ongoing command to finish", + ZB_FALSE); + ret_err = -ENOEXEC; + goto error; + } + + /* Initialize context and send a request. */ + ctx_entry->shell = shell; + ctx_entry->zdo_data.zdo_req.buffer_id = bufid; + if (bind) { + ctx_entry->zdo_data.zdo_req.req_fn = zb_zdo_bind_req; + ctx_entry->zdo_data.zdo_req.req_cb_fn = cmd_zb_bind_unbind_cb; + } else { + ctx_entry->zdo_data.zdo_req.req_fn = zb_zdo_unbind_req; + ctx_entry->zdo_data.zdo_req.req_cb_fn = cmd_zb_bind_unbind_cb; + } + ctx_entry->zdo_data.zdo_req.ctx_timeout = ZIGBEE_SHELL_BIND_RESP_TIMEOUT; + ctx_entry->zdo_data.zdo_req.timeout_cb_fn = cmd_zb_bind_unbind_timeout; + + uint8_t ctx_entry_index = ctx_mgr_get_index_by_entry(ctx_entry); + + if (ctx_entry_index == CTX_MGR_ENTRY_IVALID_INDEX) { + zb_shell_print_error(shell, "Invalid index of entry", ZB_FALSE); + ctx_mgr_delete_entry(ctx_entry); + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + + return -ENOEXEC; + } + zb_err_code = ZB_SCHEDULE_APP_CALLBACK(zb_zdo_req, ctx_entry_index); + + if (zb_err_code != RET_OK) { + zb_shell_print_error(shell, "Unable to schedule zdo request", ZB_FALSE); + ret_err = -ENOEXEC; + goto error; + } + + return ret_err; + +error: + if (ctx_entry != NULL) { + ctx_mgr_delete_entry(ctx_entry); + } + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + + return ret_err; +} + +/**@brief Resolve eui64 address to a short network address. + * + * @code + * zdo nwk_addr + * @endcode + * + * Example: + * @code + * zdo nwk_addr 0B010E0405060708 + * @endcode + * + */ +static int cmd_zb_nwk_addr(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + + zb_zdo_nwk_addr_req_param_t *nwk_addr_req; + zb_ret_t zb_err_code; + zb_bufid_t bufid; + int ret_err = 0; + struct ctx_entry *ctx_entry = NULL; + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + bufid = zb_buf_get_out(); + zb_osif_enable_all_inter(); + + if (!bufid) { + zb_shell_print_error( + shell, + "Failed to execute command (buf alloc failed)", + ZB_FALSE); + return -ENOEXEC; + } + + nwk_addr_req = ZB_BUF_GET_PARAM(bufid, zb_zdo_nwk_addr_req_param_t); + + if (!parse_long_address(argv[1], nwk_addr_req->ieee_addr)) { + zb_shell_print_error(shell, "Incorrect EUI64 address format", ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + + ctx_entry = ctx_mgr_new_entry(CTX_MGR_ZDO_ENTRY_TYPE); + if (!ctx_entry) { + zb_shell_print_error( + shell, + "Request pool empty - wait for ongoing command to finish", + ZB_FALSE); + ret_err = -ENOEXEC; + goto error; + } + + /* Construct network address request. */ + nwk_addr_req->dst_addr = ZB_NWK_BROADCAST_ALL_DEVICES; + nwk_addr_req->request_type = ZB_ZDO_SINGLE_DEVICE_RESP; + nwk_addr_req->start_index = 0; + + /* Initialize context and send a request. */ + ctx_entry->shell = shell; + ctx_entry->zdo_data.zdo_req.buffer_id = bufid; + ctx_entry->zdo_data.zdo_req.req_fn = zb_zdo_nwk_addr_req; + ctx_entry->zdo_data.zdo_req.req_cb_fn = cmd_zb_nwk_addr_cb; + ctx_entry->zdo_data.zdo_req.ctx_timeout = ZIGBEE_SHELL_NWK_ADDR_RESP_TIMEOUT; + ctx_entry->zdo_data.zdo_req.timeout_cb_fn = cmd_zb_nwk_addr_timeout; + + uint8_t ctx_entry_index = ctx_mgr_get_index_by_entry(ctx_entry); + + if (ctx_entry_index == CTX_MGR_ENTRY_IVALID_INDEX) { + zb_shell_print_error(shell, "Invalid index of entry", ZB_FALSE); + ctx_mgr_delete_entry(ctx_entry); + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + + return -ENOEXEC; + } + zb_err_code = ZB_SCHEDULE_APP_CALLBACK(zb_zdo_req, ctx_entry_index); + + if (zb_err_code != RET_OK) { + zb_shell_print_error(shell, "Unable to schedule zdo request", ZB_FALSE); + ret_err = -ENOEXEC; + goto error; + } + + return ret_err; + +error: + if (ctx_entry != NULL) { + ctx_mgr_delete_entry(ctx_entry); + } + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + + return ret_err; +} + +/**@brief Resolve EUI64 by sending IEEE address request. + * + * @code + * zdo ieee_addr + * @endcode + * + */ +static int cmd_zb_ieee_addr(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + + zb_ret_t zb_err_code; + zb_bufid_t bufid; + zb_uint16_t addr; + int ret_err = 0; + struct ctx_entry *ctx_entry = NULL; + zb_zdo_ieee_addr_req_param_t *ieee_addr_req = NULL; + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + bufid = zb_buf_get_out(); + zb_osif_enable_all_inter(); + + if (!bufid) { + zb_shell_print_error( + shell, + "Failed to execute command (buf alloc failed)", + ZB_FALSE); + return -ENOEXEC; + } + + /* Create new IEEE address request and fill with default values. */ + ieee_addr_req = ZB_BUF_GET_PARAM(bufid, zb_zdo_ieee_addr_req_param_t); + ieee_addr_req->start_index = 0; + ieee_addr_req->request_type = 0; + + if (!parse_hex_u16(argv[1], &addr)) { + zb_shell_print_error(shell, "Incorrect network address", ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + ieee_addr_req->nwk_addr = addr; + ieee_addr_req->dst_addr = ieee_addr_req->nwk_addr; + + ctx_entry = ctx_mgr_new_entry(CTX_MGR_ZDO_ENTRY_TYPE); + if (!ctx_entry) { + zb_shell_print_error( + shell, + "Request pool empty - wait for ongoing command to finish", + ZB_FALSE); + ret_err = -ENOEXEC; + goto error; + } + + /* Initialize context and send a request. */ + ctx_entry->shell = shell; + ctx_entry->zdo_data.zdo_req.buffer_id = bufid; + ctx_entry->zdo_data.zdo_req.req_fn = zb_zdo_ieee_addr_req; + ctx_entry->zdo_data.zdo_req.req_cb_fn = cmd_zb_ieee_addr_cb; + ctx_entry->zdo_data.zdo_req.ctx_timeout = ZIGBEE_SHELL_IEEE_ADDR_RESP_TIMEOUT; + ctx_entry->zdo_data.zdo_req.timeout_cb_fn = cmd_zb_ieee_addr_timeout; + + uint8_t ctx_entry_index = ctx_mgr_get_index_by_entry(ctx_entry); + + if (ctx_entry_index == CTX_MGR_ENTRY_IVALID_INDEX) { + zb_shell_print_error(shell, "Invalid index of entry", ZB_FALSE); + ctx_mgr_delete_entry(ctx_entry); + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + + return -ENOEXEC; + } + zb_err_code = ZB_SCHEDULE_APP_CALLBACK(zb_zdo_req, ctx_entry_index); + + if (zb_err_code != RET_OK) { + zb_shell_print_error(shell, "Unable to schedule zdo request", ZB_FALSE); + ret_err = -ENOEXEC; + goto error; + } + + return ret_err; + +error: + if (ctx_entry != NULL) { + ctx_mgr_delete_entry(ctx_entry); + } + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + + return ret_err; +} + +/**@brief Get the short 16-bit address of the Zigbee device. + * + * @code + * > zdo short + * 0000 + * Done + * @endcode + */ +static int cmd_zb_short(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + zb_uint16_t short_addr; + zb_ieee_addr_t addr; + int i; + + zb_get_long_address(addr); + short_addr = zb_address_short_by_ieee(addr); + if (short_addr != ZB_UNKNOWN_SHORT_ADDR) { + /* We got a valid address. */ + for (i = sizeof(zb_uint16_t) - 1; i >= 0; i--) { + shell_fprintf(shell, SHELL_NORMAL, "%02x", + *((zb_uint8_t *)(&short_addr) + i)); + } + + zb_shell_print_done(shell, ZB_TRUE); + return 0; + } else { + /* Most probably there was no network to join. */ + zb_shell_print_error(shell, "Check if device was commissioned", ZB_FALSE); + return -ENOEXEC; + } +} + +/**@brief Get or set the EUI64 address of the Zigbee device. + * + * @code + * > zdo eui64 [] + * 0b010eaafd745dfa + * Done + * @endcode + */ +static int cmd_zb_eui64(const struct shell *shell, size_t argc, char **argv) +{ + zb_ieee_addr_t addr; + + if (argc == 1) { + zb_get_long_address(addr); + } else { + if (zigbee_is_stack_started()) { + zb_shell_print_error(shell, "Stack already started", ZB_FALSE); + return -ENOEXEC; + } + + if (zigbee_is_nvram_initialised() +#ifdef CONFIG_ZIGBEE_SHELL_DEBUG_CMD + && zb_shell_nvram_enabled() +#endif /* CONFIG_ZIGBEE_SHELL_DEBUG_CMD */ + ) { + shell_warn( + shell, + "Zigbee stack has been configured in the past.\r\n" + "Please disable NVRAM to change the EUI64."); + + zb_shell_print_error( + shell, + "Can't change EUI64 - NVRAM not empty", + ZB_FALSE); + return -ENOEXEC; + } + + if (parse_long_address(argv[1], addr)) { + zb_set_long_address(addr); + } else { + zb_shell_print_error(shell, "Incorrect EUI64 address format", ZB_FALSE); + return -EINVAL; + } + } + + zb_shell_print_eui64(shell, addr); + /* Prepend newline because `zb_shell_print_eui64` does not print LF. */ + zb_shell_print_done(shell, ZB_TRUE); + + return 0; +} + +/**@brief Callback called when mgmt_leave operation takes too long. + * + * @param[in] tsn ZBOSS transaction sequence number obtained as result + * of zdo_mgmt_leave_req. + */ +static void cmd_zb_mgmt_leave_timeout_cb(zb_uint8_t tsn) +{ + struct ctx_entry *ctx_entry = + ctx_mgr_find_ctx_entry(tsn, CTX_MGR_ZDO_ENTRY_TYPE); + + if (ctx_entry == NULL) { + return; + } + + zb_shell_print_error(ctx_entry->shell, "mgmt_leave request timed out", ZB_FALSE); + ctx_mgr_delete_entry(ctx_entry); +} + +/**@brief Callback called when response to mgmt_leave is received. + * + * @param[in] bufid ZBOSS buffer reference. + */ +static void cmd_zb_mgmt_leave_cb(zb_bufid_t bufid) +{ + zb_zdo_mgmt_leave_res_t *mgmt_leave_resp; + struct ctx_entry *ctx_entry; + + mgmt_leave_resp = (zb_zdo_mgmt_leave_res_t *)zb_buf_begin(bufid); + ctx_entry = ctx_mgr_find_ctx_entry(mgmt_leave_resp->tsn, + CTX_MGR_ZDO_ENTRY_TYPE); + if (ctx_entry != NULL) { + zb_ret_t zb_err_code; + + zb_err_code = ZB_SCHEDULE_APP_ALARM_CANCEL( + cmd_zb_mgmt_leave_timeout_cb, + mgmt_leave_resp->tsn); + ZB_ERROR_CHECK(zb_err_code); + + if (mgmt_leave_resp->status == ZB_ZDP_STATUS_SUCCESS) { + zb_shell_print_done(ctx_entry->shell, ZB_FALSE); + } else { + shell_error(ctx_entry->shell, "Error: Unable to remove device. Status: %u", + (uint32_t)mgmt_leave_resp->status); + } + + ctx_mgr_delete_entry(ctx_entry); + } + + zb_buf_free(bufid); +} + +/**@brief Parses command line arguments for zdo mgmt_leave command. + * + * @param[out] mgmt_leave_req Request do be filled in according to command + * line arguments. + * @param[in] shell Pointer to the shell instance, used to print + * errors if necessary. + * @param[in] argc Number of arguments in argv. + * @param[in] argv Arguments from shell to the command. + * + * @return true, if arguments were parsed correctly and mgmt_leave_req + * has been filled up. + * false, if arguments were incorrect. + * + * @sa @ref cmd_zb_mgmt_leave + */ +static bool cmd_zb_mgmt_leave_parse(zb_zdo_mgmt_leave_param_t *mgmt_leave_req, + const struct shell *shell, size_t argc, + char **argv) +{ + zb_uint16_t addr; + size_t arg_idx; + + ZB_MEMSET(mgmt_leave_req, 0, sizeof(*mgmt_leave_req)); + + /* Let it be index of the first argument to parse. */ + arg_idx = 1U; + if (arg_idx >= argc) { + zb_shell_print_error(shell, "Lack of dst_addr parameter", ZB_FALSE); + return false; + } + + if (parse_hex_u16(argv[arg_idx], &addr) != 1) { + zb_shell_print_error(shell, "Incorrect dst_addr", ZB_FALSE); + return false; + } + + mgmt_leave_req->dst_addr = addr; + arg_idx++; + + /* Try parse device_address. */ + if (arg_idx < argc) { + const char *curr_arg = argv[arg_idx]; + + if (curr_arg[0] != '-') { + bool result = parse_long_address( + curr_arg, + mgmt_leave_req->device_address); + if (!result) { + zb_shell_print_error(shell, "Incorrect device_address", ZB_FALSE); + return false; + } + + arg_idx++; + } else { + /* No device_address field. */ + } + } + + /* Parse optional fields. */ + while (arg_idx < argc) { + const char *curr_arg = argv[arg_idx]; + + if (strcmp(curr_arg, "--children") == 0) { + mgmt_leave_req->remove_children = ZB_TRUE; + } else if (strcmp(curr_arg, "--rejoin") == 0) { + mgmt_leave_req->rejoin = ZB_TRUE; + } else { + zb_shell_print_error(shell, "Incorrect argument", ZB_FALSE); + return false; + } + arg_idx++; + } + + return true; +} + +/**@brief Send a request to a remote device in order to leave network + * through zdo mgmt_leave_req (see spec. 2.4.3.3.5). + * + * @code + * zdo mgmt_leave [h:device_address eui64] + * [--children] [--rejoin] + * @endcode + * + * Send @c mgmt_leave_req to a remote node specified by @c dst_addr. + * If @c device_address is omitted or it has value @c 0000000000000000, + * the remote device at address @c dst_addr will remove itself from the network. + * If @c device_address has other value, it must be a long address + * corresponding to @c dst_addr or a long address of child node of @c dst_addr. + * The request is sent with Remove Children and Rejoin flags + * set to @c 0 by default. Use options: + * @c \--children or @c \--rejoin do change respective flags to @c 1. + * For more details, see section 2.4.3.3.5 of the specification. + * + * Examples: + * @code + * zdo mgmt_leave 0x1234 + * @endcode + * Sends @c mgmt_leave_req to the device with short address @c 0x1234, + * asking it to remove itself from the network. @n + * @code + * zdo mgmt_leave 0x1234 --rejoin + * @endcode + * Sends @c mgmt_leave_req to device with short address @c 0x1234, asking it + * to remove itself from the network and perform rejoin.@n + * @code + * zdo mgmt_leave 0x1234 0b010ef8872c633e + * @endcode + * Sends @c mgmt_leave_req to device with short address @c 0x1234, asking it + * to remove device @c 0b010ef8872c633e from the network. + * If the target device with short address @c 0x1234 has also a long address + * @c 0b010ef8872c633e, it will remove itself from the network + * If the target device with short address @c 0x1234 has a child with long + * address @c 0b010ef8872c633e, it will remove the child from the network.@n + * @code + * zdo mgmt_leave 0x1234 --children + * @endcode + * Sends @c mgmt_leave_req to the device with short address @c 0x1234, + * asking it to remove itself and all its children from the network.@n + */ +static int cmd_zb_mgmt_leave(const struct shell *shell, size_t argc, + char **argv) +{ + zb_zdo_mgmt_leave_param_t *mgmt_leave_req; + zb_ret_t zb_err_code; + zb_bufid_t bufid = 0; + struct ctx_entry *ctx_entry = NULL; + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + bufid = zb_buf_get_out(); + zb_osif_enable_all_inter(); + + if (bufid == 0) { + zb_shell_print_error( + shell, + "Failed to execute command (buf alloc failed)", + ZB_FALSE); + goto error; + } + + mgmt_leave_req = ZB_BUF_GET_PARAM(bufid, zb_zdo_mgmt_leave_param_t); + if (!cmd_zb_mgmt_leave_parse(mgmt_leave_req, shell, argc, argv)) { + /* Make sure ZBOSS buffer API is called safely. + * Also, the error message has already been printed + * by cmd_zb_mgmt_leave_parse. + */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + + return -EINVAL; + } + + ctx_entry = ctx_mgr_new_entry(CTX_MGR_ZDO_ENTRY_TYPE); + if (ctx_entry == NULL) { + zb_shell_print_error( + shell, + "Request pool empty - wait for ongoing command to finish", + ZB_FALSE); + goto error; + } + + /* Initialize context and send a request. */ + ctx_entry->shell = shell; + ctx_entry->zdo_data.zdo_req.buffer_id = bufid; + ctx_entry->zdo_data.zdo_req.req_fn = zdo_mgmt_leave_req; + ctx_entry->zdo_data.zdo_req.req_cb_fn = cmd_zb_mgmt_leave_cb; + ctx_entry->zdo_data.zdo_req.ctx_timeout = ZIGBEE_SHELL_MGMT_LEAVE_RESP_TIMEOUT; + ctx_entry->zdo_data.zdo_req.timeout_cb_fn = + cmd_zb_mgmt_leave_timeout_cb; + + uint8_t ctx_entry_index = ctx_mgr_get_index_by_entry(ctx_entry); + + if (ctx_entry_index == CTX_MGR_ENTRY_IVALID_INDEX) { + zb_shell_print_error(shell, "Invalid index of entry", ZB_FALSE); + ctx_mgr_delete_entry(ctx_entry); + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + + return -ENOEXEC; + } + zb_err_code = ZB_SCHEDULE_APP_CALLBACK(zb_zdo_req, ctx_entry_index); + + if (zb_err_code != RET_OK) { + zb_shell_print_error(shell, "Unable to schedule zdo request", ZB_FALSE); + goto error; + } + + return 0; + +error: + if (bufid != 0) { + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + } + if (ctx_entry != NULL) { + ctx_mgr_delete_entry(ctx_entry); + } + return -ENOEXEC; +} + +/**@brief Request timeout callback. + * + * @param[in] tsn ZDO transaction sequence number returned by request. + */ +static void ctx_timeout_cb(zb_uint8_t tsn) +{ + struct ctx_entry *ctx_entry = + ctx_mgr_find_ctx_entry(tsn, CTX_MGR_ZDO_ENTRY_TYPE); + + if (ctx_entry == NULL) { + LOG_ERR("Unable to find context for ZDO request %u.", tsn); + return; + } + + shell_error(ctx_entry->shell, "Error: ZDO request %u timed out.", tsn); + ctx_mgr_delete_entry(ctx_entry); +} + +/**@brief A generic ZDO request callback. + * + * This will print status code for the message and, if not overridden, free + * resources associated with the request. + * + * @param[in] bufid ZBOSS buffer id. + */ +static void zdo_request_cb(zb_bufid_t bufid) +{ + struct ctx_entry *ctx_entry; + zb_zdo_callback_info_t *cb_info; + bool is_request_complete; + zb_ret_t zb_err_code; + + cb_info = (zb_zdo_callback_info_t *)zb_buf_begin(bufid); + ctx_entry = ctx_mgr_find_ctx_entry(cb_info->tsn, + CTX_MGR_ZDO_ENTRY_TYPE); + if (ctx_entry == NULL) { + LOG_ERR("Unable to find context for TSN %d", cb_info->tsn); + zb_buf_free(bufid); + return; + } + + zb_err_code = ZB_SCHEDULE_APP_ALARM_CANCEL(ctx_timeout_cb, + cb_info->tsn); + ZB_ERROR_CHECK(zb_err_code); + + /* Call custom callback if set. If the callback returns false, + * i.e.,request isn't complete, then don't print status, + * invalidate context, or free input buffer. Request might not be + * complete if more messages must be send, e.g., to get multiple + * table entries from a remote device. + */ + if (ctx_entry->zdo_data.app_cb_fn != NULL) { + is_request_complete = + ctx_entry->zdo_data.app_cb_fn(ctx_entry, bufid); + } else { + is_request_complete = true; + } + + if (is_request_complete) { + /* We can free all resources. */ + if (cb_info->status == ZB_ZDP_STATUS_SUCCESS) { + shell_print(ctx_entry->shell, "ZDO request %u complete", + cb_info->tsn); + zb_shell_print_done(ctx_entry->shell, ZB_FALSE); + } else { + shell_error(ctx_entry->shell, "Error: ZDO request %u failed with status %u", + (uint32_t)cb_info->tsn, + (uint32_t)cb_info->status); + } + } else { + /* The request isn't complete, i.e., another ZDO transaction + * went out, hence we need to reschedule a timeout callback. + */ + zb_err_code = + ZB_SCHEDULE_APP_ALARM( + ctx_timeout_cb, + ctx_entry->id, + (ZIGBEE_SHELL_MGMT_LEAVE_RESP_TIMEOUT * ZB_TIME_ONE_SECOND)); + if (zb_err_code != RET_OK) { + zb_shell_print_error( + ctx_entry->shell, + "Unable to schedule timeout callback", + ZB_FALSE); + is_request_complete = true; + } + } + + if (is_request_complete) { + ctx_mgr_delete_entry(ctx_entry); + zb_buf_free(bufid); + } +} + +/**@brief Prints one binding table record. + * + * @param[out] shell The Shell the output is printed to. + * @param[in] idx Record index in binding table. + * @param[in] tbl_rec Record to be printed out. + */ +static void print_bind_resp_record(const struct shell *shell, uint32_t idx, + const zb_zdo_binding_table_record_t *tbl_rec) +{ + char ieee_address_str[sizeof(tbl_rec->src_address) * 2U + 1U]; + + if (ieee_addr_to_str(ieee_address_str, sizeof(ieee_address_str), + tbl_rec->src_address) <= 0) { + + strcpy(ieee_address_str, "(error) "); + } + /* Ensure null-terminated string. */ + ieee_address_str[sizeof(ieee_address_str) - 1U] = '\0'; + + /* Note: Fields in format string are scattered to match position + * in the header, printed by print_bind_resp_records_header. + */ + shell_fprintf(shell, SHELL_NORMAL, "[%3u] %s %8u %#10.4x", + (uint32_t)idx, ieee_address_str, + (uint32_t)tbl_rec->src_endp, + (uint32_t)tbl_rec->cluster_id); + + shell_fprintf(shell, SHELL_NORMAL, "%14.3u ", + (uint32_t)tbl_rec->dst_addr_mode); + + switch (tbl_rec->dst_addr_mode) { + /* 16-bit group address for DstAddr and DstEndp not present. */ + case ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT: + shell_fprintf(shell, SHELL_NORMAL, "%#16.4x N/A", + (uint32_t)tbl_rec->dst_address.addr_short); + break; + + /* 64-bit extended address for DstAddr and DstEndp present. */ + case ZB_APS_ADDR_MODE_64_ENDP_PRESENT: + if (ieee_addr_to_str(ieee_address_str, sizeof(ieee_address_str), + tbl_rec->dst_address.addr_long) <= 0) { + + strcpy(ieee_address_str, "(error) "); + } + /* Ensure null-terminated string. */ + ieee_address_str[sizeof(ieee_address_str) - 1U] = '\0'; + + shell_fprintf(shell, SHELL_NORMAL, "%s %8.3u", ieee_address_str, + (uint32_t)tbl_rec->dst_endp); + break; + + default: + /* This should not happen, as the above case values + * are the only ones allowed by R21 Zigbee spec. + */ + shell_fprintf(shell, SHELL_NORMAL, " N/A N/A"); + break; + } + + shell_print(shell, ""); +} + +/**@brief Prints a header for binding records table. + * + * @param[out] shell The Shell the output is printed to. + */ +static void print_bind_resp_records_header(const struct shell *shell) +{ + /* Note: Position of fields matches corresponding fields printed + * by print_bind_resp_record. + */ + shell_print(shell, "[idx] src_address src_endp cluster_id dst_addr_mode dst_addr dst_endp"); +} + +/**@brief Prints records of binding table received from zdo_mgmt_bind_resp. + * + * @param[out] shell The Shell the output is printed to. + * @param[in] bind_resp Response received from remote device to be printed + * out. + * + * @note Records of type @ref zb_zdo_binding_table_record_t are located + * just after the @ref zb_zdo_mgmt_bind_resp_t structure pointed + * by bind_resp parameter. + */ +static void print_bind_resp(const struct shell *shell, + const zb_zdo_mgmt_bind_resp_t *bind_resp) +{ + const zb_zdo_binding_table_record_t *tbl_rec; + uint32_t idx; + uint32_t next_start_index = ((uint32_t)bind_resp->start_index + + bind_resp->binding_table_list_count); + + /* Set pointer to first binding table record. */ + tbl_rec = (const zb_zdo_binding_table_record_t *)(bind_resp + 1); + + for (idx = bind_resp->start_index; idx < next_start_index; ++idx) { + print_bind_resp_record(shell, idx, tbl_rec); + + /* Handle group binding case - shorter binding table record. */ + if (tbl_rec->dst_addr_mode == ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT) { + tbl_rec = (zb_zdo_binding_table_record_t *)((zb_uint8_t *)tbl_rec + + GROUP_BIND_TABLE_RECORD_SIZE); + } else { + ++tbl_rec; + } + } +} + +/**@brief Callback terminating single mgmt_bind_req transaction. + * + * @note When the binding table is too large to fit into a single mgmt_bind_rsp + * command frame, this function will issue a new mgmt_bind_req_t + * with start_index increased by the number of just received entries + * to download remaining part of the binding table. This process may + * involve several round trips of mgmt_bind_req followed + * by mgmt_bind_rsp until the whole binding table is downloaded. + * + * @param bufid Reference to ZBOSS buffer (as required by Zigbee stack API). + */ +static void cmd_zb_mgmt_bind_cb(zb_bufid_t bufid) +{ + zb_zdo_mgmt_bind_resp_t *resp; + struct ctx_entry *ctx_entry; + + resp = (zb_zdo_mgmt_bind_resp_t *)zb_buf_begin(bufid); + ctx_entry = ctx_mgr_find_ctx_entry(resp->tsn, CTX_MGR_ZDO_ENTRY_TYPE); + + if (ctx_entry != NULL) { + if (resp->status == ZB_ZDP_STATUS_SUCCESS) { + if ((resp->start_index == + ctx_entry->zdo_data.req_seq.start_index)) { + print_bind_resp_records_header( + ctx_entry->shell); + } + print_bind_resp(ctx_entry->shell, resp); + + uint32_t next_start_index = + (resp->start_index + + resp->binding_table_list_count); + + if (next_start_index < resp->binding_table_entries && + (next_start_index < 0xFFU) && + (resp->binding_table_list_count != 0U)) { + + /* We have more entries to get. */ + (void)(zb_buf_reuse(bufid)); + zb_zdo_mgmt_bind_param_t *bind_req = + ZB_BUF_GET_PARAM( + bufid, + zb_zdo_mgmt_bind_param_t); + + bind_req->dst_addr = + ctx_entry->zdo_data.req_seq.dst_addr; + bind_req->start_index = next_start_index; + + ctx_entry->id = zb_zdo_mgmt_bind_req( + bufid, + cmd_zb_mgmt_bind_cb); + if (ctx_entry->id == ZB_ZDO_INVALID_TSN) { + zb_shell_print_error( + ctx_entry->shell, + "Failed to send request", + ZB_FALSE); + goto finish; + } + + /* bufid reused, mark NULL not to free it. */ + bufid = 0; + /* Ctx entry reused, set NULL not to free it. */ + ctx_entry = NULL; + } else { + shell_print( + ctx_entry->shell, + "Total entries for the binding table: %u", + (uint32_t)resp->binding_table_entries); + zb_shell_print_done(ctx_entry->shell, ZB_FALSE); + } + } else { + shell_error(ctx_entry->shell, "Error: Unable to get binding table. Status: %u", + (uint32_t)resp->status); + } + } + +finish: + if (bufid != 0) { + zb_buf_free(bufid); + } + + if (ctx_entry != NULL) { + ctx_mgr_delete_entry(ctx_entry); + } +} + +/**@brief Send a request to a remote device in order to read the binding table + * through zdo mgmt_bind_req (see spec. 2.4.3.3.4). + * + * @note If whole binding table does not fit into single @c mgmt_bind_resp + * frame, the request initiates a series of requests performing full + * binding table download. + * + * @code + * zdo mgmt_bind [d:start_index] + * @endcode + * + * Example: + * @code + * zdo mgmt_bind 0x1234 + * @endcode + * Sends @c mgmt_bind_req to the device with short address @c 0x1234, + * asking it to return its binding table. + */ +static int cmd_zb_mgmt_bind(const struct shell *shell, size_t argc, char **argv) +{ + zb_zdo_mgmt_bind_param_t *bind_req; + zb_ret_t zb_err_code; + int ret_err = 0; + size_t arg_idx = 1U; + zb_bufid_t bufid = 0; + struct ctx_entry *ctx_entry = NULL; + + ctx_entry = ctx_mgr_new_entry(CTX_MGR_ZDO_ENTRY_TYPE); + if (ctx_entry == NULL) { + zb_shell_print_error( + shell, + "Request pool empty - wait for ongoing command to finish", + ZB_FALSE); + ret_err = -ENOEXEC; + goto error; + } + ctx_entry->shell = shell; + + if (arg_idx < argc) { + if (!parse_short_address( + argv[arg_idx], + &(ctx_entry->zdo_data.req_seq.dst_addr))) { + + zb_shell_print_error(shell, "Incorrect dst_addr", ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + arg_idx++; + } else { + zb_shell_print_error(shell, "dst_addr parameter missing", ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + + if (arg_idx < argc) { + if (!zb_shell_sscan_uint8( + argv[arg_idx], + &ctx_entry->zdo_data.req_seq.start_index)) { + + zb_shell_print_error(shell, "Incorrect start_index", ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + arg_idx++; + } else { + /* This parameter was optional, no error. */ + ctx_entry->zdo_data.req_seq.start_index = 0; + } + + if (arg_idx < argc) { + zb_shell_print_error(shell, "Unexpected extra parameters", ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + bufid = zb_buf_get_out(); + zb_osif_enable_all_inter(); + + if (!bufid) { + zb_shell_print_error( + shell, + "Failed to execute command (buf alloc failed)", + ZB_FALSE); + ret_err = -ENOEXEC; + goto error; + } + + bind_req = ZB_BUF_GET_PARAM(bufid, zb_zdo_mgmt_bind_param_t); + ZB_BZERO(bind_req, sizeof(*bind_req)); + bind_req->start_index = ctx_entry->zdo_data.req_seq.start_index; + bind_req->dst_addr = ctx_entry->zdo_data.req_seq.dst_addr; + + /* Initialize context and send a request. */ + ctx_entry->shell = shell; + ctx_entry->zdo_data.zdo_req.buffer_id = bufid; + ctx_entry->zdo_data.zdo_req.req_fn = zb_zdo_mgmt_bind_req; + ctx_entry->zdo_data.zdo_req.req_cb_fn = cmd_zb_mgmt_bind_cb; + ctx_entry->zdo_data.zdo_req.ctx_timeout = 0; + ctx_entry->zdo_data.zdo_req.timeout_cb_fn = NULL; + + uint8_t ctx_entry_index = ctx_mgr_get_index_by_entry(ctx_entry); + + if (ctx_entry_index == CTX_MGR_ENTRY_IVALID_INDEX) { + zb_shell_print_error(shell, "Invalid index of entry", ZB_FALSE); + ctx_mgr_delete_entry(ctx_entry); + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + + return -ENOEXEC; + } + zb_err_code = ZB_SCHEDULE_APP_CALLBACK(zb_zdo_req, ctx_entry_index); + + if (zb_err_code != RET_OK) { + zb_shell_print_error(shell, "Unable to schedule zdo request", ZB_FALSE); + ret_err = -ENOEXEC; + goto error; + } + + return ret_err; + +error: + if (bufid != 0) { + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + } + if (ctx_entry != NULL) { + ctx_mgr_delete_entry(ctx_entry); + } + return ret_err; +} + +/**@brief Callback for a single mgmt_lqi_req transaction. + * + * @note When the lqi table is too large to fit into a single mgmt_bind_rsp + * command frame, this function will issue a new mgmt_lqi_req to download + * reminder of the table. This process may involve several round trips + * of mgmt_lqi_req followed by mgmt_lqi_resp until the whole binding table + * is downloaded. + * + * @param bufid Reference to ZBOSS buffer (as required by Zigbee stack API). + */ +static bool zdo_mgmt_lqi_cb(struct ctx_entry *ctx_entry, zb_bufid_t bufid) +{ + zb_zdo_neighbor_table_record_t *tbl_rec; + zb_zdo_mgmt_lqi_resp_t *resp; + bool result = true; + + resp = (zb_zdo_mgmt_lqi_resp_t *)zb_buf_begin(bufid); + + if (resp->status == ZB_ZDP_STATUS_SUCCESS) { + if (resp->start_index == + ctx_entry->zdo_data.req_seq.start_index) { + shell_print(ctx_entry->shell, + "[idx] ext_pan_id ext_addr short_addr flags permit_join depth lqi"); + } + + tbl_rec = (zb_zdo_neighbor_table_record_t *)((uint8_t *)resp + + sizeof(*resp)); + + for (uint8_t i = 0; i < resp->neighbor_table_list_count; i++) { + shell_fprintf(ctx_entry->shell, SHELL_NORMAL, + "[%3u] ", resp->start_index + i); + + zb_shell_print_eui64(ctx_entry->shell, tbl_rec->ext_pan_id); + + shell_fprintf(ctx_entry->shell, SHELL_NORMAL, " "); + zb_shell_print_eui64(ctx_entry->shell, tbl_rec->ext_addr); + + shell_print(ctx_entry->shell, + "%#7.4x %#8.2x %u %11u %5u", + tbl_rec->network_addr, tbl_rec->type_flags, + tbl_rec->permit_join, + tbl_rec->depth, tbl_rec->lqa); + tbl_rec++; + } + + uint16_t next_index = (resp->start_index + + resp->neighbor_table_list_count); + + /* Get next portion of lqi table if needed. */ + if ((next_index < resp->neighbor_table_entries) && + (next_index < 0xff) && + (resp->neighbor_table_list_count > 0)) { + zb_zdo_mgmt_lqi_param_t *request; + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + (void)(zb_buf_reuse(bufid)); + zb_osif_enable_all_inter(); + + request = ZB_BUF_GET_PARAM(bufid, + zb_zdo_mgmt_lqi_param_t); + + request->start_index = next_index; + request->dst_addr = + ctx_entry->zdo_data.req_seq.dst_addr; + + ctx_entry->id = zb_zdo_mgmt_lqi_req(bufid, + zdo_request_cb); + if (ctx_entry->id != ZB_ZDO_INVALID_TSN) { + /* The request requires further communication, + * hence the outer callback shouldn't free + * resources. + */ + result = false; + } + } + } + + return result; +} + +/**@brief Send a ZDO Mgmt_Lqi_Req command to a remote device. + * + * @code + * zdo mgmt_lqi [d:start index] + * @endcode + * + * Example: + * @code + * zdo mgmt_lqi 0x1234 + * @endcode + * Sends @c mgmt_lqi_req to the device with short address @c 0x1234, + * asking it to return its neighbor table. + */ +static int cmd_zb_mgmt_lqi(const struct shell *shell, size_t argc, char **argv) +{ + zb_zdo_mgmt_lqi_param_t *request; + zb_ret_t zb_err_code; + int ret_err = 0; + zb_bufid_t bufid = 0; + struct ctx_entry *ctx_entry = NULL; + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + bufid = zb_buf_get_out(); + zb_osif_enable_all_inter(); + + if (!bufid) { + zb_shell_print_error(shell, "Failed to allocate request buffer", ZB_FALSE); + ret_err = -ENOEXEC; + goto error; + } + + request = ZB_BUF_GET_PARAM(bufid, zb_zdo_mgmt_lqi_param_t); + if (!parse_short_address(argv[1], &(request->dst_addr))) { + zb_shell_print_error(shell, "Failed to parse destination address", ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + + if (argc >= 3) { + if (!zb_shell_sscan_uint8(argv[2], &(request->start_index))) { + zb_shell_print_error(shell, "Failed to parse start index", ZB_FALSE); + ret_err = -EINVAL; + goto error; + } + } else { + request->start_index = 0; + } + + ctx_entry = ctx_mgr_new_entry(CTX_MGR_ZDO_ENTRY_TYPE); + if (ctx_entry == NULL) { + zb_shell_print_error( + shell, + "Request pool empty - wait for ongoing command to finish", + ZB_FALSE); + ret_err = -ENOEXEC; + goto error; + } + + /* Initialize context and send a request. */ + ctx_entry->shell = shell; + ctx_entry->zdo_data.zdo_req.buffer_id = bufid; + ctx_entry->zdo_data.app_cb_fn = zdo_mgmt_lqi_cb; + ctx_entry->zdo_data.zdo_req.req_fn = zb_zdo_mgmt_lqi_req; + ctx_entry->zdo_data.zdo_req.req_cb_fn = zdo_request_cb; + ctx_entry->zdo_data.zdo_req.ctx_timeout = ZIGBEE_SHELL_MGMT_LEAVE_RESP_TIMEOUT; + ctx_entry->zdo_data.zdo_req.timeout_cb_fn = ctx_timeout_cb; + + uint8_t ctx_entry_index = ctx_mgr_get_index_by_entry(ctx_entry); + + if (ctx_entry_index == CTX_MGR_ENTRY_IVALID_INDEX) { + zb_shell_print_error(shell, "Invalid index of entry", ZB_FALSE); + ctx_mgr_delete_entry(ctx_entry); + + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + + return -ENOEXEC; + } + zb_err_code = ZB_SCHEDULE_APP_CALLBACK(zb_zdo_req, ctx_entry_index); + + if (zb_err_code != RET_OK) { + zb_shell_print_error(shell, "Unable to schedule zdo request", ZB_FALSE); + ret_err = -ENOEXEC; + goto error; + } + + return ret_err; + +error: + if (bufid != 0) { + /* Make sure ZBOSS buffer API is called safely. */ + zb_osif_disable_all_inter(); + zb_buf_free(bufid); + zb_osif_enable_all_inter(); + } + if (ctx_entry != NULL) { + ctx_mgr_delete_entry(ctx_entry); + } + + return ret_err; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_bind, + SHELL_CMD_ARG(on, NULL, BIND_ON_HELP, cmd_zb_bind, 7, 0), + SHELL_CMD_ARG(off, NULL, BIND_OFF_HELP, cmd_zb_bind, 7, 0), + SHELL_SUBCMD_SET_END); + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_zdo, + SHELL_CMD_ARG(active_ep, NULL, ACTIVE_EP_HELP, cmd_zb_active_ep, 2, 0), + SHELL_CMD_ARG(simple_desc_req, NULL, SIMPLE_DESC_HELP, + cmd_zb_simple_desc, 3, 0), + SHELL_CMD_ARG(match_desc, NULL, MATCH_DESC_HELP, cmd_zb_match_desc, 6, + SHELL_OPT_ARG_CHECK_SKIP), + SHELL_CMD_ARG(nwk_addr, NULL, NWK_ADDR_HELP, cmd_zb_nwk_addr, 2, 0), + SHELL_CMD_ARG(ieee_addr, NULL, IEEE_ADDR_HELP, cmd_zb_ieee_addr, 2, 0), + SHELL_CMD_ARG(eui64, NULL, EUI64_HELP, cmd_zb_eui64, 1, 1), + SHELL_CMD_ARG(short, NULL, "Get the short address of the node.", + cmd_zb_short, 1, 0), + SHELL_CMD(bind, &sub_bind, + "Create/remove the binding entry in the remote node.", NULL), + SHELL_CMD_ARG(mgmt_bind, NULL, MGMT_BIND_HELP, cmd_zb_mgmt_bind, 2, 1), + SHELL_CMD_ARG(mgmt_leave, NULL, MGMT_LEAVE_HELP, cmd_zb_mgmt_leave, + 2, 3), + SHELL_CMD_ARG(mgmt_lqi, NULL, MGMT_LQI_HELP, cmd_zb_mgmt_lqi, 2, 1), + SHELL_SUBCMD_SET_END); + +SHELL_CMD_REGISTER(zdo, &sub_zdo, "ZDO manipulation.", NULL); diff --git a/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_zscheduler.c b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_zscheduler.c new file mode 100644 index 00000000..8ba32bb2 --- /dev/null +++ b/subsys/lib/zigbee_shell/src/zigbee_shell_cmd_zscheduler.c @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include + +#include +#include +#include +#include "zigbee_shell_utils.h" + + +#ifdef CONFIG_ZIGBEE_SHELL_DEBUG_CMD +/**@brief Suspend Zigbee scheduler processing + * + * @code + * zscheduler suspend + * @endcode + * + */ +static int cmd_zb_suspend(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + if (!zigbee_debug_zboss_thread_is_created()) { + zb_shell_print_error( + shell, + "Can't suspend Zigbee scheduler - ZBOSS thread not created.", + ZB_FALSE); + return -ENOEXEC; + } + + zigbee_debug_suspend_zboss_thread(); + zb_shell_print_done(shell, ZB_FALSE); + + return 0; +} + +/**@brief Resume Zigbee scheduler processing + * + * @code + * zscheduler resume + * @endcode + * + */ +static int cmd_zb_resume(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + if (!zigbee_debug_zboss_thread_is_created()) { + zb_shell_print_error( + shell, + "Can't resume Zigbee scheduler - ZBOSS thread not created.", + ZB_FALSE); + return -ENOEXEC; + } + + zigbee_debug_resume_zboss_thread(); + zb_shell_print_done(shell, ZB_FALSE); + + return 0; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_zigbee, + SHELL_CMD_ARG(resume, NULL, "Suspend Zigbee scheduler processing.", + cmd_zb_resume, 1, 0), + SHELL_CMD_ARG(suspend, NULL, "Suspend Zigbee scheduler processing.", + cmd_zb_suspend, 1, 0), + SHELL_SUBCMD_SET_END); + +SHELL_CMD_REGISTER(zscheduler, &sub_zigbee, "Zigbee scheduler manipulation.", + NULL); +#endif diff --git a/subsys/lib/zigbee_shell/src/zigbee_shell_ctx_mgr.c b/subsys/lib/zigbee_shell/src/zigbee_shell_ctx_mgr.c new file mode 100644 index 00000000..6ab23740 --- /dev/null +++ b/subsys/lib/zigbee_shell/src/zigbee_shell_ctx_mgr.c @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include + +#include "zigbee_shell_ctx_mgr.h" + + +/* The table to store context manager entries. */ +static struct ctx_entry zb_shell_ctx[CONFIG_ZIGBEE_SHELL_CTX_MGR_ENTRIES_NBR]; + +/* Mutex to protect context manager from being simultaneously accessed + * by multiple threads. + */ +static K_MUTEX_DEFINE(ctx_mgr_mutex); + +void ctx_mgr_delete_entry(struct ctx_entry *ctx_entry) +{ + k_mutex_lock(&ctx_mgr_mutex, K_FOREVER); + + memset(ctx_entry, 0, sizeof(struct ctx_entry)); + + k_mutex_unlock(&ctx_mgr_mutex); +} + +struct ctx_entry *ctx_mgr_new_entry(enum ctx_entry_type type) +{ + uint8_t i; + + k_mutex_lock(&ctx_mgr_mutex, K_FOREVER); + + for (i = 0; i < CONFIG_ZIGBEE_SHELL_CTX_MGR_ENTRIES_NBR; i++) { + if (zb_shell_ctx[i].taken == false) { + /* Mark the entry as used and assign an entry type. */ + zb_shell_ctx[i].taken = true; + zb_shell_ctx[i].type = type; + k_mutex_unlock(&ctx_mgr_mutex); + + return (zb_shell_ctx + i); + } + } + k_mutex_unlock(&ctx_mgr_mutex); + + return NULL; +} + +struct ctx_entry *ctx_mgr_find_ctx_entry(uint8_t id, enum ctx_entry_type type) +{ + uint8_t i; + + k_mutex_lock(&ctx_mgr_mutex, K_FOREVER); + + /* Iterate over the context entries to find a matching entry. */ + for (i = 0; i < CONFIG_ZIGBEE_SHELL_CTX_MGR_ENTRIES_NBR; i++) { + if ((zb_shell_ctx[i].taken == true) && + (zb_shell_ctx[i].type == type) && (zb_shell_ctx[i].id == id)) { + k_mutex_unlock(&ctx_mgr_mutex); + + return (zb_shell_ctx + i); + } + } + k_mutex_unlock(&ctx_mgr_mutex); + + return NULL; +} + +uint8_t ctx_mgr_get_index_by_entry(struct ctx_entry *ctx_entry) +{ + k_mutex_lock(&ctx_mgr_mutex, K_FOREVER); + + if (!PART_OF_ARRAY(zb_shell_ctx, ctx_entry)) { + k_mutex_unlock(&ctx_mgr_mutex); + + return CTX_MGR_ENTRY_IVALID_INDEX; + } + k_mutex_unlock(&ctx_mgr_mutex); + + return (ctx_entry - zb_shell_ctx); +} + +struct ctx_entry *ctx_mgr_get_entry_by_index(uint8_t index) +{ + k_mutex_lock(&ctx_mgr_mutex, K_FOREVER); + + if (index < CONFIG_ZIGBEE_SHELL_CTX_MGR_ENTRIES_NBR) { + k_mutex_unlock(&ctx_mgr_mutex); + + return (zb_shell_ctx + index); + } + k_mutex_unlock(&ctx_mgr_mutex); + + return NULL; +} + +struct ctx_entry *ctx_mgr_find_zcl_entry_by_bufid(zb_bufid_t bufid, enum ctx_entry_type type) +{ + uint8_t i; + + k_mutex_lock(&ctx_mgr_mutex, K_FOREVER); + + /* Iterate over the context entries to find a matching entry. */ + for (i = 0; i < CONFIG_ZIGBEE_SHELL_CTX_MGR_ENTRIES_NBR; i++) { + if ((!zb_shell_ctx[i].taken) || (zb_shell_ctx[i].type != type)) { + continue; + } + + if (zb_shell_ctx[i].zcl_data.pkt_info.buffer == bufid) { + k_mutex_unlock(&ctx_mgr_mutex); + return (zb_shell_ctx + i); + } + } + + k_mutex_unlock(&ctx_mgr_mutex); + + return NULL; +} diff --git a/subsys/lib/zigbee_shell/src/zigbee_shell_utils.c b/subsys/lib/zigbee_shell/src/zigbee_shell_utils.c new file mode 100644 index 00000000..4df76267 --- /dev/null +++ b/subsys/lib/zigbee_shell/src/zigbee_shell_utils.c @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include "zigbee_shell_utils.h" + + +LOG_MODULE_DECLARE(zigbee_shell, CONFIG_ZIGBEE_SHELL_LOG_LEVEL); + +extern zb_uint8_t zb_shell_ep_handler_attr(zb_bufid_t bufid); +extern zb_uint8_t zb_shell_ep_handler_generic_cmd(zb_bufid_t bufid); +extern zb_uint8_t zb_shell_ep_handler_report(zb_bufid_t bufid); +extern zb_uint8_t zb_shell_ep_handler_ping(zb_bufid_t bufid); +extern zb_uint8_t zb_shell_ep_handler_groups_cmd(zb_bufid_t bufid); + +static zb_device_handler_t zb_ep_handlers[] = { + zb_shell_ep_handler_attr, + zb_shell_ep_handler_generic_cmd, + zb_shell_ep_handler_report, + zb_shell_ep_handler_ping, + zb_shell_ep_handler_groups_cmd +}; + +zb_uint8_t zb_shell_ep_handler(zb_bufid_t bufid) +{ + unsigned int idx; + uint8_t ep_handler_cnt = (sizeof(zb_ep_handlers) / + sizeof(zb_device_handler_t)); + + if (IS_ENABLED(CONFIG_ZIGBEE_LOGGER_EP)) { + (void)(zigbee_logger_eprxzcl_ep_handler(bufid)); + } + + for (idx = 0; idx < ep_handler_cnt; idx++) { + if ((zb_ep_handlers[idx])(bufid) == ZB_TRUE) { + return ZB_TRUE; + } + } + + return ZB_FALSE; +} + +int zb_shell_zcl_attr_to_str(char *str_buf, uint16_t buf_len, zb_uint16_t attr_type, + zb_uint8_t *attr) +{ + int bytes_written = 0; + int string_len; + int i; + + if ((str_buf == NULL) || (attr == NULL)) { + return -1; + } + + switch (attr_type) { + /* Boolean. */ + case ZB_ZCL_ATTR_TYPE_BOOL: + bytes_written = snprintf(str_buf, buf_len, "%s", + *((zb_bool_t *)attr) ? "True" + : "False"); + break; + + /* 1 byte. */ + case ZB_ZCL_ATTR_TYPE_8BIT: + case ZB_ZCL_ATTR_TYPE_8BITMAP: + case ZB_ZCL_ATTR_TYPE_U8: + case ZB_ZCL_ATTR_TYPE_8BIT_ENUM: + bytes_written = snprintf(str_buf, buf_len, "%hu", + *((zb_uint8_t *)attr)); + break; + + case ZB_ZCL_ATTR_TYPE_S8: + bytes_written = snprintf(str_buf, buf_len, "%hd", + *((zb_int8_t *)attr)); + break; + + /* 2 bytes. */ + case ZB_ZCL_ATTR_TYPE_16BIT: + case ZB_ZCL_ATTR_TYPE_16BITMAP: + case ZB_ZCL_ATTR_TYPE_U16: + case ZB_ZCL_ATTR_TYPE_16BIT_ENUM: + bytes_written = snprintf(str_buf, buf_len, "%hu", + *((zb_uint16_t *)attr)); + break; + + case ZB_ZCL_ATTR_TYPE_S16: + bytes_written = snprintf(str_buf, buf_len, "%hd", + *((zb_int16_t *)attr)); + break; + + /* 4 bytes. */ + case ZB_ZCL_ATTR_TYPE_32BIT: + case ZB_ZCL_ATTR_TYPE_32BITMAP: + case ZB_ZCL_ATTR_TYPE_U32: + bytes_written = snprintf(str_buf, buf_len, "%u", + *((zb_uint32_t *)attr)); + break; + + case ZB_ZCL_ATTR_TYPE_S32: + bytes_written = snprintf(str_buf, buf_len, "%d", + *((zb_int32_t *)attr)); + break; + + /* String. */ + case ZB_ZCL_ATTR_TYPE_CHAR_STRING: + string_len = attr[0]; + attr++; + + if ((buf_len - bytes_written) < (string_len + 1)) { + return -1; + } + + for (i = 0; i < string_len; i++) { + str_buf[bytes_written + i] = ((char *)attr)[i]; + } + str_buf[bytes_written + i] = '\0'; + bytes_written += string_len + 1; + break; + + case ZB_ZCL_ATTR_TYPE_IEEE_ADDR: + bytes_written = to_hex_str(str_buf, buf_len, + (const uint8_t *)attr, + sizeof(zb_64bit_addr_t), true); + break; + + default: + bytes_written = snprintf(str_buf, buf_len, + "Value type 0x%x unsupported", + attr_type); + break; + } + + return bytes_written; +} + +zb_bool_t zb_shell_is_zcl_cmd_response(zb_zcl_parsed_hdr_t *zcl_hdr, struct ctx_entry *entry) +{ + zb_uint16_t remote_node_short = 0; + struct zcl_packet_info *packet_info = &entry->zcl_data.pkt_info; + + if (zcl_hdr->addr_data.common_data.source.addr_type != ZB_ZCL_ADDR_TYPE_SHORT) { + return ZB_FALSE; + } + + if (packet_info->dst_addr_mode == ZB_APS_ADDR_MODE_64_ENDP_PRESENT) { + remote_node_short = zb_address_short_by_ieee(packet_info->dst_addr.addr_long); + } else { + remote_node_short = packet_info->dst_addr.addr_short; + } + + if (zcl_hdr->addr_data.common_data.source.u.short_addr != remote_node_short) { + return ZB_FALSE; + } + + if (zcl_hdr->profile_id != packet_info->prof_id) { + return ZB_FALSE; + } + + if (zcl_hdr->addr_data.common_data.src_endpoint != packet_info->dst_ep) { + return ZB_FALSE; + } + + return ZB_TRUE; +} + +void zb_shell_zcl_cmd_timeout_cb(zb_uint8_t index) +{ + struct ctx_entry *entry = ctx_mgr_get_entry_by_index(index); + + if (entry == NULL) { + LOG_ERR("Couldn't get attr entry %d - entry not found", index); + return; + } + + zb_shell_print_error(entry->shell, "Request timed out", ZB_FALSE); + ctx_mgr_delete_entry(entry); +} + +int zb_shell_sscan_uint8(const char *bp, uint8_t *value) +{ + /* strtoul() used as a replacement for lacking sscanf() and its output + * is validated to ensure that conversion was successful. + */ + char *end = NULL; + unsigned long tmp_val; + + tmp_val = strtoul(bp, &end, 10); + + if (((tmp_val == 0) && (bp == end)) || (tmp_val > UINT8_MAX)) { + return 0; + } + + *value = tmp_val & 0xFF; + + return 1; +} + +int zb_shell_sscan_uint(const char *bp, uint8_t *value, uint8_t size, uint8_t base) +{ + char *end = NULL; + unsigned long tmp_val; + + tmp_val = strtoul(bp, &end, base); + + /* Validation steps: + * - check if returned tmp_val is not zero - strtoul returns zero + * if failed to convert string. + * - check if end is not equal `bp` - end is set to point + * to the first character after the number or to `pb` + * if nothing is matched. + * - check if returned tmp_val can be stored in variable of length + * given by `size` argument. + */ + if ((tmp_val == 0) && (bp == end)) { + return 0; + } + if (size == 4) { + *((uint32_t *)value) = tmp_val & ((1 << (size * 8)) - 1); + } else if (size == 2) { + *((uint16_t *)value) = tmp_val & ((1 << (size * 8)) - 1); + } else { + *value = tmp_val & ((1 << (size * 8)) - 1); + } + + return 1; +} + +void zb_shell_print_hexdump(const struct shell *shell, const uint8_t *data, uint8_t size, + bool reverse) +{ + char addr_buf[2 * size + 1]; + int bytes_written = 0; + + memset(addr_buf, 0, sizeof(addr_buf)); + + bytes_written = to_hex_str(addr_buf, (uint16_t)sizeof(addr_buf), data, + size, reverse); + if (bytes_written < 0) { + shell_fprintf(shell, SHELL_ERROR, "%s", "Unable to print hexdump"); + } else { + shell_fprintf(shell, SHELL_NORMAL, "%s", addr_buf); + } +} diff --git a/subsys/osif/zb_nrf_async_serial.c b/subsys/osif/zb_nrf_async_serial.c new file mode 100644 index 00000000..52d50699 --- /dev/null +++ b/subsys/osif/zb_nrf_async_serial.c @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include "zb_nrf_platform.h" +#include + +#define DEFAULT_SINGLE_PORT_INSTANCE 0 + +static K_SEM_DEFINE(tx_done_sem, 1, 1); +static K_SEM_DEFINE(rx_done_sem, 1, 1); +static const struct device *uart_dev = DEVICE_DT_GET(DT_CHOSEN(ncs_zigbee_uart)); +static bool is_sleeping; +static bool uart_initialized; + +static zb_callback_t char_handler; +static zb_mserial_recv_data_cb_t rx_data_cb; +static zb_serial_send_data_cb_t tx_data_cb; +static zb_serial_send_data_cb_t tx_trx_data_cb; + +#ifdef CONFIG_ZBOSS_TRACE_BINARY_NCP_TRANSPORT_LOGGING +static uint8_t uart_tx_buf_mem[CONFIG_ZIGBEE_UART_TX_BUF_LEN]; +static size_t uart_tx_buf_size; +#endif /* CONFIG_ZBOSS_TRACE_BINARY_NCP_TRANSPORT_LOGGING */ + +static uint8_t *uart_tx_buf; +static uint8_t *uart_tx_buf_bak; +static volatile size_t uart_tx_buf_offset; +static volatile size_t uart_tx_buf_len; + +static uint8_t uart_rx_buf_mem[CONFIG_ZIGBEE_UART_RX_BUF_LEN]; +static struct ring_buf rx_ringbuf; + +static uint8_t *uart_rx_buf; +static volatile size_t uart_rx_buf_offset; +static volatile size_t uart_rx_buf_len; + +static void uart_tx_timeout(struct k_timer *dummy); +static void uart_rx_timeout(struct k_timer *dummy); + +static K_TIMER_DEFINE(uart_tx_timer, uart_tx_timeout, NULL); +static K_TIMER_DEFINE(uart_rx_timer, uart_rx_timeout, NULL); + +/** + * Inform user about received data and unlock for the next reception. + */ +static void uart_rx_notify(zb_bufid_t bufid) +{ + uint8_t *rx_buf = uart_rx_buf; + size_t rx_buf_len = uart_rx_buf_offset; + + ARG_UNUSED(bufid); + + uart_rx_buf_len = 0; + uart_rx_buf_offset = 0; + uart_rx_buf = NULL; + k_timer_stop(&uart_rx_timer); + k_sem_give(&rx_done_sem); + + if (rx_data_cb) { + rx_data_cb(DEFAULT_SINGLE_PORT_INSTANCE, rx_buf, rx_buf_len); + } +} + +static void uart_rx_bytes(uint8_t *buf, size_t len) +{ + if (char_handler) { + for (size_t i = 0; i < len; i++) { + char_handler(buf[i]); + } + } +} + +/** + * Inform user about transmission timeout. + */ +static void uart_tx_timeout(struct k_timer *dummy) +{ + uart_tx_buf_offset = 0; + uart_tx_buf_len = 0; + uart_tx_buf = uart_tx_buf_bak; + k_sem_give(&tx_done_sem); + + if (tx_trx_data_cb) { + zigbee_schedule_callback( + tx_trx_data_cb, + SERIAL_SEND_TIMEOUT_EXPIRED); + tx_trx_data_cb = NULL; + } +} + +/** + * Inform user about reception timeout. + */ +static void uart_rx_timeout(struct k_timer *dummy) +{ + if (uart_rx_buf) { + uart_rx_buf_len = 0; + + if (zigbee_schedule_callback(uart_rx_notify, 0)) { + uart_rx_buf_offset = 0; + uart_rx_buf = NULL; + k_sem_give(&rx_done_sem); + } + } +} + +static void handle_rx_ready_evt(const struct device *dev) +{ + int recv_len = 0; + uint8_t buffer[CONFIG_ZIGBEE_UART_RX_BUF_LEN] = {0}; + + /* Copy data to the user's buffer. */ + if (uart_rx_buf && (uart_rx_buf_offset < uart_rx_buf_len)) { + uart_rx_buf_offset += ring_buf_get( + &rx_ringbuf, + &uart_rx_buf[uart_rx_buf_offset], + uart_rx_buf_len - uart_rx_buf_offset); + + recv_len = uart_fifo_read( + dev, + &uart_rx_buf[uart_rx_buf_offset], + uart_rx_buf_len - uart_rx_buf_offset); + + uart_rx_bytes(&uart_rx_buf[uart_rx_buf_offset], recv_len); + uart_rx_buf_offset += recv_len; + + if (uart_rx_buf_offset == uart_rx_buf_len) { + uart_rx_buf_len = 0; + k_timer_stop(&uart_rx_timer); + + if (zigbee_schedule_callback(uart_rx_notify, 0)) { + uart_rx_buf_offset = 0; + uart_rx_buf = NULL; + k_sem_give(&rx_done_sem); + } + } else { + k_timer_start( + &uart_rx_timer, + K_MSEC(CONFIG_ZIGBEE_UART_PARTIAL_RX_TIMEOUT), + K_NO_WAIT); + } + } else { + recv_len = uart_fifo_read(dev, buffer, sizeof(buffer)); + uart_rx_bytes(buffer, recv_len); + + /* Store remaining bytes inside the ring buffer. */ + if (recv_len > ring_buf_space_get(&rx_ringbuf)) { + uint8_t dummy_buffer[CONFIG_ZIGBEE_UART_RX_BUF_LEN]; + + (void)ring_buf_get(&rx_ringbuf, dummy_buffer, + recv_len - ring_buf_space_get(&rx_ringbuf)); + } + + (void)ring_buf_put(&rx_ringbuf, buffer, recv_len); + } +} + +static void handle_tx_ready_evt(const struct device *dev) +{ + if (uart_tx_buf_len <= uart_tx_buf_offset) { + uart_irq_tx_disable(dev); + return; + } + + uart_tx_buf_offset += uart_fifo_fill( + dev, + &uart_tx_buf[uart_tx_buf_offset], + uart_tx_buf_len - uart_tx_buf_offset); + + if (uart_tx_buf_len == uart_tx_buf_offset) { + uart_tx_buf_offset = 0; + uart_tx_buf_len = 0; + uart_tx_buf = uart_tx_buf_bak; + + k_timer_stop(&uart_tx_timer); + k_sem_give(&tx_done_sem); + + if (tx_trx_data_cb) { + zigbee_schedule_callback( + tx_trx_data_cb, + SERIAL_SEND_SUCCESS); + tx_trx_data_cb = NULL; + } + } else { + k_timer_start( + &uart_tx_timer, + K_MSEC(CONFIG_ZIGBEE_UART_PARTIAL_TX_TIMEOUT), + K_NO_WAIT); + } +} + +static void interrupt_handler(const struct device *dev, void *user_data) +{ + while (uart_irq_update(dev) && uart_irq_is_pending(dev)) { + if (uart_irq_rx_ready(dev)) { + handle_rx_ready_evt(dev); + } + + if (uart_irq_tx_ready(dev)) { + handle_tx_ready_evt(dev); + } + } +} + +void zb_osif_async_serial_init(void) +{ + if (uart_initialized) { + return; + } + + /* + * Reset all static variables in case of runtime init/uninit sequence. + */ + char_handler = NULL; + rx_data_cb = NULL; + tx_data_cb = NULL; + tx_trx_data_cb = NULL; + uart_tx_buf_len = 0; + uart_tx_buf_offset = 0; + uart_rx_buf = NULL; + uart_rx_buf_len = 0; + uart_rx_buf_offset = 0; + +#ifdef CONFIG_ZBOSS_TRACE_BINARY_NCP_TRANSPORT_LOGGING + uart_tx_buf_size = sizeof(uart_tx_buf_mem); + uart_tx_buf = uart_tx_buf_mem; + uart_tx_buf_bak = uart_tx_buf_mem; +#else + uart_tx_buf = NULL; + uart_tx_buf_bak = NULL; +#endif /* CONFIG_ZBOSS_TRACE_BINARY_NCP_TRANSPORT_LOGGING */ + + if (!device_is_ready(uart_dev)) { + return; + } + + ring_buf_init(&rx_ringbuf, sizeof(uart_rx_buf_mem), uart_rx_buf_mem); + uart_irq_callback_set(uart_dev, interrupt_handler); + + /* Enable rx interrupts. */ + uart_irq_rx_enable(uart_dev); + + uart_initialized = true; +} + +void zb_osif_async_serial_sleep(void) +{ + if (uart_dev == NULL) { + return; + } + + is_sleeping = true; + uart_irq_tx_disable(uart_dev); + uart_irq_rx_disable(uart_dev); +} + +void zb_osif_async_serial_wake_up(void) +{ + if (uart_dev == NULL) { + return; + } + + is_sleeping = false; + + /* Enable rx interrupts. */ + uart_irq_rx_enable(uart_dev); +} + +void zb_osif_serial_recv_data(zb_uint8_t *buf, zb_ushort_t len) +{ + if (!rx_data_cb) { + return; + } + + if ((uart_dev == NULL) || (len == 0) || is_sleeping) { + if (rx_data_cb) { + rx_data_cb(DEFAULT_SINGLE_PORT_INSTANCE, NULL, 0); + } + return; + } + + if (k_sem_take(&rx_done_sem, + K_MSEC(CONFIG_ZIGBEE_UART_RX_TIMEOUT))) { + /* Ongoing asynchronous reception. */ + if (rx_data_cb) { + rx_data_cb(DEFAULT_SINGLE_PORT_INSTANCE, NULL, 0); + } + return; + } + + /* + * Flush already received data. + * Disable interrupt to block buffer reads from the interrupt handler. + */ + uart_irq_rx_disable(uart_dev); + uart_rx_buf_offset = ring_buf_get(&rx_ringbuf, buf, len); + uart_irq_rx_enable(uart_dev); + + if (uart_rx_buf_offset == len) { + uart_rx_buf_offset = 0; + k_sem_give(&rx_done_sem); + rx_data_cb(0, buf, len); + return; + } + + /* + * Since the driver is kept in a continuous reception, it is enough to + * pass the buffer through a variable. + */ + uart_rx_buf_len = len; + uart_rx_buf = buf; +} + +void zb_osif_serial_set_cb_recv_data(zb_mserial_recv_data_cb_t cb) +{ + rx_data_cb = cb; +} + +void zb_osif_serial_send_data(zb_uint8_t *buf, zb_ushort_t len) +{ + if ((uart_dev == NULL) || is_sleeping) { + if (tx_data_cb) { + tx_data_cb(SERIAL_SEND_ERROR); + } + return; + } + + if (k_sem_take(&tx_done_sem, K_MSEC(CONFIG_ZIGBEE_UART_TX_TIMEOUT))) { + /* Ongoing synchronous transmission. */ + if (tx_data_cb) { + tx_data_cb(SERIAL_SEND_BUSY); + } + return; + } + + uart_tx_buf_bak = uart_tx_buf; + uart_tx_buf = buf; + uart_tx_buf_len = len; + uart_tx_buf_offset = 0; + + /* Pass the TX callback for a single (ongoing) transmission. */ + tx_trx_data_cb = tx_data_cb; + + /* Enable TX ready event. */ + uart_irq_tx_enable(uart_dev); +} + +void zb_osif_serial_set_cb_send_data(zb_serial_send_data_cb_t cb) +{ + tx_data_cb = cb; +} + +void zb_osif_async_serial_flush(void) +{ + (void)k_sem_take(&tx_done_sem, K_FOREVER); + k_sem_give(&tx_done_sem); +} + +void zb_osif_async_serial_set_uart_byte_received_cb(zb_callback_t hnd) +{ + char_handler = hnd; +} + +#ifdef CONFIG_ZBOSS_TRACE_BINARY_NCP_TRANSPORT_LOGGING +void zb_osif_set_user_io_buffer(zb_byte_array_t *buf_ptr, zb_ushort_t capacity) +{ + (void)k_sem_take(&tx_done_sem, K_FOREVER); + + uart_tx_buf = buf_ptr->ring_buf; + uart_tx_buf_bak = uart_tx_buf; + uart_tx_buf_size = capacity; + + k_sem_give(&tx_done_sem); +} + +void zb_osif_async_serial_put_bytes(const zb_uint8_t *buf, zb_short_t len) +{ +#if !(defined(ZB_HAVE_ASYNC_SERIAL) && \ + defined(CONFIG_ZBOSS_TRACE_LOG_LEVEL_OFF)) + + if ((uart_dev == NULL) || is_sleeping) { + return; + } + + if (len > uart_tx_buf_size) { + return; + } + + /* + * Wait forever since there is no way to inform higher layer + * about TX busy state. + */ + (void)k_sem_take(&tx_done_sem, K_FOREVER); + memcpy(uart_tx_buf, buf, len); + + uart_tx_buf_len = len; + uart_tx_buf_offset = 0; + + /* Enable tx interrupts. */ + uart_irq_tx_enable(uart_dev); + +#endif /* !(ZB_HAVE_ASYNC_SERIAL && CONFIG_ZBOSS_TRACE_LOG_LEVEL_OFF) */ +} +#endif /* CONFIG_ZBOSS_TRACE_BINARY_NCP_TRANSPORT_LOGGING */ diff --git a/subsys/osif/zb_nrf_crypto.c b/subsys/osif/zb_nrf_crypto.c new file mode 100644 index 00000000..cc1a05ed --- /dev/null +++ b/subsys/osif/zb_nrf_crypto.c @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#if CONFIG_NRF_SECURITY +#include +#include +#else +#error No crypto suite for Zigbee stack has been selected +#endif + +#include "zb_nrf_crypto.h" + +#define ECB_AES_KEY_SIZE 16 +#define ECB_AES_BLOCK_SIZE 16 + +void zb_osif_rng_init(void) +{ +} + +zb_uint32_t zb_random_seed(void) +{ + zb_uint32_t rnd_val = 0; + int err_code; + +#if defined(CONFIG_ENTROPY_HAS_DRIVER) + err_code = sys_csrand_get(&rnd_val, sizeof(rnd_val)); +#else +#warning Entropy driver required to generate cryptographically secure random numbers + sys_rand_get(&rnd_val, sizeof(rnd_val)); + err_code = 0; +#endif /* CONFIG_ENTROPY_HAS_DRIVER */ + __ASSERT_NO_MSG(err_code == 0); + return rnd_val; +} + +void psa_init(void) +{ + psa_status_t status = psa_crypto_init(); + ZVUNUSED(status); + __ASSERT(status == PSA_SUCCESS, "Cannot initialize PSA crypto"); +} + +void zb_osif_aes_init(void) +{ + psa_init(); +} + +void zb_osif_aes128_hw_encrypt(const zb_uint8_t *key, const zb_uint8_t *msg, zb_uint8_t *c) +{ + if (!(c && msg && key)) { + __ASSERT(false, "NULL argument passed"); + return; + } + + psa_status_t status; + psa_key_id_t key_id; + uint32_t out_len; + + ZVUNUSED(status); + + psa_key_attributes_t key_attributes = PSA_KEY_ATTRIBUTES_INIT; + psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT); + psa_set_key_lifetime(&key_attributes, PSA_KEY_LIFETIME_VOLATILE); + psa_set_key_algorithm(&key_attributes, PSA_ALG_ECB_NO_PADDING); + psa_set_key_type(&key_attributes, PSA_KEY_TYPE_AES); + psa_set_key_bits(&key_attributes, 128); + + status = psa_import_key(&key_attributes, key, ECB_AES_KEY_SIZE, &key_id); + __ASSERT(status == PSA_SUCCESS, "psa_import failed! (Error: %d)", status); + + psa_reset_key_attributes(&key_attributes); + + status = psa_cipher_encrypt(key_id, PSA_ALG_ECB_NO_PADDING, msg, ECB_AES_KEY_SIZE, c, + ECB_AES_KEY_SIZE, &out_len); + __ASSERT(status == PSA_SUCCESS, "psa_cipher_encrypt failed! (Error: %d)", status); + + psa_destroy_key(key_id); +} + +zb_int_t zb_osif_scalarmult(zb_uint8_t *result_point, + const zb_uint8_t *scalar, + const zb_uint8_t *point) +{ + ocrypto_curve25519_scalarmult(result_point, scalar, point); + return 0; +} diff --git a/subsys/osif/zb_nrf_crypto.h b/subsys/osif/zb_nrf_crypto.h new file mode 100644 index 00000000..1afa1684 --- /dev/null +++ b/subsys/osif/zb_nrf_crypto.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef ZB_NRF_CRYPTO_H__ +#define ZB_NRF_CRYPTO_H__ + +void zb_osif_rng_init(void); +void zb_osif_aes_init(void); + +#endif /* ZB_NRF_CRYPTO_H__ */ diff --git a/subsys/osif/zb_nrf_led_button.c b/subsys/osif/zb_nrf_led_button.c new file mode 100644 index 00000000..6bb5c201 --- /dev/null +++ b/subsys/osif/zb_nrf_led_button.c @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include + + +#define LED_PWM_PERIOD_US (USEC_PER_SEC / 100U) + +#define LED_PWM_ENABLED(idx) DT_NODE_HAS_STATUS(DT_NODELABEL(pwm_led##idx), okay) +#define LED_PWM_DT_SPEC(idx) PWM_DT_SPEC_GET(DT_NODELABEL(pwm_led##idx)) + +#ifdef CONFIG_ZIGBEE_USE_DIMMABLE_LED +static const struct pwm_dt_spec *led_pwm; +#if LED_PWM_ENABLED(0) +static const struct pwm_dt_spec led_pwm0 = LED_PWM_DT_SPEC(0); +#endif +#if LED_PWM_ENABLED(1) +static const struct pwm_dt_spec led_pwm1 = LED_PWM_DT_SPEC(1); +#endif +#if LED_PWM_ENABLED(2) +static const struct pwm_dt_spec led_pwm2 = LED_PWM_DT_SPEC(2); +#endif +#if LED_PWM_ENABLED(3) +static const struct pwm_dt_spec led_pwm3 = LED_PWM_DT_SPEC(3); +#endif +#endif /* CONFIG_ZIGBEE_USE_DIMMABLE_LED */ + +LOG_MODULE_DECLARE(zboss_osif, CONFIG_ZBOSS_OSIF_LOG_LEVEL); + + +#ifdef CONFIG_ZIGBEE_USE_BUTTONS +static void button_update_state(zb_uint8_t button_id, zb_bool_t state) +{ + if (state) { + zb_button_on_cb(button_id); + } else { + zb_button_off_cb(button_id); + } +} + +static void button_handler(uint32_t button_state, uint32_t has_changed) +{ + switch (has_changed) { + case DK_BTN1_MSK: + button_update_state(0, button_state & DK_BTN1_MSK); + break; + case DK_BTN2_MSK: + button_update_state(1, button_state & DK_BTN2_MSK); + break; + case DK_BTN3_MSK: + button_update_state(2, button_state & DK_BTN3_MSK); + break; + case DK_BTN4_MSK: + button_update_state(3, button_state & DK_BTN4_MSK); + break; + default: + break; + } +} +#endif /* CONFIG_ZIGBEE_USE_BUTTONS */ + + +#ifdef CONFIG_ZIGBEE_USE_LEDS +void zb_osif_led_button_init(void) +{ + int err = dk_leds_init(); + + if (err) { + LOG_ERR("Cannot init LEDs (err: %d)", err); + } +} + +void zb_osif_led_on(zb_uint8_t led_no) +{ + switch (led_no) { + case 0: + dk_set_led(DK_LED1, 1); + break; + case 1: + dk_set_led(DK_LED2, 1); + break; + case 2: + dk_set_led(DK_LED3, 1); + break; + case 3: + dk_set_led(DK_LED4, 1); + break; + default: + break; + } +} + +void zb_osif_led_off(zb_uint8_t led_no) +{ + switch (led_no) { + case 0: + dk_set_led(DK_LED1, 0); + break; + case 1: + dk_set_led(DK_LED2, 0); + break; + case 2: + dk_set_led(DK_LED3, 0); + break; + case 3: + dk_set_led(DK_LED4, 0); + break; + default: + break; + } +} +#endif /* CONFIG_ZIGBEE_USE_LEDS */ + +#ifdef CONFIG_ZIGBEE_USE_DIMMABLE_LED +zb_bool_t zb_osif_led_level_init(zb_uint8_t led_no) +{ + if (led_pwm != NULL) { + /* Driver is already initialized. */ + return ZB_FALSE; + } + + switch (led_no) { +#if LED_PWM_ENABLED(0) + case 0: + led_pwm = &led_pwm0; + break; +#endif +#if LED_PWM_ENABLED(1) + case 1: + led_pwm = &led_pwm1; + break; +#endif +#if LED_PWM_ENABLED(2) + case 2: + led_pwm = &led_pwm2; + break; +#endif +#if LED_PWM_ENABLED(3) + case 3: + led_pwm = &led_pwm3; + break; +#endif + default: + return ZB_FALSE; + } + + if (!device_is_ready(led_pwm->dev)) { + LOG_ERR("Device %s is not ready!", led_pwm->dev->name); + + return ZB_FALSE; + } + + return ZB_TRUE; +} + +void zb_osif_led_on_set_level(zb_uint8_t level) +{ + uint32_t pulse = level * LED_PWM_PERIOD_US / 255U; + + if (pwm_set_dt(led_pwm, PWM_USEC(LED_PWM_PERIOD_US), PWM_USEC(pulse))) { + LOG_ERR("Pwm led 4 set fails:\n"); + } +} +#endif /* CONFIG_ZIGBEE_USE_DIMMABLE_LED */ + +#ifdef CONFIG_ZIGBEE_USE_BUTTONS +void zb_osif_button_cb(zb_uint8_t arg) +{ + /* Intentionally left empty. */ +} + +zb_bool_t zb_setup_buttons_cb(zb_callback_t cb) +{ + static bool is_init; + int err; + + if (is_init) { + return ZB_TRUE; + } + err = dk_buttons_init(button_handler); + + if (err) { + LOG_ERR("Cannot init buttons (err: %d)", err); + + return ZB_FALSE; + } + is_init = true; + + return ZB_TRUE; +} + +zb_bool_t zb_osif_button_state(zb_uint8_t arg) +{ + uint32_t button_state, has_changed; + uint32_t button_mask = 0; + + dk_read_buttons(&button_state, &has_changed); + + switch (arg) { + case 0: + button_mask = DK_BTN1_MSK; + break; + case 1: + button_mask = DK_BTN2_MSK; + break; + case 2: + button_mask = DK_BTN3_MSK; + break; + case 3: + button_mask = DK_BTN4_MSK; + break; + default: + break; + } + + return (button_state & button_mask ? ZB_FALSE : ZB_TRUE); +} +#endif /* CONFIG_ZIGBEE_USE_BUTTONS */ diff --git a/subsys/osif/zb_nrf_logger.c b/subsys/osif/zb_nrf_logger.c new file mode 100644 index 00000000..1c13e480 --- /dev/null +++ b/subsys/osif/zb_nrf_logger.c @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include + +#if defined ZB_NRF_TRACE + +#include + +LOG_MODULE_DECLARE(zboss_osif, CONFIG_ZBOSS_OSIF_LOG_LEVEL); + +RING_BUF_DECLARE(logger_buf, CONFIG_ZBOSS_TRACE_LOGGER_BUFFER_SIZE); + +void zb_osif_logger_put_bytes(const zb_uint8_t *buf, zb_short_t len) +{ + zb_uint8_t *buf_dest = NULL; + zb_uint32_t allocated = 0; + + if (IS_ENABLED(CONFIG_ZBOSS_TRACE_LOG_LEVEL_OFF)) { + return; + } + + allocated = ring_buf_put_claim(&logger_buf, &buf_dest, len); + while (allocated != 0) { + ZB_MEMCPY(buf_dest, buf, allocated); + ring_buf_put_finish(&logger_buf, allocated); + len -= allocated; + buf += allocated; + allocated = ring_buf_put_claim(&logger_buf, &buf_dest, len); + } + + if (len) { + LOG_DBG("Dropping %u bytes, ring buffer is full", len); + } +} + +/* Is called when complete Trace message is put in the ring buffer. + * Triggers sending buffered data through UART API. + */ +void zb_trace_msg_port_do(void) +{ + zb_uint32_t data_len; + int ret_val; + zb_uint8_t *data_ptr = NULL; + + if (IS_ENABLED(CONFIG_ZBOSS_TRACE_LOG_LEVEL_OFF)) { + return; + } + + if (ring_buf_is_empty(&logger_buf)) { + return; + } + + data_len = ring_buf_get_claim(&logger_buf, + &data_ptr, + CONFIG_ZBOSS_TRACE_LOGGER_BUFFER_SIZE); + + LOG_HEXDUMP_DBG(data_ptr, data_len, ""); + + ret_val = ring_buf_get_finish(&logger_buf, data_len); + + if (ret_val != 0) { + LOG_ERR("%u exceeds valid bytes in the logger ring buffer", data_len); + } +} + +/* Prints remaining bytes. */ +void zb_osif_logger_flush(void) +{ + while (!ring_buf_is_empty(&logger_buf)) { + zb_trace_msg_port_do(); + } +} + +#if defined(CONFIG_ZB_NRF_TRACE_RX_ENABLE) +/* Function set UART RX callback function */ +void zb_osif_logger_set_uart_byte_received_cb(zb_callback_t cb) +{ + LOG_ERR("Command reception is not available through logger"); +} +#endif /* CONFIG_ZB_NRF_TRACE_RX_ENABLE */ + +#endif /* defined ZB_NRF_TRACE */ diff --git a/subsys/osif/zb_nrf_nvram.c b/subsys/osif/zb_nrf_nvram.c new file mode 100644 index 00000000..79f58874 --- /dev/null +++ b/subsys/osif/zb_nrf_nvram.c @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include + +#include + +#ifdef ZB_USE_NVRAM + +/* Size of logical ZBOSS NVRAM page in bytes. */ +#define ZBOSS_NVRAM_PAGE_SIZE (PM_ZBOSS_NVRAM_SIZE / CONFIG_ZIGBEE_NVRAM_PAGE_COUNT) +#define PHYSICAL_PAGE_SIZE 0x1000 +BUILD_ASSERT((ZBOSS_NVRAM_PAGE_SIZE % PHYSICAL_PAGE_SIZE) == 0, + "The size must be a multiply of physical page size."); + +LOG_MODULE_DECLARE(zboss_osif, CONFIG_ZBOSS_OSIF_LOG_LEVEL); + +/* ZBOSS callout that should be called once flash erase page operation + * is finished. + */ +void zb_nvram_erase_finished(zb_uint8_t page); + +static const struct flash_area *fa; /* ZBOSS nvram */ + +#ifdef ZB_PRODUCTION_CONFIG +static const struct flash_area *fa_pc; /* production config */ +#endif + +void zb_osif_nvram_init(const zb_char_t *name) +{ + ARG_UNUSED(name); + int ret; + + ret = flash_area_open(PM_ZBOSS_NVRAM_ID, &fa); + if (ret) { + LOG_ERR("Can't open ZBOSS NVRAM flash area"); + } + +#ifdef ZB_PRODUCTION_CONFIG + ret = flash_area_open(PM_ZBOSS_PRODUCT_CONFIG_ID, &fa_pc); + if (ret) { + LOG_ERR("Can't open product config flash area"); + } +#endif +} + +zb_uint32_t zb_get_nvram_page_length(void) +{ + return ZBOSS_NVRAM_PAGE_SIZE; +} + +zb_uint8_t zb_get_nvram_page_count(void) +{ + return CONFIG_ZIGBEE_NVRAM_PAGE_COUNT; +} + +static zb_uint32_t get_page_base_offset(int page_num) +{ + return (page_num * zb_get_nvram_page_length()); +} + +zb_ret_t zb_osif_nvram_read(zb_uint8_t page, zb_uint32_t pos, zb_uint8_t *buf, + zb_uint16_t len) +{ + if (page >= zb_get_nvram_page_count()) { + return RET_PAGE_NOT_FOUND; + } + + if (pos + len > zb_get_nvram_page_length()) { + return RET_INVALID_PARAMETER; + } + + if (!buf) { + return RET_INVALID_PARAMETER_3; + } + + if (!len) { + return RET_INVALID_PARAMETER_4; + } + LOG_DBG("Function: %s, page: %d, pos: %d, len: %d", + __func__, page, pos, len); + + uint32_t flash_addr = get_page_base_offset(page) + pos; + + int err = flash_area_read(fa, flash_addr, buf, len); + + if (err) { + LOG_ERR("Read error: %d", err); + return RET_ERROR; + } + return RET_OK; +} + +zb_ret_t zb_osif_nvram_write(zb_uint8_t page, zb_uint32_t pos, void *buf, + zb_uint16_t len) +{ + uint32_t flash_addr = get_page_base_offset(page) + pos; + + if (page >= zb_get_nvram_page_count()) { + return RET_PAGE_NOT_FOUND; + } + + if (pos + len > zb_get_nvram_page_length()) { + return RET_INVALID_PARAMETER; + } + + if (!buf) { + return RET_INVALID_PARAMETER_3; + } + + if (len == 0) { + return RET_OK; + } + + if (!(len >> 2)) { + return RET_INVALID_PARAMETER_4; + } + + LOG_DBG("Function: %s, page: %d, pos: %d, len: %d", + __func__, page, pos, len); + + int err = flash_area_write(fa, flash_addr, buf, len); + + if (err) { + LOG_ERR("Write error: %d", err); + return RET_ERROR; + } + + return RET_OK; +} + +zb_ret_t zb_osif_nvram_erase_async(zb_uint8_t page) +{ + zb_ret_t ret = RET_OK; + + if (page < zb_get_nvram_page_count()) { + int err = flash_area_erase(fa, get_page_base_offset(page), + zb_get_nvram_page_length()); + if (err) { + LOG_ERR("Erase error: %d", err); + ret = RET_ERROR; + } + } + zb_nvram_erase_finished(page); + return ret; +} + +void zb_osif_nvram_wait_for_last_op(void) +{ + /* empty for synchronous erase and write */ +} + +void zb_osif_nvram_flush(void) +{ + /* empty for synchronous erase and write */ +} + + +#ifdef ZB_PRODUCTION_CONFIG + +#define ZB_OSIF_PRODUCTION_CONFIG_MAGIC { 0xE7, 0x37, 0xDD, 0xF6 } +#define ZB_OSIF_PRODUCTION_CONFIG_MAGIC_SIZE 4 + +zb_bool_t zb_osif_prod_cfg_check_presence(void) +{ + zb_uint8_t hdr[ZB_OSIF_PRODUCTION_CONFIG_MAGIC_SIZE] = + ZB_OSIF_PRODUCTION_CONFIG_MAGIC; + zb_uint8_t buffer[ZB_OSIF_PRODUCTION_CONFIG_MAGIC_SIZE] = {0}; + + int err = flash_area_read(fa_pc, 0, buffer, + ZB_OSIF_PRODUCTION_CONFIG_MAGIC_SIZE); + + if (!err) { + return ((zb_bool_t) !memcmp(buffer, hdr, sizeof(buffer))); + + } else { + return ZB_FALSE; + } +} + +zb_ret_t zb_osif_prod_cfg_read_header(zb_uint8_t *prod_cfg_hdr, + zb_uint16_t hdr_len) +{ + int err = flash_area_read(fa_pc, ZB_OSIF_PRODUCTION_CONFIG_MAGIC_SIZE, + prod_cfg_hdr, hdr_len); + + if (err) { + LOG_ERR("Prod conf header read error: %d", err); + return RET_ERROR; + } + return RET_OK; +} + + +zb_ret_t zb_osif_prod_cfg_read(zb_uint8_t *buffer, + zb_uint16_t len, + zb_uint16_t offset) +{ + uint32_t pc_offset = ZB_OSIF_PRODUCTION_CONFIG_MAGIC_SIZE + offset; + int err = flash_area_read(fa_pc, pc_offset, buffer, len); + + if (err) { + LOG_ERR("Prod conf read error: %d", err); + return RET_ERROR; + } + return RET_OK; +} + +#endif /* ZB_PRODUCTION_CONFIG */ + +#endif /* ZB_USE_NVRAM */ diff --git a/subsys/osif/zb_nrf_nvram_none.c b/subsys/osif/zb_nrf_nvram_none.c new file mode 100644 index 00000000..a2f2d289 --- /dev/null +++ b/subsys/osif/zb_nrf_nvram_none.c @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "zboss_api.h" + + +#ifdef ZB_USE_NVRAM +static uint8_t zb_nvram_buf[CONFIG_ZIGBEE_NVRAM_PAGE_COUNT][CONFIG_ZIGBEE_NVRAM_PAGE_SIZE]; + + +/* ZBOSS callout that should be called once flash erase page operation is finished. */ +void zb_nvram_erase_finished(zb_uint8_t page); + + +void zb_osif_nvram_init(const zb_char_t *name) +{ + ZVUNUSED(name); + memset(zb_nvram_buf, 0xFF, sizeof(zb_nvram_buf)); +} + +zb_uint32_t zb_get_nvram_page_length(void) +{ + return CONFIG_ZIGBEE_NVRAM_PAGE_SIZE; +} + +zb_uint8_t zb_get_nvram_page_count(void) +{ + return CONFIG_ZIGBEE_NVRAM_PAGE_COUNT; +} + +zb_ret_t zb_osif_addr_read(zb_uint32_t address, zb_uint16_t len, zb_uint8_t *buf) +{ + uint8_t *p_address = (uint8_t *)address; + + if (p_address < &zb_nvram_buf[0][0]) { + return RET_INVALID_PARAMETER; + } + + if ((p_address + len) > &zb_nvram_buf[CONFIG_ZIGBEE_NVRAM_PAGE_COUNT - 1] + [CONFIG_ZIGBEE_NVRAM_PAGE_SIZE - 1]) { + return RET_INVALID_PARAMETER_2; + } + + memcpy(buf, p_address, len); + + return RET_OK; +} + +zb_ret_t zb_osif_nvram_read(zb_uint8_t page, zb_uint32_t pos, zb_uint8_t *buf, + zb_uint16_t len) +{ + if (page >= zb_get_nvram_page_count()) { + return RET_PAGE_NOT_FOUND; + } + + if (pos + len > zb_get_nvram_page_length()) { + return RET_INVALID_PARAMETER; + } + + if (!buf) { + return RET_INVALID_PARAMETER_3; + } + + if (len == 0) { + return RET_OK; + } + + memcpy(buf, &zb_nvram_buf[page][pos], len); + + return RET_OK; +} + +zb_ret_t zb_osif_nvram_write(zb_uint8_t page, zb_uint32_t pos, void *buf, + zb_uint16_t len) +{ + if (page >= zb_get_nvram_page_count()) { + return RET_PAGE_NOT_FOUND; + } + + if (pos + len > zb_get_nvram_page_length()) { + return RET_INVALID_PARAMETER; + } + + if (!buf) { + return RET_INVALID_PARAMETER_3; + } + + if (len == 0) { + return RET_OK; + } + + memcpy(&zb_nvram_buf[page][pos], buf, len); + + return RET_OK; +} + +zb_ret_t zb_osif_nvram_erase_async(zb_uint8_t page) +{ + if (page < zb_get_nvram_page_count()) { + memset(&zb_nvram_buf[page][0], 0xFF, zb_get_nvram_page_length()); + } + + zb_nvram_erase_finished(page); + return RET_OK; +} + +void zb_osif_nvram_wait_for_last_op(void) +{ + /* empty for synchronous erase and write */ +} + +void zb_osif_nvram_flush(void) +{ + /* empty for synchronous erase and write */ +} + + +#ifdef ZB_PRODUCTION_CONFIG +zb_bool_t zb_osif_prod_cfg_check_presence(void) +{ + return ZB_FALSE; +} + +zb_ret_t zb_osif_prod_cfg_read_header(zb_uint8_t *prod_cfg_hdr, + zb_uint16_t hdr_len) +{ + memset(prod_cfg_hdr, 0xFF, hdr_len); + return RET_OK; +} + + +zb_ret_t zb_osif_prod_cfg_read(zb_uint8_t *buffer, zb_uint16_t len, zb_uint16_t offset) +{ + ZVUNUSED(offset); + memset(buffer, 0xFF, len); + return RET_OK; +} + +#endif /* ZB_PRODUCTION_CONFIG */ + +#endif /* ZB_USE_NVRAM */ diff --git a/subsys/osif/zb_nrf_platform.c b/subsys/osif/zb_nrf_platform.c new file mode 100644 index 00000000..95c64853 --- /dev/null +++ b/subsys/osif/zb_nrf_platform.c @@ -0,0 +1,789 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#if !NRF_POWER_HAS_RESETREAS +#include +#endif + +#ifdef CONFIG_ZIGBEE_SHELL +#include +#endif +#include +#include "zb_nrf_platform.h" +#include "zb_nrf_crypto.h" + +#ifdef CONFIG_ZIGBEE_LIBRARY_NCP_DEV +#define SYS_REBOOT_NCP 0x10 +#endif /* CONFIG_ZIGBEE_LIBRARY_NCP_DEV */ + +/* Value that is returned while reading a single byte from the erased flash page .*/ +#define FLASH_EMPTY_BYTE 0xFF +/* Broadcast Pan ID value */ +#define ZB_BROADCAST_PAN_ID 0xFFFFU +/* The number of bytes to be checked before concluding that the ZBOSS NVRAM is not initialized. */ +#define ZB_PAGE_INIT_CHECK_LEN 32 + + +/** + * Enumeration representing type of application callback to execute from ZBOSS + * context. + */ +typedef enum { + ZB_CALLBACK_TYPE_SINGLE_PARAM, + ZB_CALLBACK_TYPE_TWO_PARAMS, + ZB_CALLBACK_TYPE_ALARM_SET, + ZB_CALLBACK_TYPE_ALARM_CANCEL, + ZB_GET_OUT_BUF_DELAYED, + ZB_GET_IN_BUF_DELAYED, + ZB_GET_OUT_BUF_DELAYED_EXT, + ZB_GET_IN_BUF_DELAYED_EXT, +} zb_callback_type_t; + +/** + * Type definition of element of the application callback and alarm queue. + */ +typedef struct { + zb_callback_type_t type; + zb_callback_t func; + zb_callback2_t func2; + zb_uint16_t param; + zb_uint16_t user_param; + int64_t alarm_timestamp; +} zb_app_cb_t; + + +LOG_MODULE_REGISTER(zboss_osif, CONFIG_ZBOSS_OSIF_LOG_LEVEL); + +/* Signal object to indicate that frame has been received */ +static struct k_poll_signal zigbee_sig = K_POLL_SIGNAL_INITIALIZER(zigbee_sig); + +/** Global mutex to protect access to the ZBOSS global state. + * + * @note Functions for locking/unlocking the mutex are called directly from + * ZBOSS core, when the main ZBOSS global variable is accessed. + */ +static K_MUTEX_DEFINE(zigbee_mutex); + +/** + * Message queue, that is used to pass ZBOSS callbacks and alarms from + * ISR and other threads to ZBOSS main loop context. + */ +K_MSGQ_DEFINE(zb_app_cb_msgq, sizeof(zb_app_cb_t), + CONFIG_ZIGBEE_APP_CB_QUEUE_LENGTH, 4); + +/** + * Work queue that will schedule processing of callbacks from the message queue. + */ +static struct k_work zb_app_cb_work; + +/** + * Atomic flag, indicating that the processing callback is still scheduled for + * execution, + */ +volatile atomic_t zb_app_cb_process_scheduled = ATOMIC_INIT(0); + +K_THREAD_STACK_DEFINE(zboss_stack_area, CONFIG_ZBOSS_DEFAULT_THREAD_STACK_SIZE); +static struct k_thread zboss_thread_data; +static k_tid_t zboss_tid; +static bool stack_is_started; + +#ifdef CONFIG_ZIGBEE_DEBUG_FUNCTIONS +/**@brief Function for checking if the ZBOSS thread has been created. + */ +bool zigbee_debug_zboss_thread_is_created(void) +{ + if (zboss_tid) { + return true; + } + return false; +} + +/**@brief Function for suspending ZBOSS thread. + */ +void zigbee_debug_suspend_zboss_thread(void) +{ + k_thread_suspend(zboss_tid); +} + +/**@brief Function for resuming ZBOSS thread. + */ +void zigbee_debug_resume_zboss_thread(void) +{ + k_thread_resume(zboss_tid); +} + +/**@brief Function for getting the state of the Zigbee stack thread + * processing suspension. + */ +bool zigbee_is_zboss_thread_suspended(void) +{ + if (zboss_tid) { + if (!(zboss_tid->base.thread_state & _THREAD_SUSPENDED)) { + return false; + } + } + return true; +} +#endif /* defined(CONFIG_ZIGBEE_DEBUG_FUNCTIONS) */ + +/**@brief Function for checking if the Zigbee stack has been started. + * + * @retval true Zigbee stack has been started. + * @retval false Zigbee stack has not been started yet. + */ +bool zigbee_is_stack_started(void) +{ + return stack_is_started; +} + +static void zb_app_cb_process(zb_bufid_t bufid) +{ + zb_ret_t ret_code = RET_OK; + zb_app_cb_t new_app_cb; + + /* Mark the processing callback as non-scheduled. */ + (void)atomic_set((atomic_t *)&zb_app_cb_process_scheduled, 0); + + /** + * From ZBOSS main loop context: process all requests. + * + * Note: the ZB_SCHEDULE_APP_ALARM is not thread-safe. + */ + while (!k_msgq_peek(&zb_app_cb_msgq, &new_app_cb)) { + switch (new_app_cb.type) { + case ZB_CALLBACK_TYPE_SINGLE_PARAM: + ret_code = zb_schedule_app_callback( + new_app_cb.func, + (zb_uint8_t)new_app_cb.param); + break; + case ZB_CALLBACK_TYPE_TWO_PARAMS: + ret_code = zb_schedule_app_callback2( + new_app_cb.func2, + (zb_uint8_t)new_app_cb.param, + new_app_cb.user_param); + break; + case ZB_CALLBACK_TYPE_ALARM_SET: + { + /** + * Check if the timeout already passed. If so, use the + * lowest value that schedules an alarm, so the user + * is still able to cancel the alarm. + */ + zb_time_t delay = + (k_uptime_get() > new_app_cb.alarm_timestamp ? + 1 : + ZB_MILLISECONDS_TO_BEACON_INTERVAL( + new_app_cb.alarm_timestamp - + k_uptime_get()) + ); + ret_code = zb_schedule_app_alarm( + new_app_cb.func, + (zb_uint8_t)new_app_cb.param, + delay); + break; + } + case ZB_CALLBACK_TYPE_ALARM_CANCEL: + ret_code = zb_schedule_alarm_cancel( + new_app_cb.func, + (zb_uint8_t)new_app_cb.param, + NULL); + break; + case ZB_GET_OUT_BUF_DELAYED: + ret_code = zb_buf_get_out_delayed_func( + TRACE_CALL(new_app_cb.func)); + break; + case ZB_GET_IN_BUF_DELAYED: + ret_code = zb_buf_get_in_delayed_func( + TRACE_CALL(new_app_cb.func)); + break; + case ZB_GET_OUT_BUF_DELAYED_EXT: + ret_code = zb_buf_get_out_delayed_ext_func( + TRACE_CALL(new_app_cb.func2), + new_app_cb.user_param, + new_app_cb.param); + break; + case ZB_GET_IN_BUF_DELAYED_EXT: + ret_code = zb_buf_get_in_delayed_ext_func( + TRACE_CALL(new_app_cb.func2), + new_app_cb.user_param, + new_app_cb.param); + break; + default: + break; + } + + /* Check for ZBOSS scheduler queue overflow. */ + if (ret_code == RET_OVERFLOW) { + break; + } + + /* Flush the element from the message queue. */ + k_msgq_get(&zb_app_cb_msgq, &new_app_cb, K_NO_WAIT); + } + + /** + * In case of overflow error - reschedule the processing callback + * to process remaining requests later. + */ + if (ret_code == RET_OVERFLOW) { + k_work_submit(&zb_app_cb_work); + } +} + +static void zb_app_cb_process_schedule(struct k_work *item) +{ + zb_app_cb_t new_app_cb; + + if (k_msgq_peek(&zb_app_cb_msgq, &new_app_cb)) { + return; + } + + /* Check if processing callback is already scheduled. */ + if (atomic_set((atomic_t *)&zb_app_cb_process_scheduled, 1) == 1) { + return; + } + + /** + * From working thread, non-ISR context: schedule processing callback. + * Repeat endlessly, because the user was already informed that the + * request will be handled. + * + * Note: the ZB_SCHEDULE_APP_CALLBACK is thread-safe. + */ + while (zb_schedule_app_callback(zb_app_cb_process, 0) != RET_OK) { + k_sleep(K_MSEC(1000)); + } + zigbee_event_notify(ZIGBEE_EVENT_APP); + + (void)item; +} + +int zigbee_init(void) +{ + /* Initialise work queue for processing app callback and alarms. */ + k_work_init(&zb_app_cb_work, zb_app_cb_process_schedule); + +#if ZB_TRACE_LEVEL + /* Set Zigbee stack logging level and traffic dump subsystem. */ + ZB_SET_TRACE_LEVEL(CONFIG_ZBOSS_TRACE_LOG_LEVEL); + ZB_SET_TRACE_MASK(CONFIG_ZBOSS_TRACE_MASK); +#if CONFIG_ZBOSS_TRAF_DUMP + ZB_SET_TRAF_DUMP_ON(); +#else /* CONFIG_ZBOSS_TRAF_DUMP */ + ZB_SET_TRAF_DUMP_OFF(); +#endif /* CONFIG_ZBOSS_TRAF_DUMP */ +#endif /* ZB_TRACE_LEVEL */ + +#ifndef CONFIG_ZB_TEST_MODE_MAC + /* Initialize Zigbee stack. */ + ZB_INIT("zigbee_thread"); + + /* Set device address to the value read from FICR registers. */ + zb_ieee_addr_t ieee_addr; + zb_osif_get_ieee_eui64(ieee_addr); + zb_set_long_address(ieee_addr); + + /* Keep or erase NVRAM to save the network parameters + * after device reboot or power-off. + */ + zb_set_nvram_erase_at_start(ZB_FALSE); + + if (IS_ENABLED(CONFIG_ZIGBEE_TC_REJOIN_ENABLED)) { + zb_secur_set_tc_rejoin_enabled((zb_bool_t)CONFIG_ZIGBEE_TC_REJOIN_ENABLED); + } + + /* Don't set zigbee role for NCP device */ +#ifndef CONFIG_ZIGBEE_LIBRARY_NCP_DEV + + /* Set channels on which the coordinator will try + * to create a new network + */ +#if defined(CONFIG_ZIGBEE_CHANNEL_SELECTION_MODE_SINGLE) + zb_uint32_t channel_mask = (1UL << CONFIG_ZIGBEE_CHANNEL); +#elif defined(CONFIG_ZIGBEE_CHANNEL_SELECTION_MODE_MULTI) + zb_uint32_t channel_mask = CONFIG_ZIGBEE_CHANNEL_MASK; +#else +#error Channel mask undefined! +#endif + + +#if defined(CONFIG_ZIGBEE_ROLE_COORDINATOR) + zb_set_network_coordinator_role(channel_mask); +#elif defined(CONFIG_ZIGBEE_ROLE_ROUTER) + zb_set_network_router_role(channel_mask); + +/* Enable full distributed network operability. */ +#if (ZBOSS_MAJOR == 3U) && (ZBOSS_MINOR == 11U) + zb_enable_distributed(); +#endif + +#elif defined(CONFIG_ZIGBEE_ROLE_END_DEVICE) + zb_set_network_ed_role(channel_mask); +#else +#error Zigbee device role undefined! +#endif + +#endif /* CONFIG_ZIGBEE_LIBRARY_NCP_DEV */ + +#endif /* CONFIG_ZB_TEST_MODE_MAC */ + + return 0; +} + +static void zboss_thread(void *arg1, void *arg2, void *arg3) +{ + zb_ret_t zb_err_code; + + zb_err_code = zboss_start_no_autostart(); + __ASSERT(zb_err_code == RET_OK, "Error when starting ZBOSS stack!"); + + stack_is_started = true; +#ifdef CONFIG_ZIGBEE_SHELL + zb_shell_configure_endpoint(); +#endif /* defined(CONFIG_ZIGBEE_SHELL) */ + + while (1) { + zboss_main_loop_iteration(); + } +} + +zb_bool_t zb_osif_is_inside_isr(void) +{ + return (zb_bool_t)(__get_IPSR() != 0); +} + +void zb_osif_enable_all_inter(void) +{ + __ASSERT(zb_osif_is_inside_isr() == 0, + "Unable to unlock mutex from interrupt context"); + k_mutex_unlock(&zigbee_mutex); +} + +void zb_osif_disable_all_inter(void) +{ + __ASSERT(zb_osif_is_inside_isr() == 0, + "Unable to lock mutex from interrupt context"); + k_mutex_lock(&zigbee_mutex, K_FOREVER); +} + +zb_ret_t zigbee_schedule_callback(zb_callback_t func, zb_uint8_t param) +{ + if ((zboss_tid) && (k_current_get() == zboss_tid) && (!zb_osif_is_inside_isr())) { + return zb_schedule_app_callback(func, param); + } + + zb_app_cb_t new_app_cb = { + .type = ZB_CALLBACK_TYPE_SINGLE_PARAM, + .func = func, + .param = param, + }; + + if (k_msgq_put(&zb_app_cb_msgq, &new_app_cb, K_NO_WAIT)) { + return RET_OVERFLOW; + } + + k_work_submit(&zb_app_cb_work); + return RET_OK; +} + +zb_ret_t zigbee_schedule_callback2(zb_callback2_t func, + zb_uint8_t param, + zb_uint16_t user_param) +{ + if ((zboss_tid) && (k_current_get() == zboss_tid) && (!zb_osif_is_inside_isr())) { + return zb_schedule_app_callback2(func, param, user_param); + } + + zb_app_cb_t new_app_cb = { + .type = ZB_CALLBACK_TYPE_TWO_PARAMS, + .func2 = func, + .param = param, + .user_param = user_param, + }; + + if (k_msgq_put(&zb_app_cb_msgq, &new_app_cb, K_NO_WAIT)) { + return RET_OVERFLOW; + } + + k_work_submit(&zb_app_cb_work); + return RET_OK; +} + +zb_ret_t zigbee_schedule_alarm(zb_callback_t func, + zb_uint8_t param, + zb_time_t run_after) +{ + if ((zboss_tid) && (k_current_get() == zboss_tid) && (!zb_osif_is_inside_isr())) { + return zb_schedule_app_alarm(func, param, run_after); + } + + zb_app_cb_t new_app_cb = { + .type = ZB_CALLBACK_TYPE_ALARM_SET, + .func = func, + .param = param, + .alarm_timestamp = k_uptime_get() + + ZB_TIME_BEACON_INTERVAL_TO_MSEC(run_after), + }; + + if (k_msgq_put(&zb_app_cb_msgq, &new_app_cb, K_NO_WAIT)) { + return RET_OVERFLOW; + } + + k_work_submit(&zb_app_cb_work); + return RET_OK; +} + +zb_ret_t zigbee_schedule_alarm_cancel(zb_callback_t func, zb_uint8_t param) +{ + if ((zboss_tid) && (k_current_get() == zboss_tid) && (!zb_osif_is_inside_isr())) { + return zb_schedule_alarm_cancel(func, param, NULL); + } + + zb_app_cb_t new_app_cb = { + .type = ZB_CALLBACK_TYPE_ALARM_CANCEL, + .func = func, + .param = param, + }; + + if (k_msgq_put(&zb_app_cb_msgq, &new_app_cb, K_NO_WAIT)) { + return RET_OVERFLOW; + } + + k_work_submit(&zb_app_cb_work); + return RET_OK; +} + +zb_ret_t zigbee_get_out_buf_delayed(zb_callback_t func) +{ + if ((zboss_tid) && (k_current_get() == zboss_tid) && (!zb_osif_is_inside_isr())) { + return zb_buf_get_out_delayed_func(func); + } + + zb_app_cb_t new_app_cb = { + .type = ZB_GET_OUT_BUF_DELAYED, + .func = func, + }; + + if (k_msgq_put(&zb_app_cb_msgq, &new_app_cb, K_NO_WAIT)) { + return RET_OVERFLOW; + } + + k_work_submit(&zb_app_cb_work); + return RET_OK; +} + +zb_ret_t zigbee_get_in_buf_delayed(zb_callback_t func) +{ + if ((zboss_tid) && (k_current_get() == zboss_tid) && (!zb_osif_is_inside_isr())) { + return zb_buf_get_in_delayed_func(func); + } + + zb_app_cb_t new_app_cb = { + .type = ZB_GET_IN_BUF_DELAYED, + .func = func, + }; + + if (k_msgq_put(&zb_app_cb_msgq, &new_app_cb, K_NO_WAIT)) { + return RET_OVERFLOW; + } + + k_work_submit(&zb_app_cb_work); + return RET_OK; +} + +zb_ret_t zigbee_get_out_buf_delayed_ext(zb_callback2_t func, zb_uint16_t param, + zb_uint16_t max_size) +{ + if ((zboss_tid) && (k_current_get() == zboss_tid) && (!zb_osif_is_inside_isr())) { + return zb_buf_get_out_delayed_ext_func(func, param, max_size); + } + + zb_app_cb_t new_app_cb = { + .type = ZB_GET_OUT_BUF_DELAYED_EXT, + .func2 = func, + .user_param = param, + .param = max_size, + }; + + if (k_msgq_put(&zb_app_cb_msgq, &new_app_cb, K_NO_WAIT)) { + return RET_OVERFLOW; + } + + k_work_submit(&zb_app_cb_work); + return RET_OK; +} + +zb_ret_t zigbee_get_in_buf_delayed_ext(zb_callback2_t func, zb_uint16_t param, + zb_uint16_t max_size) +{ + if ((zboss_tid) && (k_current_get() == zboss_tid) && (!zb_osif_is_inside_isr())) { + return zb_buf_get_in_delayed_ext_func(func, param, max_size); + } + + zb_app_cb_t new_app_cb = { + .type = ZB_GET_IN_BUF_DELAYED_EXT, + .func2 = func, + .user_param = param, + .param = max_size, + }; + + if (k_msgq_put(&zb_app_cb_msgq, &new_app_cb, K_NO_WAIT)) { + return RET_OVERFLOW; + } + + k_work_submit(&zb_app_cb_work); + return RET_OK; +} + +/**@brief SoC general initialization. */ +void zb_osif_init(void) +{ + static bool platform_inited; + + if (platform_inited) { + return; + } + platform_inited = true; + +#ifdef CONFIG_ZIGBEE_HAVE_SERIAL + /* Initialise serial trace */ + zb_osif_serial_init(); +#endif + + /* Initialise random generator */ + zb_osif_rng_init(); + + /* Initialise AES ECB */ + zb_osif_aes_init(); + +#ifdef ZB_USE_SLEEP + /* Initialise power consumption routines */ + zb_osif_sleep_init(); +#endif /*ZB_USE_SLEEP*/ +} + +void zb_osif_abort(void) +{ + /* Log ZBOSS error message and flush logs. */ + LOG_ERR("ZBOSS fatal error occurred"); + LOG_PANIC(); + +#ifdef CONFIG_ZIGBEE_HAVE_SERIAL + /* Flush ZBOSS trace logs. */ + ZB_OSIF_SERIAL_FLUSH(); +#endif + + /* By default reset device or halt if so configured. */ + if (IS_ENABLED(CONFIG_ZBOSS_RESET_ON_ASSERT)) { + zb_reset(0); + } + if (IS_ENABLED(CONFIG_ZBOSS_HALT_ON_ASSERT)) { + k_fatal_halt(K_ERR_KERNEL_PANIC); + } +} + +uint32_t zigbee_pibcache_pan_id_clear(void) +{ + /* For consistency with zb_nwk_nib_init(), the 0xFFFFU is used, + * i.e. ZB_BROADCAST_PAN_ID. + */ + ZB_PIBCACHE_PAN_ID() = ZB_BROADCAST_PAN_ID; + return ZB_BROADCAST_PAN_ID; +} + +void zb_reset(zb_uint8_t param) +{ + uint8_t reas = (uint8_t)SYS_REBOOT_COLD; + + ZVUNUSED(param); + +#ifdef CONFIG_ZIGBEE_LIBRARY_NCP_DEV + reas = (uint8_t)SYS_REBOOT_NCP; +#endif /* CONFIG_ZIGBEE_LIBRARY_NCP_DEV */ + + nrf_power_gpregret_set(NRF_POWER, 0, reas); + + /* Power on unused sections of RAM to allow MCUboot to use it. */ + if (IS_ENABLED(CONFIG_RAM_POWER_DOWN_LIBRARY)) { + power_up_unused_ram(); + } + + sys_reboot(reas); +} + +void zb_osif_busy_loop_delay(zb_uint32_t count) +{ + k_busy_wait(count); +} + +__weak zb_uint32_t zb_get_utc_time(void) +{ + LOG_ERR("Unable to obtain UTC time. " + "Please implement %s in your application to provide the current UTC time.", + __func__); + return ZB_TIME_BEACON_INTERVAL_TO_MSEC(ZB_TIMER_GET()) / 1000; +} + +void zigbee_event_notify(zigbee_event_t event) +{ + k_poll_signal_raise(&zigbee_sig, event); +} + +uint32_t zigbee_event_poll(uint32_t timeout_us) +{ + /* Configure event/signals to wait for in zigbee_event_poll function. */ + static struct k_poll_event wait_events[] = { + K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL, + K_POLL_MODE_NOTIFY_ONLY, + &zigbee_sig), + }; + + unsigned int signaled = 0; + int result; + /* Store timestamp of event polling start. */ + int64_t timestamp_poll_start = k_uptime_ticks(); + + k_poll(wait_events, 1, K_USEC(timeout_us)); + + k_poll_signal_check(&zigbee_sig, &signaled, &result); + if (signaled) { + k_poll_signal_reset(&zigbee_sig); + + LOG_DBG("Received new Zigbee event: 0x%02x", result); + } + + return k_ticks_to_us_floor32(k_uptime_ticks() - timestamp_poll_start); +} + +void zigbee_enable(void) +{ + zboss_tid = k_thread_create(&zboss_thread_data, + zboss_stack_area, + K_THREAD_STACK_SIZEOF(zboss_stack_area), + zboss_thread, + NULL, NULL, NULL, + CONFIG_ZBOSS_DEFAULT_THREAD_PRIORITY, + 0, K_NO_WAIT); + k_thread_name_set(&zboss_thread_data, "zboss"); +} + +/** + * @brief Get the reason that triggered the last reset + * + * @return @ref reset_source + * */ +zb_uint8_t zb_get_reset_source(void) +{ + uint32_t reas; + uint8_t zb_reason; +#ifdef CONFIG_ZIGBEE_LIBRARY_NCP_DEV + static uint8_t zephyr_reset_type = 0xFF; + + /* Read the value at the first API call, then use data from RAM. */ + if (zephyr_reset_type == 0xFF) { + zephyr_reset_type = nrf_power_gpregret_get(NRF_POWER, 0); + } +#endif /* CONFIG_ZIGBEE_LIBRARY_NCP_DEV */ + +#if NRF_POWER_HAS_RESETREAS + + reas = nrf_power_resetreas_get(NRF_POWER); + nrf_power_resetreas_clear(NRF_POWER, reas); + if (reas & NRF_POWER_RESETREAS_RESETPIN_MASK) { + zb_reason = ZB_RESET_SRC_RESET_PIN; + } else if (reas & NRF_POWER_RESETREAS_SREQ_MASK) { + zb_reason = ZB_RESET_SRC_SW_RESET; + } else if (reas) { + zb_reason = ZB_RESET_SRC_OTHER; + } else { + zb_reason = ZB_RESET_SRC_POWER_ON; + } + +#else + + reas = nrf_reset_resetreas_get(NRF_RESET); + nrf_reset_resetreas_clear(NRF_RESET, reas); + if (reas & NRF_RESET_RESETREAS_RESETPIN_MASK) { + zb_reason = ZB_RESET_SRC_RESET_PIN; + } else if (reas & NRF_RESET_RESETREAS_SREQ_MASK) { + zb_reason = ZB_RESET_SRC_SW_RESET; + } else if (reas) { + zb_reason = ZB_RESET_SRC_OTHER; + } else { + zb_reason = ZB_RESET_SRC_POWER_ON; + } + +#endif + +#ifdef CONFIG_ZIGBEE_LIBRARY_NCP_DEV + if ((zb_reason == ZB_RESET_SRC_SW_RESET) && + (zephyr_reset_type != SYS_REBOOT_NCP)) { + zb_reason = ZB_RESET_SRC_OTHER; + } + + /* The NCP reset type is used only by this API call. + * Reset the value inside the register, so after the next, external + * SW reset, the value will not trigger NCP logic. + */ + if (zephyr_reset_type == SYS_REBOOT_NCP) { + nrf_power_gpregret_set(NRF_POWER, 0, (uint8_t)SYS_REBOOT_COLD); + } +#endif /* CONFIG_ZIGBEE_LIBRARY_NCP_DEV */ + + return zb_reason; +} + +zb_bool_t zigbee_is_nvram_initialised(void) +{ + zb_uint8_t buf[ZB_PAGE_INIT_CHECK_LEN] = {0}; + zb_uint8_t i; + zb_ret_t ret_code; + + ret_code = zb_osif_nvram_read(0, 0, buf, sizeof(buf)); + if (ret_code != RET_OK) { + return ZB_FALSE; + } + + for (i = 0; i < sizeof(buf); i++) { + if (buf[i] != FLASH_EMPTY_BYTE) { + return ZB_TRUE; + } + } + + return ZB_FALSE; +} + +ZB_WEAK_PRE zb_uint32_t ZB_WEAK zb_osif_get_fw_version(void) +{ + return 0x01; +} + +ZB_WEAK_PRE zb_uint32_t ZB_WEAK zb_osif_get_ncp_protocol_version(void) +{ +#ifdef ZB_NCP_PROTOCOL_VERSION + return ZB_NCP_PROTOCOL_VERSION; +#else /* ZB_NCP_PROTOCOL_VERSION */ + return 0x01; +#endif /* ZB_NCP_PROTOCOL_VERSION */ +} + +ZB_WEAK_PRE zb_ret_t ZB_WEAK zb_osif_bootloader_run_after_reboot(void) +{ + return RET_OK; +} + +ZB_WEAK_PRE void ZB_WEAK zb_osif_bootloader_report_successful_loading(void) +{ +} diff --git a/subsys/osif/zb_nrf_platform.h b/subsys/osif/zb_nrf_platform.h new file mode 100644 index 00000000..0418fbdf --- /dev/null +++ b/subsys/osif/zb_nrf_platform.h @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** @file + * @brief Zigbee ZBOSS OSIF layer API header. + */ + +#ifndef ZB_NRF_PLATFORM_H__ +#define ZB_NRF_PLATFORM_H__ + +#include +#include + +typedef enum { + ZIGBEE_EVENT_TX_FAILED, + ZIGBEE_EVENT_TX_DONE, + ZIGBEE_EVENT_RX_DONE, + ZIGBEE_EVENT_APP, +} zigbee_event_t; + +/** + * @defgroup zigbee_zboss_osif Zigbee ZBOSS OSIF API + * @{ + * + */ + +/**@brief Function for checking if the Zigbee stack has been started. + * + * @retval true Zigbee stack has been started. + * @retval false Zigbee stack has not been started yet. + */ +bool zigbee_is_stack_started(void); + +/**@brief Function for starting the Zigbee thread. */ +void zigbee_enable(void); + +#ifdef CONFIG_ZIGBEE_DEBUG_FUNCTIONS +/**@brief Function for checking if the ZBOSS thread has been created. */ +bool zigbee_debug_zboss_thread_is_created(void); + +/**@brief Function for suspending the ZBOSS thread. */ +void zigbee_debug_suspend_zboss_thread(void); + +/**@brief Function for resuming the ZBOSS thread. */ +void zigbee_debug_resume_zboss_thread(void); + +/**@brief Function for getting the state of the Zigbee stack thread + * processing suspension. + * + * @retval true Scheduler processing is suspended or the ZBOSS thread + * is not yet created. + * @retval false Scheduler processing is not suspended and the ZBOSS thread + * is created. + */ +bool zigbee_is_zboss_thread_suspended(void); +#endif /* defined(CONFIG_ZIGBEE_DEBUG_FUNCTIONS) */ + +/** + * @} + */ + +/**@brief Function for Zigbee stack initialization + * + * @return 0 if success + */ +int zigbee_init(void); + +/**@brief Notify the ZBOSS thread about a new event. + * + * @param[in] event Event to notify. + */ +void zigbee_event_notify(zigbee_event_t event); + +/**@brief Function which waits for event in case + * of empty Zigbee stack scheduler queue. + * + * @param[in] timeout_us Maximum amount of time, in microseconds + * for which the ZBOSS task processing may be blocked. + * + * @returns The amount of microseconds that the ZBOSS task was blocked. + */ +uint32_t zigbee_event_poll(uint32_t timeout_us); + +/**@brief Function for checking if the Zigbee NVRAM has been initialised. + * + * @retval ZB_TRUE Zigbee NVRAM is initialised. + * @retval ZB_FALSE Zigbee NVRAM is not initialised + */ +zb_bool_t zigbee_is_nvram_initialised(void); + +/**@brief Clears the PAN_ID value held in the Zigbee PIB cache. + * + * @details The value set is consistent with the behavior of + * @c zb_nwk_nib_init() from Zigbee stack NWK layer. + * + * Function can be used to ensure that the PIB cache does not store any valid + * PAN_ID value in scenarios where the device is in "absent from the network" + * phase (not yet joined or has already left). + * + * @return The newly set PAN_ID value. + */ +uint32_t zigbee_pibcache_pan_id_clear(void); + +#endif /* ZB_NRF_PLATFORM_H__ */ diff --git a/subsys/osif/zb_nrf_pwr_mgmt.c b/subsys/osif/zb_nrf_pwr_mgmt.c new file mode 100644 index 00000000..79dd038d --- /dev/null +++ b/subsys/osif/zb_nrf_pwr_mgmt.c @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include + +#include +#include "zb_nrf_platform.h" + + +#ifdef ZB_USE_SLEEP +static volatile atomic_t is_sleeping = ATOMIC_INIT(0); + +/** + * Enable ability to stop timer to save power. + */ +void zb_timer_enable_stop(void); + + +/** + * SoC sleep subsystem initialization + */ +void zb_osif_sleep_init(void) +{ + /* Disable timer in inactivity periods on all device types. */ + if (IS_ENABLED(CONFIG_ZIGBEE_TIME_COUNTER)) { + zb_timer_enable_stop(); + } +} + +/**@brief Function which tries to put the MMCU into sleep mode, + * caused by an empty Zigbee stack scheduler queue. + * + * Function is defined as weak; to be redefined if someone wants + * to implement their own going-to-sleep policy. + */ +__weak zb_uint32_t zb_osif_sleep(zb_uint32_t sleep_tmo) +{ + /* The amount of microseconds that the ZBOSS task was blocked. */ + zb_uint32_t time_slept_us; + /* The amount of milliseconds that the ZBOSS task was blocked. */ + zb_uint32_t time_slept_ms; + + if (!sleep_tmo) { + return sleep_tmo; + } + +#if (ZIGBEE_TRACE_LEVEL != 0) + /* In case of trace libraries - the Zigbee stack may generate + * logs on each loop iteration, resulting in immediate + * return from zigbee_event_poll() each time. In such case, + * Zigbee stack should not be forced to increment counters. + * Such solution may break the internal logic of the stack. + */ + ZVUNUSED(time_slept_ms); + ZVUNUSED(time_slept_us); + return ZB_SLEEP_INVALID_VALUE; +#else + +#if ZB_TRACE_LEVEL + ZB_SET_TRACE_OFF(); +#endif /* ZB_TRACE_LEVEL */ + + /* Lock timer value from updating during sleep period. */ + ZVUNUSED(atomic_set((atomic_t *)&is_sleeping, 1)); + + /* Wait for an event. */ + time_slept_us = zigbee_event_poll(sleep_tmo * USEC_PER_MSEC); + + /* Calculate sleep duration in milliseconds. Round up the result + * using the basic time unit of ZBOSS API to avoid possible errors + * in the time unit conversion. + */ + time_slept_ms = ZB_TIME_BEACON_INTERVAL_TO_MSEC( + DIV_ROUND_UP(time_slept_us, ZB_BEACON_INTERVAL_USEC)); + + /* Unlock timer value updates. */ + ZVUNUSED(atomic_set((atomic_t *)&is_sleeping, 0)); + + /* Enable Zigbee stack-related peripherals. */ + /*zb_osif_priph_enable();*/ + + return time_slept_ms; +#endif +} + +/**@brief Function which is called after zb_osif_sleep + * finished and ZBOSS timer is re-enabled. + * + * Function is defined as weak; to be redefined if someone + * wants to implement their own going-to-deep-sleep policy/share resources + * between Zigbee stack and other components. + */ +__weak void zb_osif_wake_up(void) +{ +#if ZB_TRACE_LEVEL + ZB_SET_TRACE_ON(); +#endif /* ZB_TRACE_LEVEL */ + /* Restore trace interrupts. TODO: Restore something else if needed */ +} + +zb_bool_t zb_osif_is_sleeping(void) +{ + return atomic_get((atomic_t *)&is_sleeping) ? ZB_TRUE : ZB_FALSE; +} + +#endif /* ZB_USE_SLEEP */ diff --git a/subsys/osif/zb_nrf_serial.c b/subsys/osif/zb_nrf_serial.c new file mode 100644 index 00000000..9457ab87 --- /dev/null +++ b/subsys/osif/zb_nrf_serial.c @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include "zb_nrf_platform.h" + + +/* Forward declarations */ +void zb_osif_serial_logger_init(void); +void zb_osif_async_serial_init(void); +void zb_osif_logger_put_bytes(const zb_uint8_t *buf, zb_short_t len); +void zb_osif_serial_logger_put_bytes(const zb_uint8_t *buf, zb_short_t len); +void zb_osif_async_serial_put_bytes(const zb_uint8_t *buf, zb_short_t len); +void zb_osif_logger_set_uart_byte_received_cb(zb_callback_t cb); +void zb_osif_serial_logger_set_uart_byte_received_cb(zb_callback_t cb); +void zb_osif_async_serial_set_uart_byte_received_cb(zb_callback_t cb); +void zb_osif_async_serial_flush(void); +void zb_osif_logger_flush(void); +void zb_osif_serial_logger_flush(void); +void zb_osif_async_serial_sleep(void); +void zb_osif_async_serial_wake_up(void); + +void zb_osif_serial_init(void) +{ +#if defined(CONFIG_ZBOSS_TRACE_BINARY_LOGGING) && defined(CONFIG_ZIGBEE_HAVE_ASYNC_SERIAL) + __ASSERT(DEVICE_DT_GET(DT_CHOSEN(ncs_zigbee_uart)) != + DEVICE_DT_GET(DT_CHOSEN(ncs_zboss_trace_uart)), + "The same serial device used for serial logger and async serial!"); +#endif /* defined(CONFIG_ZBOSS_TRACE_BINARY_LOGGING) && defined(CONFIG_ZIGBEE_HAVE_ASYNC_SERIAL) */ + + if (IS_ENABLED(CONFIG_ZBOSS_TRACE_BINARY_LOGGING)) { + zb_osif_serial_logger_init(); + } + if (IS_ENABLED(CONFIG_ZIGBEE_HAVE_ASYNC_SERIAL)) { + zb_osif_async_serial_init(); + } +} + +void zb_osif_serial_put_bytes(const zb_uint8_t *buf, zb_short_t len) +{ + if (IS_ENABLED(CONFIG_ZBOSS_TRACE_HEXDUMP_LOGGING)) { + zb_osif_logger_put_bytes(buf, len); + } else if (IS_ENABLED(CONFIG_ZBOSS_TRACE_BINARY_LOGGING)) { + zb_osif_serial_logger_put_bytes(buf, len); + } else if (IS_ENABLED(CONFIG_ZBOSS_TRACE_BINARY_NCP_TRANSPORT_LOGGING)) { + zb_osif_async_serial_put_bytes(buf, len); + } +} + +/* Function set UART RX callback function */ +void zb_osif_set_uart_byte_received_cb(zb_callback_t cb) +{ + if (IS_ENABLED(CONFIG_ZBOSS_TRACE_HEXDUMP_LOGGING)) { + zb_osif_logger_set_uart_byte_received_cb(cb); + } else if (IS_ENABLED(CONFIG_ZBOSS_TRACE_BINARY_LOGGING)) { + zb_osif_serial_logger_set_uart_byte_received_cb(cb); + } else if (IS_ENABLED(CONFIG_ZBOSS_TRACE_BINARY_NCP_TRANSPORT_LOGGING)) { + zb_osif_async_serial_set_uart_byte_received_cb(cb); + } +} + +/* Prints remaining bytes. */ +void zb_osif_serial_flush(void) +{ + if (IS_ENABLED(CONFIG_ZBOSS_TRACE_HEXDUMP_LOGGING)) { + zb_osif_logger_flush(); + } + if (IS_ENABLED(CONFIG_ZBOSS_TRACE_BINARY_LOGGING)) { + zb_osif_serial_logger_flush(); + } + if (IS_ENABLED(CONFIG_ZIGBEE_HAVE_ASYNC_SERIAL)) { + zb_osif_async_serial_flush(); + } +} + +void zb_osif_uart_sleep(void) +{ + if (IS_ENABLED(CONFIG_ZIGBEE_HAVE_ASYNC_SERIAL)) { + zb_osif_async_serial_sleep(); + } +} + +void zb_osif_uart_wake_up(void) +{ + if (IS_ENABLED(CONFIG_ZIGBEE_HAVE_ASYNC_SERIAL)) { + zb_osif_async_serial_wake_up(); + } +} diff --git a/subsys/osif/zb_nrf_serial_logger.c b/subsys/osif/zb_nrf_serial_logger.c new file mode 100644 index 00000000..77e1073b --- /dev/null +++ b/subsys/osif/zb_nrf_serial_logger.c @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include + +#if defined ZB_NRF_TRACE + +#include + +#define ZIGBEE_RX_BUF_LEN 16 + +LOG_MODULE_DECLARE(zboss_osif, CONFIG_ZBOSS_OSIF_LOG_LEVEL); + +RING_BUF_DECLARE(logger_buf, CONFIG_ZBOSS_TRACE_LOGGER_BUFFER_SIZE); + +static K_SEM_DEFINE(ringbuf_sem, 1, 1); + +/* UART device used to print ZBOSS Trace messages. */ +static const struct device *uart_dev = DEVICE_DT_GET(DT_CHOSEN(ncs_zboss_trace_uart)); +static bool uart_dev_initialized; +static zb_callback_t char_handler; + +static void handle_tx_ready_evt(const struct device *dev) +{ + zb_uint32_t data_len; + zb_uint32_t data_taken; + int ret_val; + zb_uint8_t *data_ptr = NULL; + + ret_val = k_sem_take(&ringbuf_sem, K_NO_WAIT); + + if (ret_val == 0) { + if (ring_buf_is_empty(&logger_buf)) { + uart_irq_tx_disable(dev); + k_sem_give(&ringbuf_sem); + return; + } + } else { + uart_irq_tx_disable(dev); + return; + } + + data_len = ring_buf_get_claim(&logger_buf, + &data_ptr, + CONFIG_ZBOSS_TRACE_LOGGER_BUFFER_SIZE); + + data_taken = uart_fifo_fill(dev, data_ptr, data_len); + + ret_val = ring_buf_get_finish(&logger_buf, data_taken); + + if (ret_val != 0) { + LOG_ERR("%u exceeds valid bytes in the logger ring buffer", data_taken); + } + + k_sem_give(&ringbuf_sem); +} + +static void uart_rx_bytes(uint8_t *buf, size_t len) +{ + if (char_handler) { + for (size_t i = 0; i < len; i++) { + char_handler(buf[i]); + } + } +} + +static void handle_rx_ready_evt(const struct device *dev) +{ + int recv_len = 0; + uint8_t buffer[ZIGBEE_RX_BUF_LEN] = {0}; + + recv_len = uart_fifo_read(dev, buffer, sizeof(buffer)); + + while (recv_len != 0) { + uart_rx_bytes(buffer, recv_len); + + recv_len = uart_fifo_read(dev, buffer, sizeof(buffer)); + } +} + +static void interrupt_handler(const struct device *dev, void *user_data) +{ + while (uart_irq_update(dev) && uart_irq_is_pending(dev)) { + if (uart_irq_rx_ready(dev)) { + handle_rx_ready_evt(dev); + } + + if (uart_irq_tx_ready(dev)) { + handle_tx_ready_evt(dev); + } + } +} + +void zb_osif_serial_logger_put_bytes(const zb_uint8_t *buf, zb_short_t len) +{ + zb_uint8_t *buf_dest; + zb_uint32_t allocated = 0; + zb_uint32_t bytes_copied = 0; + + if (IS_ENABLED(CONFIG_ZBOSS_TRACE_LOG_LEVEL_OFF)) { + return; + } + + if (k_sem_take(&ringbuf_sem, K_FOREVER)) { + LOG_ERR("Couldn't take semaphore, dropping %u bytes", len); + return; + } + + allocated = ring_buf_put_claim(&logger_buf, &buf_dest, len); + + while (allocated != 0) { + ZB_MEMCPY(buf_dest, &buf[bytes_copied], allocated); + ring_buf_put_finish(&logger_buf, allocated); + bytes_copied += allocated; + len -= allocated; + allocated = ring_buf_put_claim(&logger_buf, &buf_dest, len); + } + + if (len != 0) { + LOG_DBG("Dropping %u bytes, ring buffer is full", len); + } + + k_sem_give(&ringbuf_sem); +} + +/* Is called when complete Trace message is put in the ring buffer. + * Triggers sending buffered data through UART API. + */ +void zb_trace_msg_port_do(void) +{ + if (IS_ENABLED(CONFIG_ZBOSS_TRACE_LOG_LEVEL_OFF)) { + return; + } + + if (!uart_dev_initialized) { + return; + } + + uart_irq_tx_enable(uart_dev); +} + +void zb_osif_serial_logger_init(void) +{ + /* Prevent multiple initiaizations as serial init function may be called more than once + * by ZBOSS stack. + */ + if (uart_dev_initialized) { + return; + } + + if (!device_is_ready(uart_dev)) { + LOG_ERR("ZBOSS Trace UART device not ready"); + return; + } + + /* Enable and configure USB device if USB CDC ACM is used to log ZBOSS Traces. */ + if (IS_ENABLED(CONFIG_ZBOSS_TRACE_USB_CDC_LOGGING)) { + int ret = usb_enable(NULL); + + if ((ret != 0) && (ret != -EALREADY)) { + LOG_ERR("USB initialization failed - No UART device to log ZBOSS Traces"); + return; + } + + /* Data Carrier Detect Modem - mark connection as established. */ + (void)uart_line_ctrl_set(uart_dev, UART_LINE_CTRL_DCD, 1); + /* Data Set Ready - the NCP SoC is ready to communicate. */ + (void)uart_line_ctrl_set(uart_dev, UART_LINE_CTRL_DSR, 1); + } + + uart_irq_callback_set(uart_dev, interrupt_handler); + + /* Enable rx interrupts. */ + uart_irq_rx_enable(uart_dev); + + uart_dev_initialized = true; +} + +void zb_osif_serial_logger_flush(void) +{ + if (!uart_dev_initialized) { + return; + } + + uart_irq_tx_enable(uart_dev); + while (!ring_buf_is_empty(&logger_buf)) { + k_sleep(K_MSEC(100)); + } +} + +#if defined(CONFIG_ZB_NRF_TRACE_RX_ENABLE) +/* Function set UART RX callback function */ +void zb_osif_serial_logger_set_uart_byte_received_cb(zb_callback_t cb) +{ + char_handler = cb; +} +#endif /* CONFIG_ZB_NRF_TRACE_RX_ENABLE */ + +#endif /* defined ZB_NRF_TRACE */ diff --git a/subsys/osif/zb_nrf_timer_counter.c b/subsys/osif/zb_nrf_timer_counter.c new file mode 100644 index 00000000..59ee957b --- /dev/null +++ b/subsys/osif/zb_nrf_timer_counter.c @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#define ALARM_CHANNEL_ID 0 + +typedef struct { + const struct device *device; + struct counter_alarm_cfg alarm_cfg; + uint32_t counter_period_us; + uint32_t counter_acc_us; + uint8_t alarm_ch_id; + volatile zb_bool_t is_init; + volatile atomic_t is_running; +} zb_timer_t; + +static zb_timer_t zb_timer = { + .device = DEVICE_DT_GET(DT_CHOSEN(ncs_zigbee_timer)), + .is_init = ZB_FALSE, + .is_running = ATOMIC_INIT(0) +}; + +/* Forward declaration, dependency to ZBOSS */ +void zb_osif_zboss_timer_tick(void); + +/* Timer interrupt handler. */ +static void zb_timer_alarm_handler(const struct device *counter_dev, + uint8_t chan_id, + uint32_t ticks, void *user_data) +{ + switch (chan_id) { + case ALARM_CHANNEL_ID: + if (atomic_set((atomic_t *)&zb_timer.is_running, 0) == 1) { + /* The atomic flag is_running was 1 and now set to 0. */ + zb_timer.counter_acc_us += zb_timer.counter_period_us; + + /* ZBOSS reschedules the timer inside the + * zb_osif_zboss_timer_tick(), so it is required to + * reshedule it manually if the function will not be + * called at the end of this function. + */ + if (zb_timer.counter_acc_us < ZB_BEACON_INTERVAL_USEC) { + zb_osif_timer_start(); + } + } + break; + + default: + /*Do nothing*/ + break; + } + + if (zb_timer.counter_acc_us >= ZB_BEACON_INTERVAL_USEC) { + zb_timer.counter_acc_us -= ZB_BEACON_INTERVAL_USEC; + zb_osif_zboss_timer_tick(); + } +} + +static void zb_osif_timer_set_default_config(zb_timer_t *timer) +{ + timer->alarm_ch_id = ALARM_CHANNEL_ID; + timer->alarm_cfg.flags = 0; + timer->alarm_cfg.ticks = counter_us_to_ticks(timer->device, + ZB_BEACON_INTERVAL_USEC / 2); + timer->counter_period_us = counter_ticks_to_us(timer->device, + timer->alarm_cfg.ticks); + timer->counter_acc_us = 0; + timer->alarm_cfg.callback = zb_timer_alarm_handler; +} + +static void zb_timer_init(void) +{ + __ASSERT(device_is_ready(zb_timer.device), "Timer device not ready"); + + zb_osif_timer_set_default_config(&zb_timer); + + zb_timer.is_init = ZB_TRUE; +} + +void zb_osif_timer_stop(void) +{ + if (atomic_set((atomic_t *)&zb_timer.is_running, 0) == 1) { + /* The atomic flag is_running was 1 and now set to 0. */ + counter_stop(zb_timer.device); + counter_cancel_channel_alarm(zb_timer.device, + zb_timer.alarm_ch_id); + } +} + +void zb_osif_timer_start(void) +{ + if (atomic_set((atomic_t *)&zb_timer.is_running, 1) == 0) { + /* The atomic flag is_running was 0 and now set to 1. */ + if (zb_timer.is_init == ZB_FALSE) { + zb_timer_init(); + } + counter_start(zb_timer.device); + + /* Set a single shot alarm. */ + counter_set_channel_alarm(zb_timer.device, + zb_timer.alarm_ch_id, + &zb_timer.alarm_cfg); + } +} + +zb_bool_t zb_osif_timer_is_on(void) +{ + return atomic_get((atomic_t *)&zb_timer.is_running) + ? ZB_TRUE : ZB_FALSE; +} + +/* + * Get current time, us. + */ +zb_uint64_t osif_transceiver_time_get_long(void) +{ + zb_uint64_t time_sys; + zb_uint64_t time_cur; + + if (zb_osif_timer_is_on() == ZB_TRUE) { + uint32_t ticks = 0; + (void)counter_get_value(zb_timer.device, &ticks); + time_cur = (zb_uint64_t)counter_ticks_to_us(zb_timer.device, + ticks); + } else { + time_cur = 0; + } + time_sys = ZB_TIME_BEACON_INTERVAL_TO_USEC(ZB_TIMER_GET()); + + return time_sys + time_cur; +} + +zb_time_t osif_transceiver_time_get(void) +{ + return (zb_time_t)osif_transceiver_time_get_long(); +} + +void osif_sleep_using_transc_timer(zb_time_t timeout_us) +{ + zb_uint64_t tstart = osif_transceiver_time_get_long(); + zb_uint64_t tend = tstart + timeout_us; + + if (tend < tstart) { + while (tend < osif_transceiver_time_get_long()) { + zb_osif_busy_loop_delay(10); + } + } else { + while (osif_transceiver_time_get_long() < tend) { + zb_osif_busy_loop_delay(10); + } + } +} diff --git a/subsys/osif/zb_nrf_timer_ktimer.c b/subsys/osif/zb_nrf_timer_ktimer.c new file mode 100644 index 00000000..c9ad9488 --- /dev/null +++ b/subsys/osif/zb_nrf_timer_ktimer.c @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include + +#include +#include +#include + + +/* + * Get uptime value in ms. + */ +static zb_uint64_t zb_osif_timer_get_ms(void) +{ + return (zb_uint64_t)k_uptime_get(); +} + +/* + * Get ZBOSS time value in beacon intervals. + * The single beacon interval duration in us is 15360us. + */ +zb_time_t zb_timer_get(void) +{ + return (10ULL * zb_osif_timer_get_ms()) / ((zb_uint64_t)(ZB_BEACON_INTERVAL_USEC / 100U)); +} + +void zb_osif_timer_stop(void) +{ +} + +void zb_osif_timer_start(void) +{ +} + +zb_bool_t zb_osif_timer_is_on(void) +{ + return ZB_TRUE; +} + +/* + * Get current time, us. + */ +zb_uint64_t osif_transceiver_time_get_long(void) +{ + return (zb_osif_timer_get_ms() * 1000); +} + +zb_time_t osif_transceiver_time_get(void) +{ + return (zb_time_t)osif_transceiver_time_get_long(); +} + +void osif_sleep_using_transc_timer(zb_time_t timeout_us) +{ + zb_uint64_t tstart = osif_transceiver_time_get_long(); + zb_uint64_t tend = tstart + timeout_us; + + if (tend < tstart) { + while (tend < osif_transceiver_time_get_long()) { + zb_osif_busy_loop_delay(10); + } + } else { + while (osif_transceiver_time_get_long() < tend) { + zb_osif_busy_loop_delay(10); + } + } +} diff --git a/subsys/osif/zb_nrf_transceiver.c b/subsys/osif/zb_nrf_transceiver.c new file mode 100644 index 00000000..a1ddac2f --- /dev/null +++ b/subsys/osif/zb_nrf_transceiver.c @@ -0,0 +1,624 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "zb_nrf_platform.h" + +#define PHR_LENGTH 1 +#define FCS_LENGTH 2 +#define ACK_PKT_LENGTH 5 +#define FRAME_TYPE_MASK 0x07 +#define FRAME_TYPE_ACK 0x02 + +#if defined(NRF52840_XXAA) || defined(NRF52811_XXAA) +/* Minimum value in dBm detectable by the radio. */ +#define MIN_RADIO_SENSITIVITY (-92) +/* Factor needed to calculate the ED result based on the data from the RADIO peripheral. */ +#define ZBOSS_ED_RESULT_FACTOR 4 + +#elif defined(NRF52833_XXAA) || defined(NRF52820_XXAA) \ +|| defined(NRF5340_XXAA_NETWORK) || defined(NRF5340_XXAA_APPLICATION) +/* Minimum value in dBm detectable by the radio. */ +#define MIN_RADIO_SENSITIVITY (-93) +/* Factor needed to calculate the ED result based on the data from the RADIO peripheral. */ +#define ZBOSS_ED_RESULT_FACTOR 5 + +#else +/* Minimum value in dBm detectable by the radio. */ +#define MIN_RADIO_SENSITIVITY (-92) +/* Factor needed to calculate the ED result based on the data from the RADIO peripheral. */ +#define ZBOSS_ED_RESULT_FACTOR 4 +// #error "Selected chip is not supported." +#endif + +/* dBm value corresponding to value 0 of the energy scan result. */ +#define ZBOSS_ED_MIN_DBM (-75) +/* dBm value corresponding to value 255 of the energy scan result. */ +#define ZBOSS_ED_MAX_DBM (MIN_RADIO_SENSITIVITY + (255/ZBOSS_ED_RESULT_FACTOR)) + +BUILD_ASSERT(IS_ENABLED(CONFIG_NET_PKT_TIMESTAMP), "Timestamp is required"); +BUILD_ASSERT(!IS_ENABLED(CONFIG_IEEE802154_NET_IF_NO_AUTO_START), + "Option not supported"); + +/* Required by workaround for KRKNWK-12301. */ +#define NO_ACK_DELAY_MS 23U + +LOG_MODULE_DECLARE(zboss_osif, CONFIG_ZBOSS_OSIF_LOG_LEVEL); + +enum ieee802154_radio_state { + RADIO_802154_STATE_SLEEP, + RADIO_802154_STATE_ACTIVE, + RADIO_802154_STATE_RECEIVE, + RADIO_802154_STATE_TRANSMIT, +}; + +struct ieee802154_state_cache { + int8_t power; + enum ieee802154_radio_state radio_state; +}; + +static struct ieee802154_state_cache state_cache = { + .power = SCHAR_MIN, + .radio_state = RADIO_802154_STATE_SLEEP, +}; + +/* RX fifo queue. */ +static struct k_fifo rx_fifo; + +static uint8_t ack_frame_buf[ACK_PKT_LENGTH + PHR_LENGTH]; +static uint8_t *ack_frame; + +static struct { + /* Semaphore for waiting for end of energy detection procedure. */ + struct k_sem sem; + volatile bool failed; /* Energy detection procedure failed. */ + volatile uint32_t time_ms; /* Duration of energy detection procedure. */ + volatile uint8_t rssi_val; /* Detected energy level. */ +} energy_detect; + +static const struct device *radio_dev; +static struct ieee802154_radio_api *radio_api; +static struct net_if *net_iface; + +void zb_trans_hw_init(void) +{ + /* Radio hardware is initialized in 802.15.4 driver */ +} + +/* Sets the PAN ID used by the device. */ +void zb_trans_set_pan_id(zb_uint16_t pan_id) +{ + struct ieee802154_filter filter = { .pan_id = pan_id }; + + LOG_DBG("Function: %s, PAN ID: 0x%x", __func__, pan_id); + + radio_api->filter(radio_dev, true, IEEE802154_FILTER_TYPE_PAN_ID, &filter); +} + +/* Sets the long address in the radio driver. */ +void zb_trans_set_long_addr(zb_ieee_addr_t long_addr) +{ + struct ieee802154_filter filter = { .ieee_addr = long_addr }; + + LOG_DBG("Function: %s, long addr: 0x%llx", __func__, (uint64_t)*long_addr); + + radio_api->filter(radio_dev, + true, + IEEE802154_FILTER_TYPE_IEEE_ADDR, + &filter); +} + +/* Sets the short address of the device. */ +void zb_trans_set_short_addr(zb_uint16_t addr) +{ + struct ieee802154_filter filter = { .short_addr = addr }; + + LOG_DBG("Function: %s, 0x%x", __func__, addr); + + radio_api->filter(radio_dev, + true, + IEEE802154_FILTER_TYPE_SHORT_ADDR, + &filter); +} + +/* Energy detection callback */ +static void energy_scan_done(const struct device *dev, int16_t max_ed) +{ + ARG_UNUSED(dev); + + if (max_ed == SHRT_MAX) { + energy_detect.failed = true; + } else { + energy_detect.rssi_val = + 255 * (max_ed - ZBOSS_ED_MIN_DBM) / (ZBOSS_ED_MAX_DBM - ZBOSS_ED_MIN_DBM); + } + k_sem_give(&energy_detect.sem); +} + +/* Start the energy detection procedure */ +void zb_trans_start_get_rssi(zb_uint8_t scan_duration_bi) +{ + energy_detect.failed = false; + energy_detect.time_ms = + ZB_TIME_BEACON_INTERVAL_TO_MSEC(scan_duration_bi); + + LOG_DBG("Function: %s, scan duration: %d ms", __func__, + energy_detect.time_ms); + + k_sem_take(&energy_detect.sem, K_FOREVER); + while (radio_api->ed_scan(radio_dev, + energy_detect.time_ms, + energy_scan_done)) { + k_usleep(500); + } +} + +/* Waiting for the end of energy detection procedure and reads the RSSI */ +void zb_trans_get_rssi(zb_uint8_t *rssi_value_p) +{ + LOG_DBG("Function: %s", __func__); + + /*Wait until the ED scan finishes.*/ + while (1) { + k_sem_take(&energy_detect.sem, K_FOREVER); + if (!energy_detect.failed) { + *rssi_value_p = energy_detect.rssi_val; + LOG_DBG("Energy detected: %d", *rssi_value_p); + break; + } + + /* Try again */ + LOG_DBG("Energy detect failed, tries again"); + energy_detect.failed = false; + while (radio_api->ed_scan(radio_dev, + energy_detect.time_ms, + energy_scan_done)) { + k_usleep(500); + } + } + k_sem_give(&energy_detect.sem); +} + +/* Set channel and go to the normal (not ed scan) mode */ +zb_ret_t zb_trans_set_channel(zb_uint8_t channel_number) +{ + LOG_DBG("Function: %s, channel number: %d", __func__, channel_number); + + radio_api->set_channel(radio_dev, channel_number); + + return RET_OK; +} + +/* Sets the transmit power. */ +void zb_trans_set_tx_power(zb_int8_t power) +{ + LOG_DBG("Function: %s, power: %d", __func__, power); + + radio_api->set_txpower(radio_dev, power); + state_cache.power = power; +} + +/* Gets the currently set transmit power. */ +void zb_trans_get_tx_power(zb_int8_t *power) +{ + __ASSERT_NO_MSG(state_cache.power != SCHAR_MIN); + + *power = state_cache.power; + + LOG_DBG("Function: %s, power: %d", __func__, *power); +} + +/* Configures the device as the PAN coordinator. */ +void zb_trans_set_pan_coord(zb_bool_t enabled) +{ + struct ieee802154_config config = { .pan_coordinator = enabled }; + + LOG_DBG("Function: %s, enabled: %d", __func__, enabled); + + radio_api->configure(radio_dev, + IEEE802154_CONFIG_PAN_COORDINATOR, + &config); +} + +/* Enables or disables the automatic acknowledgments (auto ACK) */ +void zb_trans_set_auto_ack(zb_bool_t enabled) +{ + struct ieee802154_config config = { + .auto_ack_fpb = { + .enabled = enabled, + .mode = IEEE802154_FPB_ADDR_MATCH_ZIGBEE + } + }; + + LOG_DBG("Function: %s, enabled: %d", __func__, enabled); + + radio_api->configure(radio_dev, + IEEE802154_CONFIG_AUTO_ACK_FPB, + &config); +} + +/* Enables or disables the promiscuous radio mode. */ +void zb_trans_set_promiscuous_mode(zb_bool_t enabled) +{ + struct ieee802154_config config = { + .promiscuous = enabled + }; + + LOG_DBG("Function: %s, enabled: %d", __func__, enabled); + + radio_api->configure(radio_dev, + IEEE802154_CONFIG_PROMISCUOUS, + &config); +} + +/* Changes the radio state to receive. */ +void zb_trans_enter_receive(void) +{ + LOG_DBG("Function: %s", __func__); + + radio_api->start(radio_dev); + state_cache.radio_state = RADIO_802154_STATE_RECEIVE; +} + +/* Changes the radio state to sleep. */ +void zb_trans_enter_sleep(void) +{ + LOG_DBG("Function: %s", __func__); + + (void)radio_api->stop(radio_dev); + state_cache.radio_state = RADIO_802154_STATE_SLEEP; +} + +/* Returns ZB_TRUE if radio is in receive state, otherwise ZB_FALSE */ +zb_bool_t zb_trans_is_receiving(void) +{ + zb_bool_t is_receiv = + (state_cache.radio_state == RADIO_802154_STATE_RECEIVE) ? + ZB_TRUE : ZB_FALSE; + + LOG_DBG("Function: %s, is receiv: %d", __func__, is_receiv); + return is_receiv; +} + +/* Returns ZB_TRUE if radio is ON or ZB_FALSE if is in sleep state. */ +zb_bool_t zb_trans_is_active(void) +{ + zb_bool_t is_active = + (state_cache.radio_state != RADIO_802154_STATE_SLEEP) ? + ZB_TRUE : ZB_FALSE; + + LOG_DBG("Function: %s, is active: %d", __func__, is_active); + return is_active; +} + +zb_bool_t zb_trans_transmit(zb_uint8_t wait_type, zb_time_t tx_at, + zb_uint8_t *tx_buf, zb_uint8_t current_channel) +{ + struct net_pkt *pkt = NULL; + struct net_buf frag = { + .frags = NULL, + .b = { + .data = &tx_buf[1], + .len = tx_buf[0] - FCS_LENGTH, + .size = tx_buf[0] - FCS_LENGTH, + .__buf = &tx_buf[1] + } + }; + int err = 0; + + LOG_DBG("Function: %s, channel: %d", __func__, current_channel); + +#ifndef ZB_ENABLE_ZGP_DIRECT + ARG_UNUSED(tx_at); + ARG_UNUSED(current_channel); +#endif + + pkt = net_pkt_alloc(K_NO_WAIT); + if (!pkt) { + ZB_ASSERT(0); + return ZB_FALSE; + } + + ack_frame = NULL; + + switch (wait_type) { + case ZB_MAC_TX_WAIT_CSMACA: { + state_cache.radio_state = RADIO_802154_STATE_TRANSMIT; + enum ieee802154_tx_mode mode; + if (radio_api->get_capabilities(radio_dev) + & IEEE802154_HW_CSMA) { + mode = IEEE802154_TX_MODE_CSMA_CA; + } else { + mode = IEEE802154_TX_MODE_CCA; + } + + err = radio_api->tx(radio_dev, mode, pkt, &frag); + break; + } + +#ifdef ZB_ENABLE_ZGP_DIRECT + case ZB_MAC_TX_WAIT_ZGP: { + if (!(radio_api->get_capabilities(radio_dev) + & IEEE802154_HW_TXTIME)) { + net_pkt_unref(pkt); + return ZB_FALSE; + } + + net_pkt_set_txtime(pkt, (uint64_t)tx_at * NSEC_PER_USEC); + state_cache.radio_state = RADIO_802154_STATE_TRANSMIT; + err = radio_api->tx(radio_dev, + IEEE802154_TX_MODE_TXTIME, + pkt, + &frag); + break; + } +#endif + case ZB_MAC_TX_WAIT_NONE: + /* First transmit attempt without CCA. */ + state_cache.radio_state = RADIO_802154_STATE_TRANSMIT; + err = radio_api->tx(radio_dev, + IEEE802154_TX_MODE_DIRECT, + pkt, + &frag); + break; + default: + LOG_DBG("Illegal wait_type parameter: %d", wait_type); + ZB_ASSERT(0); + net_pkt_unref(pkt); + return ZB_FALSE; + } + + net_pkt_unref(pkt); + state_cache.radio_state = RADIO_802154_STATE_RECEIVE; + + switch (err) { + case 0: + /* ack_frame is overwritten if ack frame was received */ + zb_macll_transmitted_raw(ack_frame); + + /* Raise signal to indicate radio event */ + zigbee_event_notify(ZIGBEE_EVENT_TX_DONE); + break; + case -ENOMSG: + zb_macll_transmit_failed(ZB_TRANS_NO_ACK); + zigbee_event_notify(ZIGBEE_EVENT_TX_FAILED); + + /* Workaround for KRKNWK-12301. */ + k_sleep(K_MSEC(NO_ACK_DELAY_MS)); + /* End of workaround. */ + break; + case -EBUSY: + case -EIO: + default: + zb_macll_transmit_failed(ZB_TRANS_CHANNEL_BUSY_ERROR); + zigbee_event_notify(ZIGBEE_EVENT_TX_FAILED); + break; + } + + return ZB_TRUE; +} + +/* Notifies the driver that the buffer containing the received frame + * is not used anymore + */ +void zb_trans_buffer_free(zb_uint8_t *buf) +{ + ARG_UNUSED(buf); + LOG_DBG("Function: %s", __func__); + + /* The buffer containing the released frame is freed + * in 802.15.4 shim driver + */ +} + +zb_bool_t zb_trans_set_pending_bit(zb_uint8_t *addr, zb_bool_t value, + zb_bool_t extended) +{ + struct ieee802154_config config = { + .ack_fpb = { + .addr = addr, + .extended = extended, + .enabled = !value + } + }; + int ret; + + LOG_DBG("Function: %s, value: %d", __func__, value); + + ret = radio_api->configure(radio_dev, + IEEE802154_CONFIG_ACK_FPB, + &config); + return !ret ? ZB_TRUE : ZB_FALSE; +} + +void zb_trans_src_match_tbl_drop(void) +{ + struct ieee802154_config config = { + .ack_fpb = { + .addr = NULL, + .enabled = false + } + }; + + LOG_DBG("Function: %s", __func__); + + /* reset for short addresses */ + config.ack_fpb.extended = false; + radio_api->configure(radio_dev, IEEE802154_CONFIG_ACK_FPB, &config); + + /* reset for long addresses */ + config.ack_fpb.extended = true; + radio_api->configure(radio_dev, IEEE802154_CONFIG_ACK_FPB, &config); +} + +zb_time_t osif_sub_trans_timer(zb_time_t t2, zb_time_t t1) +{ + return ZB_TIME_SUBTRACT(t2, t1); +} + +zb_bool_t zb_trans_rx_pending(void) +{ + return k_fifo_is_empty(&rx_fifo) ? ZB_FALSE : ZB_TRUE; +} + +zb_uint8_t zb_trans_get_next_packet(zb_bufid_t buf) +{ + zb_uint8_t *data_ptr; + size_t length; + + LOG_DBG("Function: %s", __func__); + + if (!buf) { + return 0; + } + + /* Packet received with correct CRC, PANID and address */ + struct net_pkt *pkt = k_fifo_get(&rx_fifo, K_NO_WAIT); + + if (!pkt) { + return 0; + } + + length = net_pkt_get_len(pkt); + data_ptr = zb_buf_initial_alloc(buf, length); + + /* Copy received data */ + net_pkt_cursor_init(pkt); + net_pkt_read(pkt, data_ptr, length); + + /* Put LQI, RSSI */ + zb_macll_metadata_t *metadata = ZB_MACLL_GET_METADATA(buf); + + metadata->lqi = net_pkt_ieee802154_lqi(pkt); + metadata->power = net_pkt_ieee802154_rssi_dbm(pkt); + + /* Put timestamp (usec) into the packet tail */ + *ZB_BUF_GET_PARAM(buf, zb_time_t) = + net_pkt_timestamp(pkt)->second * USEC_PER_SEC + + net_pkt_timestamp(pkt)->nanosecond / NSEC_PER_USEC; + /* Additional buffer status for Data Request command */ + zb_macll_set_received_data_status(buf, + net_pkt_ieee802154_ack_fpb(pkt)); + + /* Release the packet */ + net_pkt_unref(pkt); + + return 1; +} + +zb_ret_t zb_trans_cca(void) +{ + int cca_result = radio_api->cca(radio_dev); + + switch (cca_result) { + case 0: + return RET_OK; + case -EBUSY: + return RET_BUSY; + default: + return RET_ERROR; + } +} + +void zb_osif_get_ieee_eui64(zb_ieee_addr_t ieee_eui64) +{ + __ASSERT_NO_MSG(net_iface); + __ASSERT_NO_MSG(net_if_get_link_addr(net_iface)->len == + sizeof(zb_ieee_addr_t)); + + sys_memcpy_swap(ieee_eui64, + net_if_get_link_addr(net_iface)->addr, + net_if_get_link_addr(net_iface)->len); +} + +void ieee802154_init(struct net_if *iface) +{ + __ASSERT_NO_MSG(iface); + net_iface = iface; + + radio_dev = net_if_get_device(iface); + __ASSERT_NO_MSG(radio_dev); + + radio_api = (struct ieee802154_radio_api *)radio_dev->api; + __ASSERT_NO_MSG(radio_api); + + zb_trans_set_auto_ack(ZB_TRUE); + + zigbee_init(); + + k_fifo_init(&rx_fifo); + k_sem_init(&energy_detect.sem, 1, 1); + + radio_api->stop(radio_dev); + net_if_up(iface); + LOG_DBG("The 802.15.4 interface initialized."); +} + +enum net_verdict ieee802154_handle_ack(struct net_if *iface, + struct net_pkt *pkt) +{ + ARG_UNUSED(iface); + + size_t ack_len = net_pkt_get_len(pkt); + + if (ack_len != ACK_PKT_LENGTH) { + LOG_ERR("%s: ACK length error", __func__); + return NET_CONTINUE; + } + + if ((*net_pkt_data(pkt) & FRAME_TYPE_MASK) != FRAME_TYPE_ACK) { + LOG_ERR("%s: ACK frame was expected", __func__); + return NET_CONTINUE; + } + + if (ack_frame != NULL) { + LOG_ERR("Overwriting unhandled ACK frame."); + } + + ack_frame_buf[0] = ack_len; + if (net_pkt_read(pkt, &ack_frame_buf[1], ack_len) < 0) { + LOG_ERR("Failed to read ACK frame."); + return NET_CONTINUE; + } + + /* ack_frame != NULL informs that ACK frame has been received */ + ack_frame = ack_frame_buf; + + return NET_OK; +} + +static enum net_verdict zigbee_l2_recv(struct net_if *iface, + struct net_pkt *pkt) +{ + ARG_UNUSED(iface); + + k_fifo_put(&rx_fifo, pkt); + + zb_macll_set_rx_flag(); + zb_macll_set_trans_int(); + + /* Raise signal to indicate rx event */ + zigbee_event_notify(ZIGBEE_EVENT_RX_DONE); + + return NET_OK; +} + +static enum net_l2_flags zigbee_l2_flags(struct net_if *iface) +{ + ARG_UNUSED(iface); + + return 0; +} + +NET_L2_INIT(ZIGBEE_L2, zigbee_l2_recv, NULL, NULL, zigbee_l2_flags); diff --git a/subsys/partition_manager/pm.yml.zboss b/subsys/partition_manager/pm.yml.zboss new file mode 100644 index 00000000..13e7ac9c --- /dev/null +++ b/subsys/partition_manager/pm.yml.zboss @@ -0,0 +1,21 @@ +#include + +zboss_nvram: + placement: + after: [app] +#ifdef CONFIG_BUILD_WITH_TFM + align: {start: CONFIG_NRF_TRUSTZONE_FLASH_REGION_SIZE} +#else + align: {start: CONFIG_FPROTECT_BLOCK_SIZE} +#endif + inside: [nonsecure_storage] + size: CONFIG_PM_PARTITION_SIZE_ZBOSS_NVRAM + +zboss_product_config: + placement: + after: [zboss_nvram] +#ifdef CONFIG_BUILD_WITH_TFM + align: {start: CONFIG_NRF_TRUSTZONE_FLASH_REGION_ALIGNMENT} +#endif + inside: [nonsecure_storage] + size: CONFIG_PM_PARTITION_SIZE_ZBOSS_PRODUCT_CONFIG