I am trying to make my own custom device using an ESP32-C6 with zigbee. I cannot find any good documentation on how to make a custom device, and not even a good description of endpoint vs cluster, vs attribute lists.
I have posted my code below, the 2 functions that are bad are esp_zb_task and zb_attribute_handler
When I get this working, I will write up more info so others don't have to fight it as badly as I am
Thank you
Code: Select all
// Copyright 2023 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#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 the model and manufacturer
#define DEVICE_MODEL "\x14""Beeton Train Decoder" // 20 characters (14 in hex is 20 in decimal)
#define VERSION_NUMBER "\x06""0.0.01"
#define DEVICE_MANUFACTURER "\x14""YottaRock Industries" // 20 characters (14 in hex is 20 in decimal)
#define ZCL_BASIC_POWER_SOURCE 4
// Custom endpoint definitions
#define MOTOR_CONTROL_ENDPOINT 1
#define OUTPUT_CONTROL_ENDPOINT 2
#define IO_CONTROL_ENDPOINT 3
#define SOUND_CONTROL_ENDPOINT 4
// Motion control attributes
int motor_speed = 0; // Motor speed range from -1024 to 1023
bool motor_direction = true; // True: forward, False: reverse
// Digital output attributes
bool outputs[8] = {false}; // On/Off states for 8 outputs
// IO control attributes
bool io_direction = true; // True: output, False: input
// Sound control attributes
char sound_file[128] = ""; // Current sound file being played
bool is_playing = false;
int volume = 50; // Volume level (0-100)
/* Default End Device config */
#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 3000 /* 3000 millisecond */
#define BEETON_TRAIN_ENDPOINT 1 /* esp light bulb device endpoint, used to process light controlling commands */
#define ESP_ZB_PRIMARY_CHANNEL_MASK ESP_ZB_TRANSCEIVER_ALL_CHANNELS_MASK /* Zigbee primary channel mask use in the example */
/********************* Zigbee 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));
}
void esp_zb_app_signal_handler(esp_zb_app_signal_t* signal_struct) {
log_i("Handling signals...");
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("Device started up in %s factory-reset mode", esp_zb_bdb_is_factory_new() ? "" : "non");
if (esp_zb_bdb_is_factory_new()) {
log_i("Start network formation");
esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING);
} else {
log_i("Device rebooted");
}
} else {
log_w("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);
rgbLedWrite(RGB_BUILTIN, 10, 0, 0);
log_i(
"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 {
rgbLedWrite(RGB_BUILTIN, 10, 0, 10);
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;
}
}
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:
log_w("Receive Zigbee action(0x%x) callback", callback_id);
break;
}
return ret;
}
static void esp_zb_task(void* pvParameters) {
esp_zb_cfg_t zb_nwk_cfg = ESP_ZB_ZED_CONFIG();
esp_zb_init(&zb_nwk_cfg);
//info list
esp_zb_attribute_list_t *esp_zb_basic_cluster = esp_zb_basic_cluster_create(NULL);
uint8_t powerSource = ZCL_BASIC_POWER_SOURCE;
esp_zb_cluster_update_attr(esp_zb_basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_POWER_SOURCE_ID, &powerSource);
esp_zb_basic_cluster_add_attr(esp_zb_basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID, (void *) DEVICE_MODEL);
esp_zb_basic_cluster_add_attr(esp_zb_basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID, (void *) DEVICE_MANUFACTURER);
// Motor control (Endpoint 1)
esp_zb_attribute_list_t *motor_control = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL);
esp_zb_attribute_list_t *motor_onoff = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_ON_OFF);
esp_zb_cluster_list_t *motor_cluster = esp_zb_zcl_cluster_list_create();
esp_zb_cluster_list_add_custom_cluster( motor_cluster, motor_control, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE) ;
esp_zb_cluster_list_add_custom_cluster(motor_cluster, motor_onoff, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
// Output control (Endpoint 2)
esp_zb_attribute_list_t *output_control_attributes = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_ON_OFF);
esp_zb_cluster_list_t *output_control_cluster = esp_zb_zcl_cluster_list_create();
esp_zb_cluster_list_add_on_off_cluster(output_control_cluster, output_control_attributes, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
// IO control (Endpoint 3)
esp_zb_attribute_list_t *io_control_attributes = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_MULTI_VALUE);
esp_zb_cluster_list_t *io_control_cluster = esp_zb_zcl_cluster_list_create();
esp_zb_cluster_list_add_multistate_value_cluster(io_control_cluster, io_control_attributes, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
// Sound control (Endpoint 4)
esp_zb_attribute_list_t *sound_control_attribute = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_ON_OFF);
esp_zb_attribute_list_t *sound_volume_attribute = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL);
esp_zb_attribute_list_t *sound_file_attribute = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_BASIC); // Custom attribute for file name
esp_zb_cluster_list_t *sound_control_list = esp_zb_zcl_cluster_list_create();
esp_zb_cluster_list_add_on_off_cluster(sound_control_list, sound_control_attribute, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_zb_cluster_list_add_multistate_value_cluster(sound_control_list, sound_volume_attribute, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_zb_cluster_list_add_custom_cluster(sound_control_list, sound_file_attribute, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); // Add custom file cluster
// Register endpoints
esp_zb_ep_list_t *ep_list = esp_zb_ep_list_create();
// Motion control endpoint (1)
esp_zb_endpoint_config_t motion_endpoint = {
.endpoint = MOTOR_CONTROL_ENDPOINT,
.app_profile_id = ESP_ZB_AF_HA_PROFILE_ID,
.app_device_id = ESP_ZB_HA_ON_OFF_SWITCH_DEVICE_ID,
.app_device_version = 0
};
esp_zb_ep_list_add_ep(ep_list, motor_cluster, motion_endpoint);
// Output control endpoint (2)
esp_zb_endpoint_config_t output_endpoint = {
.endpoint = OUTPUT_CONTROL_ENDPOINT,
.app_profile_id = ESP_ZB_AF_HA_PROFILE_ID,
.app_device_id = ESP_ZB_HA_ON_OFF_SWITCH_DEVICE_ID,
.app_device_version = 0
};
esp_zb_ep_list_add_ep(ep_list, output_control_cluster, output_endpoint);
// IO control endpoint (3)
esp_zb_endpoint_config_t io_endpoint = {
.endpoint = IO_CONTROL_ENDPOINT,
.app_profile_id = ESP_ZB_AF_HA_PROFILE_ID,
.app_device_id = ESP_ZB_HA_ON_OFF_SWITCH_DEVICE_ID,
.app_device_version = 0
};
esp_zb_ep_list_add_ep(ep_list, io_control_cluster, io_endpoint);
// Sound control endpoint (4)
esp_zb_endpoint_config_t sound_endpoint = {
.endpoint = SOUND_CONTROL_ENDPOINT,
.app_profile_id = ESP_ZB_AF_HA_PROFILE_ID,
.app_device_id = ESP_ZB_HA_ON_OFF_SWITCH_DEVICE_ID,
.app_device_version = 0
};
esp_zb_ep_list_add_ep(ep_list, sound_control_list, sound_endpoint);
// Register and start Zigbee
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);
//Erase NVRAM before creating connection to new Coordinator
esp_zb_nvram_erase_at_start(true); //Comment out this line to erase NVRAM data if you are connecting to new Coordinator
ESP_ERROR_CHECK(esp_zb_start(false));
esp_zb_main_loop_iteration();
}
/* Handle the light attribute */
static esp_err_t zb_attribute_handler(const esp_zb_zcl_set_attr_value_message_t* message) {
esp_err_t ret = ESP_OK;
uint8_t endpoint = message->info.dst_endpoint;
uint16_t cluster_id = message->info.cluster;
uint16_t attr_id = message->attribute.id;
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 message: endpoint(%d), cluster(0x%x), attribute(0x%x), data size(%d)", endpoint, cluster_id, attr_id,
message->attribute.data.size);
switch (endpoint) {
case MOTOR_CONTROL_ENDPOINT:
if (cluster_id == ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL) {
if (attr_id == ESP_ZB_ZCL_ATTR_LEVEL_CONTROL_CURRENT_LEVEL_ID) {
motor_speed = *(int *)message->attribute.data.value;
ESP_LOGI("MOTION", "Motor speed set to %d", motor_speed);
}
if (attr_id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID) {
motor_direction = *(bool *)message->attribute.data.value;
ESP_LOGI("MOTION", "Motor direction set to %s", motor_direction ? "Forward" : "Reverse");
}
}
break;
case OUTPUT_CONTROL_ENDPOINT:
if (cluster_id == ESP_ZB_ZCL_CLUSTER_ID_ON_OFF) {
int output_id = attr_id - ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID;
if (output_id >= 0 && output_id < 8) {
outputs[output_id] = *(bool *)message->attribute.data.value;
ESP_LOGI("OUTPUT", "Output %d set to %s", output_id, outputs[output_id] ? "On" : "Off");
}
}
break;
case IO_CONTROL_ENDPOINT:
if (cluster_id == ESP_ZB_ZCL_CLUSTER_ID_ON_OFF) {
io_direction = *(bool *)message->attribute.data.value;
ESP_LOGI("IO", "IO direction set to %s", io_direction ? "Output" : "Input");
}
break;
case SOUND_CONTROL_ENDPOINT:
if (cluster_id == ESP_ZB_ZCL_CLUSTER_ID_ON_OFF) {
if (attr_id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID) {
is_playing = *(bool *)message->attribute.data.value;
ESP_LOGI("SOUND", "%s audio playback", is_playing ? "Starting" : "Stopping");
}
} else if (cluster_id == ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL) {
if (attr_id == ESP_ZB_ZCL_ATTR_LEVEL_CONTROL_CURRENT_LEVEL_ID) {
volume = *(int *)message->attribute.data.value;
ESP_LOGI("SOUND", "Volume set to %d", volume);
}
} else if (cluster_id == ESP_ZB_ZCL_CLUSTER_ID_BASIC && attr_id == 0x000F) { // Custom attribute for sound file
strcpy(sound_file, (char *)message->attribute.data.value);
ESP_LOGI("SOUND", "Sound file set to: %s", sound_file);
}
break;
}
return ret;
}
/********************* Arduino functions **************************/
void setup() {
// Show power light
rgbLedWrite(RGB_BUILTIN, 0, 10, 0);
// 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));
// Start Zigbee task
xTaskCreate(esp_zb_task, "Zigbee_main", 4096, NULL, 5, NULL);
}
void loop() {
}