espressif / esp-zigbee-sdk

Espressif Zigbee SDK
Apache License 2.0
151 stars 26 forks source link

Sensor and actuator clusters definition (TZ-1024) #389

Open matteovisotto opened 1 month ago

matteovisotto commented 1 month ago

Question

Hi, I would like to define a more complex application compared to the examples. I need to configure three clusters:

I write some code that compile but I have some questions:

This is the code I have at the moment:

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

static const char *TAG = "ESP_ZB_PLANT_SYSTEM";

#if !defined CONFIG_ZB_ZCZR
#error Define ZB_ZCZR in idf.py menuconfig to compile light (Router) source code.
#endif

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 commissioning");
}

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;
    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");
                esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING);
            } else {
                ESP_LOGI(TAG, "Device rebooted");
            }
        } else {
            ESP_LOGW(TAG, "Failed to initialize Zigbee stack (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 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());
        } 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)bdb_start_top_level_commissioning_cb, ESP_ZB_BDB_MODE_NETWORK_STEERING, 1000);
        }
        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;
    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;
    }
}

/*
ACTION MANAGEMENT
*/

static esp_err_t zb_attribute_handler(const esp_zb_zcl_set_attr_value_message_t *message)
{
    esp_err_t ret = ESP_OK;
    bool pump_status = 0;

    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_PLANT_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) {
                pump_status = message->attribute.data.value ? *(bool *)message->attribute.data.value : pump_status;
                ESP_LOGI(TAG, "Rele sets to %s", pump_status ? "On" : "Off");
                //Real action
            }
        }
    }
    return ret;
}

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

/*
MAIN
*/

static void esp_zb_task(void *pvParameters){
    ESP_LOGI(TAG, "Initializing Plant System...");
    uint8_t minWaterPercent = 0;
    uint8_t maxWaterPercent = 100;
    uint8_t waterPercent = 0;

    /* initialize Zigbee stack */
    esp_zb_cfg_t zb_nwk_cfg = ESP_ZB_ZR_CONFIG();
    esp_zb_init(&zb_nwk_cfg);

    char manufname[] = {7, 'M', 'V', 'D', 'e', 'v', 'Z', 'B'};
    char modelid[] = {8, 'P', 'L', 'S', 'Y', 'S', 'Z', 'B', '1'};

    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_MANUFACTURER_NAME_ID, &manufname[0]);
    esp_zb_basic_cluster_add_attr(esp_zb_basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID, &modelid[0]);

    esp_zb_attribute_list_t *esp_zb_switch_cluster = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_ON_OFF);

    esp_zb_attribute_list_t *esp_zb_water_level_cluster = esp_zb_zcl_attr_list_create(0xfc01);
    esp_zb_cluster_add_attr(esp_zb_water_level_cluster, 0xfc01, 0x0000, ESP_ZB_ZCL_ATTR_TYPE_8BIT, ESP_ZB_ZCL_ATTR_ACCESS_REPORTING, &waterPercent);
    esp_zb_cluster_add_attr(esp_zb_water_level_cluster, 0xfc01, 0x0001, ESP_ZB_ZCL_ATTR_TYPE_8BIT, ESP_ZB_ZCL_ATTR_ACCESS_REPORTING, &minWaterPercent);
    esp_zb_cluster_add_attr(esp_zb_water_level_cluster, 0xfc01, 0x0002, ESP_ZB_ZCL_ATTR_TYPE_8BIT, ESP_ZB_ZCL_ATTR_ACCESS_REPORTING, &maxWaterPercent);

    esp_zb_cluster_list_t *esp_zb_cluster_list = esp_zb_zcl_cluster_list_create();
    esp_zb_cluster_list_add_basic_cluster(esp_zb_cluster_list, esp_zb_basic_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
    esp_zb_cluster_list_add_on_off_cluster(esp_zb_cluster_list, esp_zb_switch_cluster, ESP_ZB_ZCL_CLUSTER_CLIENT_ROLE);
    esp_zb_cluster_list_add_custom_cluster(esp_zb_cluster_list, esp_zb_water_level_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);

    esp_zb_ep_list_t *zb_plant_ep_list = esp_zb_ep_list_create();
    esp_zb_ep_list_add_ep(zb_plant_ep_list, esp_zb_cluster_list, HA_PLANT_ENDPOINT, ESP_ZB_AF_HA_PROFILE_ID, ESP_ZB_HA_CUSTOM_ATTR_DEVICE_ID);

    // Register endpoint list
    esp_zb_device_register(zb_plant_ep_list);
    esp_zb_core_action_handler_register(zb_action_handler);

    // Error check, and start zigbee main loop
    ESP_ERROR_CHECK(esp_zb_start(false));
    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", 4096, NULL, 5, NULL);
}

Thanks

Additional context.

No response

xieqinan commented 1 month ago

@matteovisotto ,

Attribute definition is correct?

The attribute definition appears correct from the overview. However, I recommend using the code below to create the basic cluster, which includes all the mandatory attributes of the basic cluster.

