YOGYUI

[PROJ] Matter::LevelControl 클러스터 개발 예제 (ESP32) 본문

PROJECT

[PROJ] Matter::LevelControl 클러스터 개발 예제 (ESP32)

요겨 2023. 3. 28. 00:46
반응형

Matter - LevelControl Cluster Developing Example using ESP32 SoC

다음으로, 밝기 조절이 가능한 조명을 제어할 수 있는 LevelControl 클러스터(cluster)를 구현해보자

마찬가지로, 일전에 개발한 색상 및 밝기 변경이 가능한 WS2812 x16 Array 모듈을 활용한다

https://yogyui.tistory.com/entry/PROJ-Dimmable-WS2812S-RGB-LED-%EB%AA%A8%EB%93%88-%EC%A0%9C%EC%9E%91-2

 

[PROJ] Dimmable WS2812S RGB LED 모듈 제작 - (2)

5. MCU 선정 및 HW 연결 2023년 1월부터 시작한 Matter 프로젝트는 EspressIf사의 ESP32 SoC를 메인 타겟으로 개발해왔기에, 앞으로 Matter 관련 포스팅도 (일단은) ESP32 위주로 작성해보려 한다 프로토타이핑

yogyui.tistory.com

1. 소스코드 커밋

  • 저장소명: matter-esp32-ws2812
  • 태그명: cluster-levelcontrol

https://github.com/YOGYUI/matter-esp32-ws2812/tree/cluster-levelcontrol

 

GitHub - YOGYUI/matter-esp32-ws2812

Contribute to YOGYUI/matter-esp32-ws2812 development by creating an account on GitHub.

github.com

2. LevelControl Light 클래스 구현

OnOff Light와 마찬가지로, CDevice를 상속하여 구현한 뒤, Matter 엔드포인트(endpoint) 생성 및 추가 메서드 및 Matter 어트리뷰트(attribute) 변경 콜백 함수에 대한 메서드만 오버라이드해서 구현해주면 된다

2.1. device_levelcontrol_light.h

class CDeviceLevelControlLight : public CDevice
{
public:
    CDeviceLevelControlLight();

    bool matter_add_endpoint() override;
    bool matter_init_endpoint() override;
    void matter_on_change_attribute_value(
        esp_matter::attribute::callback_type_t type,
        uint32_t cluster_id,
        uint32_t attribute_id,
        esp_matter_attr_val_t *value
    ) override;
    void matter_update_all_attribute_values() override;

public:
    void toggle_state_action() override;

private:
    bool m_matter_update_by_client_clus_onoff_attr_onoff;
    bool m_matter_update_by_client_clus_levelcontrol_attr_currentlevel;

    void matter_update_clus_onoff_attr_onoff();
    void matter_update_clus_levelcontrol_attr_currentlevel();
};

2.2. device_levelcontrol_light.cpp

bool CDeviceLevelControlLight::matter_add_endpoint()
{
    esp_matter::node_t *root = GetSystem()->get_root_node();
    esp_matter::endpoint::dimmable_light::config_t config_endpoint;
    config_endpoint.on_off.on_off = false;
    config_endpoint.on_off.lighting.start_up_on_off = nullptr;
    config_endpoint.level_control.current_level = m_state_brightness;
    config_endpoint.level_control.lighting.min_level = 1;
    config_endpoint.level_control.lighting.max_level = 254;
    config_endpoint.level_control.lighting.start_up_current_level = m_state_brightness;
    uint8_t flags = esp_matter::ENDPOINT_FLAG_DESTROYABLE;
    m_endpoint = esp_matter::endpoint::dimmable_light::create(root, &config_endpoint, flags, nullptr);
    if (!m_endpoint) {
        GetLogger(eLogType::Error)->Log("Failed to create endpoint");
        return false;
    }

    return CDevice::matter_add_endpoint();
}

