Category (Click)
개발보드 덕질하기

[ESP32-C6] ZigBee로 Home Assistant Z2M에 연동하기

 차완기 - @3/19/2023, 2:54:00 AM
지난 ESP32-C6-DevKitC-1 핸즈온에서 기본 HA 프로필에서는 ZCL의 모든 cluster attribute가 정의되어 있지 않아 Home Assistant(HA)에서 ZigBee 디바이스를 사용하기 위한 브릿지인 Zigbee2MQTT(Z2M)의 사용이 불가능하다는 결론을 내렸습니다. 그렇게 포기했었는데, koreassistant 네이버 카페의 한 능력자분께서 가능하다는 댓글을 남겨주셔서 테스트해 보기로 하였습니다.
ESP-IDF의 Issue의 답글 중 하나였는데요, Issue 자체는 지그비 채널과 관련된, HA와의 연결과는 다른 내용이기는 하지만 댓글을 남겨주신 분께서 HA에서 사용하는 예시를 첨부해 주셨습니다. ESP-Zigbee-SDK의 HA 프로필 API를 사용하지 않고 cluster와 attribute를 직접 생성하는 방식을 사용하고 있었습니다.
*참고로 esp_zb_set_network_channel()은 5818e5d 커밋에서 삭제되고 esp_zb_set_primary_network_channel_set()가 추가되었습니다.
남겨주신 내용을 바탕으로 HA_on_off_light 예제를 수정해 보았습니다.

cluster attribute list 생성

ESP-Zigbee-SDK의 device 생성 과정을 정리하자면 가장 작은 단위인 cluster의 attribute를 list로 묶어 cluster를 설정하고, 이걸 cluster list로 묶어 endpoint를 설정하고, endpoint들을 또 list로 묶어 device를 구성하는 과정을 거칩니다.
우선 각 클러스터의 attribute들은 esp_zb_attribute_list_t 구조체에 저장되며 순차적 배열로 저장되지 않고 멤버 포인터 변수 next에 다음 attribute 포인터가 저장되게끔 되어있습니다. 다른 list들도 이러한 구조로 되어있는데, 굳이 이렇게 한 이유는 대부분의 ZigBee 장치가 저전력 디바이스임을 고려해 메모리 단편화, 메모리 복사 등으로 비용(오버헤드)이 높은 realloc()를 사용하지 않기 위한 것 같습니다.
좌 - 1번 방식 리스트 생성 함수, 우 - 2번 방식 리스트 생성 함수
cluster attribute를 생성할 때에는 크게 두 가지 방식이 있습니다. (1)비어있는 attribute list를 생성해 esp_zb_basic_cluster_add_attr() 함수로 모든 attribute를 입력하거나 (2)ZCL(Zigbee Cluster Library)에서 mandatory로 명시된 데이터를 함수 인자 값으로 전달해 mandatory attribute로만 구성된 list를 생성하는 것입니다.
저는 개인적으로 후자의 방법을 선호하기 때문에 기본값은 0x00으로 설정하기 위해 esp_zb_basic_cluster_create() 함수에 NULL을 전달해 attribute list를 생성한 후, 미리 만들어진 attribute에 대해서는 esp_zb_cluster_update_attr() 함수를 이용해 값을 수정하였습니다.
ModelIdentifier와 ManufacturerName attribute는 Z2M 커스텀 디바이스 추가 과정에서 필수적이기 때문에 추가해 주었습니다.
이때 위 두 attribute들은 string 타입이었는데, ZCL에서 정한 string 규칙을 따르기 위해 입력받은 리터럴 문자열 const char *를 규칙이 적용된 char *로 변환하는 함수를 만들었습니다.
ChatGPT에 개념을 설명하고 "해줘" 하니 비슷하게 만들어주네요. 적당히 수정해 사용했습니다.
참고로 attribute를 list에 추가한 후 데이터를 넘기기 위해 생성한 메모리를 해제해도 작동에 문제가 없었습니다. 아마 추가할 때 데이터를 리스트에 복사하는 것 같은데, 내부 코드를 공개하지 않아 정확하게는 모르겠습니다.
나머지 cluster attribute list들도 동일한 방법으로 생성했습니다.

