espressif / esp-zigbee-sdk

Espressif Zigbee SDK
Apache License 2.0
169 stars 28 forks source link

Bidirectional Communication (TZ-964) #373

Closed kgkask closed 4 months ago

kgkask commented 4 months ago

Question

How to make the Endpoint write attribute to the coordinator to add a bidirectional communication ability?

Additional context.

I am using the code in #244, I wanted to edit it to make both ESP32H2 send string messages. Form the documentation I found that I have to set an extra cluster for each sides to add this feature, I added the clusters in both sides and I expected to work just fine I have updated the codes to handle the inquiries for each side, however when the end point send the write commands the coordinator does react at all NOT SURE WHAT'S HAPPENING?!

I have tried multiple examples and similar opened issues but I couldn't figure out the source of the issue.

Coordinator node : `#ifndef ZIGBEE_MODE_ZCZR

error "Zigbee coordinator mode is not selected in Tools->Zigbee mode"

endif

include "esp_zigbee_core.h"

include "freertos/FreeRTOS.h"

include "freertos/task.h"

include "ha/esp_zigbee_ha_standard.h"

/ Switch configuration /

define GPIO_INPUT_IO_TOGGLE_SWITCH GPIO_NUM_9

define PAIR_SIZE(TYPE_STR_PAIR) (sizeof(TYPE_STR_PAIR) / sizeof(TYPE_STR_PAIR[0]))

define CUSTOM_CLUSTER_ID 0xFC00

define CUSTOM_CLUSTER_ID1 0xFC01

define CUSTOM_STRING_MAX_SIZE 127

define LED_PIN RGB_BUILTIN

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 enum { SWITCH_IDLE, SWITCH_PRESS_ARMED, SWITCH_PRESS_DETECTED, SWITCH_PRESSED, SWITCH_RELEASE_DETECTED, } switch_state_t;

typedef struct { uint8_t pin; switch_func_t func; } switch_func_pair_t;

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;

/ Default Coordinator config /

define ESP_ZB_ZC_CONFIG() \

{ \ .esp_zb_role = ESP_ZB_DEVICE_TYPE_COORDINATOR, \ .install_code_policy = INSTALLCODE_POLICY_ENABLE, \ .nwk_cfg = { \ .zczr_cfg = \ { \ .max_children = MAX_CHILDREN, \ }, \ } \ }

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

/ 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 ESP_ZB_TRANSCEIVER_ALL_CHANNELS_MASK / Zigbee primary channel mask use in the example /

//#define ESP_ZB_PRIMARY_CHANNEL_MASK (1l << 18) / Zigbee primary channel mask use in the example /

/***** Zigbee functions **/ bool ZBM_Set_Zigbee_Attr_String(void attr_data, uint16_t attr_len, char s) { bool res = false;

if(NULL == attr_data || 0 == attr_len || NULL == s) { return res; }

((uint8_t*)attr_data)[0] = (uint8_t)attr_len;

if(0 < attr_len) { memcpy(&((uint8_t*)attr_data)[1], s, attr_len); }

res = true; return res;
}

// done converting to Arduino code static void esp_zb_buttons_handler(switch_func_pair_t *button_func_pair) {

if (button_func_pair->func == SWITCH_ONOFF_TOGGLE_CONTROL) { char msg= "This message has a length of 230: COMPUTER, LAMP, FOCUS, HOTMIGA, KEYBOARD, GUITAR, BOTTLE, GLASS, CELL PHONE, DESK, WATER, BATHROOM, FIRE, TRUCK, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN "; char value; value = (char )malloc(sizeof(uint8_t) (strlen(msg) + 1)); memset( value,0,sizeof(uint8_t) * (strlen(msg) + 1)); ZBM_Set_Zigbee_Attr_String(value,strlen(msg),msg); // ID changing the ID esp_zb_zcl_attribute_t attrs = {2, {ESP_ZB_ZCL_ATTR_TYPE_CHAR_STRING, sizeof(value), value}};
// define the command first then assign values to its subfields values. esp_zb_zcl_write_attr_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.clusterID = CUSTOM_CLUSTER_ID; cmd_req.attr_number = 1; cmd_req.attr_field = &attrs; esp_zb_zcl_write_attr_cmd_req(&cmd_req); log_i("Send 'write large data(%d)' command", sizeof(value)); } }

// converted to Arduino static void bdb_start_top_level_commissioning_cb(uint8_t mode_mask) { ESP_ERROR_CHECK(esp_zb_bdb_start_top_level_commissioning(mode_mask)); }

// converted to Arduino from the original code static void bind_cb(esp_zb_zdp_status_t zdo_status, void user_ctx) { if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) { log_i("Bound successfully!"); if (user_ctx) { light_bulb_device_params_t light = (light_bulb_device_params_t *)user_ctx; log_i("The light originating from address(0x%x) on endpoint(%d)", light->short_addr, light->endpoint); free(light); } neopixelWrite(LED_PIN,0,255,0); //Green LED to indecate connection RGB } }

// converted from the original code 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_DEVICE_NOT_FOUND) { / Bound the light device to its own bind table. / log_i("ESP_ZB_ZDP_STATUS_DEVICE_NOT_FOUND");

}
if (zdo_status == ESP_ZB_ZDP_STATUS_DEVICE_NOT_FOUND) { / Bound the light device to its own bind table. / log_i("ESP_ZB_ZDP_STATUS_DEVICE_NOT_FOUND");

} if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) { / Bound the light device to its own bind table. / log_i("Found light"); esp_zb_zdo_bind_req_param_t bind_req; // allocate memory with required size 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(); log_i("Try to bind On/Off"); log_i("Found light, binding..."); esp_zb_zdo_device_bind_req(&bind_req, bind_cb, NULL); bind_req.cluster_id = CUSTOM_CLUSTER_ID; esp_zb_zdo_device_bind_req(&bind_req, bind_cb, (void *)light); }
}

// done converting to IDF code to arduino 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 = (esp_zb_app_signal_type_t)p_sg_p; esp_zb_zdo_signal_device_annce_params_t dev_annce_params = NULL; // keep it for trubleshotting it might be needed this part is from the original code //esp_zb_app_signal_type_t sig_type = *p_sg_p;

switch (sig_type) { case ESP_ZB_ZDO_SIGNAL_SKIP_STARTUP: log_i("Zigbee stack initialized"); 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) { if (esp_zb_bdb_is_factory_new()) { log_i("Start network formation"); esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_FORMATION); } else { log_i("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); log_i( "Formed 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() ); neopixelWrite(LED_PIN, 0, 255, 0); // Green --> esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING); } else { log_i("Restart network formation (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_FORMATION, 1000); } break; case ESP_ZB_BDB_SIGNAL_STEERING: if (err_status == ESP_OK) { log_i("Network steering started"); neopixelWrite(LED_PIN, 255, 255, 255); // white --> } 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); log_i("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_NLME_STATUS_INDICATION: printf("%s, status: 0x%x\n", esp_zb_zdo_signal_to_string(sig_type), (uint8_t *)esp_zb_app_signal_get_params(p_sg_p)); neopixelWrite(LED_PIN, 255, 0, 0); // white --> break; default: log_i("ZDO signal: %s (0x%x), status: %s", esp_zb_zdo_signal_to_string(sig_type), sig_type, esp_err_to_name(err_status)); break; } } }

// handle case from zb_action_handler: static esp_err_t zb_write_resp_handler(const esp_zb_zcl_cmd_write_attr_resp_message_t *message) { neopixelWrite(LED_PIN, 0, 0, 255); // Blue --> if (!message) { log_e("Empty message"); } if (message->info.status == ESP_ZB_ZCL_STATUS_SUCCESS) { log_e( "Received message: error status(%d)", message->info.status); } log_i("Write attribute successfully From ED: 0x%d", message->info.src_endpoint); vTaskDelay(80 / portTICK_PERIOD_MS); neopixelWrite(LED_PIN, 0, 255, 0); // green --> Rx'ed Ack return ESP_OK; }

static esp_err_t zb_custom_resp_handler(const esp_zb_zcl_custom_cluster_command_message_t *message){ if (!message) { log_e("Empty message"); } if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) { log_e("Received message: error status(%d)", message->info.status); } // some logs are missing if needed check the original code log_i("Received %s message: endpoint(%d), cluster(0x%x), data size(%d)",message->info.dst_endpoint, message->info.cluster, message->data.size); log_i("response %s: request %s ", message->info.dst_endpoint, message->info.cluster);

log_i("Receive(%d) response: ", message->data.size); for (int i = 0; i < message->data.size; i++) { log_i("%02x, ", ((uint8_t )message->data.value + i)); } log_i("\n");

return ESP_OK; }

static esp_err_t zb_read_attr_resp_handler(const esp_zb_zcl_cmd_read_attr_resp_message_t *message) { if (!message) { log_e("Empty message"); } if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) { log_e("Received message: error status(%d)", message->info.status); } log_i( "Received %s message: endpoint(%d), cluster(0x%x)", message->info.command.direction); log_i("response %s: request %s ", message->info.dst_endpoint, message->info.cluster);

esp_zb_zcl_read_attr_resp_variable_t vars = message->variables; //Serial.println("size: %d\n", message->variables->attribute.data.size); log_i("size: %d\n", message->variables->attribute.data.size); while (vars) { for (int i = 0; i < vars->attribute.data.size; i++) { //Serial.println("0x%x ", (uint8_t )(vars->attribute.data.value + i)); log_i("0x%x ", (uint8_t *)(vars->attribute.data.value + i));

  }
  Serial.println("\n");
  vars = vars->next;

} return ESP_OK; }

// addition on the original code: 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_CMD_WRITE_ATTR_RESP_CB_ID: zb_write_resp_handler((esp_zb_zcl_cmd_write_attr_resp_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_CUSTOM_CLUSTER_RESP_CB_ID: ret = zb_custom_resp_handler((esp_zb_zcl_custom_cluster_command_message_t )message); break; default: log_w("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_ZC_CONFIG(); esp_zb_init(&zb_nwk_cfg);

// esp_zb_ep_list_t ep_list = esp_zb_ep_list_create(); esp_zb_cluster_list_t cluster_list = esp_zb_zcl_cluster_list_create();

// cluster to turn ON/OFF esp_zb_on_off_cluster_cfg_t on_off_cfg; on_off_cfg.on_off = ESP_ZB_ZCL_ON_OFF_ON_OFF_DEFAULT_VALUE; esp_zb_attribute_list_t *esp_zb_on_off_cluster = esp_zb_on_off_cluster_create(&on_off_cfg); esp_zb_cluster_list_add_on_off_cluster(cluster_list, esp_zb_on_off_cluster, ESP_ZB_ZCL_CLUSTER_CLIENT_ROLE);

// add custom attribute esp_zb_attribute_list_t *custom_attr = esp_zb_zcl_attr_list_create(CUSTOM_CLUSTER_ID); uint8_t custom_string[CUSTOM_STRING_MAX_SIZE] = "_hello_world"; custom_string[0] = CUSTOM_STRING_MAX_SIZE - 1;

esp_zb_custom_cluster_add_custom_attr(custom_attr, 0x02, ESP_ZB_ZCL_ATTR_TYPE_CHAR_STRING, ESP_ZB_ZCL_ATTR_ACCESS_READ_WRITE, custom_string); esp_zb_cluster_list_add_custom_cluster(cluster_list, custom_attr, ESP_ZB_ZCL_CLUSTER_CLIENT_ROLE);

// ####################### add Rx cluster to Rx custom attribute ###################################### // cluster to turn ON/OFF esp_zb_on_off_cluster_cfg_t on_off_cfg1; on_off_cfg.on_off = ESP_ZB_ZCL_ON_OFF_ON_OFF_DEFAULT_VALUE; esp_zb_attribute_list_t *esp_zb_on_off_cluster1 = esp_zb_on_off_cluster_create(&on_off_cfg1);// SERVER esp_zb_cluster_list_add_on_off_cluster(cluster_list, esp_zb_on_off_cluster1, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);

esp_zb_attribute_list_t *custom_attr1 = esp_zb_zcl_attr_list_create(CUSTOM_CLUSTER_ID1); uint8_t custom_string1[CUSTOM_STRING_MAX_SIZE] = "_hello_world"; custom_string[0] = CUSTOM_STRING_MAX_SIZE - 1;

esp_zb_custom_cluster_add_custom_attr(custom_attr1, 0x02, ESP_ZB_ZCL_ATTR_TYPE_CHAR_STRING, ESP_ZB_ZCL_ATTR_ACCESS_READ_WRITE, custom_string1); esp_zb_cluster_list_add_custom_cluster(cluster_list, custom_attr1, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);

// device discription struc 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_CUSTOM_ATTR_DEVICE_ID, .app_device_version = 0, };

esp_zb_ep_list_add_ep(ep_list, cluster_list, endpoint_config); esp_zb_device_register(ep_list); esp_zb_set_primary_network_channel_set(ESP_ZB_PRIMARY_CHANNEL_MASK); esp_zb_core_action_handler_register(zb_action_handler); //Erase NVRAM before creating connection to new Coordinator | keeps binding table esp_zb_nvram_erase_at_start(true); //Comment out this line to erase NVRAM data if you are conneting to new Coordinator ESP_ERROR_CHECK(esp_zb_start(false)); esp_zb_main_loop_iteration(); }

/***** GPIO functions **/ static QueueHandle_t gpio_evt_queue = NULL;

static void IRAM_ATTR gpio_isr_handler(void arg) { xQueueSendFromISR(gpio_evt_queue, (switch_func_pair_t )arg, NULL); }

static void switch_gpios_intr_enabled(bool enabled) { for (int i = 0; i < PAIR_SIZE(button_func_pair); ++i) { if (enabled) { enableInterrupt((button_func_pair[i]).pin); } else { disableInterrupt((button_func_pair[i]).pin); } } } /***** Arduino functions **/ // DONE conversion void setup() { // might be needed for logging to print the Rx'ed msg //Serial.begin(115200);

// Init Zigbee esp_zb_platform_config_t config = { .radio_config = ESP_ZB_DEFAULT_RADIO_CONFIG(), .host_config = ESP_ZB_DEFAULT_HOST_CONFIG(), }; ESP_ERROR_CHECK(esp_zb_platform_config(&config));

// Init button switch for (int i = 0; i < PAIR_SIZE(button_func_pair); i++) { pinMode(button_func_pair[i].pin, INPUT_PULLUP); / create a queue to handle gpio event from isr / gpio_evt_queue = xQueueCreate(10, sizeof(switch_func_pair_t)); if (gpio_evt_queue == 0) { log_e("Queue was not created and must not be used"); while (1); } attachInterruptArg(button_func_pair[i].pin, gpio_isr_handler, (void *)(button_func_pair + i), FALLING); } // LED start off neopixelWrite(LED_PIN, 0, 0, 0); // white --> // Start Zigbee task xTaskCreate(esp_zb_task, "Zigbee_main", 4096, NULL, 5, NULL); }

void loop() { // Handle button switch in loop() uint8_t pin = 0; switch_func_pair_t button_func_pair; static switch_state_t switch_state = SWITCH_IDLE; bool evt_flag = false;

/ 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)) { pin = button_func_pair.pin; switch_gpios_intr_enabled(false); evt_flag = true; } while (evt_flag) { bool value = digitalRead(pin); switch (switch_state) { case SWITCH_IDLE: switch_state = (value == LOW) ? SWITCH_PRESS_DETECTED : SWITCH_IDLE; break; case SWITCH_PRESS_DETECTED: switch_state = (value == LOW) ? SWITCH_PRESS_DETECTED : SWITCH_RELEASE_DETECTED; break; case SWITCH_RELEASE_DETECTED: switch_state = SWITCH_IDLE; / callback to button_handler / (*esp_zb_buttons_handler)(&button_func_pair); break; default: break; } if (switch_state == SWITCH_IDLE) { switch_gpios_intr_enabled(true); evt_flag = false; break; } vTaskDelay(10 / portTICK_PERIOD_MS); } }`