void CDeviceLevelControlLight::matter_on_change_attribute_value(esp_matter::attribute::callback_type_t type, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *value)
{
    if (type == esp_matter::attribute::callback_type_t::PRE_UPDATE) {
        if (cluster_id == chip::app::Clusters::OnOff::Id) {
            if (attribute_id == chip::app::Clusters::OnOff::Attributes::OnOff::Id) {
                GetLogger(eLogType::Info)->Log("MATTER::PRE_UPDATE >> cluster: OnOff(0x%04X), attribute: OnOff(0x%04X), value: %d", cluster_id, attribute_id, value->val.b);
                if (!m_matter_update_by_client_clus_onoff_attr_onoff) {
                    m_state_onoff = value->val.b;
                    if (m_state_onoff) {
                        GetWS2812Ctrl()->set_brightness(m_state_brightness);
                    } else {
                        GetWS2812Ctrl()->set_brightness(0);
                    }
                } else {
                    m_matter_update_by_client_clus_onoff_attr_onoff = false;
                }
            }
        } else if (cluster_id == chip::app::Clusters::LevelControl::Id) {
            if (attribute_id == chip::app::Clusters::LevelControl::Attributes::CurrentLevel::Id) {
                GetLogger(eLogType::Info)->Log("MATTER::PRE_UPDATE >> cluster: LevelControl(0x%04X), attribute: CurrentLevel(0x%04X), value: %d", cluster_id, attribute_id, value->val.u8);
                if (!m_matter_update_by_client_clus_levelcontrol_attr_currentlevel) {
                    m_state_brightness = value->val.u8;
                    GetWS2812Ctrl()->set_brightness(value->val.u8);
                } else {
                   m_matter_update_by_client_clus_levelcontrol_attr_currentlevel = false;
                }
            }
        }
    }
}

LevelControl 클러스터는 OnOff 클러스터와는 달리 CurrentLevel, RemainingTime, MinLevel, MaxLevel 등 총 14개의 속성(attribute)를 가질 수 있다 

※ matter 1.0에서 PWM 관련 주파수(frequency) 속성들은 아직 사용할 수 없음

이 중 CurrentLevel, MinLevel, MaxLevel은 조명의 밝기 등 'level' 관련 속성들인데, 8비트 데이터형으로 0 ~ 254 총 255단계의 레벨을 가질 수 있다 (MinLevel, MaxLevel 속성을 통해, 최소값과 최대값을 변경할 수 있다)

단, 조명 관련 클러스터일 경우 최소값은 1이어야 하며, On/Off 클러스터를 통해 켜고 끄는 동작을 함께 수행해야 한다 

※ OnOff 및 LevelControl이 보통 함께 구현되므로, state transition이 다소 복잡하다

 

레벨 변경에 대한 명령은 LevelControl 클러스터(id: 0x0008)의 CurrentLevel 어트리뷰트(id: 0x0000)의 값을 변경하게 되므로 이에 대한 처리만 받아서 WS2812의 전원단에 물린 LED 드라이버의 PWM duty를 위와 같이 변경해주면 된다

 

엔드포인트 생성 시, esp-matter의 dimmable-light namespace를 활용했는데, sdk의 소스코드 내부는 다음과 같다

endpoint_t *add(endpoint_t *endpoint, config_t *config)
{
    if (!endpoint) {
        ESP_LOGE(TAG, "Endpoint cannot be NULL");
        return NULL;
    }
    add_device_type(endpoint, get_device_type_id(), get_device_type_version());

    descriptor::create(endpoint, CLUSTER_FLAG_SERVER);
    cluster_t *identify_cluster = identify::create(endpoint, &(config->identify), CLUSTER_FLAG_SERVER);
    identify::command::create_trigger_effect(identify_cluster);
    groups::create(endpoint, &(config->groups), CLUSTER_FLAG_SERVER);
    scenes::create(endpoint, &(config->scenes), CLUSTER_FLAG_SERVER);
    on_off::create(endpoint, &(config->on_off), CLUSTER_FLAG_SERVER, on_off::feature::lighting::get_id());
    level_control::create(endpoint, &(config->level_control), CLUSTER_FLAG_SERVER,
                          level_control::feature::on_off::get_id() | level_control::feature::lighting::get_id());

    return endpoint;
}

dimmable_light는 on_off 클러스터와 level_control 클러스터를 함께 엔드포인트에 추가하게 된다

level_control 클러스터 생성 함수 내부를 보면

