diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 7742b98..13aba50 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register( SRC_DIRS "." INCLUDE_DIRS "." - PRIV_REQUIRES nvs_flash esp_driver_gpio esp_driver_uart ieee802154 + PRIV_REQUIRES nvs_flash esp_driver_gpio esp_driver_uart ieee802154 heap esp_driver_rmt ) diff --git a/main/esp_zb_switch.c b/main/esp_zb_switch.c index 87544ef..c9ad05b 100644 --- a/main/esp_zb_switch.c +++ b/main/esp_zb_switch.c @@ -22,6 +22,7 @@ #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; @@ -58,6 +59,9 @@ static void zb_buttons_handler(switch_func_pair_t *button_func_pair) static esp_err_t deferred_driver_init(void) { + /* Inicializa o LED RGB */ + led_indicator_init(GPIO_LED_RGB); + /* Inicializa o relay */ relay_driver_init(); @@ -161,9 +165,11 @@ void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) 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 { @@ -193,6 +199,7 @@ void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) 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..."); @@ -200,6 +207,7 @@ void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) 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; @@ -266,7 +274,7 @@ static void esp_zb_task(void *pvParameters) esp_zb_core_action_handler_register(zb_action_handler); - ESP_LOGI(TAG, "Setting channel mask: 0x%08lx (channel 20)", (unsigned long)ESP_ZB_PRIMARY_CHANNEL_MASK); + 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(); diff --git a/main/esp_zb_switch.h b/main/esp_zb_switch.h index 4e71179..1660202 100644 --- a/main/esp_zb_switch.h +++ b/main/esp_zb_switch.h @@ -18,7 +18,7 @@ #define MAX_CHILDREN 10 /* the max amount of connected devices */ #define INSTALLCODE_POLICY_ENABLE false /* enable the install code policy for security */ #define HA_ONOFF_SWITCH_ENDPOINT 1 /* esp light switch device endpoint */ -#define ESP_ZB_PRIMARY_CHANNEL_MASK (1l << 20) /* Zigbee primary channel mask - Channel 20 for ZHA */ +#define ESP_ZB_PRIMARY_CHANNEL_MASK ESP_ZB_TRANSCEIVER_ALL_CHANNELS_MASK /* Zigbee primary channel mask use all channels */ /* Basic manufacturer information */ #define ESP_MANUFACTURER_NAME "\x09""ESPRESSIF" /* Customized manufacturer name */ diff --git a/main/led_indicator.c b/main/led_indicator.c new file mode 100644 index 0000000..b1a8a9d --- /dev/null +++ b/main/led_indicator.c @@ -0,0 +1,149 @@ +#include "led_indicator.h" +#include "led_strip_encoder.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/rmt_tx.h" +#include "esp_log.h" +#include + +static const char *TAG = "LED_INDICATOR"; + +#define LED_STRIP_RMT_RES_HZ (10 * 1000 * 1000) // 10MHz resolution + +static rmt_channel_handle_t led_channel = NULL; +static rmt_encoder_handle_t led_encoder = NULL; +static TaskHandle_t led_task_handle = NULL; +static led_state_t current_led_state = LED_OFF; +static gpio_num_t led_gpio = GPIO_NUM_NC; + +typedef struct { + uint8_t green; + uint8_t red; + uint8_t blue; +} grb_t; + +static void led_write(grb_t color) { + uint8_t led_data[3]; + led_data[0] = color.green; + led_data[1] = color.red; + led_data[2] = color.blue; + + rmt_transmit_config_t tx_config = { + .loop_count = 0, + }; + + ESP_ERROR_CHECK(rmt_enable(led_channel)); + ESP_ERROR_CHECK(rmt_transmit(led_channel, led_encoder, led_data, sizeof(led_data), &tx_config)); + ESP_ERROR_CHECK(rmt_tx_wait_all_done(led_channel, portMAX_DELAY)); + ESP_ERROR_CHECK(rmt_disable(led_channel)); +} + +static void led_indicator_task(void *arg) { + grb_t color; + uint32_t tick = 0; + + while (1) { + switch (current_led_state) { + case LED_OFF: + color = (grb_t){0, 0, 0}; + led_write(color); + vTaskDelay(pdMS_TO_TICKS(1000)); + break; + + case LED_PAIRING: + // Azul piscando + color = (tick % 2 == 0) ? (grb_t){0, 0, 50} : (grb_t){0, 0, 0}; + led_write(color); + vTaskDelay(pdMS_TO_TICKS(500)); + tick++; + break; + + case LED_CONNECTED: + // Verde fixo + color = (grb_t){30, 0, 0}; + led_write(color); + vTaskDelay(pdMS_TO_TICKS(1000)); + break; + + case LED_DISCONNECTED: + // Vermelho piscando + color = (tick % 2 == 0) ? (grb_t){0, 50, 0} : (grb_t){0, 0, 0}; + led_write(color); + vTaskDelay(pdMS_TO_TICKS(300)); + tick++; + break; + + case LED_RELAY_ON: + // Verde brilhante + color = (grb_t){60, 0, 0}; + led_write(color); + vTaskDelay(pdMS_TO_TICKS(100)); + break; + + case LED_RELAY_OFF: + // Verde escuro + color = (grb_t){10, 0, 0}; + led_write(color); + vTaskDelay(pdMS_TO_TICKS(100)); + break; + + case LED_FACTORY_RESET: + // Arco-íris rotativo + { + float hue = (tick % 360) * 3.14159f / 180.0f; + color = (grb_t){ + (uint8_t)(30 * (1 + sinf(hue))), + (uint8_t)(30 * (1 + sinf(hue + 2.0943f))), + (uint8_t)(30 * (1 + sinf(hue + 4.1888f))) + }; + led_write(color); + vTaskDelay(pdMS_TO_TICKS(20)); + tick++; + } + break; + } + } +} + +esp_err_t led_indicator_init(gpio_num_t gpio_num) { + esp_err_t ret; + led_gpio = gpio_num; + + ESP_LOGI(TAG, "Inicializando LED RGB no GPIO %d", gpio_num); + + // Configurar canal RMT TX + rmt_tx_channel_config_t tx_chan_config = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .gpio_num = gpio_num, + .mem_block_symbols = 64, + .resolution_hz = LED_STRIP_RMT_RES_HZ, + .trans_queue_depth = 4, + }; + + ret = rmt_new_tx_channel(&tx_chan_config, &led_channel); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Falha ao criar canal RMT TX: %s", esp_err_to_name(ret)); + return ret; + } + + // Criar encoder customizado para LED strip + led_strip_encoder_config_t encoder_config = { + .resolution = LED_STRIP_RMT_RES_HZ, + }; + + ret = rmt_new_led_strip_encoder(&encoder_config, &led_encoder); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Falha ao criar encoder LED: %s", esp_err_to_name(ret)); + return ret; + } + + // Criar task para controlar LED + xTaskCreate(led_indicator_task, "led_indicator", 2048, NULL, 5, &led_task_handle); + + ESP_LOGI(TAG, "LED RGB inicializado com sucesso"); + return ESP_OK; +} + +void led_indicator_set_state(led_state_t state) { + current_led_state = state; +} diff --git a/main/led_indicator.h b/main/led_indicator.h new file mode 100644 index 0000000..47b1e62 --- /dev/null +++ b/main/led_indicator.h @@ -0,0 +1,20 @@ +#ifndef LED_INDICATOR_H +#define LED_INDICATOR_H + +#include "driver/gpio.h" +#include "esp_err.h" + +typedef enum { + LED_OFF, + LED_PAIRING, + LED_CONNECTED, + LED_DISCONNECTED, + LED_RELAY_ON, + LED_RELAY_OFF, + LED_FACTORY_RESET +} led_state_t; + +esp_err_t led_indicator_init(gpio_num_t gpio_num); +void led_indicator_set_state(led_state_t state); + +#endif // LED_INDICATOR_H diff --git a/main/led_strip_encoder.c b/main/led_strip_encoder.c new file mode 100644 index 0000000..bb37a8c --- /dev/null +++ b/main/led_strip_encoder.c @@ -0,0 +1,126 @@ +/* + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "esp_check.h" +#include "led_strip_encoder.h" + +static const char *TAG = "led_encoder"; + +typedef struct { + rmt_encoder_t base; + rmt_encoder_t *bytes_encoder; + rmt_encoder_t *copy_encoder; + int state; + rmt_symbol_word_t reset_code; +} rmt_led_strip_encoder_t; + +static size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder; + rmt_encoder_handle_t copy_encoder = led_encoder->copy_encoder; + rmt_encode_state_t session_state = RMT_ENCODING_RESET; + rmt_encode_state_t state = RMT_ENCODING_RESET; + size_t encoded_symbols = 0; + + switch (led_encoder->state) { + case 0: // send RGB data + encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, primary_data, data_size, &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + led_encoder->state = 1; // switch to next state when current encoding session finished + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space for encoding artifacts + } + // fall-through + case 1: // send reset code + encoded_symbols += copy_encoder->encode(copy_encoder, channel, &led_encoder->reset_code, + sizeof(led_encoder->reset_code), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + led_encoder->state = RMT_ENCODING_RESET; // back to the initial encoding session + state |= RMT_ENCODING_COMPLETE; + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space for encoding artifacts + } + } +out: + *ret_state = state; + return encoded_symbols; +} + +static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_del_encoder(led_encoder->bytes_encoder); + rmt_del_encoder(led_encoder->copy_encoder); + free(led_encoder); + return ESP_OK; +} + +static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_encoder_reset(led_encoder->bytes_encoder); + rmt_encoder_reset(led_encoder->copy_encoder); + led_encoder->state = RMT_ENCODING_RESET; + return ESP_OK; +} + +esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder) +{ + esp_err_t ret = ESP_OK; + rmt_led_strip_encoder_t *led_encoder = NULL; + ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + led_encoder = calloc(1, sizeof(rmt_led_strip_encoder_t)); + ESP_GOTO_ON_FALSE(led_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for led strip encoder"); + led_encoder->base.encode = rmt_encode_led_strip; + led_encoder->base.del = rmt_del_led_strip_encoder; + led_encoder->base.reset = rmt_led_strip_encoder_reset; + + // WS2812 timing: 0: 0.4us/0.85us, 1: 0.8us/0.45us + rmt_bytes_encoder_config_t bytes_encoder_config = { + .bit0 = { + .level0 = 1, + .duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us + .level1 = 0, + .duration1 = 0.9 * config->resolution / 1000000, // T0L=0.9us + }, + .bit1 = { + .level0 = 1, + .duration0 = 0.9 * config->resolution / 1000000, // T1H=0.9us + .level1 = 0, + .duration1 = 0.3 * config->resolution / 1000000, // T1L=0.3us + }, + .flags.msb_first = 1 // WS2812 transfer bit order: G7...G0R7...R0B7...B0 + }; + ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder), err, TAG, "create bytes encoder failed"); + + rmt_copy_encoder_config_t copy_encoder_config = {}; + ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_encoder_config, &led_encoder->copy_encoder), err, TAG, "create copy encoder failed"); + + uint32_t reset_ticks = config->resolution / 1000000 * 50 / 2; // reset code duration defaults to 50us + led_encoder->reset_code = (rmt_symbol_word_t) { + .level0 = 0, + .duration0 = reset_ticks, + .level1 = 0, + .duration1 = reset_ticks, + }; + *ret_encoder = &led_encoder->base; + return ESP_OK; +err: + if (led_encoder) { + if (led_encoder->bytes_encoder) { + rmt_del_encoder(led_encoder->bytes_encoder); + } + if (led_encoder->copy_encoder) { + rmt_del_encoder(led_encoder->copy_encoder); + } + free(led_encoder); + } + return ret; +} diff --git a/main/led_strip_encoder.h b/main/led_strip_encoder.h new file mode 100644 index 0000000..96e8817 --- /dev/null +++ b/main/led_strip_encoder.h @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "driver/rmt_encoder.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type of led strip encoder configuration + */ +typedef struct { + uint32_t resolution; /*!< Encoder resolution, in Hz */ +} led_strip_encoder_config_t; + +/** + * @brief Create RMT encoder for encoding LED strip pixels into RMT symbols + * + * @param[in] config Encoder configuration + * @param[out] ret_encoder Returned encoder handle + * @return + * - ESP_ERR_INVALID_ARG for any invalid arguments + * - ESP_ERR_NO_MEM out of memory when creating led strip encoder + * - ESP_OK if creating encoder successfully + */ +esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder); + +#ifdef __cplusplus +} +#endif diff --git a/main/switch_driver.c b/main/switch_driver.c index ea71256..f052e31 100644 --- a/main/switch_driver.c +++ b/main/switch_driver.c @@ -41,6 +41,7 @@ #include "freertos/queue.h" #include "esp_zigbee_core.h" #include "switch_driver.h" +#include "led_indicator.h" /* Estado atual do relay */ static bool relay_state = false; @@ -123,13 +124,9 @@ static void switch_driver_button_detected(void *arg) if ((current_time - press_start_time) >= LONG_PRESS_DURATION_MS) { ESP_LOGI(TAG, "Botão pressionado por 5 segundos - Iniciando factory reset..."); ESP_LOGI(TAG, "Dispositivo entrará em modo de pareamento após reiniciar"); - /* Piscar relay para feedback visual */ - for (int i = 0; i < 5; i++) { - relay_set_state(true); - vTaskDelay(pdMS_TO_TICKS(100)); - relay_set_state(false); - vTaskDelay(pdMS_TO_TICKS(100)); - } + /* Animação arco-íris no LED para feedback visual */ + led_indicator_set_state(LED_FACTORY_RESET); + vTaskDelay(pdMS_TO_TICKS(2000)); /* 2 segundos de animação */ /* Factory reset - limpa NVS e reinicia */ esp_zb_factory_reset(); switch_state = SWITCH_IDLE; @@ -229,6 +226,10 @@ void relay_set_state(bool state) { relay_state = state; gpio_set_level(GPIO_OUTPUT_RELAY, state ? 1 : 0); + + /* Atualiza LED para refletir estado do relay */ + led_indicator_set_state(state ? LED_RELAY_ON : LED_RELAY_OFF); + ESP_LOGI(TAG, "Relay %s", state ? "LIGADO" : "DESLIGADO"); } diff --git a/main/switch_driver.h b/main/switch_driver.h index 328a1f1..73d237e 100644 --- a/main/switch_driver.h +++ b/main/switch_driver.h @@ -46,7 +46,10 @@ extern "C" { #define GPIO_INPUT_IO_TOGGLE_SWITCH GPIO_NUM_9 /* GPIO para controlar o relay */ -#define GPIO_OUTPUT_RELAY GPIO_NUM_8 +#define GPIO_OUTPUT_RELAY GPIO_NUM_4 + +/* GPIO para o LED RGB (WS2812) */ +#define GPIO_LED_RGB GPIO_NUM_8 /* config button level depends on the pull up/down setting push button level is on level = 1 when pull-down enable