Как стать автором
Обновить

Создаём свое собственное ZigBee устройство на чипах от Espressif ( ESP32-C6/H2)

Уровень сложностиСредний
Время на прочтение9 мин
Количество просмотров33K
Первые упоминания про новые чипы от Espressif были еще два года назад. Но тогда про них мало что было известно, и они были не доступны в свободной продаже, и SDK был на самом начальном этапе. И вот наконец их анонсировали, и можно купить, так давайте же сделаем на них что нибудь хорошее.

Я давно хотел себе домой СО2 сенсор, но то денег не хватает, то жаба квакает… А тут мне пришла заказанная плата на базе ESP32-C6 и я решил попробовать собрать сам, благо различных Ардуино модулей был целый ящик. Если Вам интересно что у меня получилось добро пожаловать под кат. (длинная портянка и много картинок)

Самым первым делом для работы необходимо установить среду разработки. В моем случае это ESP-IDF. В качестве визуального редактора я использую Visual Studio Code. Он есть для всех платформ. Загружаем и устанавливаем его ссылка на дистрибутив.

Запустив VSCode устанавливаем в нем расширение ESP-IDF.

Внимание! Расширение не любит русских символов в пути установки, возможны проблемы в работе. Лучше устанавливать или в корень диска или в отдельную папку.



Подождем немного когда расширение полностью установится.

После установки нам нужно склонировать проект с Github по ссылке.



В окне VSCode выбираем с левой стороны пункт Source Control (1), нажимаем clone_repository (2), вставляем адрес репозитория (3), нажимаем enter и указываем папку куда сохранить.

Теперь немного отвлечемся на теорию. Без базовых основ теории, рассказывать дальше смысла не имеет.

В стандарте ZigBee каждое устройство описывается определенным типом.

Самым верхним уровнем идет профиль устройства. Он определяет назначение устройства. В нашем случае стандартный профиль id 0x0104.



А уже внутри профиля идет описание устройства.


  1. Самый первый параметр это endpoint — конечная точка, которая содержит в себе полное описание конечного устройства. Внутри одного эндпоинта может быть множество различных моделей, но не повторяющихся. Нельзя создать 2 реле внутри одного эндпоинта, но можно создать два разных эндпоинта в каждом из которых будет одно реле. И в сети ваше устройство будет определяться как несколько устройств в одном.
  2. Второй параметр это cluster — кластер. Кластер — это совокупность атрибутов и набора команд, которые предназначены для манипуляции этими атрибутами. Например кластер температуры, или кластер влажности.
  3. Последний параметр это attribute — атрибуты. Атрибуты являются значениями которые описывают текущее состояние кластера. Совокупность атрибутов всех кластеров, поддерживаемых устройством, определяет состояние устройства. Например в кластере температуры атрибут измеренного значения будет содержать непосредственно саму температуру.

В своем устройстве я буду использовать датчики температуры, влажности, давления и углекислого газа. Повторяющихся у меня нет, соответственно конечная точка будет одна.

Теперь давайте поймем какие кластеры мне нужны. Для этого есть официальная документация, которая полностью описывает все кластеры и их спецификацию, pdf онлайн Zigbee Cluster Library.

Каждый кластер в какой то степени уникален и имеет свой собственный идентификатор, и свой собственный формат данных.



Zigbee Cluster Library должна быть у вас под рукой, чтобы всегда знать какой тип данных принимает тот или иной кластер в свои атрибуты.

Это действительно минимально необходимый набор знаний, для разработки своих устройств. Остальную логику обрабатывает ZigBee Stack, условно «драйвер» ZigBee сети, конкретно в sdk espressif используется ZigBee Stack Zboss.

По самым базовым основам пробежались, приступим к работе непосредственно с кодом.

В качестве отправной точки я буду рассматривать свой собственный пример. В нем, вроде как, реализован минимально необходимый набор для получения работоспособного устройства.

Откроем его в VSCode. Через File — Open folder. необходимо выбрать папку со скачанным проектом.



ESP-IDF основан на freertos.

Главным исполняемым файлом в проекте является esp_zigbee_co2.c, откроем его. И прокрутим до самого низа, до старта приложения.

 xTaskCreate(esp_zb_task, "Zigbee_main", 4096, NULL, 6, NULL);

Это интересующая нас часть, непосредственно задача которая описывает структуру устройства ZigBee, его кластеры и атрибуты кластеров. Для минимального устройства хватает ее и еще одной задачи.

Перейдем непосредственно к ней:

Первым делом инициализируется сам stack, с описанием и параметрами.

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