esp_zb_attribute_list_t *esp_zb_basic_cluster = esp_zb_basic_cluster_create(NULL);
    esp_zb_basic_cluster_add_attr(esp_zb_basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID, &manufname[0]);
    esp_zb_basic_cluster_add_attr(esp_zb_basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID, &modelid[0]);

The switch configuration is able to get commands from the coordinator, but it also report its state?

The esp-zigbee-sdk does not currently support the discovery command, but it will be supported in the future.

How can I make the application to report a change in the water value ?

You can refer to the temperature sensor example in the esp-zigbee-sdk to implement the reporting feature.

I would like to add an additional attribute to the custom cluster to add a calibration parameter (that I have to save). How can I do this?

I think it has already been done correctly in your code.

By the way, which ESP Zigbee libraries are you using in your project? I believe they might be outdated, and I advise updating to the latest version.

matteovisotto commented 1 month ago

Thanks @xieqinan. I'm using sdk v.1.0.9

For the configuration parameter I mean: let's take the minWaterpercent and maxWaterPercent (the name should be changed since are not a percentage). Both should be configured from zigbee2mqtt to set the sensor value corresponding to the empty and full state. So I need to read the actual configuration but also to write and persist in NVRAM a new configuration. I suppose I have to set the attribute type as ESP_ZB_ZCL_ATTR_ACCESS_READ_WRITE (correct?) Then the second problem arises in zb_attribute_handler where I do not know how to target the correct attribute in message->attribute.id == ???? since it is a custom cluster.

matteovisotto commented 1 month ago

While I was writing the converter for Zigbee2MQTT a new question arise.

Setting an attribute as ACCESS_READ_WRITE in a SERVER_ROLE cluster, require a command action or a write action from the coordinator send the value correctly ?

xieqinan commented 1 month ago

@matteovisotto ,

So I need to read the actual configuration but also to write and persist in NVRAM a new configuration. I suppose I have to set the attribute type as ESP_ZB_ZCL_ATTR_ACCESS_READ_WRITE (correct?)

The latest v1.4.1 version still does not support recording the attribute to NVRAM. This operation needs to be completed at the application layer for now.

Then the second problem arises in zb_attribute_handler where I do not know how to target the correct attribute in message-attribute.id == ???? since it is a custom cluster.

The custom cluster ID and attribute ID are defined by you, so why are you unsure how to set them?

Setting an attribute as ACCESS_READ_WRITE in a SERVER_ROLE cluster, require a command action or a write action from the coordinator send the value correctly ?

I think you need to provide a more detailed description of this issue, as I can only understand part of your message. Do you mean that you expect to write an attribute with ACCESS_READ_WRITE access in the SERVER_ROLE cluster? Should the writing operation be done in the CLIENT_ROLE (coordinator)? The answer is yes.

matteovisotto commented 1 month ago

@xieqinan I solved the first two problems and they work. For the third one, I'm sorry, it's confusing. I mean: As a coordinator I use the Zigbee2MQTT software that plays the role of client. The device I'm developing is a ROUTER (so similar to an end device), I have the custom cluster defined as follow (something changed from the first message):

esp_zb_attribute_list_t *esp_zb_water_level_cluster = esp_zb_zcl_attr_list_create(0xfc01);
    esp_zb_cluster_add_attr(esp_zb_water_level_cluster, 0xfc01, 0x0000, ESP_ZB_ZCL_ATTR_TYPE_U16, ESP_ZB_ZCL_ATTR_ACCESS_REPORTING, &water_values->value);
    esp_zb_cluster_add_attr(esp_zb_water_level_cluster, 0xfc01, 0x0001, ESP_ZB_ZCL_ATTR_TYPE_U16, ESP_ZB_ZCL_ATTR_ACCESS_READ_WRITE, &water_values->tankHeight);
    esp_zb_cluster_add_attr(esp_zb_water_level_cluster, 0xfc01, 0x0002, ESP_ZB_ZCL_ATTR_TYPE_U16, ESP_ZB_ZCL_ATTR_ACCESS_READ_WRITE, &water_values->elevation);

the first attribute is a REPORTING one and it works. The other two attribute which I set to ESP_ZB_ZCL_ATTR_ACCESS_READ_WRITE must accept bot set and get operation from the coordinator. While the set command is correctly executed, the device receive the new value as a ESP_ZB_CORE_SET_ATTR_VALUE_CB_ID, the get operation (a read) doesn't work. When the coordinator try to request the value nothing happens.

To give more information, even if out of scope here, on the coordinator side the attribute definition is Zigbee2MQTT is:

tankHeight: {
        key: ['tankHeight'],
        convertSet: async (entity, key, value, meta) => {
            var v = value * 100;
            const payload = {tankHeight: v}; 
            await entity.write('mvManuWaterLevel',payload); // THIS WORK
            return {'tankHeight': v};
        },
        convertGet: async (entity, key, meta) => {
            await entity.read('mvManuWaterLevel', ['tankHeight']); //THIS DOES NOT WORK
        },
    },

where mvManuWaterLevel is the cluster id and tankHeight is the attribute id.

xieqinan commented 1 month ago

@matteovisotto ,

The read attribute command will not trigger any application operation on the device. Has the read attribute response from device been received by the coordinator?