cluster list 생성

다음으로 이렇게 만들어진 attribute list들을 cluster list에 추가하였습니다. 이때 Z2M은 cluster 제어에 HA가 MQTT로 개입하기 위해 ZigBee의 binding 기능을 사용하지 않고, 자동 생성되는 endpoint가 client가 되어 attribute에 command를 보내기 때문에 cluster는 server로 생성되어야 합니다. 물론 Z2M에서는 binding 기능 역시 지원해 필요하다면 coordinator를 거치지 않고도 제어가 가능합니다.

endpoint list 생성 및 device 생성, zigbee 시작

위에서 묶은 cluster list를 endpoint list에 추가합니다. 여기서는 하나의 endpoint만 존재하기 때문에 코드가 짧네요. 그 후 디바이스를 생성하고 zigbee 콜백 등록, 지그비 시작, 무한 루프 시작 과정을 거치게 됩니다.

attribute callback

앞서 언급했듯이 Z2M에서는 client endpoint를 자동 생성해 server인 디바이스를 attribute command를 통해 제어하는데요, attribute command가 도착했을 때 실행하는 callback이 위의 부분입니다.
전체 예제는 링크를 참고해 주세요.

Z2M 커스텀 디바이스 추가

const fz = require('zigbee-herdsman-converters/converters/fromZigbee'); const tz = require('zigbee-herdsman-converters/converters/toZigbee'); const exposes = require('zigbee-herdsman-converters/lib/exposes'); const reporting = require('zigbee-herdsman-converters/lib/reporting'); const extend = require('zigbee-herdsman-converters/lib/extend'); const e = exposes.presets; const ea = exposes.access; const definition = { zigbeeModel: ['ESP32C6_TestLight'], model: 'ESP32C6_TestLight', vendor: 'Espressif', description: 'light_bulb', fromZigbee: [fz.on_off], toZigbee: [tz.on_off], exposes: [e.switch()], // The configure method below is needed to make the device reports on/off state changes // when the device is controlled manually through the button on it. configure: async (device, coordinatorEndpoint, logger) => { const endpoint = device.getEndpoint(1); await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']); await reporting.onOff(endpoint); }, }; module.exports = definition;
JavaScript
복사
Z2M의 가이드라인을 참고해 컨버터 파일을 추가하였습니다. 그 후 Z2M을 재시작하면 ESP32-C6 장치를 인식할 수 있습니다.
잘 작동하네요!

attribute 변경

스위치를 이용해 물리적으로 조명을 제어한다면 서버에 상태를 보고해야겠죠. server인 디바이스에서 자신의 attribute를 변경하면 Z2M의 client가 report 주기에 맞춰 이를 읽는 방식으로 attribute report가 진행됩니다. 실시간으로 변경 사항이 반영되도록 하기 위해서는 Z2M의 report 주기를 변경해야 합니다.
위 사진처럼 수동으로 주기를 변경하거나 앞서 Z2M에 추가하는 컨버터 파일에서 minimumReportInterval를 0으로 설정하면 장치 등록과 동시에 자동으로 반영됩니다.
esp_zb_zcl_set_attribute_val() 함수를 사용해 특정 endpoint에 속한 cluster의 attribute를 변경할 수 있습니다. 위와 같이 5초 간격으로 attribute를 수정하는 task를 생성해 추가해 보았습니다.
전체 코드는 링크를 참고해 주세요.
여기까지 ESP32-C6에서 ESP-Zigbee-SDK를 이용해 Home Assistant의 Z2M에 연결하는 과정을 정리해 보았습니다. ZigBee에 대한 지식이 부족해 개념을 이해하고 대응하는 API를 찾느라 시간이 많이 걸린 것 같습니다.
이제 다음 프로젝트부터는 HA 연결에 MQTT 대신 ZigBee를 애용해야겠습니다.
다시 한번 가능함을 알려주신 koreassistant 카페 능력자분께 감사의 말씀드립니다 :)