После инициализации начинается описание кластеров и их атрибутов. Рассмотрим на примере температуры. Кластеры измерения достаточно хитрые, и требуют заполнения 3-х атрибутов для своей работы.

 /* Temperature cluster */
    esp_zb_attribute_list_t *esp_zb_temperature_meas_cluster = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT);
    esp_zb_temperature_meas_cluster_add_attr(esp_zb_temperature_meas_cluster, ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID, &undefined_value);
    esp_zb_temperature_meas_cluster_add_attr(esp_zb_temperature_meas_cluster, ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_MIN_VALUE_ID, &undefined_value);
    esp_zb_temperature_meas_cluster_add_attr(esp_zb_temperature_meas_cluster, ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_MAX_VALUE_ID, &undefined_value);

  1. Первым делом создаем структуру, указываем что нам необходимо создать список атрибутов, для конкретного кластера. В нашем случае ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT
  2. Начинаем перечислять атрибуты необходимые для работы, и указываем им значения по умолчанию. Measurment_value, min_value, max_value

Почему необходимы все 3 атрибута? В стандарте ZigBee 3.0 реализован механизм reporting. Устройство будет передавать ту же температуру не все время, а в случае если текущее измеренное значение будет отличаться от предыдущего, и прошел запрограммированный интервал времени. Хотя можно отправлять и просто принудительно.

После заполнения атрибутов кластера, необходимо создать список кластеров устройства. Кластеров ведь может быть множество, и стек должен об этом знать.

    /* Create full cluster list enabled on device */
    esp_zb_cluster_list_t *esp_zb_cluster_list = esp_zb_zcl_cluster_list_create();
    esp_zb_cluster_list_add_temperature_meas_cluster(esp_zb_cluster_list, esp_zb_temperature_meas_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
    esp_zb_cluster_list_add_humidity_meas_cluster(esp_zb_cluster_list, esp_zb_humidity_meas_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
    esp_zb_cluster_list_add_pressure_meas_cluster(esp_zb_cluster_list, esp_zb_press_meas_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
    esp_zb_cluster_list_add_time_cluster(esp_zb_cluster_list, esp_zb_server_time_cluster, ESP_ZB_ZCL_CLUSTER_CLIENT_ROLE);
    esp_zb_cluster_list_add_custom_cluster(esp_zb_cluster_list, custom_co2_attributes_list, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
    esp_zb_cluster_list_add_ota_cluster(esp_zb_cluster_list, esp_zb_ota_client_cluster, ESP_ZB_ZCL_CLUSTER_CLIENT_ROLE);

  1. Создаем структуру содержащую список кластеров
  2. Добавляем кластеры в список, и указываем их роль. Роли так же указаны в документации, для каждого кластера Но у сенсоров в большинстве своем SERVER_ROLE

И самый последний шаг, создаем список конечных точек, заполняем его и передаем стеку для начала работы.

    esp_zb_ep_list_t *esp_zb_ep_list = esp_zb_ep_list_create();
    esp_zb_ep_list_add_ep(esp_zb_ep_list, esp_zb_cluster_list, SENSOR_ENDPOINT, ESP_ZB_AF_HA_PROFILE_ID, ESP_ZB_HA_SIMPLE_SENSOR_DEVICE_ID);

    /* END */
    esp_zb_device_register(esp_zb_ep_list);

Тут мы передаем непосредственно номер конечной точки(endpoint), выбранный профиль ( id 0x0104), и примерный тип устройства (simple sensor).

Заполнив этот минимально необходимый набор данных мы получим минимально работающее зигби устройство, но внутри каждого атрибута будут данные установленные при старте. И все что вы увидите на вашем сервере будет 0 или undefined. Перейдем к следующей части.

Заполнение данных внутри атрибутов


В моем случае заполнением данных занимается функция update_attribute. Она запускается отдельной задачей, можно было бы сделать через встроенные в систему таймеры, но пока сделал так.

    xTaskCreate(update_attribute, "Update_attribute_value", 4096, NULL, 5, NULL);

Для заполнения значения температуры необходимо поместить в атрибут measured_value значение температуры (заполняем только measured_value, значение MIN и MAX заполняются стеком), оно должно быть uint16, и представлять из себя температуру умноженную на 100, грубо 23.6 градусов будет выглядеть как значение 2360. Все это описано в zigbee cluster library.

            /* Write new temperature value */
esp_zb_zcl_status_t state_tmp = esp_zb_zcl_set_attribute_val(SENSOR_ENDPOINT, ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID, &temperature, false);
            
 /* Check for error */
     if(state_tmp != ESP_ZB_ZCL_STATUS_SUCCESS)
    {
      ESP_LOGE(TAG, "Setting temperature attribute failed!");
    }

Непосредственно само заполнение выполняется одной функцией, которой мы передаем набор параметров, конечную точку устройства, название кластера, роль кластера, название атрибута, само значение атрибута, и указываем что не надо проверять роль кластера.

На этом описание процедуры передачи температуры в стек заканчивается, все достаточно просто. Теперь в нашем устройстве не только описана температура, но и заполняются ее значения.

Важное уточнение. Для получения значений температуры на сервере необходимо выполнить биндинг на кластер! Без биндинга значения приходить не будут, даже если их просто заполнять.

Теперь вы можете открыть любой пример из официального SDK ссылка и на базе них сделать свое собственное устройство.

Теперь углубимся, немного, непосредственно в сам ZigBee-SDK от Espressif. Он основан на ZBOSS как мы помним.

В нашем проекте в каталоге main присутствует файл idf_component.yml.

По аналогии с Ардуино ( кто работал ) это менеджер библиотек, которые применяются в проекте.

## IDF Component Manager Manifest File
dependencies:
  espressif/button: "^3.0.0"
  espressif/esp-zboss-lib: "~0.6.0"
  espressif/esp-zigbee-lib: "~0.9.3"
  ## Required IDF version
  idf:
    version: ">=5.1.0"

Сами библиотеки располагаются по адресу https://components.espressif.com/

В проектах ZigBee подключатся две основные библиотеки esp-zboss-lib и esp-zigbee-lib.

Пока проект только открыт, но ни разу не собран вы ни увидите в коде ни одну библиотеку, поэтому давайте выполним компиляцию проекта.



В самой нижней части окна, необходимо нажать клавишу Build_project.

В процессе компиляции начнут подтягиваться исходники всех подключенных библиотек. В эксплорере проекта появится новый каталог managed_components.



Теперь ответ на самый главный вопрос, который наверное у всех возник. Откуда я беру переменные с именами кластеров и атрибутов.

Так как SDK еще в процессе доработки, то к сожалению доступный список кластеров сильно ограничен, и список кластеров доступных для использования располагается тут managed_components\espressif__esp-zigbee-lib\include\zcl\esp_zigbee_zcl_common.h



А список доступных атрибутов описан для каждого кластера отдельно, они лежат в каталоге
managed_components\espressif__esp-zigbee-lib\include\zcl\



Вспомним что изначально проект представляет из себя набор сенсоров температуры, влажности, давления и CO2. Если с первыми тремя никаких проблем нет, они полностью есть в SDK, то вот с CO2 возникло сложности. Кластер CO2 в SDK не реализован. В дальнейшем он может появится, но на текущем этапе он отсутствует. Как поступить в такой ситуации?

В SDK существует специальная функция по созданию кастомных кластеров (не описанных в документации), но есть ограничение они должны быть вида 0xFxxx другие варианты в SDK запрещены.

Давайте рассмотрим как создается кастомный кластер.

#define CO2_CUSTOM_CLUSTER              0xFFF2                                /* Custom cluster used because standart cluster not working*/

    /* Custom cluster for CO2 ( standart cluster not working), solution only for HOMEd */
    const uint16_t attr_id = 0;
    const uint8_t attr_type = ESP_ZB_ZCL_ATTR_TYPE_U16;
    const uint8_t attr_access = ESP_ZB_ZCL_ATTR_MANUF_SPEC | ESP_ZB_ZCL_ATTR_ACCESS_READ_ONLY | ESP_ZB_ZCL_ATTR_ACCESS_REPORTING;

    esp_zb_attribute_list_t *custom_co2_attributes_list = esp_zb_zcl_attr_list_create(CO2_CUSTOM_CLUSTER);
    esp_zb_custom_cluster_add_custom_attr(custom_co2_attributes_list, attr_id, attr_type, attr_access, &undefined_value);

Для создания кастомного кластера СО2 мы полностью описываем характеристики его атрибута.
  1. Присваиваем номер атрибута, в моем случае атрибут будет 0
  2. Указываем тип данных атрибута, uint16
  3. Указываем разрешения для атрибута, что он специальный, его можно только читать и он может отсылать репорты

Через функцию esp_zb_custom_cluster_add_custom_attr заполняем атрибуты у нашего кастомного кластера.

Теперь наша прошивка готова полностью. Соберем прошивку и загрузим на плату.

Финальная часть. Собранный девайс, и тест на реальных системах


Сам устройство я спаял на макетной плате и распечатал для него простенький корпус на 3д принтере.




После заливки прошивки и запуска, девайс будет искать доступную для подключения ZigBee сеть.

Давайте подключим его к Home Assistant c интеграцией ZHA.



Подключение произошло успешно и почти все работает, но из-за того что СО2 у нас в кастомном кластере, ZHA не может его распознать, так же ведет себя и Z2M. В репозитории официального SDK я создал issue ссылка и когда добавят кластер в официальную поддержку, то устройство будет подключаться везде, без каких либо проблем.

А пока мне пришел на помощь @voznemozhno со своим замечательным проектом HOMEd, простая и очень шустрая альтернатива ZHA и Z2M.

Больше всего HOMEd похож по функционалу на Z2M, как и Z2M он с помощью mqtt прокидывает в Home Assistant доступные ZigBee устройства.

Он добавил полную поддержку моей поделки в свой координатор.



И HOMEd умеет обновлять данный девайс через OTA



На этом все.

Полезные ссылки:
Группа разработчиков ZigBee девайсов
Группа по HOMEd
Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
Всего голосов 47: ↑47 и ↓0+47
Комментарии22

Публикации

Истории

Ближайшие события