LED-Germany/01_Source/matter-light-c6-wifi/main/app_driver.cpp
2026-02-26 09:59:27 +08:00

543 lines
17 KiB
C++

/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <esp_log.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <esp_matter.h>
#include <app_priv.h>
#include <common_macros.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <driver/ledc.h>
#include <driver/rmt.h>
#include <device.h>
#include <button_gpio.h>
using namespace chip::app::Clusters;
using namespace esp_matter;
static const char *TAG = "app_driver";
extern uint16_t rgb_light_endpoint_id;
extern uint16_t cct_light_endpoint_id;
// Global variables to store current XY color coordinates
static uint16_t current_x = 0;
static uint16_t current_y = 0;
typedef enum {
APP_LIGHT_TYPE_RGB_PWM = 1,
APP_LIGHT_TYPE_SK6812 = 2,
} app_light_type_t;
typedef struct {
app_light_type_t type;
bool power;
uint8_t brightness;
} app_rgb_pwm_light_t;
typedef struct {
app_light_type_t type;
bool power;
uint8_t brightness;
uint32_t temperature_kelvin;
rmt_channel_t rmt_channel;
uint32_t strip_len;
uint8_t *buffer;
} app_sk6812_light_t;
static void kelvin_to_rgb(uint32_t kelvin, uint8_t *out_r, uint8_t *out_g, uint8_t *out_b)
{
if (!out_r || !out_g || !out_b) {
return;
}
if (kelvin < 1000) {
kelvin = 1000;
}
if (kelvin > 40000) {
kelvin = 40000;
}
float temp = (float)kelvin / 100.0f;
float r;
float g;
float b;
if (temp <= 66.0f) {
r = 255.0f;
g = 99.4708025861f * logf(temp) - 161.1195681661f;
if (temp <= 19.0f) {
b = 0.0f;
} else {
b = 138.5177312231f * logf(temp - 10.0f) - 305.0447927307f;
}
} else {
r = 329.698727446f * powf(temp - 60.0f, -0.1332047592f);
g = 288.1221695283f * powf(temp - 60.0f, -0.0755148492f);
b = 255.0f;
}
if (r < 0.0f) r = 0.0f;
if (r > 255.0f) r = 255.0f;
if (g < 0.0f) g = 0.0f;
if (g > 255.0f) g = 255.0f;
if (b < 0.0f) b = 0.0f;
if (b > 255.0f) b = 255.0f;
*out_r = (uint8_t)r;
*out_g = (uint8_t)g;
*out_b = (uint8_t)b;
}
static esp_err_t rgb_pwm_apply(app_rgb_pwm_light_t *light)
{
if (!light) {
return ESP_ERR_INVALID_ARG;
}
const uint32_t max_duty = (1U << 13) - 1;
uint32_t duty = 0;
if (light->power) {
duty = ((uint32_t)light->brightness * max_duty) / 100;
}
const uint32_t white_r_scale = 100;
const uint32_t white_g_scale = 100;
const uint32_t white_b_scale = 100;
uint32_t duty_r = (duty * white_r_scale) / 100;
uint32_t duty_g = (duty * white_g_scale) / 100;
uint32_t duty_b = (duty * white_b_scale) / 100;
if (duty_r > max_duty) duty_r = max_duty;
if (duty_g > max_duty) duty_g = max_duty;
if (duty_b > max_duty) duty_b = max_duty;
// Common-anode RGB module: LED is ON when GPIO is LOW. So invert duty in software.
uint32_t duty_r_inv = max_duty - duty_r;
uint32_t duty_g_inv = max_duty - duty_g;
uint32_t duty_b_inv = max_duty - duty_b;
ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty_r_inv);
ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1, duty_g_inv);
ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1);
ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_2, duty_b_inv);
ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_2);
return ESP_OK;
}
static uint32_t ws_t0h_ticks = 0;
static uint32_t ws_t1h_ticks = 0;
static uint32_t ws_t0l_ticks = 0;
static uint32_t ws_t1l_ticks = 0;
static void IRAM_ATTR ws2812_like_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size, size_t wanted_num,
size_t *translated_size, size_t *item_num)
{
if (src == NULL || dest == NULL) {
*translated_size = 0;
*item_num = 0;
return;
}
const rmt_item32_t bit0 = {{{ ws_t0h_ticks, 1, ws_t0l_ticks, 0 }}};
const rmt_item32_t bit1 = {{{ ws_t1h_ticks, 1, ws_t1l_ticks, 0 }}};
size_t size = 0;
size_t num = 0;
const uint8_t *psrc = (const uint8_t *)src;
rmt_item32_t *pdest = dest;
while (size < src_size && num < wanted_num) {
for (int i = 0; i < 8; i++) {
if (*psrc & (1 << (7 - i))) {
pdest->val = bit1.val;
} else {
pdest->val = bit0.val;
}
num++;
pdest++;
}
size++;
psrc++;
}
*translated_size = size;
*item_num = num;
}
static esp_err_t sk6812_apply(app_sk6812_light_t *light)
{
if (!light || !light->buffer) {
return ESP_ERR_INVALID_ARG;
}
uint8_t r = 0;
uint8_t g = 0;
uint8_t b = 0;
uint8_t w = 0;
if (light->power) {
kelvin_to_rgb(light->temperature_kelvin, &r, &g, &b);
const uint32_t scale = light->brightness;
r = (uint8_t)(((uint32_t)r * scale) / 100);
g = (uint8_t)(((uint32_t)g * scale) / 100);
b = (uint8_t)(((uint32_t)b * scale) / 100);
w = 0;
}
// SK6812 RGBW byte order is commonly GRBW
for (uint32_t i = 0; i < light->strip_len; i++) {
uint32_t start = i * 4;
light->buffer[start + 0] = g;
light->buffer[start + 1] = r;
light->buffer[start + 2] = b;
light->buffer[start + 3] = w;
}
esp_err_t ret = rmt_write_sample(light->rmt_channel, light->buffer, light->strip_len * 4, true);
if (ret != ESP_OK) {
return ret;
}
return rmt_wait_tx_done(light->rmt_channel, pdMS_TO_TICKS(100));
}
/* Do any conversions/remapping for the actual value here */
static esp_err_t app_driver_light_set_power(app_driver_handle_t handle, esp_matter_attr_val_t *val)
{
if (!handle) {
return ESP_OK;
}
if (!val) {
return ESP_ERR_INVALID_ARG;
}
app_light_type_t type = ((app_rgb_pwm_light_t *)handle)->type;
if (type == APP_LIGHT_TYPE_RGB_PWM) {
app_rgb_pwm_light_t *h = (app_rgb_pwm_light_t *)handle;
h->power = val->val.b;
return rgb_pwm_apply(h);
}
if (type == APP_LIGHT_TYPE_SK6812) {
app_sk6812_light_t *h = (app_sk6812_light_t *)handle;
h->power = val->val.b;
return sk6812_apply(h);
}
return ESP_ERR_INVALID_ARG;
}
static esp_err_t app_driver_light_set_brightness(app_driver_handle_t handle, esp_matter_attr_val_t *val)
{
if (!handle) {
return ESP_OK;
}
int value = REMAP_TO_RANGE(val->val.u8, MATTER_BRIGHTNESS, STANDARD_BRIGHTNESS);
if (value < 0) {
value = 0;
}
if (value > 100) {
value = 100;
}
app_light_type_t type = ((app_rgb_pwm_light_t *)handle)->type;
if (type == APP_LIGHT_TYPE_RGB_PWM) {
app_rgb_pwm_light_t *h = (app_rgb_pwm_light_t *)handle;
h->brightness = (uint8_t)value;
return rgb_pwm_apply(h);
}
if (type == APP_LIGHT_TYPE_SK6812) {
app_sk6812_light_t *h = (app_sk6812_light_t *)handle;
h->brightness = (uint8_t)value;
return sk6812_apply(h);
}
return ESP_ERR_INVALID_ARG;
}
static esp_err_t app_driver_light_set_hue(app_driver_handle_t handle, esp_matter_attr_val_t *val)
{
int value = REMAP_TO_RANGE(val->val.u8, MATTER_HUE, STANDARD_HUE);
(void)value;
return ESP_OK;
}
static esp_err_t app_driver_light_set_saturation(app_driver_handle_t handle, esp_matter_attr_val_t *val)
{
int value = REMAP_TO_RANGE(val->val.u8, MATTER_SATURATION, STANDARD_SATURATION);
(void)value;
return ESP_OK;
}
static esp_err_t app_driver_light_set_temperature(app_driver_handle_t handle, esp_matter_attr_val_t *val)
{
if (!handle) {
return ESP_OK;
}
uint32_t value = REMAP_TO_RANGE_INVERSE(val->val.u16, STANDARD_TEMPERATURE_FACTOR);
app_light_type_t type = ((app_rgb_pwm_light_t *)handle)->type;
if (type == APP_LIGHT_TYPE_SK6812) {
app_sk6812_light_t *h = (app_sk6812_light_t *)handle;
h->temperature_kelvin = value;
return sk6812_apply(h);
}
return ESP_OK;
}
static esp_err_t app_driver_light_set_xy(app_driver_handle_t handle, uint16_t x, uint16_t y)
{
(void)handle;
(void)x;
(void)y;
return ESP_OK;
}
static void app_driver_button_long_press_cb(void *arg, void *data)
{
ESP_LOGW(TAG, "Button LONG PRESS - entering ESP-NOW pairing mode");
app_enter_espnow_pairing_mode();
}
static void app_driver_button_toggle_cb(void *arg, void *data)
{
ESP_LOGI(TAG, "Toggle button pressed");
uint16_t endpoint_id = rgb_light_endpoint_id;
uint32_t cluster_id = OnOff::Id;
uint32_t attribute_id = OnOff::Attributes::OnOff::Id;
attribute_t *attribute = attribute::get(endpoint_id, cluster_id, attribute_id);
esp_matter_attr_val_t val = esp_matter_invalid(NULL);
attribute::get_val(attribute, &val);
val.val.b = !val.val.b;
attribute::update(endpoint_id, cluster_id, attribute_id, &val);
}
esp_err_t app_driver_attribute_update(app_driver_handle_t driver_handle, uint16_t endpoint_id, uint32_t cluster_id,
uint32_t attribute_id, esp_matter_attr_val_t *val)
{
esp_err_t err = ESP_OK;
(void)endpoint_id;
if (!driver_handle) {
return ESP_OK;
}
if (cluster_id == OnOff::Id) {
if (attribute_id == OnOff::Attributes::OnOff::Id) {
err = app_driver_light_set_power(driver_handle, val);
}
} else if (cluster_id == LevelControl::Id) {
if (attribute_id == LevelControl::Attributes::CurrentLevel::Id) {
err = app_driver_light_set_brightness(driver_handle, val);
}
} else if (cluster_id == ColorControl::Id) {
if (attribute_id == ColorControl::Attributes::CurrentHue::Id) {
err = app_driver_light_set_hue(driver_handle, val);
} else if (attribute_id == ColorControl::Attributes::CurrentSaturation::Id) {
err = app_driver_light_set_saturation(driver_handle, val);
} else if (attribute_id == ColorControl::Attributes::ColorTemperatureMireds::Id) {
err = app_driver_light_set_temperature(driver_handle, val);
} else if (attribute_id == ColorControl::Attributes::CurrentX::Id) {
current_x = val->val.u16;
err = app_driver_light_set_xy(driver_handle, current_x, current_y);
} else if (attribute_id == ColorControl::Attributes::CurrentY::Id) {
current_y = val->val.u16;
err = app_driver_light_set_xy(driver_handle, current_x, current_y);
}
}
return err;
}
esp_err_t app_driver_light_set_defaults(uint16_t endpoint_id)
{
esp_err_t err = ESP_OK;
void *priv_data = endpoint::get_priv_data(endpoint_id);
app_driver_handle_t handle = (app_driver_handle_t)priv_data;
esp_matter_attr_val_t val = esp_matter_invalid(NULL);
/* Setting brightness */
attribute_t *attribute = attribute::get(endpoint_id, LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id);
if (attribute) {
attribute::get_val(attribute, &val);
err |= app_driver_light_set_brightness(handle, &val);
}
/* Setting color */
cluster_t *color_cluster = cluster::get(endpoint_id, ColorControl::Id);
if (color_cluster) {
attribute = attribute::get(color_cluster, ColorControl::Attributes::ColorMode::Id);
if (attribute) {
attribute::get_val(attribute, &val);
if (val.val.u8 == (uint8_t)ColorControl::ColorMode::kCurrentHueAndCurrentSaturation) {
/* Setting hue */
attribute = attribute::get(color_cluster, ColorControl::Attributes::CurrentHue::Id);
if (attribute) {
attribute::get_val(attribute, &val);
err |= app_driver_light_set_hue(handle, &val);
}
/* Setting saturation */
attribute = attribute::get(color_cluster, ColorControl::Attributes::CurrentSaturation::Id);
if (attribute) {
attribute::get_val(attribute, &val);
err |= app_driver_light_set_saturation(handle, &val);
}
} else if (val.val.u8 == (uint8_t)ColorControl::ColorMode::kColorTemperature) {
/* Setting temperature */
attribute = attribute::get(color_cluster, ColorControl::Attributes::ColorTemperatureMireds::Id);
if (attribute) {
attribute::get_val(attribute, &val);
err |= app_driver_light_set_temperature(handle, &val);
}
} else if (val.val.u8 == (uint8_t)ColorControl::ColorMode::kCurrentXAndCurrentY) {
/* Setting XY coordinates */
attribute = attribute::get(color_cluster, ColorControl::Attributes::CurrentX::Id);
if (attribute) {
attribute::get_val(attribute, &val);
current_x = val.val.u16;
}
attribute = attribute::get(color_cluster, ColorControl::Attributes::CurrentY::Id);
if (attribute) {
attribute::get_val(attribute, &val);
current_y = val.val.u16;
}
err |= app_driver_light_set_xy(handle, current_x, current_y);
} else {
ESP_LOGE(TAG, "Color mode not supported");
}
}
}
/* Setting power */
attribute = attribute::get(endpoint_id, OnOff::Id, OnOff::Attributes::OnOff::Id);
if (attribute) {
attribute::get_val(attribute, &val);
err |= app_driver_light_set_power(handle, &val);
}
return err;
}
app_driver_handle_t app_driver_light_init()
{
return app_driver_rgb_light_init();
}
app_driver_handle_t app_driver_rgb_light_init()
{
const int rgb_r_gpio = 4;
const int rgb_g_gpio = 5;
const int rgb_b_gpio = 6;
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.duty_resolution = LEDC_TIMER_13_BIT,
.timer_num = LEDC_TIMER_0,
.freq_hz = 5000,
.clk_cfg = LEDC_AUTO_CLK,
};
if (ledc_timer_config(&ledc_timer) != ESP_OK) {
ESP_LOGE(TAG, "LEDC timer config failed");
return NULL;
}
ledc_channel_config_t ch0 = {
.gpio_num = rgb_r_gpio,
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = LEDC_CHANNEL_0,
.intr_type = LEDC_INTR_DISABLE,
.timer_sel = LEDC_TIMER_0,
.duty = 0,
.hpoint = 0,
};
ledc_channel_config_t ch1 = ch0;
ch1.gpio_num = rgb_g_gpio;
ch1.channel = LEDC_CHANNEL_1;
ledc_channel_config_t ch2 = ch0;
ch2.gpio_num = rgb_b_gpio;
ch2.channel = LEDC_CHANNEL_2;
if (ledc_channel_config(&ch0) != ESP_OK || ledc_channel_config(&ch1) != ESP_OK || ledc_channel_config(&ch2) != ESP_OK) {
ESP_LOGE(TAG, "LEDC channel config failed");
return NULL;
}
app_rgb_pwm_light_t *light = (app_rgb_pwm_light_t *)calloc(1, sizeof(app_rgb_pwm_light_t));
if (!light) {
return NULL;
}
light->type = APP_LIGHT_TYPE_RGB_PWM;
light->power = DEFAULT_POWER;
light->brightness = STANDARD_BRIGHTNESS;
rgb_pwm_apply(light);
return (app_driver_handle_t)light;
}
app_driver_handle_t app_driver_cct_light_init()
{
const int sk6812_gpio = 2;
const rmt_channel_t channel = RMT_CHANNEL_0;
const uint32_t strip_len = 8;
rmt_config_t rmt_cfg = RMT_DEFAULT_CONFIG_TX((gpio_num_t)sk6812_gpio, channel);
rmt_cfg.clk_div = 2;
if (rmt_config(&rmt_cfg) != ESP_OK) {
ESP_LOGE(TAG, "RMT config failed");
return NULL;
}
if (rmt_driver_install(rmt_cfg.channel, 0, 0) != ESP_OK) {
ESP_LOGE(TAG, "RMT driver install failed");
return NULL;
}
uint32_t counter_clk_hz = 0;
if (rmt_get_counter_clock(rmt_cfg.channel, &counter_clk_hz) != ESP_OK) {
ESP_LOGE(TAG, "RMT get counter clock failed");
return NULL;
}
// Use WS2812-like timings which work for many SK6812 modules
const float ratio = (float)counter_clk_hz / 1e9f;
ws_t0h_ticks = (uint32_t)(ratio * 350.0f);
ws_t0l_ticks = (uint32_t)(ratio * 1000.0f);
ws_t1h_ticks = (uint32_t)(ratio * 1000.0f);
ws_t1l_ticks = (uint32_t)(ratio * 350.0f);
rmt_translator_init(rmt_cfg.channel, ws2812_like_rmt_adapter);
app_sk6812_light_t *light = (app_sk6812_light_t *)calloc(1, sizeof(app_sk6812_light_t));
if (!light) {
return NULL;
}
light->type = APP_LIGHT_TYPE_SK6812;
light->power = DEFAULT_POWER;
light->brightness = STANDARD_BRIGHTNESS;
light->temperature_kelvin = 4000;
light->rmt_channel = rmt_cfg.channel;
light->strip_len = strip_len;
light->buffer = (uint8_t *)calloc(strip_len * 4, 1);
if (!light->buffer) {
free(light);
return NULL;
}
sk6812_apply(light);
return (app_driver_handle_t)light;
}
app_driver_handle_t app_driver_button_init()
{
/* Initialize button */
button_handle_t handle = NULL;
const button_config_t btn_cfg = {0};
const button_gpio_config_t btn_gpio_cfg = button_driver_get_config();
if (iot_button_new_gpio_device(&btn_cfg, &btn_gpio_cfg, &handle) != ESP_OK) {
ESP_LOGE(TAG, "Failed to create button device");
return NULL;
}
iot_button_register_cb(handle, BUTTON_PRESS_DOWN, NULL, app_driver_button_toggle_cb, NULL);
iot_button_register_cb(handle, BUTTON_LONG_PRESS_START, NULL, app_driver_button_long_press_cb, NULL);
return (app_driver_handle_t)handle;
}