cluster_t *create(endpoint_t *endpoint, config_t *config, uint8_t flags, uint32_t features)
{
    cluster_t *cluster = cluster::create(endpoint, LevelControl::Id, flags);
    if (!cluster) {
        ESP_LOGE(TAG, "Could not create cluster");
        return NULL;
    }

    if (flags & CLUSTER_FLAG_SERVER) {
        set_plugin_server_init_callback(cluster, MatterLevelControlPluginServerInitCallback);
        add_function_list(cluster, function_list, function_flags);
    }
    if (flags & CLUSTER_FLAG_CLIENT) {
        set_plugin_client_init_callback(cluster, MatterLevelControlPluginClientInitCallback);
        create_default_binding_cluster(endpoint);
    }

    if (flags & CLUSTER_FLAG_SERVER) {
        /* Attributes managed internally */
        global::attribute::create_feature_map(cluster, 0);

        /* Attributes not managed internally */
        if (config) {
            global::attribute::create_cluster_revision(cluster, config->cluster_revision);
            attribute::create_current_level(cluster, config->current_level);
            attribute::create_on_level(cluster, config->on_level);
            attribute::create_options(cluster, config->options, 0x0, 0x3);
        } else {
            ESP_LOGE(TAG, "Config is NULL. Cannot add some attributes.");
        }
    }

    /* Commands */
    command::create_move_to_level(cluster);
    command::create_move(cluster);
    command::create_step(cluster);
    command::create_stop(cluster);
    command::create_move_to_level_with_on_off(cluster);
    command::create_move_with_on_off(cluster);
    command::create_step_with_on_off(cluster);
    command::create_stop_with_on_off(cluster);

    /* Features */
    if (features & feature::on_off::get_id()) {
        feature::on_off::add(cluster);
    }
    if (features & feature::lighting::get_id()) {
        feature::lighting::add(cluster, &(config->lighting));
    }

    return cluster;
}

기본적으로 요구되는 CurrentLevel, MinLevel, MaxLevel 세가지 어트리뷰트를 자동으로 추가해주게 된다

(MoveToLevel, Move, Step, Stop 등 matter spec에 기재된 9개의 커맨드 중 8개를 기본으로 추가해준다, 1개는 PWM 관련 주파수 제어 명령이라 제외)

2.3. system.cpp

bool CSystem::initialize()
{
    // create matter root node
    esp_matter::node::config_t node_config;
    snprintf(node_config.root_node.basic_information.node_label, sizeof(node_config.root_node.basic_information.node_label), PRODUCT_NAME);
    m_root_node = esp_matter::node::create(&node_config, matter_attribute_update_callback, matter_identification_callback);
    if (!m_root_node) {
        return false;
    }

    // prevent endpoint id increment when board reset
    matter_set_min_endpoint_id(1);
    
    // start matter
    ret = esp_matter::start(matter_event_callback);
    if (ret != ESP_OK) {
        return false;
    }
    
    GetWS2812Ctrl()->initialize(GPIO_PIN_WS2812_DATA, WS2812_ARRAY_COUNT);
    // set matter endpoints
    CDevice *dev = nullptr;
#if LIGHT_TYPE == 0
    dev = new CDeviceOnOffLight();
#elif LIGHT_TYPE == 1
    dev = new CDeviceLevelControlLight();
#endif
    if (dev && dev->matter_add_endpoint()) {
        m_device_list.push_back(dev);
    } else {
        return false;
    }

    return true;
}

definition.h 헤더파일에 정의해둔 LIGHT_TYPE 변수값에 따라 디바이스 객체 생성을 조건문으로 분기했다 

3. Commissioning & Performance Test

OnOff 클러스터 예제때와 마찬가지로 mfg_tool로 임의 생성한 DAC Provider를 사용

  • Vendor ID: 0xFFF2
  • Product ID: 0x8001

3.1. QR Code

3.2. Apple Home 기기 추가

OnOff 클러스터때와 마찬가지로 '전등' 액세서리가 추가되는데, 액세서리를 터치해보면 밝기 조절 인터페이스가 팝업된다

3.3. 기기 제어 데모 

꽤나 중요한 문제를 이제서야 발견했다..

PWM duty를 낮게 설정 (밝기를 어둡게)하면, LED 드라이버에서 WS2812의 전원단으로 인가되는 전압 레벨이 일정 수준 이상으로 낮아져서 녹색(Green)과 청색(Blue) LED가 켜지지 않는 문제가 발생한다 ... 즉, 어두운 밝기에서는 적색(Red)만 발광되고 있다 ㅠ

