Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Need help in programming Zigbee in ESP32C6

@ADEngineer

New Member
I want to use Zigbee protocol for developing a mesh network. I am able to create a single coordinator to end device link but I don't know how to send a single character byte to the end device and vice versa. I created custom cluster but the application goes into infinite loop rebooting constantly.

Need help with esp32c6 in esp-idf.
 
maybe this help

Developing a Zigbee Mesh Network with ESP32-C6 and ESP-IDF​

The ESP32-C6 is a powerful and efficient SoC that integrates both Wi-Fi 6 and an IEEE 802.15.4 radio, making it a perfect candidate for creating Zigbee devices. This guide provides a comprehensive overview of how to get started with Zigbee mesh networking using the ESP-IDF.

1. Understanding Zigbee and Device Roles​

Zigbee is a wireless communication protocol designed for low-power, low-data-rate applications. It excels at creating robust, self-healing mesh networks where devices can relay data for each other, extending the network's range and reliability.

In any Zigbee network, devices operate in one of three roles:

  • Coordinator (ZC): The "brain" of the network. It's responsible for forming the network, managing security keys, and choosing the network channel. Every Zigbee network must have exactly one Coordinator.
  • Router (ZR): A full-function device that can join an existing network, send and receive its own data, and also act as an intermediary by routing data for other devices. Routers are mains-powered and are crucial for building a large, stable mesh.
  • End Device (ZED): A specialized, low-power device. It can be battery-operated and spends most of its time in a sleep state to conserve energy. It connects to a parent Router or Coordinator to send and receive data but does not route traffic for other nodes. Think of sensors, switches, and simple actuators.

2. The ESP-IDF Zigbee Stack​

Espressif provides a dedicated and certified Zigbee stack within the ESP-IDF.

Key Features:

  • Zigbee 3.0 Compliant: Ensures interoperability with a wide range of third-party Zigbee devices.
  • Pre-configured Examples: ESP-IDF includes ready-to-use examples for all standard device types, significantly speeding up development.
  • Integrated Configuration: All Zigbee settings, such as device role and security parameters, are configured through the standard idf.py menuconfig tool.
  • HA (Home Automation) Profile: Provides standard device definitions like lights, switches, and sensors that are common in smart home applications.

3. Development Workflow: Step-by-Step​

Here is the typical workflow for creating a Zigbee application.

Prerequisites:

  1. Hardware: At least two ESP32-C6 development boards (e.g., ESP32-C6-DevKitC-1). Three is ideal to test a Coordinator, Router, and End Device.
  2. Software: A working installation of ESP-IDF (version 5.1 or later is recommended).

Step 1: Start with an Example Project

The best way to start is by using one of the official examples. The light_sample is a classic choice, as it demonstrates how a light_switch can control a light_bulb.

Navigate to an example directory in your terminal. For instance, to set up the light bulb:

cd $IDF_PATH/examples/zigbee/light_sample/light_bulb<br><br>

Step 2: Configure the Device Role

This is the most critical step. You will configure one board as the Coordinator and the others as Routers or End Devices.

  1. Run the configuration tool:

    idf.py menuconfig<br><br>
  2. Navigate to Component config ---> ESP Zigbee.
  3. Under Zigbee Device Type, select the desired role:
    • For your first board, choose ESP_ZB_DEVICE_TYPE_COORDINATOR.
    • For your other boards, choose ESP_ZB_DEVICE_TYPE_ROUTER (for the light bulb) or ESP_ZB_DEVICE_TYPE_END_DEVICE (for the light switch).

Step 3: Build, Flash, and Monitor

After saving your configuration, use the following command to compile the project, flash it to your ESP32-C6, and view the output:

idf.py build flash monitor<br><br>
Repeat this process for each of your boards, ensuring you configure the correct role for each one before flashing.

4. Forming Your First Mesh Network​

  1. Power on the Coordinator: The first device you should power on is the one flashed as the Coordinator. It will establish the network. The serial monitor will show logs indicating that the network has been formed successfully.
  2. Power on the Router(s) and End Device(s): Once the Coordinator's network is up, power on your other devices. They will automatically scan for and join the network. The logs will show their status as they join.
  3. Test the Network: If you used the light_sample example, you can now use the buttons on the light_switch device to turn the LED on and off on the light_bulb device. This communication happens over the Zigbee mesh network you just created!