End device code: `#ifndef ZIGBEE_MODE_ED

error "Zigbee end device mode is not selected in Tools->Zigbee mode"

endif

include "esp_zigbee_core.h"

include "freertos/FreeRTOS.h"

include "freertos/task.h"

include "ha/esp_zigbee_ha_standard.h"

define LED_PIN RGB_BUILTIN

define ARRAY_LENTH(arr) (sizeof(arr) / sizeof(arr[0]))

/ Switch configuration /

define GPIO_INPUT_IO_TOGGLE_SWITCH GPIO_NUM_9

define PAIR_SIZE(TYPE_STR_PAIR) (sizeof(TYPE_STR_PAIR) / sizeof(TYPE_STR_PAIR[0]))

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 enum { SWITCH_IDLE, SWITCH_PRESS_ARMED, SWITCH_PRESS_DETECTED, SWITCH_PRESSED, SWITCH_RELEASE_DETECTED, } switch_state_t;

typedef struct { uint8_t pin; switch_func_t func; } switch_func_pair_t;

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

/ Default Coordinator config / //RADIO_MODE_NATIVE, main differnce between IDF and Arduino IDE starting of the command.

define ESP_ZB_ZED_CONFIG() \

{ \ .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, \ }, \ }, \ }

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

/ Zigbee configuration /

define INSTALLCODE_POLICY_ENABLE false / enable the install code policy for security /

define ED_AGING_TIMEOUT ESP_ZB_ED_AGING_TIMEOUT_64MIN

define ED_KEEP_ALIVE 8000 / 3000 millisecond /

define HA_ESP_LIGHT_ENDPOINT 10 / esp light bulb device endpoint, used to process light controlling commands /

define ESP_ZB_PRIMARY_CHANNEL_MASK ESP_ZB_TRANSCEIVER_ALL_CHANNELS_MASK //(1 << 18) / Zigbee primary channel mask use in the example /

/* Attribute values in ZCL string format

define CUSTOM_CLUSTER_ID 0xFC00

define CUSTOM_CLUSTER_ID1 0xFC01

define CUSTOM_STRING_MAX_SIZE 127

// bool ZBM_Set_Zigbee_Attr_String(void attr_data, uint16_t attr_len, char s) { bool res = false;

if(NULL == attr_data || 0 == attr_len || NULL == s) { return res; }

((uint8_t*)attr_data)[0] = (uint8_t)attr_len;

if(0 < attr_len) { memcpy(&((uint8_t*)attr_data)[1], s, attr_len); }

res = true; return res;
}

// done converting to Arduino code static void esp_zb_buttons_handler(switch_func_pair_t *button_func_pair) {

if (button_func_pair->func == SWITCH_ONOFF_TOGGLE_CONTROL) { char msg= " This message has a length of 230: COMPUTER, LAMP, FOCUS, HOTMIGA, KEYBOARD, GUITAR, BOTTLE, GLASS, CELL PHONE, DESK, WATER, BATHROOM, FIRE, TRUCK, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN "; char value; value = (char )malloc(sizeof(uint8_t) (strlen(msg) + 1)); memset( value,0,sizeof(uint8_t) * (strlen(msg) + 1)); ZBM_Set_Zigbee_Attr_String(value,strlen(msg),msg); // ID
esp_zb_zcl_attribute_t attrs = {1, {ESP_ZB_ZCL_ATTR_TYPE_CHAR_STRING, sizeof(value), value}};
// define the command first then assign values to its subfields values. esp_zb_zcl_write_attr_cmd_t cmd_req; cmd_req.zcl_basic_cmd.src_endpoint = HA_ESP_LIGHT_ENDPOINT; cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; cmd_req.clusterID = CUSTOM_CLUSTER_ID1; cmd_req.attr_number = 1; cmd_req.attr_field = &attrs;

esp_zb_lock_acquire(portMAX_DELAY);
esp_zb_zcl_write_attr_cmd_req(&cmd_req);
esp_zb_lock_release();

log_i("Send 'write large data(%d)' command", sizeof(value));

} }

// DONE conversion to Arduino 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 = (esp_zb_app_signal_type_t)*p_sg_p;

switch (sig_type) { case ESP_ZB_ZDO_SIGNAL_SKIP_STARTUP: log_i("Zigbee stack initialized"); 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) { log_i("Start network steering"); esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING); neopixelWrite(LED_PIN, 255, 255, 255); // white -->

  } else {
      /* commissioning failed */
      log_i("Failed to initialize Zigbee stack (status: %s)", esp_err_to_name(err_status));
      neopixelWrite(LED_PIN, 255, 0, 0); // 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); log_i("Joined network successfully (Extended PAN ID: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, PAN ID: 0x%04hx, Channel:%d)", extended_pan_id[0], extended_pan_id[1], extended_pan_id[2], extended_pan_id[3], extended_pan_id[4], extended_pan_id[5], extended_pan_id[6], extended_pan_id[7], esp_zb_get_pan_id(), esp_zb_get_current_channel()); neopixelWrite(LED_PIN, 0, 255, 0); // Green -->

  } else {
      log_i("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;

default: log_i( "ZDO signal: %s (0x%x), status: %s", esp_zb_zdo_signal_to_string(sig_type), sig_type, esp_err_to_name(err_status)); break; } }

/***** Define functions **/ static void bdb_start_top_level_commissioning_cb(uint8_t mode_mask) { ESP_ERROR_CHECK(esp_zb_bdb_start_top_level_commissioning(mode_mask)); }

// DONE conversion static esp_err_t zb_attribute_handler(const esp_zb_zcl_set_attr_value_message_t *message) { esp_err_t ret = ESP_OK; bool light_state = 0;

if (!message) { log_e("Empty message"); } if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) { log_e("Received message: error status(%d)", message->info.status); } neopixelWrite(LED_PIN, 0, 0, 0); // red --> if (message->info.dst_endpoint == HA_ESP_LIGHT_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) { light_state = message->attribute.data.value ? (bool )message->attribute.data.value : light_state; log_i("Light sets to %s", light_state ? "On" : "Off"); neopixelWrite(LED_PIN, 0, 255, 0); } } else if (message->info.cluster == CUSTOM_CLUSTER_ID) { // iterate over every single byte /for (int i = 0; i < message->attribute.data.size; i++) { printf("0x%2x", (uint8_t )(message->attribute.data.value + i)); }/ //printf("\n"); log_i("%s",(char )message->attribute.data.value); //printf("\n"); //log_e("\n %s",(char )message->attribute.data.value); } } vTaskDelay(80 / portTICK_PERIOD_MS); neopixelWrite(LED_PIN, 0, 255, 0); // green --> return ret; }

static esp_err_t zb_custom_req_handler(const esp_zb_zcl_custom_cluster_command_message_t *message){ if (!message) { log_e("Empty message"); return ESP_FAIL; // Return immediately to avoid further processing } else if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) { log_e("Received message: error status(%d)", message->info.status); return ESP_ERR_INVALID_ARG; // Return immediately to avoid further processing } // Declare the struct and initialize with default values esp_zb_zcl_custom_cluster_cmd_resp_t req = {};

// Set individual fields // Arduino does not accept decleration with assignment of value at the same time req.zcl_basic_cmd.dst_addr_u.addr_short = message->info.src_address.u.short_addr; req.zcl_basic_cmd.src_endpoint = message->info.dst_endpoint; req.zcl_basic_cmd.dst_endpoint = message->info.src_endpoint; req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; req.profile_id = ESP_ZB_AF_HA_PROFILE_ID; req.cluster_id = CUSTOM_CLUSTER_ID; req.custom_cmd_id = message->info.command.id; //.direction = message->info.command.direction; //.data = {0, 0, 0};

// #msg
//Receive(6) request: 01, 00, 66, 55, 66, 55, printf("Receive(%d) request: ", message->data.size); for (int i = 0; i < message->data.size; i++) { //printf("%02x, ", ((uint8_t )message->data.value + i)); printf("%s",(char )message->data.value); } printf("\n"); //printf("%s",(char )message->attribute.data.value); if (message->info.command.id == 0x01) { esp_zb_zcl_set_attribute_val(HA_ESP_LIGHT_ENDPOINT, CUSTOM_CLUSTER_ID, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, 0x03, message->data.value, false); req.data.type = ESP_ZB_ZCL_ATTR_TYPE_32BIT_ARRAY; req.data.value = message->data.value; esp_zb_zcl_custom_cluster_cmd_resp(&req); }

return ESP_OK; }

// DONE CONVERSION to Arduino 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); printf("#1"); break; case ESP_ZB_CORE_CMD_CUSTOM_CLUSTER_REQ_CB_ID: ret = zb_custom_req_handler((esp_zb_zcl_custom_cluster_command_message_t *)message); printf("#2"); break; default: log_w("Receive Zigbee action(0x%x) callback", callback_id); break; } return ret; }

// DONE conversion to Arduino signed int esp_zb_zcl_cluster_check_value_handler(uint16_t attr_id, uint8_t endpoint, uint8_t *value) { printf("check value endpoint:%d, attr: %d\n", endpoint, attr_id); return 0; }

// DONE conversion to Arduino void esp_zb_zcl_cluster_write_attr_handler(uint8_t endpoint, uint16_t attr_id, uint8_t *new_value, uint16_t manuf_code) { printf("write attr endpoint:%d, attr: %d\n", endpoint, attr_id); }

// DONE conversion 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_cluster_list_t cluster_list = esp_zb_zcl_cluster_list_create();

// cluster to turn ON/OFF esp_zb_on_off_cluster_cfg_t on_off_cfg; on_off_cfg.on_off = ESP_ZB_ZCL_ON_OFF_ON_OFF_DEFAULT_VALUE; esp_zb_attribute_list_t *esp_zb_on_off_cluster = esp_zb_on_off_cluster_create(&on_off_cfg);// SERVER esp_zb_cluster_list_add_on_off_cluster(cluster_list, esp_zb_on_off_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);

// add custom attribute esp_zb_attribute_list_t *custom_attr = esp_zb_zcl_attr_list_create(CUSTOM_CLUSTER_ID); uint8_t custom_string[CUSTOM_STRING_MAX_SIZE] = "_ESPRESSIF"; custom_string[0] = CUSTOM_STRING_MAX_SIZE - 1;

// esp_zb_custom_cluster_add_custom_attr(custom_attr, 0x02, ESP_ZB_ZCL_ATTR_TYPE_CHAR_STRING, ESP_ZB_ZCL_ATTR_ACCESS_READ_WRITE, custom_string); esp_zb_cluster_list_add_custom_cluster(cluster_list, custom_attr, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);

// ####################### add Tx cluster to Rx custom attribute ###################################### // cluster to turn ON/OFF esp_zb_on_off_cluster_cfg_t on_off_cfg1; on_off_cfg.on_off = ESP_ZB_ZCL_ON_OFF_ON_OFF_DEFAULT_VALUE; esp_zb_attribute_list_t *esp_zb_on_off_cluster1 = esp_zb_on_off_cluster_create(&on_off_cfg1); esp_zb_cluster_list_add_on_off_cluster(cluster_list, esp_zb_on_off_cluster1, ESP_ZB_ZCL_CLUSTER_CLIENT_ROLE);

esp_zb_attribute_list_t *custom_attr1 = esp_zb_zcl_attr_list_create(CUSTOM_CLUSTER_ID1); uint8_t custom_string1[CUSTOM_STRING_MAX_SIZE] = "_ESPRESSIF"; custom_string[0] = CUSTOM_STRING_MAX_SIZE - 1;

// esp_zb_custom_cluster_add_custom_attr(custom_attr1, 0x02, ESP_ZB_ZCL_ATTR_TYPE_CHAR_STRING, ESP_ZB_ZCL_ATTR_ACCESS_READ_WRITE, custom_string1); esp_zb_cluster_list_add_custom_cluster(cluster_list, custom_attr1, ESP_ZB_ZCL_CLUSTER_CLIENT_ROLE);

// device discription struc esp_zb_endpoint_config_t endpoint_config = { .endpoint = HA_ESP_LIGHT_ENDPOINT, .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, .app_device_id = ESP_ZB_HA_CUSTOM_ATTR_DEVICE_ID, .app_device_version = 0, };

// esp_zb_ep_list_add_ep(ep_list, cluster_list, endpoint_config); esp_zb_device_register(ep_list);

/esp_zb_zcl_custom_cluster_handlers_t obj = {.cluster_id = CUSTOM_CLUSTER_ID, .cluster_role = ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, .check_value_cb = esp_zb_zcl_cluster_check_value_handler, .write_attr_cb = esp_zb_zcl_cluster_write_attr_handler}; esp_zb_zcl_custom_cluster_handlers_update(obj);/ //** Added by me //Erase NVRAM before creating connection to new Coordinator | keeps binding table esp_zb_nvram_erase_at_start(true); //Comment out this line to erase NVRAM data if you are conneting to new Coordinator //**** 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(); }

/***** GPIO functions **/ static QueueHandle_t gpio_evt_queue = NULL;

static void IRAM_ATTR gpio_isr_handler(void arg) { xQueueSendFromISR(gpio_evt_queue, (switch_func_pair_t )arg, NULL); }

static void switch_gpios_intr_enabled(bool enabled) { for (int i = 0; i < PAIR_SIZE(button_func_pair); ++i) { if (enabled) { enableInterrupt((button_func_pair[i]).pin); } else { disableInterrupt((button_func_pair[i]).pin); } } } /***** Arduino functions **/ // DONE converting // DONE conversion void setup() { // might be needed for logging to print the Rx'ed msg //Serial.begin(115200);

// Init Zigbee esp_zb_platform_config_t config = { .radio_config = ESP_ZB_DEFAULT_RADIO_CONFIG(), .host_config = ESP_ZB_DEFAULT_HOST_CONFIG(), }; ESP_ERROR_CHECK(esp_zb_platform_config(&config));

// Init button switch for (int i = 0; i < PAIR_SIZE(button_func_pair); i++) { pinMode(button_func_pair[i].pin, INPUT_PULLUP); / create a queue to handle gpio event from isr / gpio_evt_queue = xQueueCreate(10, sizeof(switch_func_pair_t)); if (gpio_evt_queue == 0) { log_e("Queue was not created and must not be used"); while (1); } attachInterruptArg(button_func_pair[i].pin, gpio_isr_handler, (void *)(button_func_pair + i), FALLING); } // LED start off neopixelWrite(LED_PIN, 0, 0, 0); // white --> // Start Zigbee task xTaskCreate(esp_zb_task, "Zigbee_main", 4096, NULL, 5, NULL); }

void loop() { // Handle button switch in loop() uint8_t pin = 0; switch_func_pair_t button_func_pair; static switch_state_t switch_state = SWITCH_IDLE; bool evt_flag = false;

/ 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)) { pin = button_func_pair.pin; switch_gpios_intr_enabled(false); evt_flag = true; } while (evt_flag) { bool value = digitalRead(pin); switch (switch_state) { case SWITCH_IDLE: switch_state = (value == LOW) ? SWITCH_PRESS_DETECTED : SWITCH_IDLE; break; case SWITCH_PRESS_DETECTED: switch_state = (value == LOW) ? SWITCH_PRESS_DETECTED : SWITCH_RELEASE_DETECTED; break; case SWITCH_RELEASE_DETECTED: switch_state = SWITCH_IDLE; / callback to button_handler / (*esp_zb_buttons_handler)(&button_func_pair); break; default: break; } if (switch_state == SWITCH_IDLE) { switch_gpios_intr_enabled(true); evt_flag = false; break; } vTaskDelay(10 / portTICK_PERIOD_MS); } }`

