espressif / esp-zigbee-sdk

Espressif Zigbee SDK
Apache License 2.0
171 stars 30 forks source link

ESP32-C6 Zigbee windows covering example (TZ-878) #345

Closed Lorem96 closed 4 months ago

Lorem96 commented 6 months ago

Question

Hi, I’m trying to make my smart blinds. I use zigbee2mqtt with a SONOFF ZBDongle and an ESP32-C6. Can you provide an example of code that will register in the ZigBee network as a ‘window covering’ device and react to OPEN, CLOSE, and STOP commands?

Additional context.

No response

ninoweg commented 5 months ago

I am also struggling to setup a window covering device. I use an ESP32-H2. With the custom_switch as a template I was able to add a window-covering cluster to it by adding

esp_zb_attribute_list_t *esp_zb_window_covering_cluster = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING);
esp_zb_cluster_list_add_window_covering_cluster(esp_zb_cluster_list, esp_zb_window_covering_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);

I am able to join my zigbee network over ZHA but I'm not able to add attributes. I tried to add an attribute by adding the following code

esp_zb_window_covering_cluster_add_attr(esp_zb_window_covering_cluster, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_PHYSICAL_CLOSED_LIMIT_LIFT_ID, &test_attr3);
esp_zb_window_covering_cluster_add_attr(esp_zb_window_covering_cluster, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_PHY_CLOSED_LIMIT_TILT_ID, &test_attr3);
...

and also by setting up the cluster the following way:

/* window-covering cluster create with standard cluster config*/
esp_zb_window_covering_cluster_cfg_t window_covering_cfg;
esp_zb_attribute_list_t *esp_zb_window_covering_cluster = esp_zb_window_covering_cluster_create(&window_covering_cfg);
esp_zb_window_covering_cluster_add_attr(esp_zb_window_covering_cluster, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_WINDOW_COVERING_TYPE_ID, &test_attr3);

neither of which created the cluster with the expected attributes or in some cases even led to the device not being able to connect to the network. I would appreciate some guidance on this or a working example.

xieqinan commented 5 months ago

@Lorem96 @ninoweg ,

The esp-zigbee-sdk does not provide a window covering example temporarily, but it is not difficult for users to implement the window covering data model themselves. You can refer to the code below for it.

    esp_zb_ep_list_t *ep_list = esp_zb_ep_list_create();
    esp_zb_endpoint_config_t endpoint_config = {
        .endpoint = endpoint_id,
        .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID,
        .app_device_id = ESP_ZB_HA_WINDOW_COVERING_DEVICE_ID,
        .app_device_version = 0,
    };
    esp_zb_cluster_list_t *cluster_list = esp_zb_zcl_cluster_list_create();
    /* Set the NULL to esp_zb_window_covering_cluster_create() means that using the default attribute value for window covering cluster */
    esp_zb_cluster_list_add_basic_cluster(cluster_list, esp_zb_basic_cluster_create(NULL), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
    esp_zb_cluster_list_add_window_covering_cluster(cluster_list, esp_zb_window_covering_cluster_create(NULL), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
    esp_zb_ep_list_add_ep(ep_list, cluster_list, endpoint_config);
    esp_zb_device_register(ep_list);
ninoweg commented 5 months ago

@xieqinan thanks for the response!

There seems to be an issues with the esp_zb_window_covering_cluster_create function call as the device runs into errors as soon as I add it to the code. My esp_zb_task function looks something like this:

static void esp_zb_task(void *pvParameters)
{
    /* initialize Zigbee stack */
    esp_zb_cfg_t zb_nwk_cfg = ESP_ZB_ZED_CONFIG();
    esp_zb_init(&zb_nwk_cfg);  

    esp_zb_ep_list_t *ep_list = esp_zb_ep_list_create();
    esp_zb_endpoint_config_t endpoint_config = {
        .endpoint = HA_WINDOW_COVERING_ENDPOINT,
        .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID,
        .app_device_id = ESP_ZB_HA_WINDOW_COVERING_DEVICE_ID,
        .app_device_version = 0,
    };
    esp_zb_cluster_list_t *cluster_list = esp_zb_zcl_cluster_list_create();
    /* Set the NULL to esp_zb_window_covering_cluster_create() means that using the default attribute value for window covering cluster */
    esp_zb_cluster_list_add_basic_cluster(cluster_list, esp_zb_basic_cluster_create(NULL), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
    esp_zb_cluster_list_add_window_covering_cluster(cluster_list, esp_zb_window_covering_cluster_create(NULL), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
    esp_zb_ep_list_add_ep(ep_list, cluster_list, endpoint_config);
    esp_zb_device_register(ep_list);

    esp_zb_core_action_handler_register(zb_action_handler);
    esp_zb_set_primary_network_channel_set(ESP_ZB_PRIMARY_CHANNEL_MASK);
    ESP_ERROR_CHECK(esp_zb_start(true));
    esp_zb_main_loop_iteration();
}

If I remove the window covering cluster and just add other clusters everything is working as expected and I can add my device to my zigbee network. As soon as I add the esp_zb_window_covering_cluster_create the monitor shows a bunch of errors. I attached the log: eso32h2_zigbee_log.txt

My idf_component.yml is

## IDF Component Manager Manifest File
dependencies:
  espressif/esp-zboss-lib: "~1.3.0"
  espressif/esp-zigbee-lib: "~1.3.0"
  ## Required IDF version
  idf:
    version: ">=5.0.0"`

I installed everything as described here: https://docs.espressif.com/projects/esp-zigbee-sdk/en/latest/esp32h2/developing.html

xieqinan commented 5 months ago

@ninoweg

Sorry for the inconvenience. You can use the code below to replace it and test again.

static void esp_zb_task(void *pvParameters)
{
    /* initialize Zigbee stack */
    esp_zb_cfg_t zb_nwk_cfg = ESP_ZB_ZED_CONFIG();
    esp_zb_init(&zb_nwk_cfg);

    esp_zb_ep_list_t *ep_list = esp_zb_ep_list_create();
    esp_zb_endpoint_config_t endpoint_config = {
        .endpoint = HA_WINDOW_COVERING_ENDPOINT,
        .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID,
        .app_device_id = ESP_ZB_HA_WINDOW_COVERING_DEVICE_ID,
        .app_device_version = 0,
    };
    esp_zb_cluster_list_t *cluster_list = esp_zb_zcl_cluster_list_create();
    /* Set the NULL to esp_zb_window_covering_cluster_create() means that using the default attribute value for window covering cluster */
    esp_zb_cluster_list_add_basic_cluster(cluster_list, esp_zb_basic_cluster_create(NULL), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
    esp_zb_attribute_list_t *window_attr_list = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING);
    esp_zb_window_covering_cluster_cfg_t config = {
        .covering_type = ESP_ZB_ZCL_WINDOW_COVERING_WINDOW_COVERING_TYPE_DEFAULT_VALUE,
        .covering_status = ESP_ZB_ZCL_WINDOW_COVERING_CONFIG_STATUS_DEFAULT_VALUE,
        .covering_mode = ESP_ZB_ZCL_WINDOW_COVERING_MODE_DEFAULT_VALUE,
    };
    esp_zb_cluster_add_attr(window_attr_list, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_WINDOW_COVERING_TYPE_ID,
                            ESP_ZB_ZCL_ATTR_TYPE_8BIT_ENUM, ESP_ZB_ZCL_ATTR_ACCESS_READ_ONLY, &config.covering_type);
    esp_zb_cluster_add_attr(window_attr_list, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CONFIG_STATUS_ID,
                            ESP_ZB_ZCL_ATTR_TYPE_8BITMAP, ESP_ZB_ZCL_ATTR_ACCESS_READ_ONLY, &config.covering_status);
    esp_zb_cluster_add_attr(window_attr_list, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_MODE_ID,
                            ESP_ZB_ZCL_ATTR_TYPE_8BITMAP, ESP_ZB_ZCL_ATTR_ACCESS_READ_WRITE, &config.covering_mode);
    esp_zb_cluster_list_add_window_covering_cluster(cluster_list, window_attr_list, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
    esp_zb_ep_list_add_ep(ep_list, cluster_list, endpoint_config);
    esp_zb_device_register(ep_list);

    esp_zb_core_action_handler_register(zb_action_handler);
    esp_zb_set_primary_network_channel_set(ESP_ZB_PRIMARY_CHANNEL_MASK);
    ESP_ERROR_CHECK(esp_zb_start(false));
    esp_zb_main_loop_iteration();
}
ninoweg commented 5 months ago

@xieqinan thank you!

I managed to pair the device to my network. I only had to change ESP_ERROR_CHECK(esp_zb_start(false)); to ESP_ERROR_CHECK(esp_zb_start(true)); to get it working.

I'm now running into another issue when trying to send commands to device but I'm not sure whether this is an esp-zigbee or rather an ZHA issue. I can set/get attributes of the window covering endpoint but as soon as I send a command (open/stop/close cover) I'm getting the following error message: Failed to call service cover/open_cover. Failed to open cover: <Status.HARDWARE_FAILURE: 192>.

As it suggest an hardware failure I'm wondering if I'm still having issues with the window covering cluster (no issues ). When using one of the other examples the device works as expected. I'm not sure how to debug this issue and would appreciate some help. Here's the code I'm using:

/*
 * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: CC0-1.0
 *
 * Zigbee customized client 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_HA_customized_switch.h"
#include "esp_check.h"
#include "esp_err.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "string.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "zcl/esp_zigbee_zcl_common.h"

#if !defined ZB_ED_ROLE
#error Define ZB_ED_ROLE in idf.py menuconfig to compile light switch (End Device) source code.
#endif

static const char *TAG = "ESP_HA_WINDOW_COVERING";
static const char *TAG_MOTOR = "MOTOR";

char modelid[] = {24, 'E', 'S', 'P', '3', '2', '-', 'H', '2', '-', 'W', 'i', 'n', 'd', 'o', 'w', '-', 'C', 'o', 'v', 'e', 'r', 'i', 'n', 'g'};
char manufname[] = {9, 'E', 's', 'p', 'r', 'e', 's', 's', 'i', 'f'};

static switch_func_pair_t button_func_pair[] = {
    {GPIO_INPUT_IO_TOGGLE_SWITCH, SWITCH_ONOFF_TOGGLE_CONTROL}
};

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;

typedef struct zdo_info_ctx_s {
    uint8_t endpoint;
    uint16_t short_addr;
} zdo_info_user_ctx_t;

/* remote device struct for recording and managing node info */
light_bulb_device_params_t on_off_light;

static void zb_buttons_handler(switch_func_pair_t *button_func_pair)
{
    switch (button_func_pair->func) {
    case SWITCH_ONOFF_TOGGLE_CONTROL: {
        /* send on-off toggle command to remote device */
        esp_zb_zcl_on_off_cmd_t cmd_req;
        cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = on_off_light.short_addr;
        cmd_req.zcl_basic_cmd.dst_endpoint = on_off_light.endpoint;
        cmd_req.zcl_basic_cmd.src_endpoint = HA_WINDOW_COVERING_ENDPOINT;
        cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_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 to address(0x%x) endpoint(%d)", on_off_light.short_addr, on_off_light.endpoint);
    } break;
    default:
        break;
    }
}

static esp_err_t deferred_driver_init(void)
{
    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, "Bind response from address(0x%x), endpoint(%d) with status(%d)", ((zdo_info_user_ctx_t *)user_ctx)->short_addr,
                 ((zdo_info_user_ctx_t *)user_ctx)->endpoint, zdo_status);
        /* configure report attribute command */
        esp_zb_zcl_config_report_cmd_t report_cmd;
        bool report_change = 0;
        report_cmd.zcl_basic_cmd.dst_addr_u.addr_short = on_off_light.short_addr;
        report_cmd.zcl_basic_cmd.dst_endpoint = on_off_light.endpoint;
        report_cmd.zcl_basic_cmd.src_endpoint = HA_WINDOW_COVERING_ENDPOINT;
        report_cmd.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT;
        report_cmd.clusterID = ESP_ZB_ZCL_CLUSTER_ID_ON_OFF;

        esp_zb_zcl_config_report_record_t records[] = {
            {ESP_ZB_ZCL_CMD_DIRECTION_TO_SRV, ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID, ESP_ZB_ZCL_ATTR_TYPE_BOOL, 0, 30, &report_change}};
        report_cmd.record_number = sizeof(records) / sizeof(esp_zb_zcl_config_report_record_t);
        report_cmd.record_field = records;

        esp_zb_zcl_config_report_cmd_req(&report_cmd);
    }
}

static void ieee_cb(esp_zb_zdp_status_t zdo_status, esp_zb_ieee_addr_t ieee_addr, void *user_ctx)
{
    if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) {
        memcpy(&(on_off_light.ieee_addr), ieee_addr, sizeof(esp_zb_ieee_addr_t));
        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]);
        /* bind the on-off light to on-off switch */
        esp_zb_zdo_bind_req_param_t bind_req;
        memcpy(&(bind_req.src_address), on_off_light.ieee_addr, sizeof(esp_zb_ieee_addr_t));
        bind_req.src_endp = on_off_light.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;
        esp_zb_get_long_address(bind_req.dst_address_u.addr_long);
        bind_req.dst_endp = HA_WINDOW_COVERING_ENDPOINT;
        bind_req.req_dst_addr = on_off_light.short_addr;
        static zdo_info_user_ctx_t test_info_ctx;
        test_info_ctx.endpoint = HA_WINDOW_COVERING_ENDPOINT;
        test_info_ctx.short_addr = on_off_light.short_addr;
        esp_zb_zdo_device_bind_req(&bind_req, bind_cb, (void *) & (test_info_ctx));
    }
}

static void ep_cb(esp_zb_zdp_status_t zdo_status, uint8_t ep_count, uint8_t *ep_id_list, void *user_ctx)
{
    if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) {
        ESP_LOGI(TAG, "Active endpoint response: status(%d) and endpoint count(%d)", zdo_status, ep_count);
        for (int i = 0; i < ep_count; i++) {
            ESP_LOGI(TAG, "Endpoint ID List: %d", ep_id_list[i]);
        }
    }
}

static void simple_desc_cb(esp_zb_zdp_status_t zdo_status, esp_zb_af_simple_desc_1_1_t *simple_desc, void *user_ctx)
{
    if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) {
        ESP_LOGI(TAG, "Simple desc response: status(%d), device_id(%d), app_version(%d), profile_id(0x%x), endpoint_ID(%d)", zdo_status,
                 simple_desc->app_device_id, simple_desc->app_device_version, simple_desc->app_profile_id, simple_desc->endpoint);

        for (int i = 0; i < (simple_desc->app_input_cluster_count + simple_desc->app_output_cluster_count); i++) {
            ESP_LOGI(TAG, "Cluster ID list: 0x%x", *(simple_desc->app_cluster_list + i));
        }
    }
}

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, "Match desc response: status(%d), address(0x%x), endpoint(%d)", zdo_status, addr, endpoint);
        /* save into remote device record structure for future use */
        on_off_light.endpoint = endpoint;
        on_off_light.short_addr = addr;
        /* find the active endpoint */
        esp_zb_zdo_active_ep_req_param_t active_ep_req;
        active_ep_req.addr_of_interest = on_off_light.short_addr;
        esp_zb_zdo_active_ep_req(&active_ep_req, ep_cb, NULL);
        /* get the node simple descriptor */
        esp_zb_zdo_simple_desc_req_param_t simple_desc_req;
        simple_desc_req.addr_of_interest = addr;
        simple_desc_req.endpoint = endpoint;
        esp_zb_zdo_simple_desc_req(&simple_desc_req, simple_desc_cb, NULL);
        /* get the light ieee address */
        esp_zb_zdo_ieee_addr_req_param_t ieee_req;
        ieee_req.addr_of_interest = on_off_light.short_addr;
        ieee_req.dst_nwk_addr = on_off_light.short_addr;
        ieee_req.request_type = 0;
        ieee_req.start_index = 0;
        esp_zb_zdo_ieee_addr_req(&ieee_req, ieee_cb, NULL);
        esp_zb_zcl_read_attr_cmd_t read_req;
        uint16_t attributes[] = {ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID};
        read_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT;
        read_req.attr_number = sizeof(attributes) / sizeof(uint16_t);
        read_req.attr_field = attributes;
        read_req.clusterID = ESP_ZB_ZCL_CLUSTER_ID_ON_OFF;
        read_req.zcl_basic_cmd.dst_endpoint = on_off_light.endpoint;
        read_req.zcl_basic_cmd.src_endpoint = HA_WINDOW_COVERING_ENDPOINT;
        read_req.zcl_basic_cmd.dst_addr_u.addr_short = on_off_light.short_addr;
        esp_zb_zcl_read_attr_cmd_req(&read_req);
    }
}

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_leave_params_t *leave_params = NULL;
    switch (sig_type) {
    case ESP_ZB_BDB_SIGNAL_DEVICE_FIRST_START:
    case ESP_ZB_BDB_SIGNAL_DEVICE_REBOOT:
    case ESP_ZB_BDB_SIGNAL_STEERING:
        if (err_status != ESP_OK) {
            ESP_LOGW(TAG, "Stack %s failure with %s status, steering",esp_zb_zdo_signal_to_string(sig_type), esp_err_to_name(err_status));
            esp_zb_scheduler_alarm((esp_zb_callback_t)bdb_start_top_level_commissioning_cb, ESP_ZB_BDB_MODE_NETWORK_STEERING, 1000);
        } else {
            /* device auto start successfully and on a formed network */
            ESP_LOGI(TAG, "Deferred driver initialization %s", deferred_driver_init() ? "failed" : "successful");
            esp_zb_ieee_addr_t extended_pan_id;
            esp_zb_get_extended_pan_id(extended_pan_id);
            ESP_LOGI(TAG, "Joined network successfully (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());
            esp_zb_zdo_match_desc_req_param_t  find_req;
            find_req.addr_of_interest = 0x0000;
            find_req.dst_nwk_addr = 0x0000;
            /* find the match on-off light device */
            esp_zb_zdo_find_on_off_light(&find_req, user_find_cb, NULL);
        }
        break;
    case ESP_ZB_ZDO_SIGNAL_LEAVE:
        leave_params = (esp_zb_zdo_signal_leave_params_t *)esp_zb_app_signal_get_params(p_sg_p);
        if (leave_params->leave_type == ESP_ZB_NWK_LEAVE_TYPE_RESET) {
            ESP_LOGI(TAG, "Reset device");
        }
        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 esp_err_t zb_attribute_reporting_handler(const esp_zb_zcl_report_attr_message_t *message)
{
    ESP_RETURN_ON_FALSE(message, ESP_FAIL, TAG, "Empty message");
    ESP_RETURN_ON_FALSE(message->status == ESP_ZB_ZCL_STATUS_SUCCESS, ESP_ERR_INVALID_ARG, TAG, "Received message: error status(%d)",
                        message->status);
    ESP_LOGI(TAG, "Received report from address(0x%x) src endpoint(%d) to dst endpoint(%d) cluster(0x%x)", message->src_address.u.short_addr,
             message->src_endpoint, message->dst_endpoint, message->cluster);
    ESP_LOGI(TAG, "Received report information: attribute(0x%x), type(0x%x), value(%d)\n", message->attribute.id, message->attribute.data.type,
             message->attribute.data.value ? *(uint8_t *)message->attribute.data.value : 0);
    return ESP_OK;
}

static esp_err_t zb_read_attr_resp_handler(const esp_zb_zcl_cmd_read_attr_resp_message_t *message)
{
    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_zb_zcl_read_attr_resp_variable_t *variable = message->variables;
    while (variable) {
        ESP_LOGI(TAG, "Read attribute response: status(%d), cluster(0x%x), attribute(0x%x), type(0x%x), value(%d)", variable->status,
                 message->info.cluster, variable->attribute.id, variable->attribute.data.type,
                 variable->attribute.data.value ? *(uint8_t *)variable->attribute.data.value : 0);
        variable = variable->next;
    }

    return ESP_OK;
}

static esp_err_t zb_configure_report_resp_handler(const esp_zb_zcl_cmd_config_report_resp_message_t *message)
{
    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_zb_zcl_config_report_resp_variable_t *variable = message->variables;
    while (variable) {
        ESP_LOGI(TAG, "Configure report response: status(%d), cluster(0x%x), attribute(0x%x)", message->info.status, message->info.cluster,
                 variable->attribute_id);
        variable = variable->next;
    }

    return ESP_OK;
}

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_REPORT_ATTR_CB_ID:
        ret = zb_attribute_reporting_handler((esp_zb_zcl_report_attr_message_t *)message);
        break;
    case ESP_ZB_CORE_CMD_READ_ATTR_RESP_CB_ID:
        ret = zb_read_attr_resp_handler((esp_zb_zcl_cmd_read_attr_resp_message_t *)message);
        break;
    case ESP_ZB_CORE_CMD_REPORT_CONFIG_RESP_CB_ID:
        ret = zb_configure_report_resp_handler((esp_zb_zcl_cmd_config_report_resp_message_t *)message);
        break;
    default:
        ESP_LOGW(TAG, "Receive Zigbee action(0x%x) callback", callback_id);
        break;
    }
    return ret;
}

static void esp_zb_task(void *pvParameters)
{
    /* initialize Zigbee stack */
    esp_zb_cfg_t zb_nwk_cfg = ESP_ZB_ZED_CONFIG();
    esp_zb_init(&zb_nwk_cfg);

    uint8_t test_attr, test_attr2;

    test_attr = 0;
    test_attr2 = 4;

    esp_zb_ep_list_t *ep_list = esp_zb_ep_list_create();
    esp_zb_endpoint_config_t endpoint_config = {
        .endpoint = HA_WINDOW_COVERING_ENDPOINT,
        .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID,
        .app_device_id = ESP_ZB_HA_WINDOW_COVERING_DEVICE_ID,
        .app_device_version = 0,
    };
    esp_zb_cluster_list_t *cluster_list = esp_zb_zcl_cluster_list_create();
    /* basic cluster create with fully customized */
    esp_zb_attribute_list_t *esp_zb_basic_cluster = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_BASIC);
    esp_zb_basic_cluster_add_attr(esp_zb_basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_ZCL_VERSION_ID, &test_attr);
    esp_zb_basic_cluster_add_attr(esp_zb_basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_POWER_SOURCE_ID, &test_attr2);
    esp_zb_cluster_update_attr(esp_zb_basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_ZCL_VERSION_ID, &test_attr2);
    esp_zb_basic_cluster_add_attr(esp_zb_basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID, &modelid[0]);
    esp_zb_basic_cluster_add_attr(esp_zb_basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID, &manufname[0]);
    esp_zb_cluster_list_add_basic_cluster(cluster_list, esp_zb_basic_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);

    esp_zb_attribute_list_t *window_attr_list = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING);
    esp_zb_window_covering_cluster_cfg_t config = {
        .covering_type = ESP_ZB_ZCL_WINDOW_COVERING_WINDOW_COVERING_TYPE_DEFAULT_VALUE,
        .covering_status = ESP_ZB_ZCL_WINDOW_COVERING_CONFIG_STATUS_DEFAULT_VALUE,
        .covering_mode = ESP_ZB_ZCL_WINDOW_COVERING_MODE_DEFAULT_VALUE,
    };
    esp_zb_cluster_add_attr(window_attr_list, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_WINDOW_COVERING_TYPE_ID, ESP_ZB_ZCL_ATTR_TYPE_8BIT_ENUM, ESP_ZB_ZCL_ATTR_ACCESS_READ_ONLY, &config.covering_type);
    esp_zb_cluster_add_attr(window_attr_list, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CONFIG_STATUS_ID, ESP_ZB_ZCL_ATTR_TYPE_8BITMAP, ESP_ZB_ZCL_ATTR_ACCESS_READ_ONLY, &config.covering_status);
    esp_zb_cluster_add_attr(window_attr_list, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_MODE_ID, ESP_ZB_ZCL_ATTR_TYPE_8BITMAP, ESP_ZB_ZCL_ATTR_ACCESS_READ_WRITE, &config.covering_mode);

    esp_zb_cluster_list_add_window_covering_cluster(cluster_list, window_attr_list, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
    esp_zb_ep_list_add_ep(ep_list, cluster_list, endpoint_config);
    esp_zb_device_register(ep_list);

    esp_zb_core_action_handler_register(zb_action_handler);
    esp_zb_set_primary_network_channel_set(ESP_ZB_PRIMARY_CHANNEL_MASK);
    ESP_ERROR_CHECK(esp_zb_start(true));
    esp_zb_main_loop_iteration();
}

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", 16384, NULL, 5, NULL);
}
Lorem96 commented 5 months ago

@ninoweg I have implemented tilt percentage functionality with

esp_zb_device_cb_id_handler_register

and OPEN CLOSE functionality with

esp_zb_raw_command_handler_register

It's not the best implementation but it works with zigbee2mqtt.

Here is the full code:

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_check.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "ha/esp_zigbee_ha_standard.h"
#include "esp_zigbee_core.h"
#include "light_driver.h"
#include "stepper_motor_driver.h"
#include "switch_driver.h"
#include "zboss_api.h"
#include "esp_zigbee_cluster.h"
#include "zb_types.h"

// Zigbee configuration
#define INSTALLCODE_POLICY_ENABLE false
#define ED_AGING_TIMEOUT ESP_ZB_ED_AGING_TIMEOUT_64MIN
#define ED_KEEP_ALIVE 3000
#define HA_ESP_BLINDS_ENDPOINT 1
#define ESP_ZB_PRIMARY_CHANNEL_MASK ESP_ZB_TRANSCEIVER_ALL_CHANNELS_MASK

#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, \
    }

typedef struct
{
    int steps;
    int direction;
} MotorParams;

char modelid[] = {19, 'E', 'S', 'P', '3', '2', 'C', '6', '.', 'S', 'm', 'a', 'r', 't', 'B', 'l', 'i', 'n', 'd', 's'};
char manufname[] = {9, 'E', 's', 'p', 'r', 'e', 's', 's', 'i', 'f'};

static MotorParams paramsCalibrationOpen = {999, 1};
static MotorParams paramsCalibrationClose = {999, -1};

static int stepsCount = 250;
static int closedStep = 0;
static int openStep = 250;
static int currentStep = 0;

static const char *TAG = "ESP_ZB_SMART_BLINDS";

esp_zb_cfg_t zb_nwk_cfg = {
    .esp_zb_role = ESP_ZB_DEVICE_TYPE_ED,
    .install_code_policy = INSTALLCODE_POLICY_ENABLE,
    .nwk_cfg.zed_cfg = {
        .ed_timeout = ED_AGING_TIMEOUT,
        .keep_alive = ED_KEEP_ALIVE,
    }};

static switch_func_pair_t button_func_pair[] = {{GPIO_INPUT_IO_TOGGLE_SWITCH, SWITCH_ONOFF_TOGGLE_CONTROL}};
TaskHandle_t motorControlTaskRef = NULL;

#define STORAGE_NAMESPACE "storage"
#define BLIND_STATE_KEY "blind_state"

void save_blind_state(void)
{
    nvs_handle_t my_handle;
    esp_err_t err;

    err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "Error (%s) opening NVS handle!", esp_err_to_name(err));
        return;
    }

    err = nvs_set_i32(my_handle, BLIND_STATE_KEY, currentStep);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to write to NVS (%s)!", esp_err_to_name(err));
    }

    err = nvs_commit(my_handle);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to commit to NVS (%s)!", esp_err_to_name(err));
    }

    nvs_close(my_handle);
}

void load_blind_step(void)
{
    nvs_handle_t my_handle;
    esp_err_t err;
    int blind_step_local = 0;

    err = nvs_open(STORAGE_NAMESPACE, NVS_READONLY, &my_handle);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "Error (%s) opening NVS handle!", esp_err_to_name(err));
    }

    err = nvs_get_i32(my_handle, BLIND_STATE_KEY, &blind_step_local);
    if (err == ESP_OK)
    {
        currentStep = blind_step_local;
    }
    else if (err != ESP_ERR_NVS_NOT_FOUND)
    {
        ESP_LOGE(TAG, "Error (%s) reading!", esp_err_to_name(err));
    }

    nvs_close(my_handle);
}

void report_blind_state()
{
    uint8_t tilt_percentage = 100 - ((currentStep * 100) / stepsCount);

    esp_zb_zcl_status_t state = esp_zb_zcl_set_attribute_val(
        HA_ESP_BLINDS_ENDPOINT,
        ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING,
        ESP_ZB_ZCL_CLUSTER_SERVER_ROLE,
        ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CURRENT_POSITION_TILT_PERCENTAGE_ID,
        &tilt_percentage,
        ZB_FALSE);

    if (state != ESP_ZB_ZCL_STATUS_SUCCESS)
    {
        ESP_LOGE(TAG, "Setting tilt percentage attribute failed!");
        return;
    }

    esp_zb_zcl_report_attr_cmd_t report_cmd = {
        .zcl_basic_cmd = {
            .dst_addr_u.addr_short = 0x0000,
            .dst_endpoint = HA_ESP_BLINDS_ENDPOINT,
            .src_endpoint = HA_ESP_BLINDS_ENDPOINT,
        },
        .address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT,
        .clusterID = ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING,
        .attributeID = ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CURRENT_POSITION_TILT_PERCENTAGE_ID,
        .cluster_role = ESP_ZB_ZCL_CLUSTER_SERVER_ROLE,
    };

    state = esp_zb_zcl_report_attr_cmd_req(&report_cmd);
    if (state != ESP_ZB_ZCL_STATUS_SUCCESS)
    {
        ESP_LOGE(TAG, "Reporting tilt percentage attribute failed!");
    }
}

static void start_top_level_commissioning(uint8_t mode_mask)
{
    ESP_ERROR_CHECK(esp_zb_bdb_start_top_level_commissioning(mode_mask));
}

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;

    light_driver_set_color(WHITE);

    switch (sig_type)
    {
    case ESP_ZB_ZDO_SIGNAL_SKIP_STARTUP:
        ESP_LOGI(TAG, "Zigbee stack initialized");
        esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_INITIALIZATION);
        light_driver_set_color(GREEN);
        break;
    case ESP_ZB_BDB_SIGNAL_DEVICE_FIRST_START:
    case ESP_ZB_BDB_SIGNAL_DEVICE_REBOOT:
        if (err_status == ESP_OK)
        {
            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");
                esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING);
                light_driver_set_color(BLUE);
            }
            else
            {
                ESP_LOGI(TAG, "Device rebooted");
                light_driver_set_color(BLACK);
            }
        }
        else
        {
            ESP_LOGW(TAG, "Failed to initialize Zigbee stack (status: %s)", esp_err_to_name(err_status));
            light_driver_set_color(RED);
        }
        break;
    case ESP_ZB_BDB_SIGNAL_STEERING:
        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, "Joined network successfully (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());

            report_blind_state();

            light_driver_set_color(BLACK);
        }
        else
        {
            ESP_LOGI(TAG, "Network steering was not successful (status: %s)", esp_err_to_name(err_status));
            esp_zb_scheduler_alarm((esp_zb_callback_t)start_top_level_commissioning, ESP_ZB_BDB_MODE_NETWORK_STEERING, 1000);
            light_driver_set_color(BLUE);
        }
        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;
    }
}

void motor_control_task(void *pvParameters)
{
    // Initialize the motor with specific GPIO pins and a delay
    StepperMotor_Init(GPIO_NUM_21, GPIO_NUM_20, GPIO_NUM_19, GPIO_NUM_18, 10);

    MotorParams *params = (MotorParams *)pvParameters;
    StepperMotor_FullStep(params->steps, params->direction);
    StepperMotor_Cleanup();
    motorControlTaskRef = NULL;
    vTaskDelete(NULL);
}

bool device_callback(uint8_t bufid)
{
    if (ZB_ZCL_DEVICE_CMD_PARAM_CB_ID(bufid) == 82)
    {
        zb_zcl_go_to_tilt_percentage_req_t *payload =
            ZB_ZCL_DEVICE_CMD_PARAM_IN_GET(bufid, zb_zcl_go_to_tilt_percentage_req_t);

        zb_uint8_t tilt_percentage = 100 - payload->percentage_tilt_value;

        int target_position = (stepsCount * tilt_percentage) / 100;
        int steps_needed = abs(target_position - currentStep);
        int direction = (target_position > currentStep) ? 1 : -1;

        MotorParams params = {steps_needed, direction};
        xTaskCreate(motor_control_task, "MotorControlTask", 2048, &params, 5, &motorControlTaskRef);

        currentStep = target_position;
        report_blind_state();
        save_blind_state();

        ZB_ZCL_DEVICE_CMD_PARAM_STATUS(bufid) = RET_OK;
    }
    return true;
}

static void clear_motor_task(void)
{
    if (motorControlTaskRef != NULL)
    {
        vTaskDelete(motorControlTaskRef);
        motorControlTaskRef = NULL;
        StepperMotor_Cleanup();
    }
}

bool raw_command_handler(zb_uint8_t bufid)
{
    zb_zcl_parsed_hdr_t *cmd_info = ZB_BUF_GET_PARAM(bufid, zb_zcl_parsed_hdr_t);

    if (cmd_info->disable_default_response)
    {
        return false;
    }

    if (cmd_info->cluster_id == ZB_ZCL_CLUSTER_ID_WINDOW_COVERING)
    {
        zb_uint8_t command_id = cmd_info->cmd_id;
        switch (command_id)
        {
        case ESP_ZB_ZCL_CMD_WINDOW_COVERING_UP_OPEN:
            if (cmd_info->cmd_direction == ZB_ZCL_FRAME_DIRECTION_TO_SRV && currentStep != openStep)
            {

                clear_motor_task();
                int target_position = stepsCount;
                int steps_needed = abs(target_position - currentStep);
                int direction = (target_position > currentStep) ? 1 : -1;
                MotorParams params = {steps_needed, direction};
                xTaskCreate(motor_control_task, "MotorControlTask", 2048, &params, 5, &motorControlTaskRef);
                zb_zcl_send_default_handler(bufid, cmd_info, ZB_ZCL_STATUS_SUCCESS);
                currentStep = openStep;
                save_blind_state();
                report_blind_state();
                return true;
            }
            break;
        case ESP_ZB_ZCL_CMD_WINDOW_COVERING_DOWN_CLOSE:
            if (cmd_info->cmd_direction == ZB_ZCL_FRAME_DIRECTION_TO_SRV && currentStep != closedStep)
            {
                clear_motor_task();
                int target_position = closedStep;
                int steps_needed = abs(target_position - currentStep);
                int direction = (target_position > currentStep) ? 1 : -1;
                MotorParams params = {steps_needed, direction};
                xTaskCreate(motor_control_task, "MotorControlTask", 2048, &params, 5, &motorControlTaskRef);
                zb_zcl_send_default_handler(bufid, cmd_info, ZB_ZCL_STATUS_SUCCESS);
                currentStep = closedStep;
                save_blind_state();
                report_blind_state();
                return true;
            }
            break;
        default:
            break;
        }
    }
    return false;
}
static void zigbee_task(void *pvParameters)
{
    esp_zb_init(&zb_nwk_cfg);
    esp_zb_set_primary_network_channel_set(ESP_ZB_PRIMARY_CHANNEL_MASK);

    esp_zb_ep_list_t *ep_list = esp_zb_ep_list_create();
    esp_zb_endpoint_config_t endpoint_config = {
        .endpoint = HA_ESP_BLINDS_ENDPOINT,
        .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID,
        .app_device_id = ESP_ZB_HA_WINDOW_COVERING_DEVICE_ID,
        .app_device_version = 0,
    };

    esp_zb_cluster_list_t *cluster_list = esp_zb_zcl_cluster_list_create();
    esp_zb_attribute_list_t *basic_attr_list = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_BASIC);
    esp_zb_basic_cluster_add_attr(basic_attr_list, ESP_ZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID, modelid);
    esp_zb_basic_cluster_add_attr(basic_attr_list, ESP_ZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID, manufname);
    esp_zb_cluster_list_add_basic_cluster(cluster_list, basic_attr_list, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);

    esp_zb_attribute_list_t *window_attr_list = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING);
    esp_zb_window_covering_cluster_cfg_t config = {
        .covering_type = ESP_ZB_ZCL_ATTR_WINDOW_COVERING_TYPE_TILT_BLIND_TILT_ONLY,
        .covering_status = ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CONFIG_TILT_CONTROL_IS_CLOSED_LOOP,
        .covering_mode = ESP_ZB_ZCL_WINDOW_COVERING_MODE_DEFAULT_VALUE,
    };

    esp_zb_cluster_add_attr(window_attr_list, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_WINDOW_COVERING_TYPE_ID,
                            ESP_ZB_ZCL_ATTR_TYPE_8BIT_ENUM, ESP_ZB_ZCL_ATTR_ACCESS_READ_ONLY, &config.covering_type);
    esp_zb_cluster_add_attr(window_attr_list, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CONFIG_STATUS_ID,
                            ESP_ZB_ZCL_ATTR_TYPE_8BITMAP, ESP_ZB_ZCL_ATTR_ACCESS_READ_ONLY, &config.covering_status);
    esp_zb_cluster_add_attr(window_attr_list, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_MODE_ID,
                            ESP_ZB_ZCL_ATTR_TYPE_8BITMAP, ESP_ZB_ZCL_ATTR_ACCESS_READ_WRITE, &config.covering_mode);

    uint8_t current_tilt_percentage = 0;
    uint16_t installed_open_limit_tilt = 0x0000;
    uint16_t installed_closed_limit_tilt = 0xffff;
    uint16_t current_position_tilt = ESP_ZB_ZCL_WINDOW_COVERING_CURRENT_POSITION_TILT_DEFAULT_VALUE;

    esp_zb_cluster_add_attr(window_attr_list, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CURRENT_POSITION_TILT_PERCENTAGE_ID,
                            ESP_ZB_ZCL_ATTR_TYPE_8BIT, ESP_ZB_ZCL_ATTR_ACCESS_READ_ONLY | ESP_ZB_ZCL_ATTR_ACCESS_REPORTING, &current_tilt_percentage);
    esp_zb_cluster_add_attr(window_attr_list, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_INSTALLED_OPEN_LIMIT_TILT_ID,
                            ESP_ZB_ZCL_ATTR_TYPE_16BIT, ESP_ZB_ZCL_ATTR_ACCESS_READ_ONLY, &installed_open_limit_tilt);
    esp_zb_cluster_add_attr(window_attr_list, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_INSTALLED_CLOSED_LIMIT_TILT_ID,
                            ESP_ZB_ZCL_ATTR_TYPE_16BIT, ESP_ZB_ZCL_ATTR_ACCESS_READ_ONLY, &installed_closed_limit_tilt);
    esp_zb_cluster_add_attr(window_attr_list, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CURRENT_POSITION_TILT_ID,
                            ESP_ZB_ZCL_ATTR_TYPE_16BIT, ESP_ZB_ZCL_ATTR_ACCESS_READ_ONLY, &current_position_tilt);
    esp_zb_cluster_list_add_window_covering_cluster(cluster_list, window_attr_list, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);

    esp_zb_ep_list_add_ep(ep_list, cluster_list, endpoint_config);
    esp_zb_device_register(ep_list);
    esp_zb_raw_command_handler_register(raw_command_handler);
    esp_zb_device_cb_id_handler_register(device_callback);
    esp_zb_set_primary_network_channel_set(ESP_ZB_PRIMARY_CHANNEL_MASK);
    ESP_ERROR_CHECK(esp_zb_start(false));
    esp_zb_main_loop_iteration();
}

static void button_click_handler(uint8_t click_count, bool long_press)
{
    if (long_press)
    {
        clear_motor_task();
        esp_zb_factory_reset();
    }

    if (click_count == 1)
    {
        clear_motor_task();
        currentStep = closedStep;
        save_blind_state();
        report_blind_state();
    }

    if (click_count == 2)
    {
        clear_motor_task();
        xTaskCreate(motor_control_task, "CalibrationMotorMove", 4096, &paramsCalibrationOpen, 5, &motorControlTaskRef);
    }

    if (click_count == 3)
    {
        clear_motor_task();
        xTaskCreate(motor_control_task, "CalibrationMotorMove", 4096, &paramsCalibrationClose, 5, &motorControlTaskRef);
    }
}

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));

    light_driver_init();
    load_blind_step();
    switch_driver_init(button_func_pair, PAIR_SIZE(button_func_pair), button_click_handler);
    xTaskCreate(zigbee_task, "Zigbee_main", 4096, NULL, 5, NULL);
}
xieqinan commented 5 months ago

@ninoweg

Please ensure that the "window covering" device is on the same network as the "smart blinds" first. Then, you need to determine the packet sent by the "smart blinds" to control the "window covering" device using a sniffer. If the control packet is sent correctly by the "smart blinds," you can refer to https://github.com/espressif/esp-zigbee-sdk/issues/345#issuecomment-2156449315 to implement the function.

ninoweg commented 5 months ago

@Lorem96 thank you!

@xieqinan I'm only using a single device, something similar to #345 (comment). This also works for me.

The issue seems to be the missing callback id in esp_zb_core_action_handler_register() as it is not listed in esp_zb_core_action_callback_id_t? So esp_zb_core_action_handler_register(zb_action_handler); can actually not be used for this? Could a support for the cover callback be added?

Regarding handling raw data as in #345 (comment) - are there any other examples or documentation on this? Is there a possibility of using the raw handler only for callbacks that are not handles by esp_zb_core_action_handler_register?

RahulRGaikwad commented 4 months ago

@ninoweg Hello, I'm facing a similar issue. I'm also getting 'Hardware Failure' as a code in my default response.

The callback ID is accurate, as the server is supposed to generate default response to any received command.

I think the command handlers are not completely implemented for the window covering cluster. @xieqinan any update on this?

ninoweg commented 4 months ago

Hey @RahulRGaikwad!

The example from @Lorem96 didn't quite work for me but I think I managed to fix some of the issues.

First of all I think it is necessary to use both the raw command handler as well as the core action handler

esp_zb_raw_command_handler_register(raw_command_handler);
esp_zb_core_action_handler_register(zb_action_handler);

First the command is handled by the raw command handler and in case it returns false e.g. because a certain command is not handled by it, the command is passed to the core action handler so it can try to deal with it. If you only use the raw command handler you would need to deal with all commands yourself which you probably do not want.

I then used the following function to handle (or rather debug) the raw messages

bool raw_command_handler(zb_uint8_t bufid)
{
    printf("raw cmd:\n");
    uint8_t buf[zb_buf_len(bufid)];
    zb_zcl_parsed_hdr_t *cmd_info = ZB_BUF_GET_PARAM(bufid, zb_zcl_parsed_hdr_t);
    printf("cluster_id: 0x%x \n", cmd_info->cluster_id);
    printf("profile_id: 0x%x \n", cmd_info->profile_id);
    printf("cmd_id: %d \n", cmd_info->cmd_id);
    printf("cmd_direction: %d \n", cmd_info->cmd_direction);
    printf("seq_number: %d \n", cmd_info->seq_number);
    printf("is_common_command: %d \n", cmd_info->is_common_command);
    printf("disable_default_response: %d \n", cmd_info->disable_default_response);
    printf("is_manuf_specific: %d \n", cmd_info->is_manuf_specific);
    printf("manuf_specific: %d \n", cmd_info->manuf_specific);
    memcpy(buf, zb_buf_begin(bufid), sizeof(buf));
    ESP_LOGI("RAW", "bufid: %d size: %d", bufid, sizeof(buf));
    for (int i = 0; i < sizeof(buf); ++i) {
        printf("0x%02X ", buf[i]);
    }
    printf("\n");

    if (cmd_info->is_common_command)
    {
        return false;
    }

    if (cmd_info->cluster_id == ESP_ZB_ZCL_CLUSTER_ID_BASIC)
    {
        ESP_LOGW(TAG, "Basic Cluster\n\n");
        return false;
    }
    else if (cmd_info->cluster_id == ZB_ZCL_CLUSTER_ID_WINDOW_COVERING)
    {
        ESP_LOGW(TAG, "Window Covering\n\n");
        zb_uint8_t command_id = cmd_info->cmd_id;
        switch (command_id)
        {
        case ESP_ZB_ZCL_CMD_WINDOW_COVERING_UP_OPEN:
            ESP_LOGW(TAG, "Open up\n");
            return true;
            break;
        case ESP_ZB_ZCL_CMD_WINDOW_COVERING_DOWN_CLOSE:
            ESP_LOGW(TAG, "Down close\n");
            return true;
            break;
        case ESP_ZB_ZCL_CMD_WINDOW_COVERING_STOP:
            ESP_LOGW(TAG, "Stop\n");
            return true;
            break;
        case ESP_ZB_ZCL_CMD_WINDOW_COVERING_GO_TO_LIFT_PERCENTAGE:
            ESP_LOGW(TAG, "Go to lift percentage\n");
            return true;
            break;
        case ESP_ZB_ZCL_CMD_WINDOW_COVERING_GO_TO_LIFT_VALUE:
            ESP_LOGW(TAG, "Go to tilt value\n");
            return true;
            break;
        case ESP_ZB_ZCL_CMD_WINDOW_COVERING_GO_TO_TILT_PERCENTAGE:
            ESP_LOGW(TAG, "Go to tilt percentage\n");
            return true;
            break;
        case ESP_ZB_ZCL_CMD_WINDOW_COVERING_GO_TO_TILT_VALUE:
            ESP_LOGW(TAG, "Go to tilt value\n");
            return true;
            break;
        default:
            return false;
            break;
        }
    }
    else 
    {
        ESP_LOGW(TAG, "Other\n\n");
        return false;
    }
    return false;
}

I only got it to properly pair the device when not dealing with is_common_command in the raw command handler but I'm not quite sure why this is. I'm also getting some strange behavior of receiving unrequested commands, so there might still be some issues.

xieqinan commented 4 months ago

@Lorem96 @ninoweg ,

A new feature has been implemented in esp-zigbee-sdk v1.4.0. The specific commands for the window covering cluster will be triggered in ESP_ZB_CORE_WINDOW_COVERING_MOVEMENT_CB_ID when they are received correctly. Please update to this version and test again. If you have any other questions, please reopen the issue.

mukeshbauskar commented 4 months ago

@xieqinan It will be great to have a example of this in the repo.

xieqinan commented 3 months ago

@mukeshbauskar ,

The windows covering example is similar to the onoff switch example, so you can refer to it first. We will provide an example named all device types that will implement various devices and clusters via CLI in the near future.

renatomotorline commented 3 months ago

@mukeshbauskar ,

The windows covering example is similar to the onoff switch example, so you can refer to it first. We will provide an example named all device types that will implement various devices and clusters via CLI in the near future.

The all device types example would be great.

mukeshbauskar commented 3 months ago

@renatomotorline thank you...