5. Further Steps and Resources​

  • Explore Other Examples: The zigbee directory in the ESP-IDF examples contains more advanced use cases, including a Zigbee gateway that bridges to Wi-Fi.
  • Custom Applications: Once you are comfortable with the examples, you can start modifying them to create your own custom Zigbee device with unique sensors and actuators.
  • Official Documentation: For in-depth API references and guides, always refer to the official Espressif Zigbee documentation.
 
Here some code sample
Code:
/*
 * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: CC0-1.0
 */

#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "ha/esp_zigbee_ha_standard.h"
#include "esp_light_driver.h"

// A descriptive tag for logging messages from this file
static const char *TAG = "ESP_ZB_LIGHT_BULB";

/********************* Define functions **************************/

/**
 * @brief This function is the main handler for Zigbee stack events.
 *
 * It gets called whenever a Zigbee event occurs (e.g., device starts, joins a network, receives a command).
 * The logic inside this function determines how the light bulb reacts to these events.
 *
 * @param bufid   The buffer ID of the Zigbee message.
 * @param message The Zigbee message containing event details.
 */
static void esp_zb_task(void *pvParameters)
{
    // Wait for the Zigbee stack to be up and running
    esp_zb_app_signal_await();

    // Main loop to process Zigbee events
    while (true) {
        // Get the next Zigbee event from the queue
        esp_zb_app_signal_message_t *message = esp_zb_app_signal_get();
        esp_zb_zcl_command_t *zcl_command = (esp_zb_zcl_command_t *)message->data;

        // Check if the signal is a ZCL command (e.g., "turn on", "set level")
        if (message->signal == ESP_ZB_APP_SIGNAL_ZCL_ACTION) {
            ESP_LOGI(TAG, "Received ZCL command.");

            // Check if the command is for the "On/Off" cluster
            if (zcl_command->cluster_id == ESP_ZB_ZCL_CLUSTER_ID_ON_OFF) {
                ESP_LOGI(TAG, "Received an On/Off command.");
                // Get the specific command ID (On, Off, or Toggle)
                uint8_t command_id = zcl_command->command_id;

                if (command_id == ESP_ZB_ZCL_CMD_ON_OFF_ON_ID) {
                    ESP_LOGI(TAG, "Command: ON");
                    light_driver_set_power(true); // Turn the physical light ON
                } else if (command_id == ESP_ZB_ZCL_CMD_ON_OFF_OFF_ID) {
                    ESP_LOGI(TAG, "Command: OFF");
                    light_driver_set_power(false); // Turn the physical light OFF
                } else if (command_id == ESP_ZB_ZCL_CMD_ON_OFF_TOGGLE_ID) {
                    ESP_LOGI(TAG, "Command: TOGGLE");
                    light_driver_toggle_power(); // Toggle the physical light
                }
            }
            // You could add more `else if` blocks here to handle other clusters,
            // like Level Control (for dimming) or Color Control.
        }
        // Free the memory used by the message
        esp_zb_app_signal_message_release(message);
    }
}

/**
 * @brief The main entry point for the Zigbee application.
 *
 * This function handles the initialization of the Zigbee stack, defines the device's
 * characteristics (its endpoints and clusters), and starts the Zigbee network role
 * (Coordinator, Router, or End Device).
 *
 * @param arg Unused argument.
 */
static void esp_zb_app_signal_handler(void *arg)
{
    // Get the Zigbee stack policy (rules for joining/leaving networks)
    esp_zb_zdo_signal_mac_state_ind_t *mac_state = (esp_zb_zdo_signal_mac_state_ind_t *)arg;
    if (mac_state->state == ESP_ZB_ZDO_SIGNAL_DEVICE_STARTED) {
        ESP_LOGI(TAG, "Device started, waiting for network steering.");
        // If the device starts up, it will automatically try to join a network.
        // This is known as "network steering".
        esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING);
    } else {
        ESP_LOGW(TAG, "Unhandled ZDO signal: %d", mac_state->state);
    }
}

/**
 * @brief Main application entry point for FreeRTOS.
 */
