Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bidirectional Communication (TZ-964) #373

Closed
kgkask opened this issue Jun 24, 2024 · 5 comments
Closed

Bidirectional Communication (TZ-964) #373

kgkask opened this issue Jun 24, 2024 · 5 comments
Labels

Comments

@kgkask
Copy link

kgkask commented Jun 24, 2024

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

  • The string should be started with the length of its own.
    */
    #define MANUFACTURER_NAME
    "\x0B"
    "ESPRESSIF"
    #define MODEL_IDENTIFIER "\x09" CONFIG_IDF_TARGET

#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);
}
}`

@github-actions github-actions bot changed the title Bidirectional Communication Bidirectional Communication (TZ-964) Jun 24, 2024
@xieqinan
Copy link
Contributor

@kgkask ,

Could you please refer to this comment #202 (comment) 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
Copy link
Author

kgkask commented Jun 24, 2024

@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
Copy link
Author

kgkask commented Jun 26, 2024

@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
Copy link
Author

kgkask commented Jun 27, 2024

@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
Copy link
Contributor

xieqinan commented Jul 4, 2024

@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.

@kgkask kgkask closed this as completed Jul 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants