Домашняя автоматизация - вещь неоднозначная, каждый понимает под ней что-то свое: для кого-то это подключить обогреватель через wi-fi розетку, а кому-то подавай домашний сервер, километры кабеля, KNX и часы работы интеграторов. Если в городских квартирах система "умный дом" - не всегда благо, то для загородного дома или дачи необходимость ее наличия сильно возрастает: проконтролировать отопление, выключить свет или разогреть электропечку в любимой сауне перед приездом. При этом такая система должна быть простой, дешевой и уметь работать автономно: никто не хочет приехать в промёрзший из-за упавшего интернета дом. А что нужно сделать, если готовые решения полностью не устраивают, правильно - написать самому...

Для объединенеия устройств был выбран протокол zigbee по двум причинам: во-первых он беспроводной, а на даче никто не желает тратится на лишние коммуникации, а во-вторых он довольно энергоэффективный и какой-нибудь датчик или выкючатель может питаться от батареи CR2032 несколько лет без необходимости подводить внешнее питание.

Изначальной идеей было прикрутить к проекту zigbee2mqtt свой мини-сервер и повесить на него всю логику по управлению умным домом(хотя скорее дачей). Минусы такого подхода: во-первых вторично, а во вторых это отдельно сервер, отдельно zigbee2mqtt и отдельно mqtt брокер для объединения, что очевидно надежности не добавляет. Поэтому первое, к написанию чего пришлось приступить - это собственная библиотека для работы с zigbee сетью на одном из самых популярных и доступных zigbee контроллеров cc2531 от Texas Instruments.

С документацией по zigbee чипам ситуация неоднозначная - она есть, она довольно подробная, многие нюансы хорошо описаны, из нее совершенно не понятно как все работает и что с этим делать. Так с cc2531 было понятно как организовать обмен командами с контроллером, но его инициализация и настройка в роли координатора не описаны нормально нигде. После безуспешных поисков по иностранным форумам я понял, что проще изучить работу на живом чипе, благо в SDK к нему есть утилита Z-tool, позволяющая в ручном режиме слать команды и смотреть ответы. 

Правда очень быстро выяснилось, что свежезапущенный контроллер не собирается как-то работать с ранее спаренными с ним устройствами(кнопка Xiaomi WXKG01LM и датчик температуры WSDCGQ01LM и т.д.).

При этом zigbee2mqtt корректно работал с ними обоими. Самым быстрым способом начать работать через Z-tool оказалось сначала запустить zigbee2mqtt, дать ему сделать все, что он должен сделать, а потом закрыть его и, не перезагружая контроллер, подключиться через утилиту. И, как ни странно, этот способ заработал.

В итоге довольно-таки быстро удалось понять логику работы при приеме сообщений, спаривании устройств и т.д., но двигаться дальше без инициализации было невозможно. 

В течении нескольких дней я копался в исходниках zigbee2mqtt, однако полностью логику понять не удалось, тем более она размыта между самим проектом Koenkk и его отдельными модулями для работы zigbee (zigbee-herdsman и zigbee-herdsman-converters). Решив, что лучше зайти со стороны реверс-инжинеринга zigbee2mqtt был запущен через сниффер порта Advanced Serial Port Monitor, а дамп его работы записан в файл.

Вскоре был написан парсер, выдававший что-то более читаемое из дампа и началась работа по воссозданию логики работы:

SYS_PING : ;
SYS_PING_SRSP : 0x79 0x7 ;
SYS_VERSION : ;
SYS_VERSION_SRSP : 0x2 0x2 0x2 0x7 0x2 0xd9 0x14 0x34 0x1 0x2 0x0 0x0 0x0 0x0 ;
SYS_OSAL_NV_READ : 0x60 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x1 0x55 ;
SYS_OSAL_NV_READ : 0x60 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x1 0x55 ;
SYS_OSAL_NV_READ : 0x84 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x4 0x0 0x8 0x0 0x0 ;
SYS_OSAL_NV_READ : 0x63 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x1 0x0 ;
SYS_OSAL_NV_READ : 0x83 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x2 0xff 0xff ;
SYS_OSAL_NV_READ : 0x83 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x2 0xff 0xff ;
UTIL_GET_DEVICE_INFO : ;
UTIL_GET_DEVICE_INFO_SRSP : 0x0 0xf0 0xa6 0x38 0x19 0x0 0x4b 0x12 0x0 0x0 0x0 0x7 0x9 0x4 0x7d 0x14 0x7a 0x43 0x82 0x85 0xa0 0x45 ;
ZDO_ACTIVE_EP_REQ : 0x0 0x0 0x0 0x0 ;
ZDO_ACTIVE_EP_SRSP : 0x0 ;
ZDO_ACTIVE_EP_RSP : 0x0 0x0 0x0 0x0 0x0 0xe 0xf2 0x2f 0xd 0xc 0x6e 0xb 0xa 0x8 0x6 0x5 0x4 0x3 0x2 0x1 ;
ZDO_ACTIVE_EP_REQ : 0x0 0x0 0x0 0x0 ;
ZDO_ACTIVE_EP_SRSP : 0x0 ;
ZDO_ACTIVE_EP_RSP : 0x0 0x0 0x0 0x0 0x0 0xe 0xf2 0x2f 0xd 0xc 0x6e 0xb 0xa 0x8 0x6 0x5 0x4 0x3 0x2 0x1 ;
UTIL_GET_DEVICE_INFO : ;
UTIL_GET_DEVICE_INFO_SRSP : 0x0 0xf0 0xa6 0x38 0x19 0x0 0x4b 0x12 0x0 0x0 0x0 0x7 0x9 0x4 0x7d 0x14 0x7a 0x43 0x82 0x85 0xa0 0x45 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0xf2 ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0xf2 0xe0 0xa1 0x5 0x0 0x0 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0x2f ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0x2f 0x4 0x1 0x5 0x0 0x0 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0xd ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0xa 0xd 0x4 0x1 0x5 0x0 0x0 0x1 0x19 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0xc ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0xc 0x5e 0xc0 0x5 0x0 0x0 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0x6e ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0x6e 0x4 0x1 0x5 0x0 0x0 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0xb ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0xe 0xb 0x4 0x1 0x0 0x4 0x0 0x1 0x1 0x5 0x2 0x0 0x5 0x2 0x5 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0xa ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0xa 0x4 0x1 0x5 0x0 0x0 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0x8 ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0x8 0x4 0x1 0x5 0x0 0x0 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0x6 ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0x6 0x9 0x1 0x5 0x0 0x0 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0x5 ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0x5 0x8 0x1 0x5 0x0 0x0 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0x4 ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0x4 0x7 0x1 0x5 0x0 0x0 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0x3 ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0x3 0x5 0x1 0x5 0x0 0x0 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0x2 ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0x2 0x1 0x1 0x5 0x0 0x0 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0x1 ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0x1 0x4 0x1 0x5 0x0 0x0 0x0 0x0 ;
SYS_VERSION : ;
SYS_VERSION_SRSP : 0x2 0x2 0x2 0x7 0x2 0xd9 0x14 0x34 0x1 0x2 0x0 0x0 0x0 0x0 ;
SYS_OSAL_NV_READ : 0x1 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x8 0xf0 0xa6 0x38 0x19 0x0 0x4b 0x12 0x0 ;
SYS_OSAL_NV_READ : 0x21 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x6e 0x4f 0x5 0x2 0x10 0x14 0x10 0x0 0x14 0x0 0x0 0x0 0x1 0x5 0x1 0x8f 0x7 0x0 0x2 0x5 0x1e 0x0 0x0 0xb 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x62 0x1a 0x8 0x0 0x8 0x0 0x0 0xf 0xf 0x4 0x0 0x1 0x0 0x0 0x0 0x1 0x0 0x0 0x0 0x0 0xf0 0xa6 0x38 0x19 0x0 0x4b 0x12 0x0 0x1 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x3c 0x3 0x0 0x1 0x78 0xa 0x1 0x0 0x0 0x1d 0xf 0x0 ;
SYS_OSAL_NV_READ : 0x83 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x2 0xff 0xff ;
SYS_OSAL_NV_READ : 0x2d 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x8 0xdd 0xdd 0xdd 0xdd 0xdd 0xdd 0xdd 0xdd ;
SYS_OSAL_NV_READ : 0x3a 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x11 0x0 0x1 0x3 0x5 0x7 0x9 0xb 0xd 0xf 0x0 0x2 0x4 0x6 0x8 0xa 0xc 0xd ;
SYS_OSAL_NV_READ : 0x3b 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x11 0x0 0x1 0x3 0x5 0x7 0x9 0xb 0xd 0xf 0x0 0x2 0x4 0x6 0x8 0xa 0xc 0xd ;
SYS_OSAL_NV_READ : 0x47 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x8 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 ;
SYS_OSAL_NV_READ : 0x62 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x10 0x1 0x3 0x5 0x7 0x9 0xb 0xd 0xf 0x0 0x2 0x4 0x6 0x8 0xa 0xc 0xd ;
SYS_OSAL_NV_READ : 0x63 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x1 0x0 ;
SYS_OSAL_NV_READ : 0x84 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x4 0x0 0x8 0x0 0x0 ;
SYS_OSAL_NV_READ : 0x11 0x1 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x13 0x1e 0x1 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0xff 0x0 0x0 ;
SYS_OSAL_NV_READ : 0x75 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0xc 0x35 0x34 0x2f 0x0 0xf0 0xa6 0x38 0x19 0x0 0x4b 0x12 0x0 ;
ZDO_MGMT_PERMIT_JOIN_REQ : 0xf 0xfc 0xff 0xfe 0x0 ;
ZDO_MGMT_PERMIT_JOIN_SRSP : 0x0 ;
ZDO_MGMT_PERMIT_JOIN_RSP : 0x0 0x0 0x0 ;
AF_DATA_CONFIRM : 0x0 0xf2 0x1 ;
SYS_PING : ;
SYS_PING_SRSP : 0x79 0x7 ;
SYS_VERSION : ;
SYS_VERSION_SRSP : 0x2 0x2 0x2 0x7 0x2 0xd9 0x14 0x34 0x1 0x2 0x0 0x0 0x0 0x0 ;
SYS_OSAL_NV_READ : 0x60 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x1 0x55 ;
SYS_OSAL_NV_READ : 0x60 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x1 0x55 ;
SYS_OSAL_NV_READ : 0x84 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x4 0x0 0x8 0x0 0x0 ;
SYS_OSAL_NV_READ : 0x63 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x1 0x0 ;
SYS_OSAL_NV_READ : 0x83 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x2 0xff 0xff ;
SYS_OSAL_NV_READ : 0x83 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x2 0xff 0xff ;
UTIL_GET_DEVICE_INFO : ;
UTIL_GET_DEVICE_INFO_SRSP : 0x0 0xf0 0xa6 0x38 0x19 0x0 0x4b 0x12 0x0 0xfe 0xff 0x7 0x0 0x0 ;
ZDO_STARTUP_FROM_APP : 0x64 0x0 ;
ZDO_STARTUP_FROM_APP_SRSP : 0x0 ;
ZDO_STATE_CHANGE_IND : 0x9 ;
ZDO_ACTIVE_EP_REQ : 0x0 0x0 0x0 0x0 ;
ZDO_ACTIVE_EP_SRSP : 0x0 ;
ZDO_ACTIVE_EP_RSP : 0x0 0x0 0x0 0x0 0x0 0x0 ;
AF_REGISTER : 0x1 0x4 0x1 0x5 0x0 0x0 0x0 0x0 0x0 ;
AF_REGISTER_SRSP : 0x0 ;
AF_REGISTER : 0x2 0x1 0x1 0x5 0x0 0x0 0x0 0x0 0x0 ;
AF_REGISTER_SRSP : 0x0 ;
AF_REGISTER : 0x3 0x5 0x1 0x5 0x0 0x0 0x0 0x0 0x0 ;
AF_REGISTER_SRSP : 0x0 ;
AF_REGISTER : 0x4 0x7 0x1 0x5 0x0 0x0 0x0 0x0 0x0 ;
AF_REGISTER_SRSP : 0x0 ;
AF_REGISTER : 0x5 0x8 0x1 0x5 0x0 0x0 0x0 0x0 0x0 ;
AF_REGISTER_SRSP : 0x0 ;
AF_REGISTER : 0x6 0x9 0x1 0x5 0x0 0x0 0x0 0x0 0x0 ;
AF_REGISTER_SRSP : 0x0 ;
AF_REGISTER : 0x8 0x4 0x1 0x5 0x0 0x0 0x0 0x0 0x0 ;
AF_REGISTER_SRSP : 0x0 ;
AF_REGISTER : 0xa 0x4 0x1 0x5 0x0 0x0 0x0 0x0 0x0 ;
AF_REGISTER_SRSP : 0x0 ;
AF_REGISTER : 0xb 0x4 0x1 0x0 0x4 0x0 0x0 0x1 0x1 0x5 0x2 0x0 0x5 0x2 0x5 ;
AF_REGISTER_SRSP : 0x0 ;
AF_REGISTER : 0x6e 0x4 0x1 0x5 0x0 0x0 0x0 0x0 0x0 ;
AF_REGISTER_SRSP : 0x0 ;
AF_REGISTER : 0xc 0x5e 0xc0 0x5 0x0 0x0 0x0 0x0 0x0 ;
AF_REGISTER_SRSP : 0x0 ;
AF_REGISTER : 0xd 0x4 0x1 0x5 0x0 0x0 0x0 0x1 0x19 0x0 0x0 ;
AF_REGISTER_SRSP : 0x0 ;
AF_REGISTER : 0x2f 0x4 0x1 0x5 0x0 0x0 0x0 0x0 0x0 ;
AF_REGISTER_SRSP : 0x0 ;
AF_REGISTER : 0xf2 0xe0 0xa1 0x5 0x0 0x0 0x0 0x0 0x0 ;
AF_REGISTER_SRSP : 0x0 ;
ZDO_ACTIVE_EP_REQ : 0x0 0x0 0x0 0x0 ;
ZDO_ACTIVE_EP_SRSP : 0x0 ;
ZDO_ACTIVE_EP_RSP : 0x0 0x0 0x0 0x0 0x0 0xe 0xf2 0x2f 0xd 0xc 0x6e 0xb 0xa 0x8 0x6 0x5 0x4 0x3 0x2 0x1 ;
UTIL_GET_DEVICE_INFO : ;
UTIL_GET_DEVICE_INFO_SRSP : 0x0 0xf0 0xa6 0x38 0x19 0x0 0x4b 0x12 0x0 0x0 0x0 0x7 0x9 0x4 0x7d 0x14 0x7a 0x43 0x82 0x85 0xa0 0x45 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0xf2 ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0xf2 0xe0 0xa1 0x5 0x0 0x0 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0x2f ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0x2f 0x4 0x1 0x5 0x0 0x0 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0xd ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0xa 0xd 0x4 0x1 0x5 0x0 0x0 0x1 0x19 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0xc ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0xc 0x5e 0xc0 0x5 0x0 0x0 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0x6e ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0x6e 0x4 0x1 0x5 0x0 0x0 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0xb ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0xe 0xb 0x4 0x1 0x0 0x4 0x0 0x1 0x1 0x5 0x2 0x0 0x5 0x2 0x5 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0xa ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0xa 0x4 0x1 0x5 0x0 0x0 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0x8 ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0x8 0x4 0x1 0x5 0x0 0x0 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0x6 ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0x6 0x9 0x1 0x5 0x0 0x0 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0x5 ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0x5 0x8 0x1 0x5 0x0 0x0 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0x4 ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0x4 0x7 0x1 0x5 0x0 0x0 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0x3 ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0x3 0x5 0x1 0x5 0x0 0x0 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0x2 ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0x2 0x1 0x1 0x5 0x0 0x0 0x0 0x0 ;
ZDO_SIMPLE_DESC_REQ : 0x0 0x0 0x0 0x0 0x1 ;
ZDO_SIMPLE_DESC_SRSP : 0x0 ;
ZDO_SIMPLE_DESC_RSP : 0x0 0x0 0x0 0x0 0x0 0x8 0x1 0x4 0x1 0x5 0x0 0x0 0x0 0x0 ;
SYS_VERSION : ;
SYS_VERSION_SRSP : 0x2 0x2 0x2 0x7 0x2 0xd9 0x14 0x34 0x1 0x2 0x0 0x0 0x0 0x0 ;
SYS_OSAL_NV_READ : 0x1 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x8 0xf0 0xa6 0x38 0x19 0x0 0x4b 0x12 0x0 ;
SYS_OSAL_NV_READ : 0x21 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x6e 0x4f 0x5 0x2 0x10 0x14 0x10 0x0 0x14 0x0 0x0 0x0 0x1 0x5 0x1 0x8f 0x7 0x0 0x2 0x5 0x1e 0x0 0x0 0xb 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x62 0x1a 0x8 0x0 0x8 0x0 0x0 0xf 0xf 0x4 0x0 0x1 0x0 0x0 0x0 0x1 0x0 0x0 0x0 0x0 0xf0 0xa6 0x38 0x19 0x0 0x4b 0x12 0x0 0x1 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x3c 0x3 0x0 0x1 0x78 0xa 0x1 0x0 0x0 0x1d 0xf 0x0 ;
SYS_OSAL_NV_READ : 0x83 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x2 0xff 0xff ;
SYS_OSAL_NV_READ : 0x2d 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x8 0xdd 0xdd 0xdd 0xdd 0xdd 0xdd 0xdd 0xdd ;
SYS_OSAL_NV_READ : 0x3a 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x11 0x0 0x1 0x3 0x5 0x7 0x9 0xb 0xd 0xf 0x0 0x2 0x4 0x6 0x8 0xa 0xc 0xd ;
SYS_OSAL_NV_READ : 0x3b 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x11 0x0 0x1 0x3 0x5 0x7 0x9 0xb 0xd 0xf 0x0 0x2 0x4 0x6 0x8 0xa 0xc 0xd ;
SYS_OSAL_NV_READ : 0x47 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x8 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 ;
SYS_OSAL_NV_READ : 0x62 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x10 0x1 0x3 0x5 0x7 0x9 0xb 0xd 0xf 0x0 0x2 0x4 0x6 0x8 0xa 0xc 0xd ;
SYS_OSAL_NV_READ : 0x63 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x1 0x0 ;
SYS_OSAL_NV_READ : 0x84 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x4 0x0 0x8 0x0 0x0 ;
SYS_OSAL_NV_READ : 0x11 0x1 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0x13 0x29 0x1 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0xff 0x0 0x0 ;
SYS_OSAL_NV_READ : 0x75 0x0 0x0 ;
SYS_OSAL_NV_READ_SRSP : 0x0 0xc 0x17 0x39 0x2f 0x0 0xf0 0xa6 0x38 0x19 0x0 0x4b 0x12 0x0 ;
ZDO_MGMT_PERMIT_JOIN_REQ : 0xf 0xfc 0xff 0xfe 0x0 ;
ZDO_MGMT_PERMIT_JOIN_SRSP : 0x0 ;
ZDO_MGMT_PERMIT_JOIN_RSP : 0x0 0x0 0x0 ;
AF_DATA_CONFIRM : 0x0 0xf2 0x1 ;

Итого, чтобы запустить ZNP в роли координатора минимально необходимо:

  • записать настройки в энергонезависимую память контроллера, по идее делается один раз при первом включении

  • перезагрузить

  • стартовать работу командой ZDO_STARTUP_FROM_APP

  • зарегистрировать стандартные эндпоинты (хватит одного под номером один для профиля домашней автоматизации  0x0104)

Изучение дампа обмена zigbee2mqtt не раз выручало меня и дальше при проблемах в инициализации подключаемых к сети устройств.

Через какое-то время zigbee-часть мини-сервера домашней автоматизации была написана на C++ с использованием Boost.Asio, а к ней прикручена логика, объединяющая устройства в функциональные узлы (например узел отопления может объединять реле и датчик температуры). Но всем этим нужно управлять как минимум для подключения устройств, а как максимум для полноценного контроля через интернет. В итоге протоколом для второго конца системы был выбран ... mqtt(через библиотеку mqtt_cpp), и мини-сервер в каком-то роде стал еще больше похож на проект zigbee2mqtt с которого все начиналось.

С исходниками и более детальным описанием проекта можно ознакомится на github.

Комментарии (11)


  1. dsoastro
    05.01.2022 16:36

    Я использовал стандартный zigbee2mqtt для автоматизации дачи. Столкнулся с тем, что иногда теряются устройства. Например, после того, как raspberry pi, на котором бегает zigbee2mqtt, выключается на пару часов (из-за отключение электричества) и затем включается обратно, может не подцепиться датчик температуры.

    Не знаете, это проблема самого датчика (например, в отсутствии устройства, принимающего его сигнал, он засыпает навсегда) или zigbee2mqtt?


    1. teleport_future
      05.01.2022 18:25
      +1

      ZigBee это "дохлая" лошадка по моему.


      1. kuprin_n Автор
        05.01.2022 19:00

        Я так не считаю. По моему мнению zigbee - лучший беспроводной протокол для бюджетной домашней автоматизации. Но есть одно но. Дело в том, что zigbee не работает сам по себе, над ним есть более высокоуровневая надстройка Zigbee Cluster Library (ZCL), которая определяет что, как и когда определенный тип устройств должен передавать и эта вещь в теории должна стандартизировать работу всех однотипных устройства разных производителей, что свело бы к минимуму проблемы и привело бы к полной совместимости между оборудованием разных фирм. А на практике в API беспроводных устройств творится полный хаос, причем изменения настолько нелогичные, что они, скорее всего, внесены специально с целью несовместимости с оборудованием других производителей.


    1. kuprin_n Автор
      05.01.2022 18:37
      +3

      Датчики от Xiaomi (возможно и других производителей тоже, но с другими я не работал) действительно засыпают. Дело в том, что устройства с батарейным питанием большую часть времени находятся в выключенном состоянии: радочасть обесточена, а практически весь контроллер спит и просыпается либо по таймеру, либо по сигналу переферии (например при физическом нажатии кнопки) и только после пробуждения происходит передача данных по zigbee. Скорее всего, несколько раз проснувшись, отправив данные на координатор и не получив подтверждение о их получении датчик температуры понимает, что сети больше нет и перестает просыпаться. Конкретно датчики температуры и освещенности Xiaomi обратно подключаются к сети при кратковременном нажатии на кнопку сопряжения (для выключателей работет нажатие на основную кнопку, для датчика открытия - манипуляция с магнитом) и продолжают работать дальше. Но есть нюанс: при повторном подключении на координаторе происходит индикация подключения(ZDO_TC_DEV_IND) устройства как при сопряжении, и вот тут zigbee2mqtt может некорректно обрабатывать этот момент (в моей библиотеке такая ситуация учтена).


    1. tmin10
      05.01.2022 21:55

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


  1. uyrij
    06.01.2022 13:32

    Не хватает сопоставления с HA например с таким https://github.com/CCOSTAN/Home-AssistantConfig


    1. kuprin_n Автор
      06.01.2022 15:27

      Сложно сравнить с Home Assistant, слишком много различий, проще кратко описать особенности:

      • весь сервер представляет из себя бинарник (собирается под linux и windows) + yaml файл настроек

      • графический интерфейс отсутствует, управление идет через mqtt: предполагается использование готовых mqtt панелей под iOS или Android

      • до запуска сервера в файле настраивается порт для cc2530(2531), учетка mqtt брокера и колличество/тип точек автоматизации (освещение, отопление, сигнализация)

      • используется только протокол zigbee

      • при сопряжении устройства zigbee пользователь выбирает только точку автоматизации для привязки, тип, роль и настройки устройства сервер выбрает сам


  1. dukinm
    06.01.2022 13:47

    Спасибо за статью, давно не читал что-то настолько интересное!


  1. aronsky
    06.01.2022 16:00

     Минусы такого подхода: во-первых вторично, а во вторых это отдельно сервер, отдельно zigbee2mqtt и отдельно mqtt брокер для объединения, что очевидно надежности не добавляет

    Что значит 'вторично'? Почему это минус?

    Насчёт надёжности - тоже сомнительно. Да, несколько узлов, но только все узлы протестированы гораздо большим количеством пользователей, чем ваш код.


    1. kuprin_n Автор
      06.01.2022 16:25

      Минус потому что не было желания повторять чьё-то решение, а насчет надежности я соглашусь, тем более проект писался после перерыва в кодинге и предназначался изначально для портфолио


  1. Intelligent
    08.01.2022 02:50

    У меня такая же идея была для ESP32-H2, если выйдет.