void app_main(void)
{
    // Initialize the Zigbee stack with a default configuration
    esp_zb_cfg_t zb_cfg = ESP_ZB_DEFAULT_CONFIG();
    esp_zb_init(&zb_cfg);

    // *** Define the Light Bulb Device ***
    // A Zigbee device is defined by its "endpoints" and "clusters".
    // An endpoint is like a virtual device (a light bulb can have one endpoint).
    // A cluster defines a specific functionality (e.g., On/Off, Level Control).

    // 1. Create an endpoint for the light bulb
    esp_zb_ep_list_t *ep_list = esp_zb_ep_list_create();

    // 2. Define the clusters for this endpoint (what the light can do)
    // We will add the standard "On/Off" and "Level Control" clusters.
    esp_zb_cluster_list_t *cluster_list = esp_zb_cluster_list_create();

    // Create and add the "On/Off" server cluster
    esp_zb_attribute_list_t *on_off_cluster = esp_zb_on_off_cluster_create(NULL);
    esp_zb_cluster_list_add_cluster(cluster_list, on_off_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);

    // Create and add the "Level Control" server cluster
    esp_zb_attribute_list_t *level_control_cluster = esp_zb_level_control_cluster_create(NULL);
    esp_zb_cluster_list_add_cluster(cluster_list, level_control_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);

    // 3. Create the endpoint description with the Home Automation (HA) profile
    esp_zb_endpoint_config_t endpoint_config = {
        .endpoint = 1, // Endpoint ID
        .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID,
        .app_device_id = ESP_ZB_HA_ON_OFF_LIGHT_DEVICE_ID, // Standard device ID for a simple light
        .app_device_version = 0
    };

    // 4. Add the endpoint to the device's endpoint list
    esp_zb_ep_list_add_ep(ep_list, cluster_list, endpoint_config);

    // 5. Register the endpoint list with the Zigbee stack
    esp_zb_device_register(ep_list);

    // Set the initial Zigbee configuration from menuconfig (Coordinator, Router, etc.)
    esp_zb_set_primary_network_channel_set(ESP_ZB_PRIMARY_CHANNEL_MASK);
    ESP_ERROR_CHECK(esp_zb_start(true));

    // Set the Zigbee signal handler to process stack events
    esp_zb_main_loop_iteration_req();
    esp_zb_set_signal_handler(esp_zb_app_signal_handler);

    // Create the FreeRTOS task that will handle application-level logic (like turning the light on/off)
    xTaskCreate(esp_zb_task, "esp_zb_task", 4096, NULL, 5, NULL);

    // Initialize the physical LED driver
    light_driver_init(LIGHT_DEFAULT_OFF);
}
 
Thank you for the help but my requirement is to create a custom cluster that can send and receive unsigned 8 bit data from end device to coordinator. Also for my application, I don't require to bind switch to light since all I have to do is broadcast switch endpoint data to all the light endpoints via routers/ coordinators.

Whenever I try to create the custom cluster, the MCU crashes and autoboots.
 
I want to use Zigbee protocol for developing a mesh network. I am able to create a single coordinator to end device link but I don't know how to send a single character byte to the end device and vice versa. I created custom cluster but the application goes into infinite loop rebooting constantly.

Need help with esp32c6 in esp-idf.
did you edit this where's your code
 
try this out
Code:
/*****************************************************************************************
 * * --- IMPORTANT ---
 * * This file contains the code for BOTH the Coordinator and the End Device.
 * You will need to use #ifdef directives or create two separate projects.
 * In menuconfig, define either `ZIGBEE_COORDINATOR` or `ZIGBEE_END_DEVICE`.
 * *****************************************************************************************/

#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "ha/esp_zigbee_ha_standard.h"

// A descriptive tag for logging
#if defined(ZIGBEE_COORDINATOR)
static const char *TAG = "ESP_ZB_COORDINATOR";
#else
static const char *TAG = "ESP_ZB_END_DEVICE";
#endif

// --- Custom Cluster Definitions ---
// Use a manufacturer-specific cluster ID (in the range 0xFC00 - 0xFFFF)
#define ESP_ZB_CUSTOM_CLUSTER_ID        0xFC00
// Define a custom attribute ID within our custom cluster
#define ESP_ZB_CUSTOM_ATTR_ID           0x0001

/*****************************************************************************************
 * COORDINATOR SPECIFIC CODE
 *****************************************************************************************/
#if defined(ZIGBEE_COORDINATOR)

