일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- MQTT
- matter
- 파이썬
- 미국주식
- 국내주식
- 애플
- 코스피
- raspberry pi
- cluster
- 월패드
- Python
- RS-485
- Apple
- 현대통신
- 해외주식
- 티스토리챌린지
- ConnectedHomeIP
- 나스닥
- esp32
- 배당
- SK텔레콤
- 공모주
- 오블완
- Espressif
- homebridge
- 힐스테이트 광교산
- 홈네트워크
- Bestin
- 매터
- Home Assistant
- Today
- Total
YOGYUI
[PROJ] Matter::ColorControl 클러스터 개발 예제 (ESP32) 본문
Matter - ColorControl Cluster Developing Example using ESP32 SoC
Matter의 조명과 관련된 클러스터 중 가장 내용이 방대한 ColorControl 클러스터(cluster)를 지난번 LevelControl 클러스터 개발 예제때와 마찬가지로 WS2812 테스트보드에 구현 후 테스트해보자
[PROJ] Matter::LevelControl 클러스터 개발 예제 (ESP32)
Color Control 클러스터의 Matter Spec은 아래 링크에서 참고
Matter Specification - Color Control Cluster
1. 소스코드 커밋
- 저장소명: matter-esp32-ws2812
- commit id: 56df82be9a5cebb9412d925e405f0e9ad02b13dc
※ 별도로 태그를 달지 않고 main 브랜치에 통합 - 2023년 5월 18일에 Matter 1.1이 공식 release되었는데, 이에 맞춰서 SDK 버전도 함께 업데이트
https://github.com/YOGYUI/matter-esp32-ws2812
2. ColorControl Light 클래스 구현
밝기에 해당하는 'level', 색상에 해당하는 'hue(색상)', 'saturation(채도)'의 속성값 변경 명령에 대한 응답 코드를 작성해야 한다
2.1. device_colorcontrol_light.h
class CDeviceColorControlLight : public CDevice
{
public:
CDeviceColorControlLight();
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;
bool m_matter_update_by_client_clus_colorcontrol_attr_currenthue;
bool m_matter_update_by_client_clus_colorcontrol_attr_currentsaturation;
void matter_update_clus_onoff_attr_onoff();
void matter_update_clus_levelcontrol_attr_currentlevel();
void matter_update_clus_colorcontrol_attr_currenthue();
void matter_update_clus_colorcontrol_attr_currentsaturation();
};
2.2. device_colorcontrol_light.cpp
#include "device_colorcontrol_light.h"
#include "system.h"
#include "logger.h"
#include "ws2812.h"
#include <esp_matter_endpoint.h>
#include <esp_matter_attribute_utils.h>
CDeviceColorControlLight::CDeviceColorControlLight()
{
m_matter_update_by_client_clus_onoff_attr_onoff = false;
m_matter_update_by_client_clus_levelcontrol_attr_currentlevel = false;
m_matter_update_by_client_clus_colorcontrol_attr_currenthue = false;
m_matter_update_by_client_clus_colorcontrol_attr_currentsaturation = false;
m_state_brightness = MAX(1, GetWS2812Ctrl()->get_brightness());
m_state_onoff = m_state_brightness ? true : false;
}
bool CDeviceColorControlLight::matter_add_endpoint()
{
esp_matter::node_t *root = GetSystem()->get_root_node();
esp_matter::endpoint::extended_color_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::extended_color_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();
}
bool CDeviceColorControlLight::matter_init_endpoint()
{
esp_err_t ret;
esp_matter::cluster::color_control::feature::hue_saturation::config_t cfg;
cfg.current_hue = 0;
cfg.current_saturation = 0;
esp_matter::cluster_t *cluster = esp_matter::cluster::get(m_endpoint, chip::app::Clusters::ColorControl::Id);
ret = esp_matter::cluster::color_control::feature::hue_saturation::add(cluster, &cfg);
if (ret != ESP_OK) {
GetLogger(eLogType::Warning)->Log("Failed to add hue_saturation feature (ret: %d)", ret);
}
/**
* feature map & color capabilities 속성을 바꿔준다 (HS만 활성화)
* 3.2.5. Features
* | Bit | Code | Feature |
* | 0 | HS | Hue/Saturation |
* | 1 | EHUE | Enhanced Hue |
* | 2 | CL | Color Loop |
* | 3 | XY | XY |
* | 4 | CT | Color Temperature |
*/
esp_matter::attribute_t *attribute = esp_matter::attribute::get(cluster, chip::app::Clusters::Globals::Attributes::FeatureMap::Id);
esp_matter_attr_val_t val = esp_matter_invalid(NULL);
esp_matter::attribute::get_val(attribute, &val);
val.val.u32 = 0x13;
ret = esp_matter::attribute::set_val(attribute, &val);
if (ret != ESP_OK) {
GetLogger(eLogType::Warning)->Log("Failed to change feature map value (ret: %d)", ret);
}
attribute = esp_matter::attribute::get(cluster, chip::app::Clusters::ColorControl::Attributes::ColorCapabilities::Id);
esp_matter::attribute::get_val(attribute, &val);
val.val.u16 = 0x13;
ret = esp_matter::attribute::set_val(attribute, &val);
if (ret != ESP_OK) {
GetLogger(eLogType::Warning)->Log("Failed to change color capabilities value (ret: %d)", ret);
}
return true;
}
void CDeviceColorControlLight::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;
}
}
} else if (cluster_id == chip::app::Clusters::ColorControl::Id) {
if (attribute_id == chip::app::Clusters::ColorControl::Attributes::CurrentHue::Id) {
GetLogger(eLogType::Info)->Log("MATTER::PRE_UPDATE >> cluster: ColorControl(0x%04X), attribute: CurrentHue(0x%04X), value: %d", cluster_id, attribute_id, value->val.u8);
if (!m_matter_update_by_client_clus_colorcontrol_attr_currenthue) {
m_state_hue = value->val.u8;
int temp = REMAP_TO_RANGE(value->val.u8, 254, 360);
GetWS2812Ctrl()->set_hue(temp);
} else {
m_matter_update_by_client_clus_colorcontrol_attr_currenthue = false;
}
} else if (attribute_id == chip::app::Clusters::ColorControl::Attributes::CurrentSaturation::Id) {
GetLogger(eLogType::Info)->Log("MATTER::PRE_UPDATE >> cluster: ColorControl(0x%04X), attribute: CurrentSaturation(0x%04X), value: %d", cluster_id, attribute_id, value->val.u8);
if (!m_matter_update_by_client_clus_colorcontrol_attr_currentsaturation) {
m_state_saturation = value->val.u8;
int temp = REMAP_TO_RANGE(value->val.u8, 254, 100);
GetWS2812Ctrl()->set_saturation(temp);
} else {
m_matter_update_by_client_clus_colorcontrol_attr_currentsaturation = false;
}
}
/*
else if (attribute_id == chip::app::Clusters::ColorControl::Attributes::ColorTemperatureMireds::Id) {
GetLogger(eLogType::Info)->Log("MATTER::PRE_UPDATE >> cluster: ColorControl(0x%04X), attribute: ColorTemperatureMireds(0x%04X), value: %d", cluster_id, attribute_id, value->val.u8);
uint32_t temp = REMAP_TO_RANGE_INVERSE(value->val.u16, 1000000);
GetWS2812Ctrl()->set_temperature(temp);
}
*/
}
}
}
void CDeviceColorControlLight::matter_update_all_attribute_values()
{
matter_update_clus_onoff_attr_onoff();
matter_update_clus_levelcontrol_attr_currentlevel();
matter_update_clus_colorcontrol_attr_currenthue();
matter_update_clus_colorcontrol_attr_currentsaturation();
}
void CDeviceColorControlLight::matter_update_clus_onoff_attr_onoff()
{
esp_err_t ret;
uint32_t cluster_id, attribute_id;
esp_matter_attr_val_t val;
m_matter_update_by_client_clus_onoff_attr_onoff = true;
cluster_id = chip::app::Clusters::OnOff::Id;
attribute_id = chip::app::Clusters::OnOff::Attributes::OnOff::Id;
val = esp_matter_bool((bool)m_state_onoff);
ret = esp_matter::attribute::update(m_endpoint_id, cluster_id, attribute_id, &val);
if (ret != ESP_OK) {
GetLogger(eLogType::Error)->Log("Failed to update attribute (%d)", ret);
}
}
void CDeviceColorControlLight::matter_update_clus_levelcontrol_attr_currentlevel()
{
esp_err_t ret;
uint32_t cluster_id, attribute_id;
esp_matter_attr_val_t val;
m_matter_update_by_client_clus_levelcontrol_attr_currentlevel = true;
cluster_id = chip::app::Clusters::LevelControl::Id;
attribute_id = chip::app::Clusters::LevelControl::Attributes::CurrentLevel::Id;
val = esp_matter_uint8(m_state_brightness);
ret = esp_matter::attribute::update(m_endpoint_id, cluster_id, attribute_id, &val);
if (ret != ESP_OK) {
GetLogger(eLogType::Error)->Log("Failed to update attribute (%d)", ret);
}
}
void CDeviceColorControlLight::matter_update_clus_colorcontrol_attr_currenthue()
{
esp_err_t ret;
uint32_t cluster_id, attribute_id;
esp_matter_attr_val_t val;
m_matter_update_by_client_clus_colorcontrol_attr_currenthue = true;
cluster_id = chip::app::Clusters::ColorControl::Id;
attribute_id = chip::app::Clusters::ColorControl::Attributes::CurrentHue::Id;
val = esp_matter_uint8(m_state_hue);
ret = esp_matter::attribute::update(m_endpoint_id, cluster_id, attribute_id, &val);
if (ret != ESP_OK) {
GetLogger(eLogType::Error)->Log("Failed to update attribute (%d)", ret);
}
}
void CDeviceColorControlLight::matter_update_clus_colorcontrol_attr_currentsaturation()
{
esp_err_t ret;
uint32_t cluster_id, attribute_id;
esp_matter_attr_val_t val;
m_matter_update_by_client_clus_colorcontrol_attr_currentsaturation = true;
cluster_id = chip::app::Clusters::ColorControl::Id;
attribute_id = chip::app::Clusters::ColorControl::Attributes::CurrentSaturation::Id;
val = esp_matter_uint8(m_state_saturation);
ret = esp_matter::attribute::update(m_endpoint_id, cluster_id, attribute_id, &val);
if (ret != ESP_OK) {
GetLogger(eLogType::Error)->Log("Failed to update attribute (%d)", ret);
}
}
void CDeviceColorControlLight::toggle_state_action()
{
if (m_state_onoff) {
GetWS2812Ctrl()->set_brightness(0);
m_state_onoff = false;
} else {
GetWS2812Ctrl()->set_brightness(m_state_brightness);
m_state_onoff = true;
}
matter_update_all_attribute_values();
}
Color Control 클러스터는 Hue/Saturation 기반, Enhanced Hue, Color Loop, XY, Color Temperature 등 총 5종류의 색상 제어 알고리즘에 대응할 수 있다 (자세한 내용은 matter spec 확인)
본 예제에서는 Color Temperature 및 Enhanced Hue, Hue/Saturation 3개 제어 방식을 활성화하도록 한다
※ 색상쪽은 완전 문외한이라...
2.3. system.cpp
bool CSystem::initialize()
{
GetLogger(eLogType::Info)->Log("Start Initializing System");
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
if (ret != ESP_OK) {
GetLogger(eLogType::Error)->Log("Failed to initialize nsv flash (%d)", ret);
return false;
}
if (!init_default_button()) {
GetLogger(eLogType::Warning)->Log("Failed to init default on-board button");
}
// 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) {
GetLogger(eLogType::Error)->Log("Failed to create root node");
return false;
}
GetLogger(eLogType::Info)->Log("Root node (endpoint 0) added");
// 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) {
GetLogger(eLogType::Error)->Log("Failed to start matter (ret: %d)", ret);
return false;
}
GetLogger(eLogType::Info)->Log("Matter started");
// enable chip shell
// esp_matter::console::diagnostics_register_commands();
// esp_matter::console::init();
GetWS2812Ctrl()->initialize();
// set matter endpoints
CDevice *dev = nullptr;
#if LIGHT_TYPE == 0
dev = new CDeviceOnOffLight();
#elif LIGHT_TYPE == 1
dev = new CDeviceLevelControlLight();
#elif LIGHT_TYPE == 2
dev = new CDeviceColorControlLight();
#endif
if (dev && dev->matter_add_endpoint()) {
m_device_list.push_back(dev);
} else {
return false;
}
GetLogger(eLogType::Info)->Log("System Initialized");
print_system_info();
// print_matter_endpoints_info();
return true;
}
definition.h에서 LIGHT_TYPE 값을 2로 설정하면 ColorControl 객체가 생성된다
2.4. WS2812.h
Hue, Saturation에 대응하여 RGB 값으로 바꾸기 위해 Espressif사의 예제코드를 그대로 복사-붙여넣기해서 사용했다
struct rgb_t
{
uint8_t r, g, b;
rgb_t(uint8_t red = 0, uint8_t green = 0, uint8_t blue = 0) {
r = red;
g = green;
b = blue;
}
};
struct hsv_t
{
/**
* @brief
* hue range: [0, 360] degree
* saturation range: [0, 100]
* value range: [0, 100]
*/
uint32_t hue; // 색상
uint32_t saturation; // 채도
uint32_t value; // 명도
hsv_t(uint32_t h = 0, uint32_t s = 0, uint32_t v = 100) {
hue = MIN(360, h);
saturation = MIN(100, s);
value = MIN(100, v);
}
rgb_t conv2rgb() {
/**
* @brief HSV to RGB conversion formula
* @ref https://en.wikipedia.org/wiki/HSL_and_HSV
*/
rgb_t rgb;
uint32_t h = hue % 360;
uint32_t rgb_max = value * 2.55f;
uint32_t rgb_min = rgb_max * (100 - saturation) / 100.0f;
uint32_t i = h / 60;
uint32_t diff = h % 60;
// RGB adjustment amount by hue
uint32_t rgb_adj = (rgb_max - rgb_min) * diff / 60;
switch (i) {
case 0:
rgb.r = rgb_max;
rgb.g = rgb_min + rgb_adj;
rgb.b = rgb_min;
break;
case 1:
rgb.r = rgb_max - rgb_adj;
rgb.g = rgb_max;
rgb.b = rgb_min;
break;
case 2:
rgb.r = rgb_min;
rgb.g = rgb_max;
rgb.b = rgb_min + rgb_adj;
break;
case 3:
rgb.r = rgb_min;
rgb.g = rgb_max - rgb_adj;
rgb.b = rgb_max;
break;
case 4:
rgb.r = rgb_min + rgb_adj;
rgb.g = rgb_min;
rgb.b = rgb_max;
break;
default:
rgb.r = rgb_max;
rgb.g = rgb_min;
rgb.b = rgb_max - rgb_adj;
break;
}
return rgb;
}
};
또한, WS2812 제어 시 일반 GPIO Toggle 방식으로는 한계가 있어서 esp-idf의 RMT (Remote Control Transceiver) 모듈을 활용했다 (nearly 100us 단위로 GPIO 신호 제어가 가능하다)
bool CWS2812Ctrl::init_rmt()
{
esp_err_t ret;
rmt_tx_channel_config_t rmt_tx_ch_cfg;
rmt_tx_ch_cfg.gpio_num = GPIO_PIN_WS2812_DATA;
rmt_tx_ch_cfg.clk_src = RMT_CLK_SRC_DEFAULT;
rmt_tx_ch_cfg.resolution_hz = RMT_RESOLUTION_HZ;
rmt_tx_ch_cfg.mem_block_symbols = 64;
rmt_tx_ch_cfg.trans_queue_depth = 4;
rmt_tx_ch_cfg.flags.invert_out = 0;
rmt_tx_ch_cfg.flags.io_od_mode = 0;
rmt_tx_ch_cfg.flags.with_dma = 0;
ret = rmt_new_tx_channel(&rmt_tx_ch_cfg, &m_rmt_ch_handle);
if (ret != ESP_OK) {
GetLogger(eLogType::Error)->Log("Failed to create RMT TX channel (ret %d)", ret);
return false;
}
m_rmt_enc_base = new rmt_encoder_t();
if (!m_rmt_enc_base) {
GetLogger(eLogType::Error)->Log("Failed to create RMT base encoder (ret %d)", ret);
return false;
}
m_rmt_enc_base->encode = func_rmt_encode;
m_rmt_enc_base->reset = func_rmt_reset;
m_rmt_enc_base->del = func_rmt_delete;
rmt_bytes_encoder_config_t rmt_bytes_enc_cfg;
rmt_bytes_enc_cfg.bit0.duration0 = 0.3 * RMT_RESOLUTION_HZ / 1000000; // T0H=300ns
rmt_bytes_enc_cfg.bit0.level0 = 1;
rmt_bytes_enc_cfg.bit0.duration1 = 0.9 * RMT_RESOLUTION_HZ / 1000000; // T0L=900ns
rmt_bytes_enc_cfg.bit0.level1 = 0;
rmt_bytes_enc_cfg.bit1.duration0 = 0.9 * RMT_RESOLUTION_HZ / 1000000; // T1H=900ns
rmt_bytes_enc_cfg.bit1.level0 = 1;
rmt_bytes_enc_cfg.bit1.duration1 = 0.3 * RMT_RESOLUTION_HZ / 1000000; // T1L=300ns
rmt_bytes_enc_cfg.bit1.level1 = 0;
rmt_bytes_enc_cfg.flags.msb_first = 1;
ret = rmt_new_bytes_encoder(&rmt_bytes_enc_cfg, &m_rmt_enc_bytes);
if (ret != ESP_OK) {
GetLogger(eLogType::Error)->Log("Failed to create RMT bytes encoder (ret %d)", ret);
return false;
}
rmt_copy_encoder_config_t rmt_copy_enc_cfg;
ret = rmt_new_copy_encoder(&rmt_copy_enc_cfg, &m_rmt_enc_copy);
if (ret != ESP_OK) {
GetLogger(eLogType::Error)->Log("Failed to create RMT copy encoder (ret %d)", ret);
return false;
}
uint32_t reset_ticks = RMT_RESOLUTION_HZ / 1000000 * 300 / 2; // reset code = 300us
m_rmt_reset_code.duration0 = reset_ticks;
m_rmt_reset_code.level0 = 0;
m_rmt_reset_code.duration1 = reset_ticks;
m_rmt_reset_code.level1 = 0;
// set enable rmt channel
ret = rmt_enable(m_rmt_ch_handle);
if (ret != ESP_OK) {
GetLogger(eLogType::Error)->Log("Failed to enable RMT (ret %d)", ret);
return false;
}
return true;
}
RMT에 대한 자세한 설명과 예시는 Espressif의 공식 문서를 참고
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/rmt.html
3. DEMO
Matter commissioning 과정은 다른 글들에서 언급한 것과 차이가 없어서 이 글에서는 스킵하고 실제로 Apple Home 앱에서 제어되는 데모만 살펴보도록 한다
※ 근래 Matter 제품 출시 준비때문에 PAI, PAA, CD, DAC 작업하느라 간단한 예제 돌려볼 시간도 부족하다 ㅠㅠ
색상 제어의 세계는 나름 심오해서 약간 빡센 공부가 필요할 것 같다... (클러스터 스펙 문서 한번 훑어보는 데만도 반나절 이상이 소요됐다;;)
어쨌든 Matter 클러스터 구현과 홈 IoT 플랫폼과의 연동이 어느 정도 만족스러운 수준으로 구동되는 것은 확인했으니, 제품 양산 시 알게되는 새로운 내용에 대해서는 별도의 글로 포스팅하도록 한다
'PROJECT' 카테고리의 다른 글
[PROJ] Matter 온도/상대습도 측정 클러스터 개발 예제 (ESP32) (0) | 2024.02.15 |
---|---|
[PROJ] Matter::FanControl 클러스터 개발 예제 (ESP32) (2) | 2023.12.02 |
[PROJ] Matter::LevelControl 클러스터 개발 예제 (ESP32) (0) | 2023.03.28 |
[PROJ] Matter::OnOff 클러스터 개발 예제 (ESP32) (5) | 2023.03.25 |
[PROJ] Dimmable WS2812S RGB LED 모듈 제작 - (2) (0) | 2023.03.22 |