/* * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: LicenseRef-Included * * Zigbee HA_on_off_switch Example * * This example code is in the Public Domain (or CC0 licensed, at your option.) * * Unless required by applicable law or agreed to in writing, this * software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * CONDITIONS OF ANY KIND, either express or implied. */ #include "string.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_err.h" #include "esp_check.h" #include "esp_log.h" #include "nvs_flash.h" #include "ha/esp_zigbee_ha_standard.h" #include "zcl_utility.h" #include "esp_zb_switch.h" #include "led_indicator.h" typedef struct light_bulb_device_params_s { esp_zb_ieee_addr_t ieee_addr; uint8_t endpoint; uint16_t short_addr; } light_bulb_device_params_t; static switch_func_pair_t button_func_pair[] = { {GPIO_INPUT_IO_TOGGLE_SWITCH, SWITCH_ONOFF_TOGGLE_CONTROL} }; static const char *TAG = "ESP_ZB_ON_OFF_SWITCH"; /* Forward declarations */ static esp_err_t zb_attribute_handler(const esp_zb_zcl_set_attr_value_message_t *message); static void zb_buttons_handler(switch_func_pair_t *button_func_pair) { if (button_func_pair->func == SWITCH_ONOFF_TOGGLE_CONTROL) { /* Controla o relay localmente */ relay_toggle(); /* Também envia comando Zigbee para outros dispositivos */ esp_zb_zcl_on_off_cmd_t cmd_req; cmd_req.zcl_basic_cmd.src_endpoint = HA_ONOFF_SWITCH_ENDPOINT; cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_TOGGLE_ID; esp_zb_lock_acquire(portMAX_DELAY); esp_zb_zcl_on_off_cmd_req(&cmd_req); esp_zb_lock_release(); ESP_EARLY_LOGI(TAG, "Send 'on_off toggle' command"); } } static esp_err_t deferred_driver_init(void) { /* Inicializa o LED RGB */ led_indicator_init(GPIO_LED_RGB); /* Inicializa o relay */ relay_driver_init(); /* Inicializa o botão */ ESP_RETURN_ON_FALSE(switch_driver_init(button_func_pair, PAIR_SIZE(button_func_pair), zb_buttons_handler), ESP_FAIL, TAG, "Failed to initialize switch driver"); return ESP_OK; } static void bdb_start_top_level_commissioning_cb(uint8_t mode_mask) { ESP_RETURN_ON_FALSE(esp_zb_bdb_start_top_level_commissioning(mode_mask) == ESP_OK, , TAG, "Failed to start Zigbee bdb commissioning"); } static void bind_cb(esp_zb_zdp_status_t zdo_status, void *user_ctx) { if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) { ESP_LOGI(TAG, "Bound successfully!"); if (user_ctx) { light_bulb_device_params_t *light = (light_bulb_device_params_t *)user_ctx; ESP_LOGI(TAG, "The light originating from address(0x%x) on endpoint(%d)", light->short_addr, light->endpoint); free(light); } } } static void user_find_cb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx) { if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) { ESP_LOGI(TAG, "Found light"); esp_zb_zdo_bind_req_param_t bind_req; light_bulb_device_params_t *light = (light_bulb_device_params_t *)malloc(sizeof(light_bulb_device_params_t)); light->endpoint = endpoint; light->short_addr = addr; esp_zb_ieee_address_by_short(light->short_addr, light->ieee_addr); esp_zb_get_long_address(bind_req.src_address); bind_req.src_endp = HA_ONOFF_SWITCH_ENDPOINT; bind_req.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_ON_OFF; bind_req.dst_addr_mode = ESP_ZB_ZDO_BIND_DST_ADDR_MODE_64_BIT_EXTENDED; memcpy(bind_req.dst_address_u.addr_long, light->ieee_addr, sizeof(esp_zb_ieee_addr_t)); bind_req.dst_endp = endpoint; bind_req.req_dst_addr = esp_zb_get_short_address(); ESP_LOGI(TAG, "Try to bind On/Off"); esp_zb_zdo_device_bind_req(&bind_req, bind_cb, (void *)light); } } static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, const void *message) { esp_err_t ret = ESP_OK; switch (callback_id) { case ESP_ZB_CORE_SET_ATTR_VALUE_CB_ID: ret = zb_attribute_handler((esp_zb_zcl_set_attr_value_message_t *)message); break; default: ESP_LOGW(TAG, "Receive Zigbee action(0x%x) callback", callback_id); break; } return ret; } static esp_err_t zb_attribute_handler(const esp_zb_zcl_set_attr_value_message_t *message) { esp_err_t ret = ESP_OK; bool relay_state = false; ESP_RETURN_ON_FALSE(message, ESP_FAIL, TAG, "Empty message"); ESP_RETURN_ON_FALSE(message->info.status == ESP_ZB_ZCL_STATUS_SUCCESS, ESP_ERR_INVALID_ARG, TAG, "Received message: error status(%d)", message->info.status); ESP_LOGI(TAG, "Received message: endpoint(%d), cluster(0x%x), attribute(0x%x), data size(%d)", message->info.dst_endpoint, message->info.cluster, message->attribute.id, message->attribute.data.size); if (message->info.dst_endpoint == HA_ONOFF_SWITCH_ENDPOINT) { if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_ON_OFF) { if (message->attribute.id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_BOOL) { relay_state = message->attribute.data.value ? *(bool *)message->attribute.data.value : relay_state; ESP_LOGI(TAG, "Relay sets to %s", relay_state ? "On" : "Off"); relay_set_state(relay_state); } } } return ret; } void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) { uint32_t *p_sg_p = signal_struct->p_app_signal; esp_err_t err_status = signal_struct->esp_err_status; esp_zb_app_signal_type_t sig_type = *p_sg_p; esp_zb_zdo_signal_device_annce_params_t *dev_annce_params = NULL; switch (sig_type) { case ESP_ZB_ZDO_SIGNAL_SKIP_STARTUP: ESP_LOGI(TAG, "Initialize Zigbee stack"); esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_INITIALIZATION); break; case ESP_ZB_BDB_SIGNAL_DEVICE_FIRST_START: case ESP_ZB_BDB_SIGNAL_DEVICE_REBOOT: if (err_status == ESP_OK) { ESP_LOGI(TAG, "Deferred driver initialization %s", deferred_driver_init() ? "failed" : "successful"); ESP_LOGI(TAG, "Device started up in %s factory-reset mode", esp_zb_bdb_is_factory_new() ? "" : "non"); if (esp_zb_bdb_is_factory_new()) { ESP_LOGI(TAG, "Start network steering (joining network)"); ESP_LOGI(TAG, "Scanning for available Zigbee networks on all channels..."); led_indicator_set_state(LED_PAIRING); /* LED azul piscando */ esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING); } else { ESP_LOGI(TAG, "Device rebooted, rejoining network"); led_indicator_set_state(LED_PAIRING); /* LED azul piscando */ esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING); } } else { ESP_LOGE(TAG, "Failed to initialize Zigbee stack (status: %s)", esp_err_to_name(err_status)); } break; case ESP_ZB_BDB_SIGNAL_FORMATION: if (err_status == ESP_OK) { esp_zb_ieee_addr_t extended_pan_id; esp_zb_get_extended_pan_id(extended_pan_id); ESP_LOGI(TAG, "Network formed (Extended PAN ID: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, PAN ID: 0x%04hx, Channel:%d, Short Address: 0x%04hx)", extended_pan_id[7], extended_pan_id[6], extended_pan_id[5], extended_pan_id[4], extended_pan_id[3], extended_pan_id[2], extended_pan_id[1], extended_pan_id[0], esp_zb_get_pan_id(), esp_zb_get_current_channel(), esp_zb_get_short_address()); } else { ESP_LOGW(TAG, "Network formation failed (status: %s)", esp_err_to_name(err_status)); } break; case ESP_ZB_BDB_SIGNAL_STEERING: if (err_status == ESP_OK) { esp_zb_ieee_addr_t ieee_addr; esp_zb_get_long_address(ieee_addr); ESP_LOGI(TAG, "Successfully joined network!"); ESP_LOGI(TAG, " PAN ID: 0x%04hx", esp_zb_get_pan_id()); ESP_LOGI(TAG, " Channel: %d", esp_zb_get_current_channel()); ESP_LOGI(TAG, " Short Address: 0x%04hx", esp_zb_get_short_address()); ESP_LOGI(TAG, " IEEE Address: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", ieee_addr[7], ieee_addr[6], ieee_addr[5], ieee_addr[4], ieee_addr[3], ieee_addr[2], ieee_addr[1], ieee_addr[0]); led_indicator_set_state(LED_CONNECTED); /* LED verde fixo */ } else { ESP_LOGW(TAG, "Network steering failed (status: %s)", esp_err_to_name(err_status)); ESP_LOGI(TAG, "No networks found or unable to join. Retrying in 5 seconds..."); ESP_LOGI(TAG, "Make sure your Zigbee coordinator is:"); ESP_LOGI(TAG, " 1. Powered on and working"); ESP_LOGI(TAG, " 2. In 'permit join' mode (network open)"); ESP_LOGI(TAG, " 3. Within range (< 10 meters recommended)"); led_indicator_set_state(LED_DISCONNECTED); /* LED vermelho piscando */ esp_zb_scheduler_alarm((esp_zb_callback_t)bdb_start_top_level_commissioning_cb, ESP_ZB_BDB_MODE_NETWORK_STEERING, 5000); } break; case ESP_ZB_ZDO_SIGNAL_DEVICE_ANNCE: dev_annce_params = (esp_zb_zdo_signal_device_annce_params_t *)esp_zb_app_signal_get_params(p_sg_p); ESP_LOGI(TAG, "New device commissioned or rejoined (short: 0x%04hx)", dev_annce_params->device_short_addr); esp_zb_zdo_match_desc_req_param_t cmd_req; cmd_req.dst_nwk_addr = dev_annce_params->device_short_addr; cmd_req.addr_of_interest = dev_annce_params->device_short_addr; esp_zb_zdo_find_on_off_light(&cmd_req, user_find_cb, NULL); break; case ESP_ZB_NWK_SIGNAL_PERMIT_JOIN_STATUS: if (err_status == ESP_OK) { if (*(uint8_t *)esp_zb_app_signal_get_params(p_sg_p)) { ESP_LOGI(TAG, "Network(0x%04hx) is open for %d seconds", esp_zb_get_pan_id(), *(uint8_t *)esp_zb_app_signal_get_params(p_sg_p)); } else { ESP_LOGW(TAG, "Network(0x%04hx) closed, devices joining not allowed.", esp_zb_get_pan_id()); } } break; case ESP_ZB_NLME_STATUS_INDICATION: ESP_LOGI(TAG, "NLME status indication: status=%s", esp_err_to_name(err_status)); break; case ESP_ZB_ZDO_SIGNAL_LEAVE: ESP_LOGI(TAG, "Device left network"); break; default: ESP_LOGI(TAG, "ZDO signal: %s (0x%x), status: %s", esp_zb_zdo_signal_to_string(sig_type), sig_type, esp_err_to_name(err_status)); break; } } static void esp_zb_task(void *pvParameters) { /* initialize Zigbee stack */ esp_zb_cfg_t zb_nwk_cfg = ESP_ZB_ZR_CONFIG(); esp_zb_init(&zb_nwk_cfg); /* Create endpoint list */ esp_zb_ep_list_t *esp_zb_ep_list = esp_zb_ep_list_create(); /* Create on/off light endpoint */ esp_zb_on_off_light_cfg_t light_cfg = ESP_ZB_DEFAULT_ON_OFF_LIGHT_CONFIG(); esp_zb_endpoint_config_t endpoint_config = { .endpoint = HA_ONOFF_SWITCH_ENDPOINT, .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, .app_device_id = ESP_ZB_HA_ON_OFF_OUTPUT_DEVICE_ID, .app_device_version = 0 }; esp_zb_cluster_list_t *esp_zb_cluster_list = esp_zb_zcl_cluster_list_create(); esp_zb_attribute_list_t *esp_zb_basic_cluster = esp_zb_basic_cluster_create(&(light_cfg.basic_cfg)); ESP_ERROR_CHECK(esp_zb_basic_cluster_add_attr(esp_zb_basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID, (void *)ESP_MANUFACTURER_NAME)); ESP_ERROR_CHECK(esp_zb_basic_cluster_add_attr(esp_zb_basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID, (void *)ESP_MODEL_IDENTIFIER)); ESP_ERROR_CHECK(esp_zb_cluster_list_add_basic_cluster(esp_zb_cluster_list, esp_zb_basic_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE)); ESP_ERROR_CHECK(esp_zb_cluster_list_add_identify_cluster(esp_zb_cluster_list, esp_zb_identify_cluster_create(&(light_cfg.identify_cfg)), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE)); ESP_ERROR_CHECK(esp_zb_cluster_list_add_groups_cluster(esp_zb_cluster_list, esp_zb_groups_cluster_create(&(light_cfg.groups_cfg)), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE)); ESP_ERROR_CHECK(esp_zb_cluster_list_add_scenes_cluster(esp_zb_cluster_list, esp_zb_scenes_cluster_create(&(light_cfg.scenes_cfg)), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE)); ESP_ERROR_CHECK(esp_zb_cluster_list_add_on_off_cluster(esp_zb_cluster_list, esp_zb_on_off_cluster_create(&(light_cfg.on_off_cfg)), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE)); ESP_ERROR_CHECK(esp_zb_ep_list_add_ep(esp_zb_ep_list, esp_zb_cluster_list, endpoint_config)); esp_zb_device_register(esp_zb_ep_list); esp_zb_core_action_handler_register(zb_action_handler); ESP_LOGI(TAG, "Setting channel mask: 0x%08lx (all channels)", (unsigned long)ESP_ZB_PRIMARY_CHANNEL_MASK); esp_zb_set_primary_network_channel_set(ESP_ZB_PRIMARY_CHANNEL_MASK); ESP_ERROR_CHECK(esp_zb_start(false)); esp_zb_stack_main_loop(); } void app_main(void) { esp_zb_platform_config_t config = { .radio_config = ESP_ZB_DEFAULT_RADIO_CONFIG(), .host_config = ESP_ZB_DEFAULT_HOST_CONFIG(), }; ESP_ERROR_CHECK(nvs_flash_init()); ESP_ERROR_CHECK(esp_zb_platform_config(&config)); xTaskCreate(esp_zb_task, "Zigbee_main", 4096, NULL, 5, NULL); }