commit a05ac96d974a75cf9f6971daf13707af88e9e5f6 Author: FelipeCN Date: Thu Jan 15 21:46:31 2026 +0000 feat: Initialize ESP-IDF project with Zigbee HA_on_off_switch example - Added .devcontainer configuration for development environment. - Created .gitignore to exclude unnecessary files. - Configured C/C++ properties for ESP-IDF in VSCode. - Set up launch configurations for debugging. - Defined project settings for ESP-IDF in VSCode. - Created CMakeLists.txt for project build configuration. - Added README.md with project overview and usage instructions. - Established dependencies for Zigbee libraries in dependencies.lock. - Implemented main application logic in esp_zb_switch.c and esp_zb_switch.h. - Developed switch driver functionality in switch_driver.c and switch_driver.h. - Configured partition table in partitions.csv for NVS and application storage. - Set default SDK configuration in sdkconfig.defaults for project settings. diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..7901c38 --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Remove: [-f*, -m*] diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..dafb8ad --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,13 @@ +ARG DOCKER_TAG=latest +FROM espressif/idf:${DOCKER_TAG} + +ENV LC_ALL=C.UTF-8 +ENV LANG=C.UTF-8 + +RUN apt-get update -y && apt-get install udev -y + +RUN echo "source /opt/esp/idf/export.sh > /dev/null 2>&1" >> ~/.bashrc + +ENTRYPOINT [ "/opt/esp/entrypoint.sh" ] + +CMD ["/bin/bash", "-c"] \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..b801786 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,21 @@ +{ + "name": "ESP-IDF QEMU", + "build": { + "dockerfile": "Dockerfile" + }, + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.defaultProfile.linux": "bash", + "idf.espIdfPath": "/opt/esp/idf", + "idf.toolsPath": "/opt/esp", + "idf.gitPath": "/usr/bin/git" + }, + "extensions": [ + "espressif.esp-idf-extension", + "espressif.esp-idf-web" + ] + } + }, + "runArgs": ["--privileged"] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7805557 --- /dev/null +++ b/.gitignore @@ -0,0 +1,78 @@ +# macOS +.DS_Store +.AppleDouble +.LSOverride + +# Directory metadata +.directory + +# Temporary files +*~ +*.swp +*.swo +*.bak +*.tmp + +# Log files +*.log + +# Build artifacts and directories +**/build/ +build/ +*.o +*.a +*.out +*.exe # For any host-side utilities compiled on Windows + +# ESP-IDF specific build outputs +*.bin +*.elf +*.map +flasher_args.json # Generated in build directory +sdkconfig.old +sdkconfig + +# ESP-IDF dependencies +# For older versions or manual component management +/components/.idf/ +**/components/.idf/ +# For modern ESP-IDF component manager +managed_components/ +# If ESP-IDF tools are installed/referenced locally to the project +.espressif/ + +# CMake generated files +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +install_manifest.txt +CTestTestfile.cmake + +# Python environment files +*.pyc +*.pyo +*.pyd +__pycache__/ +*.egg-info/ +dist/ + +# Virtual environment folders +venv/ +.venv/ +env/ + +# Language Servers +.clangd/ +.ccls-cache/ +compile_commands.json + +# Windows specific +Thumbs.db +ehthumbs.db +Desktop.ini + +# User-specific configuration files +*.user +*.workspace # General workspace files, can be from various tools +*.suo # Visual Studio Solution User Options +*.sln.docstates # Visual Studio diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..48fa5bb --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,23 @@ +{ + "configurations": [ + { + "name": "ESP-IDF", + "compilerPath": "${config:idf.toolsPathWin}\\tools\\xtensa-esp-elf\\esp-14.2.0_20251107\\xtensa-esp-elf\\bin\\xtensa-esp32-elf-gcc.exe", + "compileCommands": "${config:idf.buildPath}/compile_commands.json", + "includePath": [ + "${config:idf.espIdfPath}/components/**", + "${config:idf.espIdfPathWin}/components/**", + "${workspaceFolder}/**" + ], + "browse": { + "path": [ + "${config:idf.espIdfPath}/components", + "${config:idf.espIdfPathWin}/components", + "${workspaceFolder}" + ], + "limitSymbolsToIncludedHeaders": true + } + } + ], + "version": 4 +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..2511a38 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "gdbtarget", + "request": "attach", + "name": "Eclipse CDT GDB Adapter" + }, + { + "type": "espidf", + "name": "Launch", + "request": "launch" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3a35a0a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,21 @@ +{ + "C_Cpp.intelliSenseEngine": "default", + "idf.espIdfPathWin": "d:\\PROGRAMAS\\esp\\v5.5.2\\esp-idf", + "idf.pythonInstallPath": "/opt/esp/python_env/idf6.1_py3.12_env/bin/python", + "idf.openOcdConfigs": [ + "board/esp32c6-builtin.cfg" + ], + "idf.portWin": "detect", + "idf.toolsPathWin": "d:\\PROGRAMAS\\.espressif", + "idf.customExtraVars": { + "IDF_TARGET": "esp32c6" + }, + "clangd.path": "d:\\PROGRAMAS\\.espressif\\tools\\esp-clang\\esp-19.1.2_20250312\\esp-clang\\bin\\clangd.exe", + "clangd.arguments": [ + "--background-index", + "--query-driver=**", + "--compile-commands-dir=d:\\PROJECTS\\bomba-esp\\build" + ], + "idf.port": "/dev/ttyACM0", + "idf.flashType": "UART" +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..852e818 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,7 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +idf_build_set_property(MINIMAL_BUILD ON) +project(bomba-esp) diff --git a/README.md b/README.md new file mode 100644 index 0000000..81c693f --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +| Supported Targets | ESP32-C5 | ESP32-C6 | ESP32-H2 | +| ----------------- | -------- | -------- | -------- | + +# Light Switch Example + +This test code shows how to configure Zigbee Coordinator and use it as an HA on/off_switch. + +The ESP Zigbee SDK provides more examples and tools for productization: +* [ESP Zigbee SDK Docs](https://docs.espressif.com/projects/esp-zigbee-sdk) +* [ESP Zigbee SDK Repo](https://github.com/espressif/esp-zigbee-sdk) + +## Hardware Required + +* One development board with ESP32-H2 SoC acting as Zigbee Coordinator (loaded with HA_on_off_switch) +* A USB cable for power supply and programming +* Choose another ESP32-H2 as Zigbee end-device (see [HA_on_off_light](../HA_on_off_light/)) + +## Configure the project + +Before project configuration and build, make sure to set the correct chip target using `idf.py --preview set-target TARGET` command. + +## Erase the NVRAM + +Before flash it to the board, it is recommended to erase NVRAM if user doesn't want to keep the previous examples or other projects stored info using `idf.py -p PORT erase-flash` + +## Build and Flash + +Build the project, flash it to the board, and start the monitor tool to view the serial output by running `idf.py -p PORT flash monitor`. + +(To exit the serial monitor, type ``Ctrl-]``.) + +## Example Output + +As you run the example, you will see the following log: + +``` +I (441) main_task: Started on CPU0 +I (441) main_task: Calling app_main() +I (461) phy: phy_version: 230,2, 9aae6ea, Jan 15 2024, 11:17:12 +I (461) phy: libbtbb version: 944f18e, Jan 15 2024, 11:17:25 +I (471) main_task: Returned from app_main() +I (601) ESP_ZB_ON_OFF_SWITCH: ZDO signal: ZDO Config Ready (0x17), status: ESP_FAIL +I (601) ESP_ZB_ON_OFF_SWITCH: Initialize Zigbee stack +I (611) gpio: GPIO[9]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:2 +I (611) ESP_ZB_ON_OFF_SWITCH: Deferred driver initialization successful +I (621) ESP_ZB_ON_OFF_SWITCH: Device started up in factory-reset mode +I (621) ESP_ZB_ON_OFF_SWITCH: Start network formation +W (781) ESP_ZB_ON_OFF_SWITCH: Network(0x13af) closed, devices joining not allowed. +I (781) ESP_ZB_ON_OFF_SWITCH: Formed network successfully (Extended PAN ID: 74:4d:bd:ff:fe:63:f7:30, PAN ID: 0x13af, Channel:13, Short Address: 0x0000) +I (1391) ESP_ZB_ON_OFF_SWITCH: Network(0x13af) is open for 180 seconds +I (1391) ESP_ZB_ON_OFF_SWITCH: Network steering started +I (9561) ESP_ZB_ON_OFF_SWITCH: ZDO signal: NWK Device Associated (0x12), status: ESP_OK +I (9561) ESP_ZB_ON_OFF_SWITCH: ZDO signal: ZDO Device Update (0x30), status: ESP_OK +I (9601) ESP_ZB_ON_OFF_SWITCH: New device commissioned or rejoined (short: 0x7c16) +I (9671) ESP_ZB_ON_OFF_SWITCH: Found light +I (9671) ESP_ZB_ON_OFF_SWITCH: Try to bind On/Off +I (9681) ESP_ZB_ON_OFF_SWITCH: Bound successfully! +I (9681) ESP_ZB_ON_OFF_SWITCH: The light originating from address(0x7c16) on endpoint(10) +I (9751) ESP_ZB_ON_OFF_SWITCH: ZDO signal: ZDO Device Authorized (0x2f), status: ESP_OK +I (9781) ESP_ZB_ON_OFF_SWITCH: Network(0x13af) is open for 180 seconds +I (16451) ESP_ZB_ON_OFF_SWITCH: Send 'on_off toggle' command +I (17011) ESP_ZB_ON_OFF_SWITCH: Send 'on_off toggle' command +I (17441) ESP_ZB_ON_OFF_SWITCH: Send 'on_off toggle' command +I (17831) ESP_ZB_ON_OFF_SWITCH: Send 'on_off toggle' command +``` + +## Light Control Functions + + * By toggling the switch button (BOOT) on this board, the LED on the board loaded with the `HA_on_off_light` example will turn on and off. + + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/dependencies.lock b/dependencies.lock new file mode 100644 index 0000000..4b069d4 --- /dev/null +++ b/dependencies.lock @@ -0,0 +1,39 @@ +dependencies: + espressif/esp-zboss-lib: + component_hash: 321883d142421f65009972408287441794250057668a11abbdfd8bec77c3309a + dependencies: + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.6.4 + espressif/esp-zigbee-lib: + component_hash: fa0812e6743e2a7d999af9f44ccdcea17ccb1e80f98d6b2956f44687503a88fd + dependencies: + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.6.8 + examples_utils: + dependencies: [] + source: + path: /opt/esp/idf/examples/zigbee/zb_common_components/examples_utils + type: local + version: '*' + idf: + source: + type: idf + version: 6.1.0 +direct_dependencies: +- espressif/esp-zboss-lib +- espressif/esp-zigbee-lib +- examples_utils +- idf +manifest_hash: 38eb32e2be3bf4f2f2c09e19faf9839c08bb3d3d2a1a18b57f8157ba44d91370 +target: esp32c6 +version: 2.0.0 diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 0000000..7742b98 --- /dev/null +++ b/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRC_DIRS "." + INCLUDE_DIRS "." + PRIV_REQUIRES nvs_flash esp_driver_gpio esp_driver_uart ieee802154 +) diff --git a/main/esp_zb_switch.c b/main/esp_zb_switch.c new file mode 100644 index 0000000..87544ef --- /dev/null +++ b/main/esp_zb_switch.c @@ -0,0 +1,285 @@ +/* + * 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" + +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 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..."); + esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING); + } else { + ESP_LOGI(TAG, "Device rebooted, rejoining network"); + 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]); + } 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)"); + 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 (channel 20)", (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); +} diff --git a/main/esp_zb_switch.h b/main/esp_zb_switch.h new file mode 100644 index 0000000..4e71179 --- /dev/null +++ b/main/esp_zb_switch.h @@ -0,0 +1,44 @@ +/* + * 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 "esp_zigbee_core.h" +#include "switch_driver.h" + +/* Zigbee configuration */ +#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 */ + +/* Basic manufacturer information */ +#define ESP_MANUFACTURER_NAME "\x09""ESPRESSIF" /* Customized manufacturer name */ +#define ESP_MODEL_IDENTIFIER "\x07"CONFIG_IDF_TARGET /* Customized model identifier */ + +#define ESP_ZB_ZR_CONFIG() \ + { \ + .esp_zb_role = ESP_ZB_DEVICE_TYPE_ROUTER, \ + .install_code_policy = INSTALLCODE_POLICY_ENABLE, \ + .nwk_cfg.zczr_cfg = { \ + .max_children = 0, \ + }, \ + } + +#define ESP_ZB_DEFAULT_RADIO_CONFIG() \ + { \ + .radio_mode = ZB_RADIO_MODE_NATIVE, \ + } + +#define ESP_ZB_DEFAULT_HOST_CONFIG() \ + { \ + .host_connection_mode = ZB_HOST_CONNECTION_MODE_NONE, \ + } diff --git a/main/idf_component.yml b/main/idf_component.yml new file mode 100644 index 0000000..5b7e1d9 --- /dev/null +++ b/main/idf_component.yml @@ -0,0 +1,9 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/esp-zboss-lib: "~1.6.0" + espressif/esp-zigbee-lib: "~1.6.0" + ## Required IDF version + idf: + version: ">=5.0.0" + examples_utils: + path: ${IDF_PATH}/examples/zigbee/zb_common_components/examples_utils diff --git a/main/switch_driver.c b/main/switch_driver.c new file mode 100644 index 0000000..ea71256 --- /dev/null +++ b/main/switch_driver.c @@ -0,0 +1,243 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: LicenseRef-Included + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Espressif Systems + * integrated circuit in a product or a software update for such product, + * must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * 4. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "esp_zigbee_core.h" +#include "switch_driver.h" + +/* Estado atual do relay */ +static bool relay_state = false; + +/** + * @brief: + * This example code shows how to configure light switch with attribute as well as button switch handler. + * + * @note: + Currently only support toggle switch functionality available + * + * @note: + * For other possible switch functions (on/off,level up/down,step up/down). User need to implement and create them by themselves + */ + +static QueueHandle_t gpio_evt_queue = NULL; +/* button function pair, should be defined in switch example source file */ +static switch_func_pair_t *switch_func_pair; +/* call back function pointer */ +static esp_switch_callback_t func_ptr; +/* which button is pressed */ +static uint8_t switch_num; +static const char *TAG = "ESP_ZB_SWITCH"; + +static void IRAM_ATTR gpio_isr_handler(void *arg) +{ + xQueueSendFromISR(gpio_evt_queue, (switch_func_pair_t *)arg, NULL); +} + +/** + * @brief Enable GPIO (switches refer to) isr + * + * @param enabled enable isr if true. + */ +static void switch_driver_gpios_intr_enabled(bool enabled) +{ + for (int i = 0; i < switch_num; ++i) { + if (enabled) { + gpio_intr_enable((switch_func_pair + i)->pin); + } else { + gpio_intr_disable((switch_func_pair + i)->pin); + } + } +} + +/** + * @brief Tasks for checking the button event and debounce the switch state + * + * @param arg Unused value. + */ +static void switch_driver_button_detected(void *arg) +{ + gpio_num_t io_num = GPIO_NUM_NC; + switch_func_pair_t button_func_pair; + static switch_state_t switch_state = SWITCH_IDLE; + bool evt_flag = false; + uint32_t press_start_time = 0; + const uint32_t LONG_PRESS_DURATION_MS = 5000; /* 5 segundos para factory reset */ + + for (;;) { + /* check if there is any queue received, if yes read out the button_func_pair */ + if (xQueueReceive(gpio_evt_queue, &button_func_pair, portMAX_DELAY)) { + io_num = button_func_pair.pin; + switch_driver_gpios_intr_enabled(false); + evt_flag = true; + } + while (evt_flag) { + bool value = gpio_get_level(io_num); + switch (switch_state) { + case SWITCH_IDLE: + if (value == GPIO_INPUT_LEVEL_ON) { + switch_state = SWITCH_PRESS_DETECTED; + press_start_time = xTaskGetTickCount() * portTICK_PERIOD_MS; + } + break; + case SWITCH_PRESS_DETECTED: + if (value == GPIO_INPUT_LEVEL_ON) { + /* Verifica se botão está pressionado há mais de 5 segundos */ + uint32_t current_time = xTaskGetTickCount() * portTICK_PERIOD_MS; + 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)); + } + /* Factory reset - limpa NVS e reinicia */ + esp_zb_factory_reset(); + switch_state = SWITCH_IDLE; + } + } else { + switch_state = SWITCH_RELEASE_DETECTED; + } + break; + case SWITCH_RELEASE_DETECTED: + switch_state = SWITCH_IDLE; + /* Verifica se foi pressionamento curto (toggle normal) */ + uint32_t press_duration = (xTaskGetTickCount() * portTICK_PERIOD_MS) - press_start_time; + if (press_duration < LONG_PRESS_DURATION_MS) { + /* callback to button_handler para toggle normal */ + (*func_ptr)(&button_func_pair); + } + break; + default: + break; + } + if (switch_state == SWITCH_IDLE) { + switch_driver_gpios_intr_enabled(true); + evt_flag = false; + break; + } + vTaskDelay(10 / portTICK_PERIOD_MS); + } + } +} + +/** + * @brief init GPIO configuration as well as isr + * + * @param button_func_pair pointer of the button pair. + * @param button_num number of button pair. + */ +static bool switch_driver_gpio_init(switch_func_pair_t *button_func_pair, uint8_t button_num) +{ + gpio_config_t io_conf = {}; + switch_func_pair = button_func_pair; + switch_num = button_num; + uint64_t pin_bit_mask = 0; + + /* set up button func pair pin mask */ + for (int i = 0; i < button_num; ++i) { + pin_bit_mask |= (1ULL << (button_func_pair + i)->pin); + } + /* interrupt of falling edge */ + io_conf.intr_type = GPIO_INTR_NEGEDGE; + io_conf.pin_bit_mask = pin_bit_mask; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = 1; + /* configure GPIO with the given settings */ + gpio_config(&io_conf); + /* create a queue to handle gpio event from isr */ + gpio_evt_queue = xQueueCreate(10, sizeof(switch_func_pair_t)); + if ( gpio_evt_queue == 0) { + ESP_LOGE(TAG, "Queue was not created and must not be used"); + return false; + } + /* start gpio task */ + xTaskCreate(switch_driver_button_detected, "button_detected", 4096, NULL, 10, NULL); + /* install gpio isr service */ + gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT); + for (int i = 0; i < button_num; ++i) { + gpio_isr_handler_add((button_func_pair + i)->pin, gpio_isr_handler, (void *) (button_func_pair + i)); + } + return true; +} + +bool switch_driver_init(switch_func_pair_t *button_func_pair, uint8_t button_num, esp_switch_callback_t cb) +{ + if (!switch_driver_gpio_init(button_func_pair, button_num)) { + return false; + } + func_ptr = cb; + return true; +} + +void relay_driver_init(void) +{ + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = (1ULL << GPIO_OUTPUT_RELAY); + io_conf.pull_down_en = 0; + io_conf.pull_up_en = 0; + gpio_config(&io_conf); + + /* Inicializa relay desligado */ + gpio_set_level(GPIO_OUTPUT_RELAY, 0); + relay_state = false; + ESP_LOGI(TAG, "Relay inicializado no GPIO %d", GPIO_OUTPUT_RELAY); +} + +void relay_set_state(bool state) +{ + relay_state = state; + gpio_set_level(GPIO_OUTPUT_RELAY, state ? 1 : 0); + ESP_LOGI(TAG, "Relay %s", state ? "LIGADO" : "DESLIGADO"); +} + +void relay_toggle(void) +{ + relay_set_state(!relay_state); +} + +bool relay_get_state(void) +{ + return relay_state; +} diff --git a/main/switch_driver.h b/main/switch_driver.h new file mode 100644 index 0000000..328a1f1 --- /dev/null +++ b/main/switch_driver.h @@ -0,0 +1,121 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: LicenseRef-Included + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Espressif Systems + * integrated circuit in a product or a software update for such product, + * must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * 4. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#pragma once + +#include "driver/gpio.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* user should configure which I/O port as toggle switch input, default is GPIO9 */ +#define GPIO_INPUT_IO_TOGGLE_SWITCH GPIO_NUM_9 + +/* GPIO para controlar o relay */ +#define GPIO_OUTPUT_RELAY GPIO_NUM_8 + +/* config button level depends on the pull up/down setting + push button level is on level = 1 when pull-down enable + push button level is on level = 0 when pull-up enable +*/ +#define GPIO_INPUT_LEVEL_ON 0 + +#define ESP_INTR_FLAG_DEFAULT 0 + +#define PAIR_SIZE(TYPE_STR_PAIR) (sizeof(TYPE_STR_PAIR) / sizeof(TYPE_STR_PAIR[0])) + +typedef enum { + SWITCH_IDLE, + SWITCH_PRESS_ARMED, + SWITCH_PRESS_DETECTED, + SWITCH_PRESSED, + SWITCH_RELEASE_DETECTED, +} switch_state_t; + +typedef enum { + SWITCH_ON_CONTROL, + SWITCH_OFF_CONTROL, + SWITCH_ONOFF_TOGGLE_CONTROL, + SWITCH_LEVEL_UP_CONTROL, + SWITCH_LEVEL_DOWN_CONTROL, + SWITCH_LEVEL_CYCLE_CONTROL, + SWITCH_COLOR_CONTROL, +} switch_func_t; + +typedef struct { + uint32_t pin; + switch_func_t func; +} switch_func_pair_t; + +typedef void (*esp_switch_callback_t)(switch_func_pair_t *param); + +/** + * @brief init function for switch and callback setup + * + * @param button_func_pair pointer of the button pair. + * @param button_num number of button pair. + * @param cb callback pointer. + */ +bool switch_driver_init(switch_func_pair_t *button_func_pair, uint8_t button_num, esp_switch_callback_t cb); + +/** + * @brief Inicializa o GPIO do relay + */ +void relay_driver_init(void); + +/** + * @brief Define o estado do relay + * + * @param state true para ligar, false para desligar + */ +void relay_set_state(bool state); + +/** + * @brief Inverte o estado atual do relay + */ +void relay_toggle(void); + +/** + * @brief Obtém o estado atual do relay + * + * @return true se ligado, false se desligado + */ +bool relay_get_state(void); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/partitions.csv b/partitions.csv new file mode 100644 index 0000000..03d66af --- /dev/null +++ b/partitions.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 900K, +zb_storage, data, fat, 0xf1000, 16K, +zb_fct, data, fat, 0xf5000, 1K, diff --git a/sdkconfig.defaults b/sdkconfig.defaults new file mode 100644 index 0000000..a29c790 --- /dev/null +++ b/sdkconfig.defaults @@ -0,0 +1,31 @@ +# +# Partition Table +# +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_OFFSET=0x8000 +CONFIG_PARTITION_TABLE_MD5=y +# end of Partition Table + +# +# mbedTLS +# +CONFIG_MBEDTLS_HARDWARE_AES=n +CONFIG_MBEDTLS_HARDWARE_MPI=n +CONFIG_MBEDTLS_HARDWARE_SHA=n +CONFIG_MBEDTLS_CMAC_C=y +CONFIG_MBEDTLS_SSL_PROTO_DTLS=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECJPAKE=y +# end of TLS Key Exchange Methods + +CONFIG_MBEDTLS_ECJPAKE_C=y +# end of mbedTLS + +# +# Zboss +# +CONFIG_ZB_ENABLED=y +CONFIG_ZB_ZCZR=y +# end of Zboss +# end of Component config