543 lines
17 KiB
C++
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;
|
|
}
|