WS2812 데이터시트에 LED별 전원 특성은 못본거 같은데 흑...

일단 데이터라인 자체는 문제가 없는 걸 확인했으니, LED 드라이버를 좀 더 좋은걸로 구해봐야겠다

3.4. CHIP 콘솔 로그


[Matter endpoint 초기화]

esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0003's Attribute 0x0001 is 0 ******
esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0004's Attribute 0x0000 is 128 ******
esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0004's Attribute 0xFFFC is <invalid type: 0> 
esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0005's Attribute 0x0000 is 0 ******
esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0006's Attribute 0xFFFC is 1 ******
esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0006's Attribute 0x4003 is null *****
esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0006's Attribute 0x0000 is 0 ******
chip[ZCL]: On/Off set value: 1 0
esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0006's Attribute 0x0000 is 0 ******
chip[ZCL]: On/off already set to new value
esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0008's Attribute 0x0002 is 1 ******
esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0008's Attribute 0x0003 is 100 *****
esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0008's Attribute 0xFFFC is 3 ******
esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0008's Attribute 0x0000 is 1 ******
esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0008's Attribute 0x4000 is 1 ******
esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0008's Attribute 0x0000 is 1 ******
esp_matter_core: Dynamic endpoint 1 added

 

[Brightness 변경 명령]

I (136687) chip[EM]: <<< [E:36575i M:12841252] (S) Msg TX to 1:73D697EBCDD128E2 [1927] --- Type 0001:05 (IM:ReportData)
I (136687) chip[IN]: (S) Sending msg 12841252 on secure session with LSID: 28427
I (136697) chip[DMG]: Refresh Subscribe Sync Timer with min 0 seconds and max 4 seconds
I (136747) chip[EM]: >>> [E:36575i M:75793572 (Ack:12841252)] (S) Msg RX from 1:73D697EBCDD128E2 [1927] --- Type 0001:01 (IM:StatusResponse)
I (136747) chip[IM]: Received status response, status is 0x00
I (136767) chip[EM]: <<< [E:36575i M:12841253 (Ack:75793572)] (S) Msg TX to 1:73D697EBCDD128E2 [1927] --- Type 0000:10 (SecureChannel:StandaloneAck)
I (136777) chip[IN]: (S) Sending msg 12841253 on secure session with LSID: 28427
I (137867) chip[EM]: >>> [E:50510r M:75793573] (S) Msg RX from 1:73D697EBCDD128E2 [1927] --- Type 0001:08 (IM:InvokeCommandRequest)
I (137877) esp_matter_command: Received command 0x00000004 for endpoint 0x0001's cluster 0x00000008

>> 1번 endpoint의 0x0008 클러스터(LevelControl)에 0x04 커맨드(MoveToLevelWithOnOff)

I (137887) chip[ZCL]: 0x3f40de70MOVE_TO_LEVEL_WITH_ON_OFF fe  0 0 0
I (137887) esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0008's Attribute 0x0000 is 145 **********

>> 1번 endpoint, 0x0008 클러스터(LevelControl)의 0x0000 어트리뷰트(CurrentLevel)의 값은 145
I (137897) chip[ZCL]: Setting on/off to 0x3f428a1f due to level change
I (137917) chip[ZCL]: On/Off set value: 1 1
I (137917) esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0006's Attribute 0x0000 is 1 **********
I (137927) chip[ZCL]: On/off already set to new value
I (137927) esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0008's Attribute 0xFFFC is 3 **********
I (137937) esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0006's Attribute 0x4000 is 1 **********
I (137957) logger: [CSystem::matter_attribute_update_callback] attribute update callback > type: 0, endpoint_id: 1, cluster_id: 0x0006, attribute_id: 0x4000 [system.cpp:417]
I (137967) logger: [CSystem::matter_attribute_update_callback] attribute update callback > type: 1, endpoint_id: 1, cluster_id: 0x0006, attribute_id: 0x4000 [system.cpp:417]
I (137987) chip[EM]: <<< [E:50510r M:12841254 (Ack:75793573)] (S) Msg TX to 1:73D697EBCDD128E2 [1927] --- Type 0001:09 (IM:InvokeCommandResponse)
I (137997) chip[IN]: (S) Sending msg 12841254 on secure session with LSID: 28427
E (138017) chip[DL]: Long dispatch time: 148 ms, for event type 3
I (138017) esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0006's Attribute 0x4000 is 1 **********
I (138027) chip[EM]: <<< [E:36576i M:12841255] (S) Msg TX to 1:73D697EBCDD128E2 [1927] --- Type 0001:05 (IM:ReportData)
I (138037) chip[IN]: (S) Sending msg 12841255 on secure session with LSID: 28427
I (138047) chip[DMG]: Refresh Subscribe Sync Timer with min 0 seconds and max 4 seconds
I (138057) esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0008's Attribute 0x0000 is 145 **********
I (138057) chip[ZCL]: Event: move from 145
I (138077) chip[ZCL]:  to 254 
I (138077) chip[ZCL]: (diff +1)