static void esp_zb_task(void *pvParameters) {
    esp_zb_app_signal_await();

    while (true) {
        esp_zb_app_signal_message_t *message = esp_zb_app_signal_get();
        esp_zb_zcl_command_t *zcl_command = (esp_zb_zcl_command_t *)message->data;

        if (message->signal == ESP_ZB_APP_SIGNAL_ZCL_ACTION) {
            // Check if the command is for our custom cluster
            if (zcl_command->cluster_id == ESP_ZB_CUSTOM_CLUSTER_ID) {
                ESP_LOGI(TAG, "Received command from custom cluster");
                // Check if it's a "Report Attributes" command
                if (zcl_command->command_id == ESP_ZB_ZCL_CMD_REPORT_ATTRIB_ID) {
                    esp_zb_zcl_report_attr_cmd_t *report_cmd = (esp_zb_zcl_report_attr_cmd_t *)zcl_command->data;
                    // Check if the attribute ID matches our custom attribute
                    if (report_cmd->attr_id == ESP_ZB_CUSTOM_ATTR_ID) {
                        uint8_t received_data = *(uint8_t *)report_cmd->data_value;
                        ESP_LOGI(TAG, "Received custom data: %d", received_data);
                        // --- You can now act on this received_data value ---
                    }
                }
            }
        }
        esp_zb_app_signal_message_release(message);
    }
}

void app_main(void) {
    esp_zb_cfg_t zb_cfg = ESP_ZB_DEFAULT_CONFIG();
    esp_zb_init(&zb_cfg);

    // 1. Create an endpoint list
    esp_zb_ep_list_t *ep_list = esp_zb_ep_list_create();

    // 2. Create a cluster list for the endpoint
    esp_zb_cluster_list_t *cluster_list = esp_zb_cluster_list_create();

    // 3. Define and create the custom cluster
    esp_zb_cluster_config_t custom_cluster_cfg = {
        .cluster_id = ESP_ZB_CUSTOM_CLUSTER_ID,
        .role = ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, // Coordinator acts as a server
    };
    esp_zb_cluster_t *custom_cluster = esp_zb_cluster_create(&custom_cluster_cfg);

    // 4. Define and create the custom attribute
    uint8_t initial_value = 0;
    esp_zb_attribute_config_t custom_attr_cfg = {
        .attr_id = ESP_ZB_CUSTOM_ATTR_ID,
        .attr_type = ESP_ZB_ZCL_ATTR_TYPE_U8,
        .attr_access = ESP_ZB_ZCL_ATTR_ACCESS_READ_ONLY, // Coordinator only reads
        .data_p = &initial_value,
    };
    esp_zb_attribute_create(custom_cluster, &custom_attr_cfg);

    // 5. Add the custom cluster to the cluster list
    esp_zb_cluster_list_add_cluster(cluster_list, custom_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);

    // 6. Create endpoint configuration
    esp_zb_endpoint_config_t endpoint_config = {
        .endpoint = 1,
        .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID,
        .app_device_id = ESP_ZB_HA_SIMPLE_DEVICE_ID, // A generic device type
        .app_device_version = 0,
    };

    // 7. Add the endpoint with its clusters
    esp_zb_ep_list_add_ep(ep_list, cluster_list, endpoint_config);
    esp_zb_device_register(ep_list);

    // Set role to Coordinator
    esp_zb_set_primary_network_channel_set(ESP_ZB_PRIMARY_CHANNEL_MASK);
    ESP_ERROR_CHECK(esp_zb_start(true));
    esp_zb_main_loop_iteration_req();

    xTaskCreate(esp_zb_task, "esp_zb_task", 4096, NULL, 5, NULL);
}

#endif // ZIGBEE_COORDINATOR

/*****************************************************************************************
 * END DEVICE SPECIFIC CODE
 *****************************************************************************************/
#if defined(ZIGBEE_END_DEVICE)

// Function to send the custom data. You would call this on a button press or sensor reading.
void send_custom_data_to_coordinator(uint8_t data_to_send) {
    esp_zb_zcl_status_t state = esp_zb_zcl_set_attribute_val(
        1,                          // Endpoint ID where the cluster is registered
        ESP_ZB_CUSTOM_CLUSTER_ID,   // Our custom cluster ID
        ESP_ZB_ZCL_CLUSTER_CLIENT_ROLE, // The End Device is the client
        ESP_ZB_CUSTOM_ATTR_ID,      // Our custom attribute ID
        &data_to_send,              // Pointer to the data
        false                       // This is not part of a transaction
    );

    if (state == ESP_ZB_ZCL_STATUS_SUCCESS) {
        ESP_LOGI(TAG, "Successfully set attribute value: %d. Reporting will be triggered.", data_to_send);
    } else {
        ESP_LOGE(TAG, "Failed to set attribute value, error: 0x%x", state);
    }
}

