diff --git a/CMakeLists.txt b/CMakeLists.txt index de72a4c..4cf3e05 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,3 +4,5 @@ find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(jamie) target_sources(app PRIVATE src/main.c) + +target_include_directories(app PRIVATE include) diff --git a/boards/nrf52840dongle_nrf52840.overlay b/boards/nrf52840dongle_nrf52840.overlay index e5ab610..716071d 100644 --- a/boards/nrf52840dongle_nrf52840.overlay +++ b/boards/nrf52840dongle_nrf52840.overlay @@ -9,6 +9,35 @@ zephyr,entropy = &rng; ncs,zigbee-timer = &timer2; }; + + leds { + network_led: network_led { + gpios = <&gpio0 6 GPIO_ACTIVE_LOW>; + label = "Network Activity LED"; + }; + }; + + buttons { + sw_0: sw0 { + gpios = <&gpio1 10 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + label = "Switch 1 Input"; + }; + + sw_1: sw1 { + gpios = <&gpio1 13 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + label = "Switch 2 Input"; + }; + + sw_2: sw2 { + gpios = <&gpio1 15 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + label = "Switch 3 Input"; + }; + + sw_3: sw3 { + gpios = <&gpio0 2 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + label = "Switch 4 Input"; + }; + }; }; &timer2 { diff --git a/include/switch.h b/include/switch.h new file mode 100644 index 0000000..3a7290d --- /dev/null +++ b/include/switch.h @@ -0,0 +1,76 @@ +#ifndef SWITCH_H +#define SWITCH_H + +#define ZB_DECLARE_INFO_EP(ep_name, ep_id, dev_ctx) \ +\ +ZB_ZCL_DECLARE_IDENTIFY_ATTRIB_LIST( \ + ep_name ## _identify_attr_list, \ + &dev_ctx.identify_attr.identify_time); \ +\ +ZB_ZCL_DECLARE_BASIC_ATTRIB_LIST_EXT( \ + ep_name ## _basic_attr_list, \ + &dev_ctx.basic_attr.zcl_version, \ + &dev_ctx.basic_attr.app_version, \ + &dev_ctx.basic_attr.stack_version, \ + &dev_ctx.basic_attr.hw_version, \ + dev_ctx.basic_attr.mf_name, \ + dev_ctx.basic_attr.model_id, \ + dev_ctx.basic_attr.date_code, \ + &dev_ctx.basic_attr.power_source, \ + dev_ctx.basic_attr.location_id, \ + &dev_ctx.basic_attr.ph_env, \ + dev_ctx.basic_attr.sw_ver); \ +\ +zb_zcl_cluster_desc_t ep_name ## _clusters[] = { \ + ZB_ZCL_CLUSTER_DESC(ZB_ZCL_CLUSTER_ID_IDENTIFY, ZB_ZCL_ARRAY_SIZE(ep_name ## _identify_attr_list, zb_zcl_attr_t), (ep_name ## _identify_attr_list), ZB_ZCL_CLUSTER_SERVER_ROLE, ZB_ZCL_MANUF_CODE_INVALID), \ + ZB_ZCL_CLUSTER_DESC(ZB_ZCL_CLUSTER_ID_BASIC, ZB_ZCL_ARRAY_SIZE(ep_name ## _basic_attr_list, zb_zcl_attr_t), (ep_name ## _basic_attr_list), ZB_ZCL_CLUSTER_SERVER_ROLE, ZB_ZCL_MANUF_CODE_INVALID), \ +}; \ +\ +ZB_AF_SIMPLE_DESC_TYPE(2, 0) ep_name ## _desc = { \ + ep_id, \ + ZB_AF_HA_PROFILE_ID, \ + 0x0008, \ + 0, \ + 0, \ + 2, \ + 0, \ + { \ + ZB_ZCL_CLUSTER_ID_BASIC, \ + ZB_ZCL_CLUSTER_ID_IDENTIFY, \ + } \ +}; \ +\ +ZB_AF_DECLARE_ENDPOINT_DESC(ep_name, ep_id, ZB_AF_HA_PROFILE_ID, 0, NULL, ZB_ZCL_ARRAY_SIZE(ep_name ## _clusters, zb_zcl_cluster_desc_t), ep_name ## _clusters, (zb_af_simple_desc_1_1_t *)&ep_name ## _desc, 0, NULL, 0, NULL) + +#define ZB_DECLARE_SW_EP(ep_name, ep_id, state, type, action) \ +\ +ZB_ZCL_DECLARE_ON_OFF_ATTRIB_LIST(ep_name ## _attribs, &state.on_off); \ +ZB_ZCL_DECLARE_ON_OFF_CLIENT_ATTRIB_LIST(ep_name ## _client_attribs); \ +ZB_ZCL_DECLARE_ON_OFF_SWITCH_CONFIGURATION_ATTRIB_LIST(ep_name ## _config_attribs, &type, &action); \ +\ +zb_zcl_cluster_desc_t ep_name ## _clusters[] = { \ + ZB_ZCL_CLUSTER_DESC(ZB_ZCL_CLUSTER_ID_ON_OFF, ZB_ZCL_ARRAY_SIZE(ep_name ## _attribs, zb_zcl_attr_t), (ep_name ## _attribs), ZB_ZCL_CLUSTER_SERVER_ROLE, ZB_ZCL_MANUF_CODE_INVALID), \ + ZB_ZCL_CLUSTER_DESC(ZB_ZCL_CLUSTER_ID_ON_OFF, ZB_ZCL_ARRAY_SIZE(ep_name ## _client_attribs, zb_zcl_attr_t), (ep_name ## _client_attribs), ZB_ZCL_CLUSTER_CLIENT_ROLE, ZB_ZCL_MANUF_CODE_INVALID), \ + ZB_ZCL_CLUSTER_DESC(ZB_ZCL_CLUSTER_ID_ON_OFF_SWITCH_CONFIG, ZB_ZCL_ARRAY_SIZE(ep_name ## _config_attribs, zb_zcl_attr_t), (ep_name ## _config_attribs), ZB_ZCL_CLUSTER_SERVER_ROLE, ZB_ZCL_MANUF_CODE_INVALID), \ +}; \ +\ +ZB_AF_SIMPLE_DESC_TYPE(2, 1) ep_name ## _desc = { \ + ep_id, \ + ZB_AF_HA_PROFILE_ID, \ + 0x0000, \ + 0, \ + 0, \ + 2, \ + 1, \ + { \ + ZB_ZCL_CLUSTER_ID_ON_OFF, \ + ZB_ZCL_CLUSTER_ID_ON_OFF, \ + ZB_ZCL_CLUSTER_ID_ON_OFF_SWITCH_CONFIG, \ + } \ +}; \ +\ +ZBOSS_DEVICE_DECLARE_REPORTING_CTX(ep_name ## _rep_ctx, 1); \ +ZB_AF_DECLARE_ENDPOINT_DESC(ep_name, ep_id, ZB_AF_HA_PROFILE_ID, 0, NULL, ZB_ZCL_ARRAY_SIZE(ep_name ## _clusters, zb_zcl_cluster_desc_t), ep_name ## _clusters, (zb_af_simple_desc_1_1_t *)&ep_name ## _desc, 1, ep_name ## _rep_ctx, 0, NULL) + + +#endif // SWITCH_H \ No newline at end of file diff --git a/prj.conf b/prj.conf index a639cbf..f355228 100644 --- a/prj.conf +++ b/prj.conf @@ -1,9 +1,16 @@ - +# # Copyright (c) 2022 Nordic Semiconductor ASA # # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # +# LOG configuration +CONFIG_LOG=y +CONFIG_LOG_MODE_DEFERRED=y +CONFIG_LOG_BUFFER_SIZE=4096 +CONFIG_LOG_PROCESS_THREAD_STACK_SIZE=1024 +CONFIG_LOG_PROCESS_THREAD_STARTUP_DELAY_MS=1000 + # Configure serial CONFIG_UART_INTERRUPT_DRIVEN=y CONFIG_SERIAL=y @@ -16,9 +23,13 @@ CONFIG_HEAP_MEM_POOL_SIZE=2048 CONFIG_MAIN_THREAD_PRIORITY=7 CONFIG_ZIGBEE=y +CONFIG_ZIGBEE_CHANNEL=11 CONFIG_ZIGBEE_APP_UTILS=y CONFIG_ZIGBEE_ROLE_ROUTER=y +# Enable DK LED and Buttons library +CONFIG_DK_LIBRARY=y + # This example requires more workqueue stack CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 @@ -31,24 +42,30 @@ CONFIG_CRYPTO_INIT_PRIORITY=80 CONFIG_NET_IPV6=n CONFIG_NET_IP_ADDR_CHECK=n -# Zigbee shell -CONFIG_ZIGBEE_SHELL=y -CONFIG_ZIGBEE_SHELL_DEBUG_CMD=y -CONFIG_ZIGBEE_SHELL_ENDPOINT=64 - -# Increase RX serial ring buffer -CONFIG_SHELL_BACKEND_SERIAL_RX_RING_BUFFER_SIZE=128 - # USB subsystem configuration CONFIG_USB_DEVICE_STACK=y CONFIG_USB_DEVICE_PRODUCT="Zigbee Shell" CONFIG_USB_CDC_ACM=y CONFIG_UART_LINE_CTRL=y - # Initialize USB device as soon as possible as the USB CDC ACM is used # as backend for both Shell and Logging subsystems. CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=y -# Enable this to duplicate Zephyr logs to default Logger serial backend, -# usually connected to on-board JLink device. -# CONFIG_LOG_BACKEND_UART=y \ No newline at end of file +CONFIG_BOARD_HAS_NRF5_BOOTLOADER=n + +CONFIG_DEBUG_OPTIMIZATIONS=y +CONFIG_DEBUG_THREAD_INFO=y + +CONFIG_RESET_ON_FATAL_ERROR=n + +CONFIG_LOG_BACKEND_SWO=y + +CONFIG_ZIGBEE_SHELL_ENDPOINT=10 +CONFIG_ZIGBEE_SHELL=y +CONFIG_ZIGBEE_SHELL_DEBUG_CMD=y + +CONFIG_DEBUG=y +CONFIG_ASSERT=y +CONFIG_ZBOSS_HALT_ON_ASSERT=y +CONFIG_ZIGBEE_LOGGER_EP=n +CONFIG_ZIGBEE_CHANNEL_SELECTION_MODE_MULTI=y \ No newline at end of file diff --git a/src/main.c b/src/main.c index ea657f3..60d0c91 100644 --- a/src/main.c +++ b/src/main.c @@ -1,31 +1,341 @@ #include #include - #include +#include +#include +#include +#include +#include +#include + +#include "switch.h" + LOG_MODULE_REGISTER(app, LOG_LEVEL_INF); -#include -void zboss_signal_handler(zb_uint8_t param) { +#define INFO_ENDPOINT CONFIG_ZIGBEE_SHELL_ENDPOINT +#define SW0_EP 1 +#define SW1_EP 2 +#define SW2_EP 3 +#define SW3_EP 4 + +const uint8_t sw_ep[4] = {SW0_EP, SW1_EP, SW2_EP, SW3_EP}; + +/* Main application customizable context. + * Stores all settings and static values. + */ +struct app_static { + zb_zcl_basic_attrs_ext_t basic_attr; + zb_zcl_identify_attrs_t identify_attr; + enum zb_zcl_on_off_switch_configuration_switch_type_e types[4]; +}; + +struct app_ram { + zb_zcl_on_off_attrs_t states[4]; +}; + +struct app_nvram { + enum zb_zcl_on_off_switch_configuration_switch_actions_e actions[4]; +}; + + +/* Zigbee device application context storage. */ +static struct app_static app_static = { + .basic_attr = { + .zcl_version = ZB_ZCL_VERSION, + .power_source = ZB_ZCL_BASIC_POWER_SOURCE_MAINS_SINGLE_PHASE, + .ph_env = ZB_ZCL_BASIC_ENV_UNSPECIFIED, + .app_version = 1, + .stack_version = 10, + .hw_version = 1, + + .mf_name = {0}, + .model_id = {0}, + .date_code = {0}, + .location_id = {0}, + }, + .identify_attr = { + .identify_time = ZB_ZCL_IDENTIFY_IDENTIFY_TIME_DEFAULT_VALUE, + }, + .types = { + ZB_ZCL_ON_OFF_SWITCH_CONFIGURATION_SWITCH_TYPE_TOGGLE, + ZB_ZCL_ON_OFF_SWITCH_CONFIGURATION_SWITCH_TYPE_TOGGLE, + ZB_ZCL_ON_OFF_SWITCH_CONFIGURATION_SWITCH_TYPE_TOGGLE, + ZB_ZCL_ON_OFF_SWITCH_CONFIGURATION_SWITCH_TYPE_TOGGLE, + }, + +}; + +static struct app_ram app_ram = { + .states = { + 0, + 0, + 0, + 0, + }, +}; + +static struct app_nvram app_nvram = { + .actions = { + ZB_ZCL_ON_OFF_SWITCH_CONFIGURATION_SWITCH_ACTIONS_TYPE1, + ZB_ZCL_ON_OFF_SWITCH_CONFIGURATION_SWITCH_ACTIONS_TYPE1, + ZB_ZCL_ON_OFF_SWITCH_CONFIGURATION_SWITCH_ACTIONS_TYPE1, + ZB_ZCL_ON_OFF_SWITCH_CONFIGURATION_SWITCH_ACTIONS_TYPE1, + }, +}; + +ZB_DECLARE_SIMPLE_DESC(2, 0); +ZB_DECLARE_SIMPLE_DESC(2, 1); + +ZB_DECLARE_INFO_EP(info_ep, INFO_ENDPOINT, app_static); + +ZB_DECLARE_SW_EP(sw0_ep, SW0_EP, app_ram.states[0], app_static.types[0], app_nvram.actions[0]); +ZB_DECLARE_SW_EP(sw1_ep, SW1_EP, app_ram.states[1], app_static.types[1], app_nvram.actions[1]); +ZB_DECLARE_SW_EP(sw2_ep, SW2_EP, app_ram.states[2], app_static.types[2], app_nvram.actions[2]); +ZB_DECLARE_SW_EP(sw3_ep, SW3_EP, app_ram.states[3], app_static.types[3], app_nvram.actions[3]); + +ZBOSS_DECLARE_DEVICE_CTX_EP_VA( + zigbee_ctx, + &info_ep, + &sw0_ep, + &sw1_ep, + &sw2_ep, + &sw3_ep); + +static const struct gpio_dt_spec network_led = GPIO_DT_SPEC_GET(DT_NODELABEL(network_led), gpios); +static const struct gpio_dt_spec sw[4] = { + GPIO_DT_SPEC_GET(DT_NODELABEL(sw_0), gpios), + GPIO_DT_SPEC_GET(DT_NODELABEL(sw_1), gpios), + GPIO_DT_SPEC_GET(DT_NODELABEL(sw_2), gpios), + GPIO_DT_SPEC_GET(DT_NODELABEL(sw_3), gpios), +}; +static struct gpio_callback sw_cb[4] = {0}; + +#define SW_DEBOUNCE_MS 500 +static uint64_t sw_last[4] = {0, 0, 0, 0}; + +/**@brief Function for initializing all clusters attributes. */ +static void zigbee_init_clusters(void) +{ + char manufacturer[] = "MetzNet"; + ZB_ZCL_SET_STRING_VAL(app_static.basic_attr.mf_name, manufacturer, ZB_ZCL_STRING_CONST_SIZE(manufacturer)); + + char model[] = "jamie"; + ZB_ZCL_SET_STRING_VAL(app_static.basic_attr.model_id, model, ZB_ZCL_STRING_CONST_SIZE(model)); + + char date[] = "jamie"; + ZB_ZCL_SET_STRING_VAL(app_static.basic_attr.date_code, date, ZB_ZCL_STRING_CONST_SIZE(date)); + + char location[] = "jamie"; + ZB_ZCL_SET_STRING_VAL(app_static.basic_attr.date_code, location, ZB_ZCL_STRING_CONST_SIZE(location)); +} + +typedef struct queue_item_s { + uint8_t sw; + int64_t time; +} queue_item_t; + +struct k_queue switch_queue = {0}; + +void sw_handler(size_t i) { + queue_item_t* item = k_malloc(sizeof(queue_item_t)); + item->sw = i; + item->time = k_uptime_get(); + k_queue_alloc_append(&switch_queue, item); +} + +void sw0_handler(const struct device *dev, struct gpio_callback *cb, gpio_port_pins_t pins) { + sw_handler(0); +} + +void sw1_handler(const struct device *dev, struct gpio_callback *cb, gpio_port_pins_t pins) { + sw_handler(1); +} + +void sw2_handler(const struct device *dev, struct gpio_callback *cb, gpio_port_pins_t pins) { + sw_handler(2); +} + +void sw3_handler(const struct device *dev, struct gpio_callback *cb, gpio_port_pins_t pins) { + sw_handler(3); +} + +gpio_callback_handler_t sw_handlers[4] = { + sw0_handler, + sw1_handler, + sw2_handler, + sw3_handler, +}; + +static void configure_gpio(void) +{ + int err; + err = gpio_pin_configure_dt(&network_led, GPIO_OUTPUT); + if(err) { + } + + k_queue_init(&switch_queue); + + for(int i = 0; i < 4; i++) { + err = gpio_pin_configure_dt(&(sw[i]), GPIO_INPUT); + err = gpio_pin_interrupt_configure_dt(&(sw[i]), GPIO_INT_EDGE_BOTH); + gpio_init_callback(&(sw_cb[i]), sw_handlers[i], BIT(sw[i].pin)); + err = gpio_add_callback(sw[i].port, &(sw_cb[i])); + sw_last[i] = k_uptime_get(); + } +} + +/**@brief Zigbee stack event handler. + * + * @param[in] bufid Reference to the Zigbee stack buffer used to pass signal. + */ +void zboss_signal_handler(zb_bufid_t bufid) +{ + gpio_pin_toggle_dt(&network_led); + + zb_zdo_app_signal_hdr_t *sig_handler = NULL; + zb_zdo_app_signal_type_t sig = zb_get_app_signal(bufid, &sig_handler); + + switch (sig) { + default: + /* Call default signal handler. */ + ZB_ERROR_CHECK(zigbee_default_signal_handler(bufid)); + break; + } + + if (bufid) { + zb_buf_free(bufid); + } +} + +static void identify_cb(zb_bufid_t bufid) { + (void)bufid; +} + +static void set_sw(zb_uint8_t param) { + uint8_t sw = (param & 0xF0) >> 4; + uint8_t val = (param & 0x0F); + ZB_ZCL_SET_ATTRIBUTE( + sw_ep[sw], + ZB_ZCL_CLUSTER_ID_ON_OFF, + ZB_ZCL_CLUSTER_SERVER_ROLE, + ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID, + (zb_uint8_t *)&val, + ZB_FALSE); + + LOG_INF("Set sw %d to %d", sw, val); +} + +zb_ret_t app_nvram_write(zb_uint8_t page, zb_uint32_t pos) { + LOG_INF("NVRAM_WRITE"); + return zb_osif_nvram_write(page, pos, (uint8_t*)(&app_nvram), sizeof(struct app_nvram)); +} + +void app_nvram_read(zb_uint8_t page, zb_uint32_t pos, zb_uint16_t payload_length) { + LOG_INF("NVRAM_READ"); + zb_uint16_t read_len = payload_length; + if(payload_length > sizeof(struct app_nvram)) { + read_len = sizeof(struct app_nvram); + } + zb_osif_nvram_read(page, pos, (uint8_t*)(&app_nvram), payload_length); +} + +zb_uint16_t app_nvram_size(void){ + return (zb_uint16_t)(sizeof(struct app_nvram)); +} + +/**@brief Callback function for handling ZCL commands. + * + * @param[in] bufid Reference to Zigbee stack buffer + * used to pass received data. + */ +static void zcl_device_cb(zb_bufid_t bufid) { + zb_uint8_t cluster_id; + zb_uint8_t attr_id; + zb_zcl_device_callback_param_t *device_cb_param = ZB_BUF_GET_PARAM(bufid, zb_zcl_device_callback_param_t); + + /* Set default response value. */ + device_cb_param->status = RET_OK; + + switch(device_cb_param->device_cb_id){ + case ZB_ZCL_SET_ATTR_VALUE_CB_ID: + cluster_id = device_cb_param->cb_param.set_attr_value_param.cluster_id; + attr_id = device_cb_param->cb_param.set_attr_value_param.attr_id; + + switch(cluster_id) { + case ZB_ZCL_CLUSTER_ID_ON_OFF_SWITCH_CONFIG: + switch(attr_id) { + case ZB_ZCL_ATTR_ON_OFF_SWITCH_CONFIGURATION_SWITCH_ACTIONS_ID: + uint8_t value = device_cb_param->cb_param.set_attr_value_param.values.data8; + uint8_t ep = device_cb_param->endpoint; + LOG_INF("New switch actions: %d - %d", ep, value); + zb_ret_t err = zb_nvram_write_dataset(ZB_NVRAM_APP_DATA1); + if(err){ + LOG_ERR("Failed to write app1 nvram: %e", err); + } else { + LOG_INF("Wrote app1 nvram"); + } + break; + default: + LOG_WRN("Unhandled on_off_switch_cfg attribute ID: %d", attr_id); + device_cb_param->status = RET_NOT_IMPLEMENTED; + break; + } + break; + default: + LOG_WRN("Unhandled cluster ID: %d", cluster_id); + device_cb_param->status = RET_NOT_IMPLEMENTED; + break; + } + + break; + default: + LOG_WRN("Unhandled callback ID: %d", device_cb_param->device_cb_id); + device_cb_param->status = RET_NOT_IMPLEMENTED; + break; + } } int main(void) { - LOG_INF("Starting main"); + LOG_INF("Starting jamie"); + + /* Initialize */ + configure_gpio(); + + /* Register device callback */ + ZB_ZCL_REGISTER_DEVICE_CB(zcl_device_cb); + + /* Register device context (endpoints). */ + ZB_AF_REGISTER_DEVICE_CTX(&zigbee_ctx); + + zigbee_init_clusters(); + + ZB_AF_SET_IDENTIFY_NOTIFICATION_HANDLER(INFO_ENDPOINT, identify_cb); - const struct gpio_dt_spec led_green = GPIO_DT_SPEC_GET(DT_NODELABEL(led0_green), gpios); + zb_nvram_register_app1_read_cb(app_nvram_read); + zb_nvram_register_app1_write_cb(app_nvram_write, app_nvram_size); - gpio_pin_configure_dt(&led_green, GPIO_OUTPUT | GPIO_ACTIVE_LOW); + zigbee_enable(); + LOG_INF("Zigbee Stack Initialized"); - while(true) { - gpio_pin_set_dt(&led_green, 1); + while(true) { + queue_item_t* item = (queue_item_t*) k_queue_get(&switch_queue, K_FOREVER); + if(item != NULL) { + int val = gpio_pin_get_dt(&sw[item->sw]); + if((item->time - sw_last[item->sw] > SW_DEBOUNCE_MS) && (val != app_ram.states[item->sw].on_off)) { + sw_last[item->sw] = item->time; + app_ram.states[item->sw].on_off = val; - k_msleep(500); + ZB_SCHEDULE_APP_CALLBACK(set_sw, ((item->sw & 0x0F) << 4) + ((val & 0x0F) << 0)); - gpio_pin_set_dt(&led_green, 0); + LOG_INF("Queue input %d to %d change", item->sw, val); + } + k_free(item); + } + } - k_msleep(500); - } + return 0; }