>> CurrentLevel 밝기 값이 145에서 254(최대)로 변경
I (138077) esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0008's Attribute 0x0000 is 254 **********
I (138087) logger: [CSystem::matter_attribute_update_callback] attribute update callback > type: 0, endpoint_id: 1, cluster_id: 0x0008, attribute_id: 0x0000 [system.cpp:417]
I (138097) logger: [CDeviceLevelControlLight::matter_on_change_attribute_value] MATTER::PRE_UPDATE >> cluster: LevelControl(0x0008), attribute: CurrentLevel(0x0000), value: 254 [device_levelcontrol_light.cpp:64]
I (138137) logger: [CMemory::save_ws2812_brightness] save <ws2812 brightness> to memory: 254 [memory.cpp:103]
I (138137) logger: [CWS2812Ctrl::set_pwm_duty] set pwm duty: 398 [ws2812.cpp:111]
I (138167) logger: [CSystem::matter_attribute_update_callback] attribute update callback > type: 1, endpoint_id: 1, cluster_id: 0x0008, attribute_id: 0x0000 [system.cpp:417]
I (138177) esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0008's Attribute 0x000F is 0 **********
I (138187) esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0005's Attribute 0x0003 is 0 **********
I (138197) logger: [CSystem::matter_attribute_update_callback] attribute update callback > type: 0, endpoint_id: 1, cluster_id: 0x0005, attribute_id: 0x0003 [system.cpp:417]
I (138207) logger: [CSystem::matter_attribute_update_callback] attribute update callback > type: 1, endpoint_id: 1, cluster_id: 0x0005, attribute_id: 0x0003 [system.cpp:417]
I (138237) chip[ZCL]: Setting on/off to 0x3f428a1f due to level change
I (138237) chip[ZCL]: On/Off set value: 1 1
I (138237) esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0006's Attribute 0x0000 is 1 **********
I (138247) chip[ZCL]: On/off already set to new value
I (138267) esp_matter_attribute: ********** Endpoint 0x0001's Cluster 0x0008's Attribute 0x0001 is 0 **********
I (138277) logger: [CSystem::matter_attribute_update_callback] attribute update callback > type: 0, endpoint_id: 1, cluster_id: 0x0008, attribute_id: 0x0001 [system.cpp:417]
I (138287) logger: [CSystem::matter_attribute_update_callback] attribute update callback > type: 1, endpoint_id: 1, cluster_id: 0x0008, attribute_id: 0x0001 [system.cpp:417]


확실히 Matter 통신은 주고받는 데이터가 자체 네트워크 레이어상에서 보안화되어 송수신되기도 하고, 워낙에 디바이스끼리 주고받는 메시지가 많아서인지,  내가 홈네트워크때 구현했던 Homebridge + MQTT message 방식에 비해 반응속도가 엄청 느리다.. 

(아직 Matter 소스코드 자체가 한창 개발중이고, 3월 중에는 1.1 버전도 릴리즈예정이라 개선의 여지가 있긴 하지만..)

 

이제 LED 드라이버 기다리는 동안 ColorControl 클러스터도 열심히 구현해봐야겠다

※ 일단 ColorControl 클러스터까지만 CHIP v1.0.0.2로 작업하고, 다음번 작업부터는 CHIP v1.1이 릴리즈되면 코드 공부 좀 한 다음에 계속 진행할 예정

반응형
Comments