// This task simulates a sensor sending data every 10 seconds
static void sensor_simulation_task(void *pvParameters) {
    uint8_t sensor_value = 0;
    while (true) {
        send_custom_data_to_coordinator(sensor_value++);
        vTaskDelay(10000 / portTICK_PERIOD_MS); // Wait for 10 seconds
    }
}

static void bdb_start_top_level_commissioning_cb(uint8_t status) {
    ESP_ERROR_CHECK(status);
    if (status == ESP_ZB_BDB_SIGNAL_DEVICE_FIRST_START) {
        ESP_LOGI(TAG, "Device is on the network, starting sensor simulation task.");
        xTaskCreate(sensor_simulation_task, "sensor_sim_task", 4096, NULL, 5, NULL);
    }
}

void app_main(void) {
    esp_zb_cfg_t zb_cfg = ESP_ZB_DEFAULT_CONFIG();
    esp_zb_init(&zb_cfg);

    // 1. Create an endpoint list
    esp_zb_ep_list_t *ep_list = esp_zb_ep_list_create();

    // 2. Create a cluster list for the endpoint
    esp_zb_cluster_list_t *cluster_list = esp_zb_cluster_list_create();
    
    // 3. Define and create the custom cluster
    esp_zb_cluster_config_t custom_cluster_cfg = {
        .cluster_id = ESP_ZB_CUSTOM_CLUSTER_ID,
        .role = ESP_ZB_ZCL_CLUSTER_CLIENT_ROLE, // End Device acts as a client
    };
    esp_zb_cluster_t *custom_cluster = esp_zb_cluster_create(&custom_cluster_cfg);

    // 4. Define and create the custom attribute
    uint8_t initial_value = 0;
    esp_zb_attribute_config_t custom_attr_cfg = {
        .attr_id = ESP_ZB_CUSTOM_ATTR_ID,
        .attr_type = ESP_ZB_ZCL_ATTR_TYPE_U8,
        .attr_access = ESP_ZB_ZCL_ATTR_ACCESS_READ_WRITE, // Client can read/write
        .data_p = &initial_value,
    };
    esp_zb_attribute_create(custom_cluster, &custom_attr_cfg);

    // 5. Add the custom cluster to the cluster list
    esp_zb_cluster_list_add_cluster(cluster_list, custom_cluster, ESP_ZB_ZCL_CLUSTER_CLIENT_ROLE);

    // 6. Create endpoint configuration
    esp_zb_endpoint_config_t endpoint_config = {
        .endpoint = 1,
        .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID,
        .app_device_id = ESP_ZB_HA_SIMPLE_DEVICE_ID,
        .app_device_version = 0,
    };

    // 7. Add the endpoint with its clusters
    esp_zb_ep_list_add_ep(ep_list, cluster_list, endpoint_config);
    esp_zb_device_register(ep_list);

    // Set role to End Device and start commissioning
    esp_zb_set_primary_network_channel_set(ESP_ZB_PRIMARY_CHANNEL_MASK);
    ESP_ERROR_CHECK(esp_zb_start(false));
    esp_zb_main_loop_iteration_req();
    esp_zb_bdb_start_top_level_commissioning(bdb_start_top_level_commissioning_cb);
}

#endif // ZIGBEE_END_DEVICE
 

How to Use This Code​



  1. Set up Your Project: Create a new ESP-IDF project or copy an existing Zigbee example.
  2. Integrate the Code: Replace the content of your main C file with the code above.
  3. Configure the Build: The easiest way to switch between the Coordinator and End Device is to use a build-time definition.
    • Open menuconfig: idf.py menuconfig
    • Go to Component config -> ESP Project IDF Settings.
    • In the Add-on options section, add ZIGBEE_COORDINATOR to the Compiler options.
    • When you want to build for the end device, change it to ZIGBEE_END_DEVICE.
  4. Flash the Devices:
    • Build and flash the project with ZIGBEE_COORDINATOR defined onto one ESP32-C6.
    • Re-configure, build, and flash the project with ZIGBEE_END_DEVICE defined onto a second ESP32-C6.
  5. Run the Network:
    • Power on the Coordinator first to establish the network.
    • Power on the End Device. It will join the network.
    • Once joined, the End Device will automatically start the sensor_simulation_task and begin sending a new number every 10 seconds.
    • Watch the serial monitor for the Coordinator. You will see the "Received custom data: X" log messages appear.
 
Back
Top