xieqinan commented 4 months ago

@kgkask ,

Could you please refer to this comment https://github.com/espressif/esp-zigbee-sdk/issues/202#issuecomment-2005690377 for your question? The example demonstrates a bidirectional communication case.

Additionally, it would be helpful to attach a patch or a zip file of your project to facilitate our review.

kgkask commented 4 months ago

@xieqinan Thanks for your quick response, I actually I have used the same code but in Arduino IDE. It allows sending the string from one side not both so based on my understanding I have to add a client cluster with the server cluster to achieve bidirectional communication (and vice versa).

sorry for the code. here is .rar file:

bidirectional _communication.zip

kgkask commented 4 months ago

@xieqinan

I have looked closely to the code, I can receive the response to the write command but when I configure the End device to send write command "esp_zb_zcl_write_attr_cmd_req(&write_req);" like the coordinator it fails

kgkask commented 4 months ago

@xieqinan I find out how to solve it, thanks.

I actually my mistake was that I used only esp_zb_zcl_write_attr_cmd_req(&write_req);, while I actually need to make use of esp_zb_zcl_custom_cluster_cmd_req_t and esp_zb_zcl_custom_cluster_cmd_resp_t which is already there in the code.

Thanks again

xieqinan commented 4 months ago

@kgkask ,

Do you have any other questions that need to be discussed here? If the bidirectional communication issue has been resolved, please consider closing it.