ble mesh: add aligenie ble mesh example

This commit is contained in:
InfiniteYuan 2020-07-08 15:51:47 +08:00 committed by bot
parent 522103b43a
commit 89bff63443
50 changed files with 10004 additions and 0 deletions

View File

@ -0,0 +1,12 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/bluetooth/esp_ble_mesh/common_components/button
$ENV{IDF_PATH}/examples/bluetooth/esp_ble_mesh/common_components/light_driver
$ENV{IDF_PATH}/examples/bluetooth/esp_ble_mesh/common_components/example_init
$ENV{IDF_PATH}/examples/bluetooth/esp_ble_mesh/common_components/example_nvs)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(aligenie_demo)

View File

@ -0,0 +1,13 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := aligenie_demo
EXTRA_COMPONENT_DIRS := $(IDF_PATH)/examples/bluetooth/esp_ble_mesh/common_components/button
EXTRA_COMPONENT_DIRS += $(IDF_PATH)/examples/bluetooth/esp_ble_mesh/common_components/light_driver
EXTRA_COMPONENT_DIRS += $(IDF_PATH)/examples/bluetooth/esp_ble_mesh/common_components/example_init
EXTRA_COMPONENT_DIRS += $(IDF_PATH)/examples/bluetooth/esp_ble_mesh/common_components/example_nvs
include $(IDF_PATH)/make/project.mk

View File

@ -0,0 +1,11 @@
| Supported Targets | ESP32 |
| ----------------- | ----- |
ESP BLE Mesh AliGenie Example
=============================
This example shows how a BLE Mesh device work with AliGenie.
For a better demonstration effect, an RGB LED can be soldered onto the ESP32-DevKitC board, by connecting their corresponding GPIO pins are GPIO\_NUM\_25, GPIO\_NUM\_26, GPIO\_NUM\_27.
Please check the [tutorial](tutorial/BLE_Mesh_AliGenie_Example.md) for more information about this example.

View File

@ -0,0 +1,13 @@
set(COMPONENT_SRCS "genie_event.c"
"genie_mesh.c"
"genie_reset.c"
"genie_util.c"
"genie_model_srv.c"
"genie_timer.c")
set(COMPONENT_ADD_INCLUDEDIRS ". include")
set(COMPONENT_REQUIRES bt nvs_flash example_nvs)
register_component()

View File

@ -0,0 +1,3 @@
#
# Component Makefile
#

View File

@ -0,0 +1,647 @@
// Copyright (C) 2018-2020 Alibaba Group Holding Limited
// Adaptations to ESP-IDF Copyright (c) 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_ble_mesh_defs.h"
#include "esp_ble_mesh_common_api.h"
#include "esp_ble_mesh_networking_api.h"
#include "esp_ble_mesh_provisioning_api.h"
#include "esp_ble_mesh_local_data_operation_api.h"
#include "genie_util.h"
#include "genie_model_srv.h"
#include "genie_event.h"
#include "genie_mesh.h"
#include "genie_reset.h"
#include "genie_timer.h"
#include "ble_mesh_example_nvs.h"
static const char *TAG = "genie_event";
extern void user_genie_event_handle(genie_event_t event, void *p_arg);
extern elem_state_t g_elem_state[];
extern nvs_handle_t NVS_HANDLE;
static uint8_t g_proving = 0;
static bool g_genie_provisioned = 0;
static bool g_genie_silent_adv = 0;
/**
* @brief Execute reset action.
*
*/
static void genie_reset_provision(void)
{
ENTER_FUNC();
g_genie_provisioned = 0;
// erase reboot count
ble_mesh_nvs_erase(NVS_HANDLE, GENIE_STORE_RESTART_COUNT_KEY);
/* reset provision */
ESP_ERROR_CHECK(esp_ble_mesh_node_local_reset());
}
/**
* @brief Handle software reset.
*
* @return genie_event_t
*/
static genie_event_t genie_event_handle_sw_reset(void)
{
ENTER_FUNC();
/* reset provision information */
genie_reset_provision();
/* restart PB-ADV and PB-GATT */
ESP_ERROR_CHECK(esp_ble_mesh_node_prov_enable(ESP_BLE_MESH_PROV_ADV | ESP_BLE_MESH_PROV_GATT));
return GENIE_EVT_SDK_MESH_PBADV_START;
}
/**
* @brief Handle hardware reset, and indicate hardware reset.
*
* @return genie_event_t
*/
static genie_event_t genie_event_handle_hw_reset_start(void)
{
ENTER_FUNC();
#ifdef CONFIG_MESH_MODEL_VENDOR_SRV
if (esp_ble_mesh_node_is_provisioned()) {
genie_indicate_hw_reset_event(); // Indicate hardware reset event to genie speaker
}
#endif
genie_reset_done_timer_start();
return GENIE_EVT_HW_RESET_START;
}
/**
* @brief Execute reset action
*
* @return genie_event_t
*/
static genie_event_t genie_event_handle_hw_reset_done(void)
{
ENTER_FUNC();
genie_reset_clean_count();
/* reset provision information */
genie_reset_provision();
/* restart PB-ADV and PB-GATT */
ESP_ERROR_CHECK(esp_ble_mesh_node_prov_enable(ESP_BLE_MESH_PROV_ADV | ESP_BLE_MESH_PROV_GATT));
return GENIE_EVT_SDK_MESH_PBADV_START;
}
/**
* @brief Genie Mesh Component Init,
* 1. Handle Mesh Init according esp-ble-mesh init status
* 2. Start power indeicate if ble-mesh already provisioned
* 3. Start PB-ADV Timer if ble-mesh not provisioned
* 4. Hardware reset ble-mesh provision information if reset flag be configed.
*
* @param p_status
*
* @return genie_event_t
*/
static genie_event_t genie_event_handle_mesh_init(int *p_status)
{
ENTER_FUNC();
if (g_genie_silent_adv) {
// ! note some expect behavior
return GENIE_EVT_SDK_MESH_INIT;
}
if (*p_status == -EALREADY) {
if (!genie_reset_get_flag()) {
#ifdef CONFIG_MESH_MODEL_VENDOR_SRV
poweron_indicate_start();
#endif
return GENIE_EVT_SDK_MESH_PROV_SUCCESS;
}
} else if (*p_status == 0) {
return GENIE_EVT_SDK_MESH_PBADV_START;
}
if (genie_reset_get_flag()) {
return GENIE_EVT_HW_RESET_START;
}
return GENIE_EVT_SDK_MESH_INIT;
}
/**
* @brief After the Bluetooth Mesh device is powered on, if it is in the unprovisioned state,
* it needs to broadcast the Unprovisioned Device Beacon, and the broadcast duration is 10 minutes by default
*
* @return genie_event_t
*/
static genie_event_t genie_event_handle_pbadv_start(void)
{
ENTER_FUNC();
genie_pbadv_timer_start();
return GENIE_EVT_SDK_MESH_PBADV_START;
}
/**
* @brief If the network is still not configured after the timeout,
* the device enters the silent broadcast state and closes the PB_GATT broadcast.
*
* @return genie_event_t
*/
static genie_event_t genie_event_handle_pbadv_timeout(void)
{
ENTER_FUNC();
genie_pbadv_timer_stop();
return GENIE_EVT_SDK_MESH_SILENT_START;
}
/**
* @brief Start silent adv
*
* @return genie_event_t
*/
static genie_event_t genie_event_handle_silent_start(void)
{
ENTER_FUNC();
g_genie_silent_adv = true;
genie_pbadv_start_silent_adv();
return GENIE_EVT_SDK_MESH_SILENT_START;
}
/**
* @brief Start provisioning
*
* @return genie_event_t
*/
static genie_event_t genie_event_handle_prov_start(void)
{
ENTER_FUNC();
if (g_proving == 0) {
g_proving = 1;
/* disable adv timer */
genie_pbadv_timer_stop();
/* enable prov timer */
genie_prov_timer_start();
}
return GENIE_EVT_SDK_MESH_PROV_START;
}
/**
* @brief Provision timeout
*
* @return genie_event_t
*/
static genie_event_t genie_event_handle_prov_timeout(void)
{
ENTER_FUNC();
return GENIE_EVT_SDK_MESH_PROV_FAIL;
}
/**
* @brief Provision success
*
* @return genie_event_t
*/
static genie_event_t genie_event_handle_prov_success(void)
{
ENTER_FUNC();
g_genie_provisioned = 1;
#ifdef CONFIG_MESH_MODEL_VENDOR_SRV
return GENIE_EVT_SDK_STATE_SYNC;
#else
return GENIE_EVT_SDK_MESH_PROV_SUCCESS;
#endif
}
/**
* @brief Provision fail
*
* @return genie_event_t
*/
static genie_event_t genie_event_handle_prov_fail(void)
{
ENTER_FUNC();
/* reset provision flag */
g_proving = 0;
genie_reset_provision();
/* restart PB-ADV and PB-GATT */
ESP_ERROR_CHECK(esp_ble_mesh_node_prov_enable(ESP_BLE_MESH_PROV_ADV | ESP_BLE_MESH_PROV_GATT));
return GENIE_EVT_SDK_MESH_PBADV_START;
}
/**
* @brief Sync device status.
*
* @return genie_event_t
*/
static genie_event_t genie_event_handle_sync(void)
{
ENTER_FUNC();
#ifdef CONFIG_MESH_MODEL_GEN_ONOFF_SRV
g_indication_flag |= INDICATION_FLAG_ONOFF;
#endif
#ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV
g_indication_flag |= INDICATION_FLAG_LIGHTNESS;
#endif
#ifdef CONFIG_MESH_MODEL_CTL_SRV
g_indication_flag |= INDICATION_FLAG_CTL;
#endif
#ifdef CONFIG_MESH_MODEL_HSL_SRV
g_indication_flag |= INDICATION_FLAG_HSL;
#endif
return GENIE_EVT_SDK_INDICATE;
}
/**
* @brief AppKey add.
*
* @return genie_event_t
*/
static genie_event_t genie_event_handle_appkey_add(void)
{
ENTER_FUNC();
if (g_proving) {
g_proving = 0;
/* stop provision timer */
genie_prov_timer_stop();
return GENIE_EVT_SDK_MESH_PROV_SUCCESS;
} else {
return GENIE_EVT_SDK_APPKEY_ADD;
}
return GENIE_EVT_SDK_APPKEY_ADD;
}
/**
* @brief Subscribe address add.
*
* @return genie_event_t
*/
static genie_event_t genie_event_handle_sub_add(void)
{
ENTER_FUNC();
return GENIE_EVT_SDK_SUB_ADD;
}
/**
* @brief handle message.
*
* @param p_elem
*
* @return genie_event_t
*/
static genie_event_t genie_event_handle_analyze_msg(elem_state_t *p_elem)
{
ENTER_FUNC();
#ifdef CONFIG_MESH_MODEL_TRANS
if (p_elem->state.trans || p_elem->state.delay) {
if (p_elem->state.delay) {
return GENIE_EVT_SDK_DELAY_START;
} else {
return GENIE_EVT_SDK_TRANS_START;
}
}
#endif
return GENIE_EVT_SDK_ACTION_DONE;
}
/**
* @brief Execute color change action.
*
* @param p_elem
* @param p_data
*
* @return genie_event_t
*/
static genie_event_t genie_event_handle_color_action(elem_state_t *p_elem, uint8_t *p_data)
{
ENTER_FUNC();
#ifdef CONFIG_MESH_MODEL_HSL_SRV
uint16_t lightness = *p_data++;
lightness += (*p_data++ << 8);
uint16_t hue = *p_data++;
hue += (*p_data++ << 8);
uint16_t saturation = *p_data++;
saturation += (*p_data++ << 8);
ESP_LOGD(TAG, "tar lightness(%d) hue(%d) saturation(%d)", p_elem->state.lightness[VALUE_TYPE_TAR], p_elem->state.hue[VALUE_TYPE_TAR], p_elem->state.saturation[VALUE_TYPE_TAR]);
#endif
return GENIE_EVT_SDK_COLOR_ACTION;
}
/**
* @brief action done.
*
* @param p_elem
*
* @return genie_event_t
*/
static genie_event_t genie_event_handle_action_done(elem_state_t *p_elem)
{
ENTER_FUNC();
#ifdef CONFIG_MESH_MODEL_GEN_ONOFF_SRV
ESP_LOGD(TAG, "onoff cur(%d) tar(%d)", p_elem->state.onoff[VALUE_TYPE_CUR], p_elem->state.onoff[VALUE_TYPE_TAR]);
if (p_elem->state.onoff[VALUE_TYPE_CUR] != p_elem->state.onoff[VALUE_TYPE_TAR]) {
p_elem->state.onoff[VALUE_TYPE_CUR] = p_elem->state.onoff[VALUE_TYPE_TAR];
}
#endif
#ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV
ESP_LOGD(TAG, "actual cur(%d) tar(%d)", p_elem->state.actual[VALUE_TYPE_CUR], p_elem->state.actual[VALUE_TYPE_TAR]);
if (p_elem->state.actual[VALUE_TYPE_CUR] != p_elem->state.actual[VALUE_TYPE_TAR]) {
p_elem->state.actual[VALUE_TYPE_CUR] = p_elem->state.actual[VALUE_TYPE_TAR];
}
#endif
#ifdef CONFIG_MESH_MODEL_CTL_SRV
ESP_LOGD(TAG, "temp cur(%d) tar(%d)", p_elem->state.temp[VALUE_TYPE_CUR], p_elem->state.temp[VALUE_TYPE_TAR]);
if (p_elem->state.temp[VALUE_TYPE_CUR] != p_elem->state.temp[VALUE_TYPE_TAR]) {
p_elem->state.temp[VALUE_TYPE_CUR] = p_elem->state.temp[VALUE_TYPE_TAR];
}
#endif
#ifdef CONFIG_MESH_MODEL_HSL_SRV
ESP_LOGD(TAG, "hue cur(%d) tar(%d)", p_elem->state.hue[VALUE_TYPE_CUR], p_elem->state.hue[VALUE_TYPE_TAR]);
if (p_elem->state.hue[VALUE_TYPE_CUR] != p_elem->state.hue[VALUE_TYPE_TAR]) {
p_elem->state.hue[VALUE_TYPE_CUR] = p_elem->state.hue[VALUE_TYPE_TAR];
}
ESP_LOGD(TAG, "saturation cur(%d) tar(%d)", p_elem->state.saturation[VALUE_TYPE_CUR], p_elem->state.saturation[VALUE_TYPE_TAR]);
if (p_elem->state.saturation[VALUE_TYPE_CUR] != p_elem->state.saturation[VALUE_TYPE_TAR]) {
p_elem->state.saturation[VALUE_TYPE_CUR] = p_elem->state.saturation[VALUE_TYPE_TAR];
}
ESP_LOGD(TAG, "lightness cur(%d) tar(%d)", p_elem->state.lightness[VALUE_TYPE_CUR], p_elem->state.lightness[VALUE_TYPE_TAR]);
if (p_elem->state.lightness[VALUE_TYPE_CUR] != p_elem->state.lightness[VALUE_TYPE_TAR]) {
p_elem->state.lightness[VALUE_TYPE_CUR] = p_elem->state.lightness[VALUE_TYPE_TAR];
}
#endif
#ifdef CONFIG_MESH_MODEL_VENDOR_SRV
if (esp_ble_mesh_node_is_provisioned()) {
return GENIE_EVT_SDK_INDICATE;
}
#endif
return GENIE_EVT_SDK_ACTION_DONE;
}
#ifdef CONFIG_MESH_MODEL_VENDOR_SRV
/**
* @brief power on indicate.
*
* @param p_elem
* @return genie_event_t
*/
static genie_event_t genie_event_handle_poweron_indication(elem_state_t *p_elem)
{
ENTER_FUNC();
g_indication_flag |= INDICATION_FLAG_POWERON;
return GENIE_EVT_SDK_INDICATE;
}
static void indicate_delay_timer_callback(void *arg)
{
ENTER_FUNC();
genie_standart_indication(arg);
}
static void reset_indicate_delay_timer(elem_state_t *p_elem)
{
ENTER_FUNC();
static esp_timer_handle_t timer = NULL;
if (!timer) {
esp_timer_create_args_t create_args = {
.callback = indicate_delay_timer_callback,
.arg = (void *)p_elem,
.name = "delay_timer"
};
ESP_ERROR_CHECK(esp_timer_create(&create_args, &timer));
}
if (esp_timer_start_once(timer, 500 * 1000) != ESP_OK) {
ESP_ERROR_CHECK(esp_timer_stop(timer));
ESP_ERROR_CHECK(esp_timer_start_once(timer, 500 * 1000));
}
}
static genie_event_t genie_event_handle_indicate(elem_state_t *p_elem)
{
ENTER_FUNC();
if (g_indication_flag) {
reset_indicate_delay_timer(p_elem); // delay send indication to avoid data congestion
}
return GENIE_EVT_SDK_INDICATE;
}
static genie_event_t genie_event_handle_vendor_msg(genie_model_msg_t *p_msg)
{
ENTER_FUNC();
genie_vendor_model_msg_handle(p_msg);
return GENIE_EVT_SDK_VENDOR_MSG;
}
#ifdef GENIE_MODEL_VENDOR_TIMER
static genie_event_t genie_event_handle_order_msg(genie_timer_attr_data_t *attr_data)
{
ENTER_FUNC();
#ifdef CONFIG_MESH_MODEL_GEN_ONOFF_SRV
if (attr_data->type == GENIE_MODEL_ATTR_ONOFF) {
g_indication_flag |= INDICATION_FLAG_ONOFF;
g_elem_state[0].state.onoff[VALUE_TYPE_TAR] = attr_data->para;
#endif
#ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV
} else if (attr_data->type == GENIE_MODEL_ATTR_LIGHTNESS) {
g_indication_flag |= INDICATION_FLAG_LIGHTNESS;
// Todo
#endif
#ifdef CONFIG_MESH_MODEL_CTL_SRV
} else if (attr_data->type == GENIE_MODEL_ATTR_TEMPERATURE) {
g_indication_flag |= INDICATION_FLAG_CTL;
// Todo
#endif
#ifdef CONFIG_MESH_MODEL_HSL_SRV
} else if (attr_data->type == GENIE_MODEL_ATTR_COLOR) {
g_indication_flag |= INDICATION_FLAG_HSL;
// Todo
#endif
}
#ifdef CONFIG_MESH_MODEL_TRANS
return GENIE_EVT_SDK_TRANS_CYCLE;
#else
return GENIE_EVT_SDK_ACTION_DONE;
#endif
}
#endif /* GENIE_MODEL_VENDOR_TIMER */
#endif /* CONFIG_MESH_MODEL_VENDOR_SRV */
void genie_event(genie_event_t event, void *p_arg)
{
uint8_t ignore_user_event = 0;
genie_event_t next_event = event;
ESP_LOGD(TAG, "genie_event event %d", event);
#ifdef CONFIG_MESH_MODEL_TRANS
if (event != GENIE_EVT_SDK_TRANS_CYCLE) {
GENIE_MESH_EVENT_PRINT(event);
}
#endif
switch (event) {
case GENIE_EVT_SW_RESET:
next_event = genie_event_handle_sw_reset();
break;
case GENIE_EVT_HW_RESET_START:
genie_event_handle_hw_reset_start();
break;
case GENIE_EVT_HW_RESET_DONE:
next_event = genie_event_handle_hw_reset_done();
break;
case GENIE_EVT_SDK_MESH_INIT:
next_event = genie_event_handle_mesh_init((int *)p_arg);
p_arg = (void *)&g_elem_state[0]; // update p_arg to element state.
break;
case GENIE_EVT_SDK_MESH_PBADV_START:
next_event = genie_event_handle_pbadv_start();
break;
case GENIE_EVT_SDK_MESH_PBADV_TIMEOUT:
next_event = genie_event_handle_pbadv_timeout();
break;
case GENIE_EVT_SDK_MESH_SILENT_START:
next_event = genie_event_handle_silent_start();
break;
case GENIE_EVT_SDK_MESH_PROV_START:
next_event = genie_event_handle_prov_start();
break;
case GENIE_EVT_SDK_MESH_PROV_TIMEOUT:
next_event = genie_event_handle_prov_timeout();
break;
case GENIE_EVT_SDK_MESH_PROV_SUCCESS:
next_event = genie_event_handle_prov_success();
break;
#ifdef CONFIG_MESH_MODEL_VENDOR_SRV
case GENIE_EVT_SDK_STATE_SYNC:
next_event = genie_event_handle_sync();
break;
#endif
case GENIE_EVT_SDK_MESH_PROV_FAIL:
next_event = genie_event_handle_prov_fail();
break;
case GENIE_EVT_SDK_APPKEY_ADD: {
next_event = genie_event_handle_appkey_add();
if (next_event == GENIE_EVT_SDK_MESH_PROV_SUCCESS) {
p_arg = &g_elem_state[0];
}
break;
}
case GENIE_EVT_SDK_APPKEY_DEL:
case GENIE_EVT_SDK_APPKEY_UPDATE:
break;
case GENIE_EVT_SDK_NETKEY_ADD:
case GENIE_EVT_SDK_NETKEY_DEL:
case GENIE_EVT_SDK_NETKEY_UPDATE:
case GENIE_EVT_SDK_IVI_UPDATE:
break;
case GENIE_EVT_SDK_SUB_ADD:
next_event = genie_event_handle_sub_add();
break;
case GENIE_EVT_SDK_SUB_DEL:
break;
case GENIE_EVT_SDK_ANALYZE_MSG:
next_event = genie_event_handle_analyze_msg((elem_state_t *)p_arg);
break;
case GENIE_EVT_SDK_COLOR_ACTION:
ESP_LOGD(TAG, "GENIE_EVT_SDK_COLOR_ACTION");
next_event = genie_event_handle_color_action(&g_elem_state[0], (uint8_t *)p_arg);
break;
case GENIE_EVT_SDK_ACTION_DONE:
ESP_LOGD(TAG, "GENIE_EVT_SDK_ACTION_DONE");
next_event = genie_event_handle_action_done((elem_state_t *)p_arg);
break;
#ifdef CONFIG_MESH_MODEL_VENDOR_SRV
case GENIE_EVT_SDK_MESH_PWRON_INDC:
next_event = genie_event_handle_poweron_indication((elem_state_t *)p_arg);
p_arg = &g_elem_state[0];
break;
case GENIE_EVT_SDK_INDICATE:
ESP_LOGD(TAG, "GENIE_EVT_SDK_INDICATE");
next_event = genie_event_handle_indicate((elem_state_t *)p_arg);
break;
case GENIE_EVT_SDK_VENDOR_MSG:
ESP_LOGD(TAG, "GENIE_EVT_SDK_VENDOR_MSG");
next_event = genie_event_handle_vendor_msg((genie_model_msg_t *)p_arg);
break;
#endif
#ifdef GENIE_MODEL_VENDOR_TIMER
case GENIE_EVT_TIME_OUT: {
ESP_LOGD(TAG, "GENIE_EVT_TIME_OUT");
next_event = genie_event_handle_order_msg((genie_timer_attr_data_t *)p_arg);
p_arg = &g_elem_state[0];
break;
}
#endif
case GENIE_EVT_APP_FAC_QUIT:
ESP_LOGD(TAG, "GENIE_EVT_APP_FAC_QUIT");
break;
default:
ESP_LOGD(TAG, "unhandle this event");
break;
}
if (!ignore_user_event) {
ESP_LOGD(TAG, "post to user_genie_event_handle");
user_genie_event_handle(event, p_arg);
}
if (next_event != event) {
ESP_LOGD(TAG, "next_event: %d, event: %d", next_event, event);
genie_event(next_event, p_arg);
}
}

View File

@ -0,0 +1,629 @@
// Copyright (C) 2018-2020 Alibaba Group Holding Limited
// Adaptations to ESP-IDF Copyright (c) 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_ble_mesh_defs.h"
#include "esp_ble_mesh_common_api.h"
#include "esp_ble_mesh_networking_api.h"
#include "esp_ble_mesh_local_data_operation_api.h"
#include "genie_mesh.h"
#include "genie_event.h"
#include "genie_util.h"
#include "genie_timer.h"
#include "genie_model_srv.h"
static const char *TAG = "genie_model_srv";
/**
* g_vnd_msg_list was used to save the unconfirmed vendor messages
* g_vnd_msg_timer was used to control when the vendor messages saved in g_vnd_msg_list will be resent
**/
static genie_dlist_t g_vnd_msg_list = {0};
static util_timer_t g_vnd_msg_timer = {0};
static void genie_model_retry_timer_cb(void *args);
/**
* @brief Get Genie Vendor Model Appkey ID
*
* @param elem
* @param p_model
* @return uint16_t
*/
static uint16_t bt_mesh_model_get_appkey_id(esp_ble_mesh_elem_t *elem, esp_ble_mesh_model_t *p_model)
{
ENTER_FUNC();
return 0;
}
/**
* @brief Get Genie Vendor Model Netkey ID
*
* @param elem
*
* @return uint16_t
*/
static uint16_t bt_mesh_model_get_netkey_id(esp_ble_mesh_elem_t *elem)
{
ENTER_FUNC();
return 0;
}
/** @def genie_model_msg_gen_tid
*
* @brief generate tid used in vendor model message
*
* @param NULL
*
* @return tid with range of [0x80, 0xff]
*/
static uint8_t genie_model_msg_gen_tid(void)
{
ENTER_FUNC();
static uint8_t tid = 0x80;
if (tid == 0xFF) {
bt_mesh_rand(&tid, 1);
tid &= 0x3F;
} else {
tid = (tid + 1) & 0x3F;
}
return (tid | 0x80);
}
/** @def genie_model_init
*
* @brief vendor model server related global parameter initialization
*
* @param NULL
*
* @return always return true
*/
static uint16_t genie_model_init(void)
{
ENTER_FUNC();
static bool init_flag = false;
if (!init_flag) {
ESP_LOGD(TAG, "init g_vnd_msg_timer");
util_timer_init(&g_vnd_msg_timer, genie_model_retry_timer_cb, &g_vnd_msg_list);
genie_dlist_init(&g_vnd_msg_list);
}
init_flag = true;
return true;
}
/** @def genie_model_msg_node_free
*
* @brief free the vendor model message node struct's memory
*
* @param pointer to the vendor model message node to be freed
*
* @return 0 for success; negative for failure
*/
static int16_t genie_model_msg_node_free(genie_model_msg_node_t *p_node)
{
ENTER_FUNC();
free(p_node);
return 0;
}
/** @def genie_model_msg_node_generate
*
* @brief duplicate genie_model_msg_t and save to genie_model_msg_node_t
*
* @param pointer to the vendor model message to be duplicated
*
* @return pointer to genie_model_msg_node_t for success, NULL for failure
*/
static genie_model_msg_node_t *genie_model_msg_node_generate(genie_model_msg_t *p_model_msg)
{
ENTER_FUNC();
genie_model_msg_node_t *p_node = NULL;
if (!p_model_msg->retry) {
p_model_msg->retry = GENIE_MODEL_MSG_DFT_RETRY_TIMES;
} else if (p_model_msg->retry > GENIE_MODEL_MSG_MAX_RETRY_TIMES) {
p_model_msg->retry = GENIE_MODEL_MSG_MAX_RETRY_TIMES;
}
p_node = malloc(sizeof(genie_model_msg_node_t) + p_model_msg->len);
if (!p_node) {
ESP_LOGE(TAG, "malloc for genie_model_msg_node_t failed");
return NULL;
}
memcpy(&p_node->msg, p_model_msg, sizeof(genie_model_msg_t));
ESP_LOGD(TAG, "p_node->msg: %p, data: %p, %p", &p_node->msg, &p_node->msg.data, &p_node->msg.data + 1);
p_node->msg.data = (uint8_t *)(&p_node->msg.data + 1);
memcpy(p_node->msg.data, p_model_msg->data, p_model_msg->len);
ESP_LOGD(TAG, "p_model_msg->data: %p, data: %s, p_node->msg.data: %p, data: %s",
p_model_msg->data, util_hex2str(p_model_msg->data, p_model_msg->len),
p_node->msg.data, util_hex2str(p_node->msg.data, p_node->msg.len));
p_node->timeout = esp_timer_get_time() + p_model_msg->retry_period;
p_node->left_retry = p_model_msg->retry;
return p_node;
}
/** @def genie_model_msg_list_append
*
* @brief duplicate genie_model_msg_t and append it to vendor model message list to be monitored
*
* @param pointer to the vendor model message to be duplicated
*
* @return 0 for success; negative for failure
*/
static int16_t genie_model_msg_list_append(genie_model_msg_t *p_model_msg)
{
ENTER_FUNC();
genie_model_msg_node_t *p_msg_node = NULL;
p_msg_node = genie_model_msg_node_generate(p_model_msg);
if (!p_msg_node) {
return -2;
}
ESP_LOGD(TAG, "append msg: %p, opid: %x, retry: %d, head: %p, node: %p", p_model_msg, p_model_msg->opid, p_model_msg->retry, &g_vnd_msg_list, &p_msg_node->node);
if (genie_dlist_node_number(&g_vnd_msg_list) >= GENIE_VENDOR_MSG_LIST_MAXSIZE) {
ESP_LOGW(TAG, "List Full, discard!!!");
return 0;
}
genie_dlist_append(&g_vnd_msg_list, &p_msg_node->node);
// Check retry timer, if timer is not started yet, start it
if (!util_timer_is_started(&g_vnd_msg_timer)) {
util_timer_start(&g_vnd_msg_timer, p_model_msg->retry_period);
}
return 0;
}
/** @def genie_model_retry_timer_cb
*
* @brief timeout handler for the g_vnd_msg_timer
*
* @param p_timer - pointer to the timer; args - pointer to g_vnd_msg_list
*
* @return N/A
*/
static void genie_model_retry_timer_cb(void *args)
{
ENTER_FUNC();
uint32_t nearest = 0;
genie_dnode_t *p_node = NULL;
genie_model_msg_node_t *p_msg_node = NULL;
genie_model_msg_t *p_msg = NULL;
genie_dlist_t *p_head = (genie_dlist_t *)args;
ESP_LOGD(TAG, "g_vnd_msg_timer timeout, p_head: %p", p_head);
/**
* 1. go through p_head
* 2. resend the no responsed messages if timeout happens and refresh timeout value
* */
GENIE_DLIST_FOR_EACH_NODE(p_head, p_node) {
p_msg_node = CONTAINER_OF(p_node, genie_model_msg_node_t, node);
nearest = p_msg_node->msg.retry_period;
p_msg = &p_msg_node->msg;
ESP_LOGD(TAG, "msg: %p, opid: %d, left: %d", p_msg, p_msg->opid, p_msg_node->left_retry);
if (p_msg_node->timeout <= esp_timer_get_time()) {
ESP_LOGD(TAG, "timeout - msg: %p, opid: %x, left: %d", p_msg, p_msg->opid, p_msg_node->left_retry);
genie_model_msg_send(p_msg);
if (--p_msg_node->left_retry <= 0) {
genie_dlist_remove(p_node);
genie_model_msg_node_free((genie_model_msg_node_t *)p_node);
break;
}
p_msg_node->timeout = esp_timer_get_time() + p_msg_node->msg.retry_period;
} else {
if (nearest > p_msg_node->timeout) {
nearest = p_msg_node->timeout;
}
}
}
/* start new timer */
if (!genie_dlist_is_empty(p_head)) {
util_timer_start(&g_vnd_msg_timer, nearest);
ESP_LOGD(TAG, "restart retry timer, timeout: %d", nearest);
} else {
util_timer_stop(&g_vnd_msg_timer);
ESP_LOGD(TAG, "list empty, stop timer");
}
return;
}
/** @def genie_model_msg_check_tid
*
* @brief check received vendor message's tid
*
* @param pointer to g_vnd_msg_list, tid of the received vendor model message
*
* @return 0 for success; negative for failure
*/
static int16_t genie_model_msg_check_tid(genie_dlist_t *p_head, uint8_t tid)
{
ENTER_FUNC();
genie_dnode_t *p_node = NULL;
if (!p_head) {
return -1;
}
if (genie_dlist_is_empty(p_head)) {
return 0;
}
/**
* go through message list and dequeue the vendor model's message and free it if received message
* s tid equals this message's tid
* */
GENIE_DLIST_FOR_EACH_NODE(p_head, p_node) {
genie_model_msg_t *p_msg = NULL;
genie_model_msg_node_t *p_msg_node = NULL;
p_msg_node = CONTAINER_OF(p_node, genie_model_msg_node_t, node);
p_msg = &p_msg_node->msg;
if (p_msg->tid == tid) {
ESP_LOGD(TAG, "dequeue msg: %p, opid: %x, retry: %2d", p_msg, p_msg->opid, p_msg->retry);
genie_dlist_remove(p_node);
genie_model_msg_node_free((genie_model_msg_node_t *)p_node);
break;
}
}
return 0;
}
/** @def genie_model_msg_send
*
* @brief send the vendor model message
*
* @param pointer to the message to be sent
*
* @return 0 for success; negative for failure
*/
int16_t genie_model_msg_send(genie_model_msg_t *p_model_msg)
{
ENTER_FUNC();
int16_t err = -1;
bool resend_flag = false;
esp_ble_mesh_msg_ctx_t ctx = {0};
esp_ble_mesh_model_t *p_model = esp_ble_mesh_find_vendor_model(p_model_msg->p_elem, CID_ALIBABA, GENIE_VENDOR_MODEL_SRV_ID);
genie_model_init();
if (!p_model) {
ESP_LOGE(TAG, "cannot find vendor model server %p", p_model_msg->p_elem);
return err;
}
ESP_LOGD(TAG, "p_model: 0x%p, cid: 0x%04x, id: 0x%04x, retry: %d", p_model, p_model->vnd.company_id, p_model->vnd.model_id, p_model_msg->retry);
/**
* no need to duplicate the following messages
* 1. retry <= 0 - the message won't want to be resent
* 2. tid is in valid range (0x00, 0x7F] [0xC0, 0xff]
* 3. opcode is not VENDOR_OP_ATTR_INDICATE/VENDOR_OP_ATTR_INDICATE_TG/VENDOR_OP_ATTR_TRANS_INDICATE
* 4. already duplicated or CONFIME/CONFIME_TG
* */
if ((p_model_msg->retry > 1) &&
(p_model_msg->tid >= 0x7F && p_model_msg->tid < 0xC0) &&
(p_model_msg->opid != GENIE_OP_ATTR_SET_UNACK) &&
(p_model_msg->opid != GENIE_OP_ATTR_CONFIME) &&
(p_model_msg->opid != GENIE_OP_ATTR_CONFIME_TG) &&
(p_model_msg->opid != GENIE_OP_ATTR_TRANS_MSG) &&
(p_model_msg->opid != GENIE_OP_ATTR_TRANS_ACK) ) {
resend_flag = true;
ESP_LOGD(TAG, "resend_flag");
}
/**
* only when opid is one of GENIE_OP_ATTR_CONFIME, GENIE_OP_ATTR_CONFIME_TG and GENIE_OP_ATTR_TRANS_ACK, shall we keep tid as it is
* */
if (!(p_model_msg->tid) &&
(p_model_msg->opid != GENIE_OP_ATTR_CONFIME) &&
(p_model_msg->opid != GENIE_OP_ATTR_CONFIME_TG) &&
(p_model_msg->opid != GENIE_OP_ATTR_TRANS_MSG) &&
(p_model_msg->opid != GENIE_OP_ATTR_TRANS_ACK)) {
p_model_msg->tid = genie_model_msg_gen_tid();
ESP_LOGD(TAG, "genie_model_msg_gen_tid");
}
// prepare buffer
uint8_t *data = malloc(p_model_msg->len + 1);
data[0] = p_model_msg->tid;
memcpy(data + 1, p_model_msg->data, p_model_msg->len);
p_model_msg->retry--;
ESP_LOGD(TAG, "p_model_msg->opid: 0x%04x, p_model_msg->data: 0x%p, len: %d, data: %s",
p_model_msg->opid, p_model_msg, p_model_msg->len, util_hex2str(p_model_msg->data, p_model_msg->len));
if (resend_flag) {
ESP_LOGD(TAG, "genie_model_msg_list_append");
genie_model_msg_list_append(p_model_msg);
}
ctx.app_idx = bt_mesh_model_get_appkey_id(p_model_msg->p_elem, p_model);
ctx.net_idx = bt_mesh_model_get_netkey_id(p_model_msg->p_elem);
ctx.addr = GENIE_RECV_ADDR;
ctx.send_ttl = BLE_MESH_TTL_DEFAULT;
ctx.send_rel = 0;
ctx.srv_send = true;
ESP_LOGI(TAG, "vendor message send: tid: 0x%02x, retry: %02d, len: %02d, opcode: 0x%x, data: 0x%s", p_model_msg->tid, p_model_msg->retry, p_model_msg->len, p_model_msg->opid, util_hex2str(p_model_msg->data, p_model_msg->len));
ESP_LOGD(TAG, "vendor message send: element: 0x%p, app_idx: %d, net_idx: %d, tid: 0x%02x, retry: %02d, len: %02d, opcode: 0x%x, data: 0x%s",
p_model_msg->p_elem, ctx.app_idx, ctx.net_idx, p_model_msg->tid, p_model_msg->retry, p_model_msg->len, p_model_msg->opid, util_hex2str(p_model_msg->data, p_model_msg->len));
err = esp_ble_mesh_server_model_send_msg(p_model, &ctx, ESP_BLE_MESH_MODEL_OP_3(p_model_msg->opid, CID_ALIBABA), p_model_msg->len + 1, data);
if (err) {
ESP_LOGE(TAG, "mesh model send msg, err: %d", err);
} else {
ESP_LOGD(TAG, "mesh model send msg success");
}
if (p_model_msg->retry == 0) {
ESP_LOGW(TAG, "The message has been retried 9 times and may be lost. This is the last retry. tid: %02x", p_model_msg->tid);
}
free(data);
return err;
}
/** @def genie_model_analyze
*
* @brief analyze the received message and notify genie SDK
*
* @param pointer to the received message (vendor model, context and the message buffer) and opid
*
* @return if success return 0; if fails return error no.
*/
static int16_t genie_model_analyze(esp_ble_mesh_model_t *p_model,
esp_ble_mesh_msg_ctx_t *p_ctx,
struct net_buf_simple *p_buf,
uint8_t opid)
{
ENTER_FUNC();
genie_model_msg_t msg = {0};
if (!p_model || !p_buf) {
return GENIE_MESH_ANALYZE_ARGS_ERROR;
}
if (p_buf->len < 3) {
ESP_LOGE(TAG, "invalid buf len: %d", p_buf->len);
return GENIE_MESH_ANALYZE_SIZE_ERROR;
}
memset(&msg, 0, sizeof(genie_model_msg_t));
msg.opid = opid;
msg.tid = net_buf_simple_pull_u8(p_buf);
if (genie_msg_check_tid(p_ctx->addr, msg.tid) != GENIE_MESH_SUCCESS) {
ESP_LOGE(TAG, "GENIE_MESH_TID_REPEAT_ERROR src_addr: 0x%04x, tid: 0x%02x", p_ctx->addr, msg.tid);
return GENIE_MESH_TID_REPEAT_ERROR;
}
msg.len = p_buf->len;
ESP_LOGD(TAG, "opcode: 0x%x, tid: %02x, len: %d", msg.opid, msg.tid, msg.len);
if (msg.len) {
msg.data = (uint8_t *)p_buf->data;
net_buf_simple_pull(p_buf, msg.len);
ESP_LOGD(TAG, "payload: %s", util_hex2str(msg.data, msg.len));
} else {
msg.data = NULL;
}
genie_event(GENIE_EVT_SDK_VENDOR_MSG, (void *)&msg);
return 0;
}
/** @def genie_model_get_status
*
* @brief handle GENIE_OP_ATTR_GET_STATUS message
*
* @param pointer to the received message (vendor model, context and the message buffer)
*
* @return N/A
*/
static void genie_model_get_status(esp_ble_mesh_model_t *model,
esp_ble_mesh_msg_ctx_t *ctx,
struct net_buf_simple *buf)
{
ENTER_FUNC();
genie_model_analyze(model, ctx, buf, GENIE_OP_ATTR_GET_STATUS);
}
/** @def genie_model_set_ack
*
* @brief handle GENIE_OP_ATTR_SET_ACK message
*
* @param pointer to the received message (vendor model, context and the message buffer)
*
* @return N/A
*/
static void genie_model_set_ack(esp_ble_mesh_model_t *model,
esp_ble_mesh_msg_ctx_t *ctx,
struct net_buf_simple *buf)
{
ENTER_FUNC();
genie_model_analyze(model, ctx, buf, GENIE_OP_ATTR_SET_ACK);
}
/** @def genie_model_set_unack
*
* @brief handle GENIE_OP_ATTR_SET_UNACK message
*
* @param pointer to the received message (vendor model, context and the message buffer)
*
* @return N/A
*/
static void genie_model_set_unack(esp_ble_mesh_model_t *model,
esp_ble_mesh_msg_ctx_t *ctx,
struct net_buf_simple *buf)
{
ENTER_FUNC();
genie_model_analyze(model, ctx, buf, GENIE_OP_ATTR_SET_UNACK);
}
/** @def genie_model_confirm
*
* @brief handle GENIE_OP_ATTR_CONFIME message
*
* @param pointer to the received message (vendor model, context and the message buffer)
*
* @return N/A
*/
static void genie_model_confirm(esp_ble_mesh_model_t *model,
esp_ble_mesh_msg_ctx_t *ctx,
struct net_buf_simple *buf)
{
ENTER_FUNC();
uint8_t tid = 0x0;
if (buf->len != 1) {
ESP_LOGE(TAG, "invalid buf len: %d", buf->len);
return;
}
tid = net_buf_simple_pull_u8(buf);
ESP_LOGI(TAG, "confirm tid: %02x", tid);
genie_model_msg_check_tid(&g_vnd_msg_list, tid);
}
/** @def genie_model_confirm_tg
*
* @brief handle GENIE_OP_ATTR_CONFIME_TG message
*
* @param pointer to the received message (vendor model, context and the message buffer)
*
* @return N/A
*/
static void genie_model_confirm_tg(esp_ble_mesh_model_t *model,
esp_ble_mesh_msg_ctx_t *ctx,
struct net_buf_simple *buf)
{
ENTER_FUNC();
uint8_t tid = 0x0;
if (buf->len != 1) {
ESP_LOGE(TAG, "invalid buf len: %d", buf->len);
return;
}
tid = net_buf_simple_pull_u8(buf);
ESP_LOGI(TAG, "confirm_tg tid: %02x", tid);
genie_model_msg_check_tid(&g_vnd_msg_list, tid);
}
/** @def genie_model_transparent
*
* @brief handle GENIE_OP_ATTR_TRANS_MSG message
*
* @param pointer to the received message (vendor model, context and the message buffer)
*
* @return N/A
*/
static void genie_model_transparent(esp_ble_mesh_model_t *model,
esp_ble_mesh_msg_ctx_t *ctx,
struct net_buf_simple *buf)
{
ENTER_FUNC();
genie_model_analyze(model, ctx, buf, GENIE_OP_ATTR_TRANS_MSG);
}
/** @def genie_model_transparent_ack
*
* @brief handle GENIE_OP_ATTR_TRANS_ACK message
*
* @param pointer to the received message (vendor model, context and the message buffer)
*
* @return N/A
*/
static void genie_model_transparent_ack(esp_ble_mesh_model_t *model,
esp_ble_mesh_msg_ctx_t *ctx,
struct net_buf_simple *buf)
{
ENTER_FUNC();
uint8_t tid = 0x0;
if (buf->len != 1) {
ESP_LOGE(TAG, "invalid buf len: %d", buf->len);
return;
}
tid = net_buf_simple_pull_u8(buf);
genie_model_msg_check_tid(&g_vnd_msg_list, tid);
}
static genie_opcode_cb_t genie_opcode_cb[] = {
{GENIE_MESSAGE_OP_ATTR_GET_STATUS, genie_model_get_status},
{GENIE_MESSAGE_OP_ATTR_SET_ACK, genie_model_set_ack},
{GENIE_MESSAGE_OP_ATTR_SET_UNACK, genie_model_set_unack},
{GENIE_MESSAGE_OP_ATTR_CONFIRMATION, genie_model_confirm},
{GENIE_MESSAGE_OP_ATTR_CONFIRMATION_TG, genie_model_confirm_tg},
{GENIE_MESSAGE_OP_ATTR_TRANSPARENT_MSG, genie_model_transparent},
{GENIE_MESSAGE_OP_ATTR_TRANSPARENT_ACK, genie_model_transparent_ack},
};
/**
* @brief Dispatch message according opcode.
*
* @param opcode
* @param model
* @param ctx
* @param msg
* @param length
*/
void genie_model_dispatch(uint32_t opcode, esp_ble_mesh_model_t *model,
esp_ble_mesh_msg_ctx_t *ctx, uint8_t *msg, uint16_t length)
{
ENTER_FUNC();
NET_BUF_SIMPLE_DEFINE(buf, length);
memcpy(buf.__buf, msg, length);
buf.len = length;
for (uint8_t i = 0; i < ARRAY_SIZE(genie_opcode_cb); i++) {
if (genie_opcode_cb[i].opcode == opcode) {
genie_opcode_cb[i].cb(model, ctx, &buf);
return;
}
}
ESP_LOGW(TAG, "not find callback function for opcode: 0x%04x", opcode);
}

View File

@ -0,0 +1,166 @@
// Copyright (C) 2018-2020 Alibaba Group Holding Limited
// Adaptations to ESP-IDF Copyright (c) 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "esp_ble_mesh_defs.h"
#include "esp_ble_mesh_common_api.h"
#include "esp_ble_mesh_networking_api.h"
#include "esp_ble_mesh_local_data_operation_api.h"
#include "genie_mesh.h"
#include "genie_reset.h"
#include "genie_event.h"
#include "genie_util.h"
#include "ble_mesh_example_nvs.h"
static const char *TAG = "genie_reset";
extern nvs_handle_t NVS_HANDLE;
util_timer_t g_genie_reset_timer;
// 1:need reset 0:no reset
uint8_t g_genie_reset_flag = 0;
uint8_t genie_reset_get_flag(void)
{
return g_genie_reset_flag;
}
/**
* @brief Set reset flag
*
* @param flag
*/
static void genie_reset_set_flag(uint8_t flag)
{
g_genie_reset_flag = flag;
}
/**
* @brief Reset timer callback timeout, Send event to genie event to handle reset action.
*
* @param p_timer
*/
static void genie_reset_done_timer_cb(void *p_timer)
{
ENTER_FUNC();
genie_event(GENIE_EVT_HW_RESET_DONE, NULL);
}
void genie_reset_done_timer_start(void)
{
ENTER_FUNC();
util_timer_init(&g_genie_reset_timer, genie_reset_done_timer_cb, NULL);
util_timer_start(&g_genie_reset_timer, GENIE_RESET_WAIT_DONE_TIMEOUT);
}
/**
* @brief Store reboot count in flash.
*
* @param count
*
* @return esp_err_t
*/
static esp_err_t genie_reset_write_count(uint8_t count)
{
ENTER_FUNC();
uint8_t data = count;
esp_err_t ret = ESP_OK;
ret = ble_mesh_nvs_store(NVS_HANDLE, GENIE_STORE_RESTART_COUNT_KEY, &data, sizeof(uint8_t));
if (ret != ESP_OK) {
ESP_LOGW(TAG, "Save the number of restarts within the set time");
}
return ret;
}
/**
* @brief Read reboot count from flash.
*
* @param p_count
* @return esp_err_t
*/
static esp_err_t genie_reset_read_count(uint8_t *p_count)
{
ENTER_FUNC();
bool exist = false;
esp_err_t ret = ESP_OK;
ret = ble_mesh_nvs_restore(NVS_HANDLE, GENIE_STORE_RESTART_COUNT_KEY, p_count, sizeof(uint8_t), &exist);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "Load the number of restarts within the set time");
}
return ret;
}
/**
* @brief GENIE_RESET_BY_REPEAT_TIMEOUT
*
* @param p_timer
*/
static void genie_reset_timer_cb(void *p_timer)
{
ENTER_FUNC();
uint8_t number = 0;
genie_reset_write_count(number);
}
void genie_reset_clean_count(void)
{
genie_reset_write_count(0);
genie_reset_set_flag(0);
}
void genie_reset_by_repeat_init(void)
{
ENTER_FUNC();
uint8_t count = 0;
esp_err_t flash_err = ESP_OK;
/* we should check flash flag first */
flash_err = genie_reset_read_count(&count);
if (flash_err != ESP_OK) {
ESP_LOGE(TAG, "flash read err");
count = 0;
}
ESP_LOGI(TAG, "read count %d", count);
count++;
if (count == GENIE_RESET_BY_REPEAT_COUNTER) {
ESP_LOGI(TAG, "Genie Event Reset By Repeat Notify");
genie_event(GENIE_EVT_RESET_BY_REPEAT_NOTIFY, NULL);
}
/* update number and check if exceeded */
if (count < GENIE_RESET_BY_REPEAT_COUNTER) {
genie_reset_write_count(count);
util_timer_init(&g_genie_reset_timer, genie_reset_timer_cb, NULL);
util_timer_start(&g_genie_reset_timer, GENIE_RESET_BY_REPEAT_TIMEOUT);
} else {
//genie_event(GENIE_EVT_HW_RESET, NULL);
ESP_LOGW(TAG, "genie_reset_set_flag need reset node");
genie_reset_set_flag(1);
util_timer_init(&g_genie_reset_timer, genie_reset_timer_cb, NULL);
util_timer_start(&g_genie_reset_timer, GENIE_RESET_BY_REPEAT_TIMEOUT);
}
EXIT_FUNC();
}

View File

@ -0,0 +1,845 @@
// Copyright (C) 2018-2020 Alibaba Group Holding Limited
// Adaptations to ESP-IDF Copyright (c) 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "genie_mesh.h"
#include "genie_event.h"
#include "genie_util.h"
#include "genie_timer.h"
#include "genie_model_srv.h"
#include "ble_mesh_example_nvs.h"
#define DAY 86400
#define HOUR 3600
#define MINU 60
#define VT_NUM (40)
#define VT_LOCK util_semaphore_take(&g_genie_timer.lock, -1)
#define VT_UNLOCK util_semaphore_give(&g_genie_timer.lock)
extern nvs_handle_t NVS_HANDLE;
static const char *TAG = "genie_timer";
static utc_time_t local_time = {0};
typedef enum {
TIMER_OFF = 0,
TIMER_ON = 1,
TIMER_INVAILD = 0xf,
} vt_state;
struct genie_timer_t {
genie_snode_t next;
uint8_t index;
uint8_t state: 4;
uint8_t periodic: 1;
uint16_t periodic_time;
uint8_t schedule;
uint32_t unixtime_match;
genie_timer_attr_data_t attr_data;
};
struct unixtime_sync_para_t {
uint16_t period_time; // Synchronization period: Time synchronization request cycle.
uint8_t retry_delay; // Retry delay: How long to retry after a failed time synchronization request
uint8_t retry_times; // Retry times of time synchronization request
};
struct {
uint16_t magic;
int8_t timezone;
struct unixtime_sync_para_t timing_sync_config; // Time synchronization parameters
struct genie_timer_t timer_data[VT_NUM]; // Store Timing operation
} g_timing_data;
struct {
uint32_t init: 1;
uint32_t update: 1;
esp_timer_handle_t timer;
struct k_work work;
util_semaphore_t lock;
genie_slist_t timer_list_active;
genie_slist_t timer_list_idle;
uint32_t unix_time;
uint32_t unix_time_sync_match;
uint8_t unix_time_sync_retry_times;
genie_timer_event_func_t cb;
} g_genie_timer;
static inline uint8_t is_leap_year(uint16_t year);
utc_time_t genie_timer_local_time_get(void)
{
return local_time;
}
uint32_t genie_timer_local_unixtime_get(void)
{
return g_genie_timer.unix_time;
}
static inline utc_time_t convert_unix_to_utc(uint32_t unix_time)
{
uint16_t g_noleap_daysbeforemonth[13] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
utc_time_t utc = {0};
uint32_t epoch = 0;
uint32_t jdn = 0;
int year = 0;
int month = 0;
int day = 0;
int hour = 0;
int minutes = 0;
int sec = 0;
int weekday = 0;
epoch = unix_time;
jdn = epoch / DAY;
epoch -= DAY * jdn;
weekday = (jdn + 4) % 7; // 1970/1/1 is thursday
hour = epoch / HOUR;
epoch -= HOUR * hour;
minutes = epoch / MINU;
epoch -= MINU * minutes;
sec = epoch;
year = jdn / (4 * 365 + 1); /* Number of 4-years periods since the epoch */
jdn -= year * (4 * 365 + 1); /* Remaining days */
year <<= 2; /* Years since the epoch */
/* Then we will brute force the next 0-3 years */
bool leapyear;
int tmp;
for (; ;) {
/* Is this year a leap year (we'll need this later too) */
leapyear = is_leap_year(year + 1970);
/* Get the number of days in the year */
tmp = (leapyear ? 366 : 365);
/* Do we have that many days? */
if (jdn >= tmp) {
/* Yes.. bump up the year */
year++;
jdn -= tmp;
} else {
/* Nope... then go handle months */
break;
}
}
/* At this point, value has the year and days has number days into this year */
year += 1970;
/* Handle the month (zero based) */
int min = 0;
int max = 11;
int value = 0;
do {
/* Get the midpoint */
value = (min + max) >> 1;
/* Get the number of days that occurred before the beginning of the month
* following the midpoint.
*/
tmp = g_noleap_daysbeforemonth[value + 1];
if (value + 1 >= 2 && is_leap_year(leapyear)) {
tmp++;
}
/* Does the number of days before this month that equal or exceed the
* number of days we have remaining?
*/
if (tmp > jdn) {
/* Yes.. then the month we want is somewhere from 'min' and to the
* midpoint, 'value'. Could it be the midpoint?
*/
tmp = g_noleap_daysbeforemonth[value];
if (value >= 2 && is_leap_year(leapyear)) {
tmp++;
}
if (tmp > jdn) {
/* No... The one we want is somewhere between min and value-1 */
max = value - 1;
} else {
/* Yes.. 'value' contains the month that we want */
break;
}
} else {
/* No... The one we want is somwhere between value+1 and max */
min = value + 1;
}
/* If we break out of the loop because min == max, then we want value
* to be equal to min == max.
*/
value = min;
} while (min < max);
/* The selected month number is in value. Subtract the number of days in the
* selected month
*/
tmp = g_noleap_daysbeforemonth[value];
if (value >= 2 && is_leap_year(leapyear)) {
tmp++;
}
jdn -= tmp;
/* At this point, value has the month into this year (zero based) and days has
* number of days into this month (zero based)
*/
month = value; // zero based
day = jdn + 1; // one based
utc.year = year;
utc.month = month;
utc.day = day;
utc.weekday = weekday;
utc.hour = hour;
utc.minutes = minutes;
utc.seconds = sec;
return utc;
}
static inline uint32_t convert_utc_to_unix(utc_time_t *utc_time)
{
uint32_t days;
uint16_t g_noleap_daysbeforemonth[13] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
days = (utc_time->year - 1970) * 365;
days += (utc_time->year - 1969) >> 2;
days += g_noleap_daysbeforemonth[utc_time->month];
if (utc_time->month >= 2 && is_leap_year(utc_time->year)) {
days++;
}
days += utc_time->day - 1;
return ((days * 24 + utc_time->hour) * 60 + utc_time->minutes) * 60 + utc_time->seconds;
}
static inline uint8_t is_leap_year(uint16_t year)
{
if (((year % 4) == 0) && ((year % 100) != 0)) {
return 1;
} else if ((year % 400) == 0) {
return 1;
} else {
return 0;
}
}
static inline void month_update(void)
{
local_time.month++;
if (local_time.month >= 12) {
local_time.month = 0;
local_time.year++;
}
}
static inline void days_update(void)
{
uint8_t month_days_list[12] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
};
local_time.day++;
if (is_leap_year(local_time.year)) {
month_days_list[1] = 29;
}
uint8_t month_day = month_days_list[local_time.month];
if (local_time.day > month_day) {
local_time.day = 0;
month_update();
}
local_time.weekday = (local_time.weekday + 1) % 7;
}
static inline void hours_update(void)
{
local_time.hour++;
if (local_time.hour == 24) {
local_time.hour = 0;
days_update();
}
}
static inline void minutes_update(void)
{
local_time.minutes++;
if (local_time.minutes == 60) {
local_time.minutes = 0;
hours_update();
}
}
static inline void seconds_update(void)
{
local_time.seconds++;
if (local_time.seconds == 60) {
local_time.seconds = 0;
minutes_update();
}
}
static void genie_timer_update(void *args)
{
if (!g_genie_timer.update) {
ESP_LOGE(TAG, "g_genie_timer.update %d", g_genie_timer.update);
return;
}
g_genie_timer.unix_time += 1;
seconds_update();
k_work_submit(&g_genie_timer.work);
if (g_genie_timer.unix_time % 60 == 0) {
ESP_LOGI(TAG, "genie_timer_update %d", g_genie_timer.unix_time);
}
}
static inline uint8_t next_weekday_diff_get(uint8_t weekday_now, uint8_t schedule)
{
uint16_t schedule_tmp = 0;
if (weekday_now == 0) {
weekday_now = 7;
}
schedule_tmp = ((schedule | ((uint16_t) schedule << 7)) >> (weekday_now - 1)) & 0x7f;
uint8_t day_diff = 0;
while (day_diff < 7) {
if ((schedule_tmp >> day_diff) & 0x0001) {
break;
}
day_diff++;
}
return day_diff;
}
static inline uint8_t is_weekday_match(uint8_t weekday_now, uint8_t schedule)
{
uint8_t weekday_mask = weekday_now ? (uint8_t)(1 << (weekday_now - 1)) : (uint8_t)(1 << 6);
return (weekday_mask == (schedule & weekday_mask));
}
static inline uint8_t next_weekday(uint8_t weekday_now)
{
return (weekday_now + 1) % 7;
}
static int genie_timer_save(void)
{
#ifdef GENIE_VENDOR_TIMER_STORE
int ret = 0;
ret = ble_mesh_nvs_store(NVS_HANDLE, GENIE_STORE_VENDOR_TIMER, &g_timing_data, sizeof(g_timing_data));
if (ret) {
ESP_LOGI(TAG, "vendor timers save fail %d", ret);
}
return ret;
#else
return -1;
#endif
}
static int genie_timer_restore(void)
{
#ifdef GENIE_VENDOR_TIMER_STORE
int ret = 0;
bool exist = false;
memset(&g_timing_data, 0, sizeof(g_timing_data));
ret = ble_mesh_nvs_restore(NVS_HANDLE, GENIE_STORE_VENDOR_TIMER, &g_timing_data, sizeof(g_timing_data), &exist);
if (ret) {
ESP_LOGI(TAG, "vendor timers restore fail %d", ret);
return ret;
}
if (g_timing_data.magic != 0xABCD
|| g_timing_data.timezone < -12
|| g_timing_data.timezone > 12) {
ESP_LOGI(TAG, "vendor timers restore missmatch");
return -1;
}
for (int i = 0; i < VT_NUM; i++) {
if (g_timing_data.timer_data[i].state != TIMER_INVAILD) {
genie_slist_append(&g_genie_timer.timer_list_active, &g_timing_data.timer_data[i].next);
} else {
genie_slist_append(&g_genie_timer.timer_list_idle, &g_timing_data.timer_data[i].next);
}
ESP_LOGI(TAG, "restore vendor timer index %d state %d periodic %d periodic_time %d schedule %d unixtime_match %d",
g_timing_data.timer_data[i].index, g_timing_data.timer_data[i].state,
g_timing_data.timer_data[i].periodic, g_timing_data.timer_data[i].periodic_time,
g_timing_data.timer_data[i].schedule, g_timing_data.timer_data[i].unixtime_match);
}
return 0;
#else
return -1;
#endif
}
static uint8_t is_genie_timer_timeout(struct genie_timer_t *vendor_timer)
{
if (vendor_timer->state == TIMER_INVAILD) {
return 0;
}
if (vendor_timer->periodic) {
if (is_weekday_match(local_time.weekday, vendor_timer->schedule)
&& vendor_timer->unixtime_match < g_genie_timer.unix_time) {
vendor_timer->unixtime_match += (1 + next_weekday_diff_get(next_weekday(local_time.weekday), vendor_timer->schedule)) * DAY;
}
}
return vendor_timer->unixtime_match == g_genie_timer.unix_time;
}
static void genie_timer_check(void)
{
struct genie_timer_t *tmp, *node;
GENIE_SLIST_FOR_EACH_CONTAINER_SAFE(&g_genie_timer.timer_list_active, node, tmp, next) {
if (is_genie_timer_timeout(node)) {
if (g_genie_timer.cb) {
g_genie_timer.cb(GENIE_TIME_EVT_TIMEOUT, node->index, &node->attr_data);
}
VT_LOCK;
if (!node->periodic) {
node->unixtime_match = 0xffffffff;
node->state = TIMER_INVAILD;
genie_slist_find_and_remove(&g_genie_timer.timer_list_active, &node->next);
genie_slist_append(&g_genie_timer.timer_list_idle, &node->next);
} else {
node->unixtime_match += 24 * HOUR;
}
VT_UNLOCK;
genie_timer_save();
}
}
if (g_genie_timer.unix_time_sync_match
&& g_genie_timer.unix_time_sync_match <= g_genie_timer.unix_time) {
if (g_genie_timer.cb) {
int ret = g_genie_timer.cb(GENIE_TIME_EVT_TIMING_SYNC, 0, NULL);
if (ret) {
if (g_genie_timer.unix_time_sync_retry_times > 0) {
g_genie_timer.unix_time_sync_match += g_timing_data.timing_sync_config.retry_delay * MINU;
g_genie_timer.unix_time_sync_retry_times--;
return;
}
}
}
g_genie_timer.unix_time_sync_match = g_genie_timer.unix_time + g_timing_data.timing_sync_config.period_time * MINU;
g_genie_timer.unix_time_sync_retry_times = g_timing_data.timing_sync_config.retry_times;
}
}
static void genie_timer_check_work(struct k_work *work)
{
genie_timer_check();
}
static genie_timer_handle_t genie_timer_find(uint8_t index)
{
ENTER_FUNC();
if (index >= VT_NUM) {
return NULL;
}
VT_LOCK;
struct genie_timer_t *tmp, *node = NULL;
genie_slist_t *list = NULL;
list = &g_genie_timer.timer_list_active;
GENIE_SLIST_FOR_EACH_CONTAINER_SAFE(list, node, tmp, next) {
if (node->index == index) {
VT_UNLOCK;
return node;
}
}
VT_UNLOCK;
return NULL;
}
static struct genie_timer_t *genie_timer_new(void)
{
ENTER_FUNC();
struct genie_timer_t *free_timer = NULL;
VT_LOCK;
free_timer = (struct genie_timer_t *)genie_slist_get(&g_genie_timer.timer_list_idle);
VT_UNLOCK;
ESP_LOGI(TAG, "timer new %p", free_timer);
return free_timer;
}
int genie_timer_start(uint8_t index, uint32_t unix_time, genie_timer_attr_data_t *attr_data)
{
ENTER_FUNC();
struct genie_timer_t *vendor_timer = NULL;
if (!attr_data) {
return -GENIE_TIMER_ERR_PARAM;
}
ESP_LOGI(TAG, "timer start index %d expect unix_time %d attr_type %d",
index, unix_time, attr_data->type);
if (!g_genie_timer.init) {
return -GENIE_TIMER_ERR_INIT;
}
if (!g_genie_timer.update) {
return -GENIE_TIMER_ERR_LOCALTIME_NOTSET;
}
if (index >= VT_NUM) {
//return -GENIE_TIMER_ERR_INDEX;
}
if (unix_time <= g_genie_timer.unix_time) {
return -GENIE_TIMER_ERR_PARAM;
}
vendor_timer = genie_timer_find(index);
if (vendor_timer == NULL) {
vendor_timer = genie_timer_new();
if (vendor_timer == NULL) {
return -GENIE_TIMER_ERR_NORESOURCE;
}
} else {
VT_LOCK;
genie_slist_find_and_remove(&g_genie_timer.timer_list_active, &vendor_timer->next);
VT_UNLOCK;
}
vendor_timer->index = index;
vendor_timer->unixtime_match = unix_time; // + g_genie_timer.timezone * HOUR;
vendor_timer->state = TIMER_ON;
vendor_timer->attr_data.type = attr_data->type;
vendor_timer->attr_data.para = attr_data->para;
VT_LOCK;
genie_slist_append(&g_genie_timer.timer_list_active, &vendor_timer->next);
VT_UNLOCK;
genie_timer_save();
return 0;
}
int genie_timer_periodic_start(uint8_t index, uint16_t periodic_time, uint8_t schedule, genie_timer_attr_data_t *attr_data)
{
ENTER_FUNC();
struct genie_timer_t *vendor_timer = NULL;
ESP_LOGI(TAG, "periodic timer start index %d periodic_time %d schedule %d attr_para %d",
index, periodic_time, schedule, attr_data->para);
if (!g_genie_timer.init) {
return -GENIE_TIMER_ERR_INIT;
}
if (!g_genie_timer.update) {
return -GENIE_TIMER_ERR_LOCALTIME_NOTSET;
}
if (index >= VT_NUM) {
//return -GENIE_TIMER_ERR_INDEX;
}
if (schedule == 0) {
return -GENIE_TIMER_ERR_PARAM;
}
vendor_timer = genie_timer_find(index);
if (vendor_timer == NULL) {
vendor_timer = genie_timer_new();
if (vendor_timer == NULL) {
return -GENIE_TIMER_ERR_NORESOURCE;
}
} else {
VT_LOCK;
genie_slist_find_and_remove(&g_genie_timer.timer_list_active, &vendor_timer->next);
VT_UNLOCK;
}
vendor_timer->index = index;
vendor_timer->periodic = 1;
vendor_timer->periodic_time = periodic_time;
vendor_timer->schedule = schedule;
vendor_timer->state = TIMER_ON;
utc_time_t utc = local_time;
utc.hour = 0;
utc.minutes = 0;
utc.seconds = 0;
utc.day = utc.day + next_weekday_diff_get(local_time.weekday, schedule);
vendor_timer->unixtime_match = convert_utc_to_unix(&utc) + periodic_time - g_timing_data.timezone * HOUR;
ESP_LOGI(TAG, "periodic timer unixtime_match %d", vendor_timer->unixtime_match);
VT_LOCK;
genie_slist_append(&g_genie_timer.timer_list_active, &vendor_timer->next);
VT_UNLOCK;
genie_timer_save();
return 0;
}
static int genie_timer_stop(int8_t index)
{
ENTER_FUNC();
ESP_LOGI(TAG, "timer stop %d", index);
if (!g_genie_timer.init) {
return -GENIE_TIMER_ERR_INIT;
}
if (!g_genie_timer.update) {
return -GENIE_TIMER_ERR_LOCALTIME_NOTSET;
}
if (index >= VT_NUM) {
return -GENIE_TIMER_ERR_INDEX;
}
struct genie_timer_t *vendor_timer = genie_timer_find(index);
if (vendor_timer == NULL) {
return -GENIE_TIMER_ERR_INDEX;
} else {
VT_LOCK;
vendor_timer->index = 0xFF;
vendor_timer->state = TIMER_INVAILD;
vendor_timer->unixtime_match = 0;
genie_slist_find_and_remove(&g_genie_timer.timer_list_active, &vendor_timer->next);
genie_slist_append(&g_genie_timer.timer_list_idle, &vendor_timer->next);
VT_UNLOCK;
}
return 0;
}
int genie_timer_remove(uint8_t index)
{
ENTER_FUNC();
int i = 0;
int ret = 0;
ESP_LOGI(TAG, "timer remove %d", index);
/* remove alll timers */
if (index == 0xFF) {
for (i = 0; i < VT_NUM; i++) {
genie_timer_stop(i);
}
return 0;
}
ret = genie_timer_stop(index);
genie_timer_save();
return ret;
}
void genie_timer_local_time_show(void)
{
ENTER_FUNC();
ESP_LOGI(TAG, "unix_time revert %d", convert_utc_to_unix(&local_time));
ESP_LOGI(TAG, "%4d/%2d/%2d %2d:%2d:%d weekday %2d %04d",
local_time.year, local_time.month + 1, local_time.day,
local_time.hour, local_time.minutes, local_time.seconds,
local_time.weekday, g_timing_data.timezone);
}
int genie_timer_timezone_update(int8_t timezone)
{
ENTER_FUNC();
ESP_LOGI(TAG, "timezone update %d", timezone);
if (timezone < -12 || timezone > 12) {
return -GENIE_TIMER_ERR_PARAM;
}
if (!g_genie_timer.init) {
return -GENIE_TIMER_ERR_INIT;
}
g_timing_data.timezone = timezone;
return 0;
}
int8_t genie_timer_timezone_get(void)
{
ENTER_FUNC();
return g_timing_data.timezone;
}
int genie_timer_time_sync_set(uint16_t period_time, uint8_t retry_delay, uint8_t retry_times)
{
ENTER_FUNC();
ESP_LOGI(TAG, "timing sync set period_time %d retry_delay %d retry_times %d",
period_time, retry_delay, retry_times);
if (period_time == 0 || retry_delay == 0 || retry_times) {
return -GENIE_TIMER_ERR_PARAM;
}
g_timing_data.timing_sync_config.period_time = period_time;
g_timing_data.timing_sync_config.retry_delay = retry_delay;
g_timing_data.timing_sync_config.retry_times = retry_times;
g_genie_timer.unix_time_sync_match = g_genie_timer.unix_time + g_timing_data.timing_sync_config.period_time * MINU;
g_genie_timer.unix_time_sync_retry_times = retry_times;
return 0;
}
int genie_timer_time_sync_get(uint16_t *period_time, uint8_t *retry_delay, uint8_t *retry_times)
{
ENTER_FUNC();
*period_time = g_timing_data.timing_sync_config.period_time;
*retry_delay = g_timing_data.timing_sync_config.retry_delay;
*retry_times = g_timing_data.timing_sync_config.retry_times;
return 0;
}
int genie_timer_local_time_update(uint32_t unix_time)
{
ENTER_FUNC();
if (!g_genie_timer.init) {
ESP_LOGE(TAG, "g_genie_timer not init");
return -GENIE_TIMER_ERR_INIT;
}
g_genie_timer.update = 1;
g_genie_timer.unix_time = unix_time;
local_time = convert_unix_to_utc(unix_time + g_timing_data.timezone * HOUR);
ESP_LOGI(TAG, "unix_time %d", unix_time);
ESP_LOGI(TAG, "localtime update %4d/%2d/%2d %2d:%2d:%d weekday %2d",
local_time.year, local_time.month + 1, local_time.day,
local_time.hour, local_time.minutes, local_time.seconds,
local_time.weekday);
ESP_LOGI(TAG, "unix_time revert %d", convert_utc_to_unix(&local_time));
return 0;
}
int genie_timer_init(genie_timer_event_func_t cb)
{
ENTER_FUNC();
int i = 0;
if (g_genie_timer.init) {
return 0;
}
if (cb == NULL) {
return -GENIE_TIMER_ERR_INIT;
}
memset(&g_genie_timer, 0, sizeof(g_genie_timer));
memset(&local_time, 0, sizeof(local_time));
g_genie_timer.cb = cb;
genie_slist_init(&g_genie_timer.timer_list_active);
genie_slist_init(&g_genie_timer.timer_list_idle);
util_semaphore_init(&g_genie_timer.lock, 1, 1);
k_work_init(&g_genie_timer.work, genie_timer_check_work);
esp_timer_create_args_t create_args = {
.callback = genie_timer_update,
.name = "genie_timer"
};
ESP_ERROR_CHECK(esp_timer_create(&create_args, &g_genie_timer.timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(g_genie_timer.timer, 1000 * 1000));
g_genie_timer.init = 1;
if (genie_timer_restore()) {
memset(&g_timing_data, 0, sizeof(g_timing_data));
g_timing_data.timezone = 8;
g_timing_data.magic = 0xABCD;
for (i = 0; i < VT_NUM; i++) {
g_timing_data.timer_data[i].unixtime_match = 0xffffffff;
g_timing_data.timer_data[i].index = 0xFF;
g_timing_data.timer_data[i].state = TIMER_INVAILD;
genie_slist_append(&g_genie_timer.timer_list_idle, &g_timing_data.timer_data[i].next);
}
}
/* sync timing */
g_genie_timer.cb(GENIE_TIME_EVT_TIMING_SYNC, 0, NULL);
return 0;
}

View File

@ -0,0 +1,202 @@
// Copyright (C) 2018-2020 Alibaba Group Holding Limited
// Adaptations to ESP-IDF Copyright (c) 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "genie_mesh.h"
#include "genie_util.h"
static const char *TAG = "genie_util";
void util_timer_init(util_timer_t *timer, util_timer_handler_t handle, void *args)
{
ENTER_FUNC();
int ret = 0;
if (NULL == timer) {
ESP_LOGE(TAG, "timer is NULL");
return;
}
ESP_LOGD(TAG, "timer: %p, handle: %p, args: %p", timer, handle, args);
esp_timer_create_args_t create_args = {
.callback = handle,
.arg = args,
.name = "genie_timer"
};
timer->handler = handle;
timer->args = args;
timer->timeout = 0;
ret = esp_timer_create(&create_args, &timer->timer);
if (ret) {
ESP_LOGE(TAG, "fail to create a timer, err: %d", ret);
}
}
void util_timer_start(util_timer_t *timer, uint32_t timeout)
{
ENTER_FUNC();
int ret = 0;
if (NULL == timer) {
ESP_LOGE(TAG, "timer is NULL");
return;
}
ESP_LOGD(TAG, "timer: %p, timeout: %u", timer, timeout);
util_timer_stop(timer);
timer->timeout = timeout;
timer->start_ms = (uint32_t)esp_timer_get_time();
ret = esp_timer_start_once(timer->timer, timeout * 1000);
if (ret) {
ESP_LOGE(TAG, "fail to change timeout and start timer, err: %d", ret);
}
}
void util_timer_stop(util_timer_t *timer)
{
ENTER_FUNC();
int ret = 0;
if (NULL == timer) {
ESP_LOGE(TAG, "timer is NULL");
return;
}
/**
* Timer may be reused, so its timeout value
* should be cleared when stopped.
*/
if (!timer->timeout) {
return;
}
ESP_LOGD(TAG, "timer: %p", timer);
ret = esp_timer_stop(timer->timer);
if (ret) {
ESP_LOGD(TAG, "fail to stop timer, err: %d", ret);
}
timer->timeout = 0;
}
bool util_timer_is_started(util_timer_t *timer)
{
ENTER_FUNC();
if (NULL == timer) {
ESP_LOGE(TAG, "timer is NULL");
return false;
}
return timer->timeout ? true : false;
}
int util_semaphore_init(util_semaphore_t *sem, uint32_t initial_count,
uint32_t limit)
{
ENTER_FUNC();
int ret = 0;
if (NULL == sem) {
ESP_LOGE(TAG, "sem is NULL");
return -EINVAL;
}
sem->sem = xSemaphoreCreateCounting(limit, initial_count);
return ret;
}
int util_semaphore_take(util_semaphore_t *sem, uint32_t timeout)
{
ENTER_FUNC();
TickType_t ticks = 0;
if (timeout == K_FOREVER) {
ticks = portMAX_DELAY;
} else {
ticks = pdMS_TO_TICKS(timeout);
}
return xSemaphoreTake(sem->sem, ticks);
}
int util_semaphore_give(util_semaphore_t *sem)
{
ENTER_FUNC();
if (NULL == sem) {
ESP_LOGE(TAG, "sem is NULL");
return -EINVAL;
}
xSemaphoreGive(sem->sem);
return 0;
}
int util_semaphore_delete(util_semaphore_t *sem)
{
ENTER_FUNC();
if (NULL == sem) {
ESP_LOGE(TAG, "sem is NULL");
return -EINVAL;
}
vSemaphoreDelete(sem->sem);
return 0;
}
uint32_t util_semaphore_count_get(util_semaphore_t *sem)
{
ENTER_FUNC();
uint32_t count = 0;
count = uxSemaphoreGetCount(sem->sem);
return count;
}
const char *util_hex2str(const void *buf, size_t len)
{
ENTER_FUNC();
int i = 0;
const uint8_t *b = buf;
static char str[129] = {0};
static const char hex[] = "0123456789abcdef";
len = MIN(len, (sizeof(str) - 1) / 2);
for (i = 0; i < len; i++) {
str[i * 2] = hex[b[i] >> 4];
str[i * 2 + 1] = hex[b[i] & 0xf];
}
str[i * 2] = '\0';
return str;
}

View File

@ -0,0 +1,524 @@
/*
* Copyright (c) 2013-2015 Wind River Systems, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Doubly-linked list implementation
*
* Doubly-linked list implementation using inline macros/functions.
* This API is not thread safe, and thus if a list is used across threads,
* calls to functions must be protected with synchronization primitives.
*
* The lists are expected to be initialized such that both the head and tail
* pointers point to the list itself. Initializing the lists in such a fashion
* simplifies the adding and removing of nodes to/from the list.
*/
#ifndef _GENIE_DLIST_H_
#define _GENIE_DLIST_H_
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
struct genie_dnode {
union {
struct genie_dnode *head; /* ptr to head of list (genie_dlist_t) */
struct genie_dnode *next; /* ptr to next node (genie_dnode_t) */
};
union {
struct genie_dnode *tail; /* ptr to tail of list (genie_dlist_t) */
struct genie_dnode *prev; /* ptr to previous node (genie_dnode_t) */
};
};
typedef struct genie_dnode genie_dlist_t;
typedef struct genie_dnode genie_dnode_t;
/**
* @brief Provide the primitive to iterate on a list
* Note: the loop is unsafe and thus __dn should not be removed
*
* User _MUST_ add the loop statement curly braces enclosing its own code:
*
* GENIE_DLIST_FOR_EACH_NODE(l, n) {
* <user code>
* }
*
* This and other SYS_DLIST_*() macros are not thread safe.
*
* @param __dl A pointer on a genie_dlist_t to iterate on
* @param __dn A genie_dnode_t pointer to peek each node of the list
*/
#define GENIE_DLIST_FOR_EACH_NODE(__dl, __dn) \
for (__dn = genie_dlist_peek_head(__dl); __dn; \
__dn = genie_dlist_peek_next(__dl, __dn))
/**
* @brief Provide the primitive to iterate on a list, from a node in the list
* Note: the loop is unsafe and thus __dn should not be removed
*
* User _MUST_ add the loop statement curly braces enclosing its own code:
*
* GENIE_DLIST_ITERATE_FROM_NODE(l, n) {
* <user code>
* }
*
* Like GENIE_DLIST_FOR_EACH_NODE(), but __dn already contains a node in the list
* where to start searching for the next entry from. If NULL, it starts from
* the head.
*
* This and other SYS_DLIST_*() macros are not thread safe.
*
* @param __dl A pointer on a genie_dlist_t to iterate on
* @param __dn A genie_dnode_t pointer to peek each node of the list;
* it contains the starting node, or NULL to start from the head
*/
#define GENIE_DLIST_ITERATE_FROM_NODE(__dl, __dn) \
for (__dn = __dn ? genie_dlist_peek_next_no_check(__dl, __dn) \
: genie_dlist_peek_head(__dl); \
__dn; \
__dn = genie_dlist_peek_next(__dl, __dn))
/**
* @brief Provide the primitive to safely iterate on a list
* Note: __dn can be removed, it will not break the loop.
*
* User _MUST_ add the loop statement curly braces enclosing its own code:
*
* GENIE_DLIST_FOR_EACH_NODE_SAFE(l, n, s) {
* <user code>
* }
*
* This and other SYS_DLIST_*() macros are not thread safe.
*
* @param __dl A pointer on a genie_dlist_t to iterate on
* @param __dn A genie_dnode_t pointer to peek each node of the list
* @param __dns A genie_dnode_t pointer for the loop to run safely
*/
#define GENIE_DLIST_FOR_EACH_NODE_SAFE(__dl, __dn, __dns) \
for (__dn = genie_dlist_peek_head(__dl), \
__dns = genie_dlist_peek_next(__dl, __dn); \
__dn; __dn = __dns, \
__dns = genie_dlist_peek_next(__dl, __dn))
/*
* @brief Provide the primitive to resolve the container of a list node
* Note: it is safe to use with NULL pointer nodes
*
* @param __dn A pointer on a genie_dnode_t to get its container
* @param __cn Container struct type pointer
* @param __n The field name of genie_dnode_t within the container struct
*/
#define GENIE_DLIST_CONTAINER(__dn, __cn, __n) \
(__dn ? CONTAINER_OF(__dn, __typeof__(*__cn), __n) : NULL)
/*
* @brief Provide the primitive to peek container of the list head
*
* @param __dl A pointer on a genie_dlist_t to peek
* @param __cn Container struct type pointer
* @param __n The field name of genie_dnode_t within the container struct
*/
#define GENIE_DLIST_PEEK_HEAD_CONTAINER(__dl, __cn, __n) \
GENIE_DLIST_CONTAINER(genie_dlist_peek_head(__dl), __cn, __n)
/*
* @brief Provide the primitive to peek the next container
*
* @param __dl A pointer on a genie_dlist_t to peek
* @param __cn Container struct type pointer
* @param __n The field name of genie_dnode_t within the container struct
*/
#define GENIE_DLIST_PEEK_NEXT_CONTAINER(__dl, __cn, __n) \
((__cn) ? GENIE_DLIST_CONTAINER(genie_dlist_peek_next(__dl, &(__cn->__n)), \
__cn, __n) : NULL)
/**
* @brief Provide the primitive to iterate on a list under a container
* Note: the loop is unsafe and thus __cn should not be detached
*
* User _MUST_ add the loop statement curly braces enclosing its own code:
*
* GENIE_DLIST_FOR_EACH_CONTAINER(l, c, n) {
* <user code>
* }
*
* @param __dl A pointer on a genie_dlist_t to iterate on
* @param __cn A pointer to peek each entry of the list
* @param __n The field name of genie_dnode_t within the container struct
*/
#define GENIE_DLIST_FOR_EACH_CONTAINER(__dl, __cn, __n) \
for (__cn = GENIE_DLIST_PEEK_HEAD_CONTAINER(__dl, __cn, __n); __cn; \
__cn = GENIE_DLIST_PEEK_NEXT_CONTAINER(__dl, __cn, __n))
/**
* @brief Provide the primitive to safely iterate on a list under a container
* Note: __cn can be detached, it will not break the loop.
*
* User _MUST_ add the loop statement curly braces enclosing its own code:
*
* GENIE_DLIST_FOR_EACH_CONTAINER_SAFE(l, c, cn, n) {
* <user code>
* }
*
* @param __dl A pointer on a genie_dlist_t to iterate on
* @param __cn A pointer to peek each entry of the list
* @param __cns A pointer for the loop to run safely
* @param __n The field name of genie_dnode_t within the container struct
*/
#define GENIE_DLIST_FOR_EACH_CONTAINER_SAFE(__dl, __cn, __cns, __n) \
for (__cn = GENIE_DLIST_PEEK_HEAD_CONTAINER(__dl, __cn, __n), \
__cns = GENIE_DLIST_PEEK_NEXT_CONTAINER(__dl, __cn, __n); __cn; \
__cn = __cns, \
__cns = GENIE_DLIST_PEEK_NEXT_CONTAINER(__dl, __cn, __n))
/**
* @brief initialize list
*
* @param list the doubly-linked list
*
* @return N/A
*/
static inline void genie_dlist_init(genie_dlist_t *list)
{
list->head = (genie_dnode_t *)list;
list->tail = (genie_dnode_t *)list;
}
#define GENIE_DLIST_STATIC_INIT(ptr_to_list) {{(ptr_to_list)}, {(ptr_to_list)}}
/**
* @brief check if a node is the list's head
*
* @param list the doubly-linked list to operate on
* @param node the node to check
*
* @return 1 if node is the head, 0 otherwise
*/
static inline int genie_dlist_is_head(genie_dlist_t *list, genie_dnode_t *node)
{
return list->head == node;
}
/**
* @brief check if a node is the list's tail
*
* @param list the doubly-linked list to operate on
* @param node the node to check
*
* @return 1 if node is the tail, 0 otherwise
*/
static inline int genie_dlist_is_tail(genie_dlist_t *list, genie_dnode_t *node)
{
return list->tail == node;
}
/**
* @brief check if the list is empty
*
* @param list the doubly-linked list to operate on
*
* @return 1 if empty, 0 otherwise
*/
static inline int genie_dlist_is_empty(genie_dlist_t *list)
{
return list->head == list;
}
/**
* @brief get node number in dlist
*
* @param list the doubly-linked list to operate on
*
* @return number
*/
static inline int genie_dlist_node_number(genie_dlist_t *list)
{
int number = 0;
genie_dnode_t *node;
if (genie_dlist_is_empty(list))
return number;
node = list->head;
while (node) {
number++;
if (node == list->tail)
break;
node = node->next;
}
return number;
}
/**
* @brief check if more than one node present
*
* This and other sys_dlist_*() functions are not thread safe.
*
* @param list the doubly-linked list to operate on
*
* @return 1 if multiple nodes, 0 otherwise
*/
static inline int genie_dlist_has_multiple_nodes(genie_dlist_t *list)
{
return list->head != list->tail;
}
/**
* @brief get a reference to the head item in the list
*
* @param list the doubly-linked list to operate on
*
* @return a pointer to the head element, NULL if list is empty
*/
static inline genie_dnode_t *genie_dlist_peek_head(genie_dlist_t *list)
{
return genie_dlist_is_empty(list) ? NULL : list->head;
}
/**
* @brief get a reference to the head item in the list
*
* The list must be known to be non-empty.
*
* @param list the doubly-linked list to operate on
*
* @return a pointer to the head element
*/
static inline genie_dnode_t *genie_dlist_peek_head_not_empty(genie_dlist_t *list)
{
return list->head;
}
/**
* @brief get a reference to the next item in the list, node is not NULL
*
* Faster than genie_dlist_peek_next() if node is known not to be NULL.
*
* @param list the doubly-linked list to operate on
* @param node the node from which to get the next element in the list
*
* @return a pointer to the next element from a node, NULL if node is the tail
*/
static inline genie_dnode_t *genie_dlist_peek_next_no_check(genie_dlist_t *list,
genie_dnode_t *node)
{
return (node == list->tail) ? NULL : node->next;
}
/**
* @brief get a reference to the next item in the list
*
* @param list the doubly-linked list to operate on
* @param node the node from which to get the next element in the list
*
* @return a pointer to the next element from a node, NULL if node is the tail
* or NULL (when node comes from reading the head of an empty list).
*/
static inline genie_dnode_t *genie_dlist_peek_next(genie_dlist_t *list,
genie_dnode_t *node)
{
return node ? genie_dlist_peek_next_no_check(list, node) : NULL;
}
/**
* @brief get a reference to the tail item in the list
*
* @param list the doubly-linked list to operate on
*
* @return a pointer to the tail element, NULL if list is empty
*/
static inline genie_dnode_t *genie_dlist_peek_tail(genie_dlist_t *list)
{
return genie_dlist_is_empty(list) ? NULL : list->tail;
}
/**
* @brief add node to tail of list
*
* This and other sys_dlist_*() functions are not thread safe.
*
* @param list the doubly-linked list to operate on
* @param node the element to append
*
* @return N/A
*/
static inline void genie_dlist_append(genie_dlist_t *list, genie_dnode_t *node)
{
node->next = list;
node->prev = list->tail;
list->tail->next = node;
list->tail = node;
}
/**
* @brief add node to head of list
*
* This and other sys_dlist_*() functions are not thread safe.
*
* @param list the doubly-linked list to operate on
* @param node the element to append
*
* @return N/A
*/
static inline void genie_dlist_prepend(genie_dlist_t *list, genie_dnode_t *node)
{
node->next = list->head;
node->prev = list;
list->head->prev = node;
list->head = node;
}
/**
* @brief insert node after a node
*
* Insert a node after a specified node in a list.
* This and other sys_dlist_*() functions are not thread safe.
*
* @param list the doubly-linked list to operate on
* @param insert_point the insert point in the list: if NULL, insert at head
* @param node the element to append
*
* @return N/A
*/
static inline void genie_dlist_insert_after(genie_dlist_t *list,
genie_dnode_t *insert_point, genie_dnode_t *node)
{
if (!insert_point) {
genie_dlist_prepend(list, node);
} else {
node->next = insert_point->next;
node->prev = insert_point;
insert_point->next->prev = node;
insert_point->next = node;
}
}
/**
* @brief insert node before a node
*
* Insert a node before a specified node in a list.
* This and other sys_dlist_*() functions are not thread safe.
*
* @param list the doubly-linked list to operate on
* @param insert_point the insert point in the list: if NULL, insert at tail
* @param node the element to insert
*
* @return N/A
*/
static inline void genie_dlist_insert_before(genie_dlist_t *list,
genie_dnode_t *insert_point, genie_dnode_t *node)
{
if (!insert_point) {
genie_dlist_append(list, node);
} else {
node->prev = insert_point->prev;
node->next = insert_point;
insert_point->prev->next = node;
insert_point->prev = node;
}
}
/**
* @brief insert node at position
*
* Insert a node in a location depending on a external condition. The cond()
* function checks if the node is to be inserted _before_ the current node
* against which it is checked.
* This and other sys_dlist_*() functions are not thread safe.
*
* @param list the doubly-linked list to operate on
* @param node the element to insert
* @param cond a function that determines if the current node is the correct
* insert point
* @param data parameter to cond()
*
* @return N/A
*/
static inline void genie_dlist_insert_at(genie_dlist_t *list, genie_dnode_t *node,
int (*cond)(genie_dnode_t *, void *), void *data)
{
if (genie_dlist_is_empty(list)) {
genie_dlist_append(list, node);
} else {
genie_dnode_t *pos = genie_dlist_peek_head(list);
while (pos && !cond(pos, data)) {
pos = genie_dlist_peek_next(list, pos);
}
genie_dlist_insert_before(list, pos, node);
}
}
/**
* @brief remove a specific node from a list
*
* The list is implicit from the node. The node must be part of a list.
* This and other sys_dlist_*() functions are not thread safe.
*
* @param node the node to remove
*
* @return N/A
*/
static inline void genie_dlist_remove(genie_dnode_t *node)
{
node->prev->next = node->next;
node->next->prev = node->prev;
}
/**
* @brief get the first node in a list
*
* This and other sys_dlist_*() functions are not thread safe.
*
* @param list the doubly-linked list to operate on
*
* @return the first node in the list, NULL if list is empty
*/
static inline genie_dnode_t *genie_dlist_get(genie_dlist_t *list)
{
genie_dnode_t *node;
if (genie_dlist_is_empty(list)) {
return NULL;
}
node = list->head;
genie_dlist_remove(node);
return node;
}
#ifdef __cplusplus
}
#endif
#endif /* _GENIE_DLIST_H_ */

View File

@ -0,0 +1,101 @@
// Copyright (C) 2018-2020 Alibaba Group Holding Limited
// Adaptations to ESP-IDF Copyright (c) 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _GENIE_EVENT_H_
#define _GENIE_EVENT_H_
#ifdef __cplusplus
extern "C" {
#endif /**< __cplusplus */
typedef enum {
/* START--- Don't add new ID before this one */
GENIE_EVT_START = 0,
/* Reset Related Operation */
GENIE_EVT_SW_RESET = GENIE_EVT_START, /* triggered from cloud */
GENIE_EVT_HW_RESET_START, /* triggered from user */
GENIE_EVT_HW_RESET_DONE, /*triggered by reset by repeat module */
/* SDK triggered event, with prefix of GENIE_EVT_SDK_MESH_ */
GENIE_EVT_SDK_START,
GENIE_EVT_SDK_MESH_INIT = GENIE_EVT_SDK_START,
GENIE_EVT_SDK_MESH_PBADV_START,
GENIE_EVT_SDK_MESH_PBADV_TIMEOUT,
GENIE_EVT_SDK_MESH_SILENT_START,
GENIE_EVT_SDK_MESH_PROV_START,
GENIE_EVT_SDK_MESH_PROV_TIMEOUT,
GENIE_EVT_SDK_MESH_PROV_SUCCESS,
GENIE_EVT_SDK_MESH_PROV_FAIL,
GENIE_EVT_SDK_APPKEY_ADD,
GENIE_EVT_SDK_APPKEY_DEL,
GENIE_EVT_SDK_APPKEY_UPDATE,
GENIE_EVT_SDK_NETKEY_ADD,
GENIE_EVT_SDK_NETKEY_DEL,
GENIE_EVT_SDK_NETKEY_UPDATE,
GENIE_EVT_SDK_SUB_ADD,
GENIE_EVT_SDK_SUB_DEL,
GENIE_EVT_SDK_HB_SET,
GENIE_EVT_SDK_SEQ_UPDATE,
GENIE_EVT_SDK_STATE_SYNC,
GENIE_EVT_SDK_IVI_UPDATE,
GENIE_EVENT_SUB_SET,
GENIE_EVENT_HB_SET,
GENIE_EVT_SDK_ANALYZE_MSG,
GENIE_EVT_SDK_AIS_DISCON,
GENIE_EVT_SDK_DELAY_START,
GENIE_EVT_SDK_DELAY_END,
GENIE_EVT_SDK_TRANS_START,
GENIE_EVT_SDK_TRANS_CYCLE,
GENIE_EVT_SDK_TRANS_END,
GENIE_EVT_SDK_ACTION_DONE,
GENIE_EVT_SDK_MESH_PWRON_INDC,
GENIE_EVT_SDK_INDICATE,
GENIE_EVT_SDK_VENDOR_MSG,
/* APP triggered event, with prefix of GENIE_EVT_APP_ */
GENIE_EVT_APP_START,
GENIE_EVT_APP_FAC_QUIT = GENIE_EVT_APP_START,
GENIE_EVT_TIME_OUT,
/* Board */
GENIE_EVT_BUTTON_TAP,
GENIE_EVT_SDK_COLOR_ACTION,
GENIE_EVT_RESET_BY_REPEAT_NOTIFY, /* triggered by reset */
/* END --- Don't add new ID after this one */
GENIE_EVT_END
} genie_event_t;
/**
* @brief The handler for the underlying events. If necessary
* this handler dispatch the user events to applications.
*
* @param event refers to the event details.
* @param args refers to the additional information for the event.
*/
void genie_event(genie_event_t event, void *args);
#ifdef __cplusplus
}
#endif /**< __cplusplus */
#endif /* _GENIE_EVENT_H_ */

View File

@ -0,0 +1,405 @@
// Copyright (C) 2018-2020 Alibaba Group Holding Limited
// Adaptations to ESP-IDF Copyright (c) 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _GENIE_MESH_H_
#define _GENIE_MESH_H_
#include "esp_ble_mesh_defs.h"
#include "esp_ble_mesh_common_api.h"
#include "genie_slist.h"
#include "genie_dlist.h"
#include "genie_util.h"
#include "genie_event.h"
#include "genie_reset.h"
#include "genie_timer.h"
#include "genie_model_srv.h"
#ifdef __cplusplus
extern "C" {
#endif /**< __cplusplus */
#define CID_ALIBABA 0x01A8 /**< Alibaba Incorporated */
#define GENIE_VENDOR_MODEL_SRV_ID 0x0000 /**< Genie Vendor Model Server ID */
#define GENIE_VENDOR_MODEL_CLI_ID 0x0001 /**< Genie Vendor Model Client ID */
#define GENIE_RECV_ADDR 0xF000 /**< Genie Receive Address */
#define GENIE_OTA_GROUP_ADDR 0xF100 /**< Genie OTA Group Address */
#define GENIE_ALL_GROUP_ADDR 0xCFFF /**< Genie All Product Group Address */
#define GENIE_LIGHT_GROUP_ADDR 0xC000 /**< Genie Light Product Group Address */
#define GENIE_SWITCH_GROUP_ADDR 0xC001 /**< Genie Switch Product Group Address */
#define GENIE_OP_ATTR_GET_STATUS (0xD0)
#define GENIE_OP_ATTR_SET_ACK (0xD1)
#define GENIE_OP_ATTR_SET_UNACK (0xD2)
#define GENIE_OP_ATTR_STATUS (0xD3)
#define GENIE_OP_ATTR_INDICATE (0xD4)
#define GENIE_OP_ATTR_CONFIME (0xD5)
#define GENIE_OP_ATTR_INDICATE_TG (0xDE)
#define GENIE_OP_ATTR_CONFIME_TG (0xDF)
#define GENIE_OP_ATTR_TRANS_MSG (0xCF)
#define GENIE_OP_ATTR_TRANS_INDICATE (0xCE)
#define GENIE_OP_ATTR_TRANS_ACK (0xCD)
#define GENIE_MESSAGE_OP_ATTR_GET_STATUS ESP_BLE_MESH_MODEL_OP_3(GENIE_OP_ATTR_GET_STATUS, CID_ALIBABA)
#define GENIE_MESSAGE_OP_ATTR_SET_ACK ESP_BLE_MESH_MODEL_OP_3(GENIE_OP_ATTR_SET_ACK, CID_ALIBABA)
#define GENIE_MESSAGE_OP_ATTR_SET_UNACK ESP_BLE_MESH_MODEL_OP_3(GENIE_OP_ATTR_SET_UNACK, CID_ALIBABA)
#define GENIE_MESSAGE_OP_ATTR_STATUS ESP_BLE_MESH_MODEL_OP_3(GENIE_OP_ATTR_STATUS, CID_ALIBABA)
#define GENIE_MESSAGE_OP_ATTR_INDICATION ESP_BLE_MESH_MODEL_OP_3(GENIE_OP_ATTR_INDICATE, CID_ALIBABA)
#define GENIE_MESSAGE_OP_ATTR_CONFIRMATION ESP_BLE_MESH_MODEL_OP_3(GENIE_OP_ATTR_CONFIME, CID_ALIBABA)
#define GENIE_MESSAGE_OP_ATTR_INDICATION_TG ESP_BLE_MESH_MODEL_OP_3(GENIE_OP_ATTR_INDICATE_TG, CID_ALIBABA)
#define GENIE_MESSAGE_OP_ATTR_CONFIRMATION_TG ESP_BLE_MESH_MODEL_OP_3(GENIE_OP_ATTR_CONFIME_TG, CID_ALIBABA)
#define GENIE_MESSAGE_OP_ATTR_TRANSPARENT_MSG ESP_BLE_MESH_MODEL_OP_3(GENIE_OP_ATTR_TRANS_MSG, CID_ALIBABA)
#define GENIE_MESSAGE_OP_ATTR_TRANSPARENT_ACK ESP_BLE_MESH_MODEL_OP_3(GENIE_OP_ATTR_TRANS_ACK, CID_ALIBABA)
#define GENIE_VENDOR_MODEL_VERSION 1 /**< Support UUID version */
#define GENIE_PROVISIONING_TIMEOUT (60 * 1000) /**< Mesh Provision Timeout, unit: ms */
#define GENIE_PBADV_TIMEOUT (600 * 1000) /**< Mesh Provision Advertise Timeout, unit: ms */
#define GENIE_STATIC_OOB_LENGTH 16
#define GENIE_MESH_TRNSATION_CYCLE 10
#define GENIE_UNPROV_ADV_FEATURE_AUTO_BIND_MODEL_SUB 0x02
#define GENIE_UNPROV_ADV_FEATURE_SILENT_ADV 0x01
#define GENIE_UNPROV_ADV_FLAG_GENIE_MESH_STACK 0x10 // bit4-7
#define GENIE_UNPROV_ADV_FEATURE_ULTRA_PROV 0x03 // bit0-1
#define GENIE_CTL_TEMP_MIN 800
#define GENIE_CTL_TEMP_MAX 20000
#define GENIE_CTL_TEMP_DEFAULT GENIE_CTL_TEMP_MAX
#define GENIE_ONOFF_DEFAULT 1
#define GENIE_LIGHTNESS_DEFAULT 0xE666 // 90%
/* Vendor Model Attr Parameter */
#define GENIE_MODEL_ATTR_ONOFF 0x0100
#define GENIE_MODEL_ATTR_LIGHTNESS 0x0121
#define GENIE_MODEL_ATTR_TEMPERATURE 0x0122
#define GENIE_MODEL_ATTR_COLOR 0x0123
#define GENIE_MODEL_ATTR_DEVICE_EVENT 0xF009
/* Event List - Refer to Device Event List defined in https://yuque.antfin-inc.com/iotconnect/wwfb/dbyytw#1c38cf1b */
#define EVENT_FAULT 0x00 /* malfunction event */
#define EVENT_LOW_BAT 0x01 /* low battery event */
#define EVENT_DEV_UP 0x03 /* device power up */
#define EVENT_HW_RESET 0x23 /* hardware reset event */
#define EVENT_TIMING_TIMEOUT 0x11 /* timer timeout event */
/* Vendor timer error code */
#define ERR_CODE_UNIXTIME 0x80
#define ERR_CODE_NOTSUP_ATTR_OP 0x82
#define ERR_CODE_NOTSUP_ATTR_PARAM 0x83
#define ERR_CODE_TIMER_SETTING 0x84
#define ERR_CODE_TIMER_INDEX 0x85
#define ERR_CODE_TIMER_FULL 0x86
#define ERR_CODE_TIMER_PRIORDIC_PARAM 0x87
/* Vendor timer ali attr type */
#define TIMER_ATTR_ERROR_CODE 0x0000
#define TIMER_ATTR_TIMING_TIMEOUT 0xF009
#define TIMER_ATTR_TIMING_SETTING 0xF010
#define TIMER_ATTR_TIMING_PERIODIC_SETTING 0xF011
#define TIMER_ATTR_TIMING_DELETE 0xF012
#define TIMER_ATTR_TIMING_SYNC 0xF01D
#define TIMER_ATTR_TIMEZONE_SETTING 0xF01E
#define TIMER_ATTR_UNIX_TIME 0xF01F
#define GENIE_MODEL_MSG_DFT_RETRY_TIMES 6
#define GENIE_MODEL_MSG_MAX_RETRY_TIMES 10
#define GENIE_MODEL_MSG_RETRY_PERIOD 400
#define CONFIG_MESH_SIMPLE_MODLE 1
#define GENIE_MODEL_VENDOR_TIMER 1
#define CONFIG_MESH_MODEL_VENDOR_SRV 1
#define CONFIG_MESH_MODEL_GEN_ONOFF_SRV 1
#define CONFIG_MESH_MODEL_LIGHTNESS_SRV 1
// #define CONFIG_MESH_MODEL_GEN_LEVEL_SRV 1
// #define CONFIG_MESH_MODEL_CTL_SRV 1
#define CONFIG_MESH_MODEL_HSL_SRV 1
// #define CONFIG_MESH_MODEL_TRANS 1
#define FUNC_TRACE 1
#ifdef FUNC_TRACE
#define ENTER_FUNC() ESP_LOGD(TAG, "enter %s", __FUNCTION__)
#define EXIT_FUNC() ESP_LOGD(TAG, "exit %s", __FUNCTION__)
#else
#define ENTER_FUNC()
#define EXIT_FUNC()
#endif
typedef enum {
VALUE_TYPE_CUR = 0,
VALUE_TYPE_TAR,
VALUE_TYPE_NUM,
} value_type_t;
typedef struct {
#ifdef CONFIG_MESH_MODEL_GEN_ONOFF_SRV
uint8_t onoff[VALUE_TYPE_NUM];
#endif
#ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV
uint16_t actual[VALUE_TYPE_NUM];
// #ifndef CONFIG_MESH_SIMPLE_MODLE
uint16_t linear[VALUE_TYPE_NUM];
// #endif
#endif
#ifdef CONFIG_MESH_MODEL_CTL_SRV
uint16_t temp[VALUE_TYPE_NUM];
uint16_t UV[VALUE_TYPE_NUM];
#endif
#ifdef CONFIG_MESH_MODEL_HSL_SRV
uint16_t hue[VALUE_TYPE_NUM];
uint16_t saturation[VALUE_TYPE_NUM];
uint16_t lightness[VALUE_TYPE_NUM];
#endif
#ifdef CONFIG_MESH_MODEL_TRANS
uint8_t delay; // unit:5ms
uint8_t trans; // unit:100ms
uint32_t trans_start_time;
uint32_t trans_last_time;
uint32_t trans_end_time;
int16_t actual_trans_step;
int16_t temp_trans_step;
util_timer_t delay_timer;
util_timer_t trans_timer;
#endif
} model_state_t;
typedef struct {
#ifdef CONFIG_MESH_MODEL_GEN_ONOFF_SRV
uint8_t default_onoff;
uint8_t last_onoff;
#endif
#ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV
uint16_t last_actual;
#endif
#ifdef CONFIG_MESH_MODEL_CTL_SRV
uint16_t last_temp;
#endif
#ifdef CONFIG_MESH_MODEL_HSL_SRV
uint16_t last_hue;
uint16_t last_saturation;
uint16_t last_lightness;
#endif
#ifndef CONFIG_MESH_SIMPLE_MODLE
#ifdef CONFIG_MESH_MODEL_TRANS
uint8_t def_trans;
#endif
#ifndef CONFIG_MESH_SIMPLE_MODLE
uint8_t range_status;
#endif
#ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV
uint16_t default_actual;
uint16_t min_actual;
uint16_t max_actual;
#endif
#ifdef CONFIG_MESH_MODEL_CTL_SRV
//temp
uint16_t default_temp;
uint16_t min_temp;
uint16_t max_temp;
uint16_t default_UV;
uint16_t last_UV;
#endif
#endif
} model_powerup_t;
typedef struct {
uint8_t elem_index;
model_state_t state; /**< Used to save the current state and target state of the device */
model_powerup_t powerup; /**< Used to save the device's power-down state and the previous state */
void *user_data;
} elem_state_t;
typedef enum {
GENIE_MESH_SUCCESS = 0,
GENIE_MESH_TID_REPEAT_ERROR,
GENIE_MESH_ANALYZE_SIZE_ERROR,
GENIE_MESH_ANALYZE_ARGS_ERROR,
GENIE_MESH_SET_TRANSTION_ERROR,
} genie_error_type_t;
#ifdef CONFIG_MESH_MODEL_VENDOR_SRV
#define INDICATION_FLAG_POWERON 0x80
#ifdef CONFIG_MESH_MODEL_GEN_ONOFF_SRV
#define INDICATION_FLAG_ONOFF 0x01
#endif
#ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV
#define INDICATION_FLAG_LIGHTNESS 0x02
#endif
#ifdef CONFIG_MESH_MODEL_CTL_SRV
#define INDICATION_FLAG_CTL 0x04
#endif
#ifdef CONFIG_MESH_MODEL_HSL_SRV
#define INDICATION_FLAG_HSL 0x05
#endif
#endif
extern uint8_t g_indication_flag;
/**
* @brief check whether there is this tid in record, and record it if not.
* @param[in] src_addr indicates the device which hold this tid.
* @param[in] tid
* @return GENIE_MESH_SUCCESS means successed, otherwise failed.
*/
genie_error_type_t genie_msg_check_tid(uint16_t src_addr, uint8_t tid);
/**
* @brief get the remain bytes of message
* @param[in] p_state: model state
* @param[in] is_ack: ack or not
* @return the bytes
*/
uint8_t get_remain_byte(model_state_t *p_state, bool is_ack);
#ifdef CONFIG_MESH_MODEL_TRANS
/**
* @brief get the transition time.
* @param[in] byte means the byte of message.
* @return the time in milliseconds.
*/
uint32_t get_transition_time(uint8_t byte);
#endif
/**
* @brief send the mesh message
*
* @param[in] p_vendor_msg refers to the message to be sent
*
* @return 0 for success; negative for failure
*/
int16_t genie_mesh_msg_send(genie_model_msg_t *p_vendor_msg);
/**
* @brief stop the delay_timer and trans_timer for element.
*
* @param[in] p_elem refers to the element.
*
*/
void mesh_timer_stop(elem_state_t *p_elem);
/**
* @brief handle the vendor message
* @param[in] p_msg refers to the message to be handled
*
* @return 0 for successed, -1 for failed.
*/
uint16_t genie_vendor_model_msg_handle(genie_model_msg_t *p_msg);
/**
* @brief start poweron indicate timer.
*
*/
void poweron_indicate_start(void);
/**
* @brief start PB-ADV timer.
*
*/
void genie_pbadv_timer_start(void);
/**
* @brief stop PB-ADV timer.
*
*/
void genie_pbadv_timer_stop(void);
/**
* @brief start provision timer.
*
*/
void genie_prov_timer_start(void);
/**
* @brief stop provision timer.
*
*/
void genie_prov_timer_stop(void);
/**
* @brief clear trans parameter.
*
* @param p_elem pointer of element state.
*/
void clear_trans_para(elem_state_t *p_elem);
/**
* @brief calculater current state.
*
* @param p_elem pointer of element state.
*
* @return uint8_t
*/
uint8_t calc_cur_state(elem_state_t *p_elem);
/**
* @brief Start silent adv.
*
*/
void genie_pbadv_start_silent_adv(void);
/**
* @brief Handle hardware reset.
*
* @return uint16_t
*/
uint16_t genie_indicate_hw_reset_event(void);
/**
* @brief Initializing element state.
*
* @param state_count the number of element.
* @param p_elem pointer of element state.
*
* @return uint8_t
*/
uint8_t elem_state_init(uint8_t state_count, elem_state_t *p_elem);
/**
* @brief Genie vendor model indicate self status.
*
* @param p_elem pointer of element state.
*/
void genie_standart_indication(elem_state_t *p_elem);
/**
* @brief Initializing Genie Mesh components.
*
*/
void genie_mesh_init(void);
#ifdef __cplusplus
}
#endif /**< __cplusplus */
#endif /* _GENIE_MESH_H_ */

View File

@ -0,0 +1,63 @@
// Copyright (C) 2018-2020 Alibaba Group Holding Limited
// Adaptations to ESP-IDF Copyright (c) 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _GENIE_MODEL_SRV_H
#define _GENIE_MODEL_SRV_H
#include "genie_dlist.h"
#include "genie_util.h"
#ifdef __cplusplus
extern "C" {
#endif /**< __cplusplus */
#define GENIE_VENDOR_MSG_LIST_MAXSIZE 8
typedef struct _genie_vendor_model_msg_node {
genie_dnode_t node;
int8_t left_retry;
int64_t timeout;
genie_model_msg_t msg;
} genie_model_msg_node_t;
typedef void (* genie_model_opcode_cb_t)(esp_ble_mesh_model_t *model, esp_ble_mesh_msg_ctx_t *ctx, struct net_buf_simple *buf);
typedef struct genie_opcode_cb_t {
uint32_t opcode;
genie_model_opcode_cb_t cb;
} genie_opcode_cb_t;
/** @def genie_model_msg_send
*
* @brief send the vendor model message
*
* @param pointer to the message to be sent
*
* @return 0 for success; negative for failure
*/
int16_t genie_model_msg_send(genie_model_msg_t *p_model_msg);
/**
* @brief
* @return
*/
void genie_model_dispatch(uint32_t opcode, esp_ble_mesh_model_t *model,
esp_ble_mesh_msg_ctx_t *ctx, uint8_t *msg, uint16_t length);
#ifdef __cplusplus
}
#endif /**< __cplusplus */
#endif /* _GENIE_MODEL_SRV_H */

View File

@ -0,0 +1,60 @@
// Copyright (C) 2018-2020 Alibaba Group Holding Limited
// Adaptations to ESP-IDF Copyright (c) 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _GENIE_RESET_H_
#define _GENIE_RESET_H_
#ifdef __cplusplus
extern "C" {
#endif /**< __cplusplus */
#define CONFIG_GENIE_RESET_BY_REPEAT 1
#define GENIE_RESET_BY_REPEAT_COUNTER 3
#define GENIE_RESET_BY_REPEAT_TIMEOUT (10 * 1000)
#define GENIE_RESET_WAIT_DONE_TIMEOUT (10 * 1000)
#define GENIE_STORE_RESTART_COUNT_KEY "restart_count"
/**
* @brief Get reset flag
*
* @return uint8_t
*/
uint8_t genie_reset_get_flag(void);
/**
* @brief Start the timer to handle the program reset operation. Avoid reset indication messages not reaching Genie Speaker.
*
*/
void genie_reset_done_timer_start(void);
/**
* @brief Initialize the function that resets whole system by multiple reboot in GENIE_RESET_BY_REPEAT_TIMEOUT.
*
*/
void genie_reset_by_repeat_init(void);
/**
* @brief Clean the recored count for reboot.
*
*/
void genie_reset_clean_count(void);
#ifdef __cplusplus
}
#endif /**< __cplusplus */
#endif /* _GENIE_RESET_H_ */

View File

@ -0,0 +1,467 @@
/*
* Copyright (c) 2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
*
* @brief Single-linked list implementation
*
* Single-linked list implementation using inline macros/functions.
* This API is not thread safe, and thus if a list is used across threads,
* calls to functions must be protected with synchronization primitives.
*/
#ifndef _GENIE_SLIST_H_
#define _GENIE_SLIST_H_
#include <stddef.h>
#include <stdbool.h>
#include "mesh_util.h"
#ifdef __cplusplus
extern "C" {
#endif
struct genie_snode {
struct genie_snode *next;
};
typedef struct genie_snode genie_snode_t;
struct genie_slist {
genie_snode_t *head;
genie_snode_t *tail;
};
typedef struct genie_slist genie_slist_t;
/**
* @brief Provide the primitive to iterate on a list
* Note: the loop is unsafe and thus __sn should not be removed
*
* User _MUST_ add the loop statement curly braces enclosing its own code:
*
* GENIE_SLIST_FOR_EACH_NODE(l, n) {
* <user code>
* }
*
* This and other SYS_SLIST_*() macros are not thread safe.
*
* @param __sl A pointer on a genie_slist_t to iterate on
* @param __sn A genie_snode_t pointer to peek each node of the list
*/
#define GENIE_SLIST_FOR_EACH_NODE(__sl, __sn) \
for (__sn = genie_slist_peek_head(__sl); __sn; \
__sn = genie_slist_peek_next(__sn))
/**
* @brief Provide the primitive to iterate on a list, from a node in the list
* Note: the loop is unsafe and thus __sn should not be removed
*
* User _MUST_ add the loop statement curly braces enclosing its own code:
*
* GENIE_SLIST_ITERATE_FROM_NODE(l, n) {
* <user code>
* }
*
* Like GENIE_SLIST_FOR_EACH_NODE(), but __dn already contains a node in the list
* where to start searching for the next entry from. If NULL, it starts from
* the head.
*
* This and other SYS_SLIST_*() macros are not thread safe.
*
* @param __sl A pointer on a genie_slist_t to iterate on
* @param __sn A genie_snode_t pointer to peek each node of the list
* it contains the starting node, or NULL to start from the head
*/
#define GENIE_SLIST_ITERATE_FROM_NODE(__sl, __sn) \
for (__sn = __sn ? genie_slist_peek_next_no_check(__sn) \
: genie_slist_peek_head(__sl); \
__sn; \
__sn = genie_slist_peek_next(__sn))
/**
* @brief Provide the primitive to safely iterate on a list
* Note: __sn can be removed, it will not break the loop.
*
* User _MUST_ add the loop statement curly braces enclosing its own code:
*
* GENIE_SLIST_FOR_EACH_NODE_SAFE(l, n, s) {
* <user code>
* }
*
* This and other SYS_SLIST_*() macros are not thread safe.
*
* @param __sl A pointer on a genie_slist_t to iterate on
* @param __sn A genie_snode_t pointer to peek each node of the list
* @param __sns A genie_snode_t pointer for the loop to run safely
*/
#define GENIE_SLIST_FOR_EACH_NODE_SAFE(__sl, __sn, __sns) \
for (__sn = genie_slist_peek_head(__sl), \
__sns = genie_slist_peek_next(__sn); \
__sn; __sn = __sns, \
__sns = genie_slist_peek_next(__sn))
/*
* @brief Provide the primitive to resolve the container of a list node
* Note: it is safe to use with NULL pointer nodes
*
* @param __ln A pointer on a sys_node_t to get its container
* @param __cn Container struct type pointer
* @param __n The field name of sys_node_t within the container struct
*/
#define GENIE_SLIST_CONTAINER(__ln, __cn, __n) \
((__ln) ? CONTAINER_OF((__ln), __typeof__(*(__cn)), __n) : NULL)
/*
* @brief Provide the primitive to peek container of the list head
*
* @param __sl A pointer on a genie_slist_t to peek
* @param __cn Container struct type pointer
* @param __n The field name of sys_node_t within the container struct
*/
#define GENIE_SLIST_PEEK_HEAD_CONTAINER(__sl, __cn, __n) \
GENIE_SLIST_CONTAINER(genie_slist_peek_head(__sl), __cn, __n)
/*
* @brief Provide the primitive to peek container of the list tail
*
* @param __sl A pointer on a genie_slist_t to peek
* @param __cn Container struct type pointer
* @param __n The field name of sys_node_t within the container struct
*/
#define GENIE_SLIST_PEEK_TAIL_CONTAINER(__sl, __cn, __n) \
GENIE_SLIST_CONTAINER(genie_slist_peek_tail(__sl), __cn, __n)
/*
* @brief Provide the primitive to peek the next container
*
* @param __cn Container struct type pointer
* @param __n The field name of sys_node_t within the container struct
*/
#define GENIE_SLIST_PEEK_NEXT_CONTAINER(__cn, __n) \
((__cn) ? GENIE_SLIST_CONTAINER(genie_slist_peek_next(&((__cn)->__n)), \
__cn, __n) : NULL)
/**
* @brief Provide the primitive to iterate on a list under a container
* Note: the loop is unsafe and thus __cn should not be detached
*
* User _MUST_ add the loop statement curly braces enclosing its own code:
*
* GENIE_SLIST_FOR_EACH_CONTAINER(l, c, n) {
* <user code>
* }
*
* @param __sl A pointer on a genie_slist_t to iterate on
* @param __cn A pointer to peek each entry of the list
* @param __n The field name of sys_node_t within the container struct
*/
#define GENIE_SLIST_FOR_EACH_CONTAINER(__sl, __cn, __n) \
for (__cn = GENIE_SLIST_PEEK_HEAD_CONTAINER(__sl, __cn, __n); __cn; \
__cn = GENIE_SLIST_PEEK_NEXT_CONTAINER(__cn, __n))
/**
* @brief Provide the primitive to safely iterate on a list under a container
* Note: __cn can be detached, it will not break the loop.
*
* User _MUST_ add the loop statement curly braces enclosing its own code:
*
* GENIE_SLIST_FOR_EACH_NODE_SAFE(l, c, cn, n) {
* <user code>
* }
*
* @param __sl A pointer on a genie_slist_t to iterate on
* @param __cn A pointer to peek each entry of the list
* @param __cns A pointer for the loop to run safely
* @param __n The field name of sys_node_t within the container struct
*/
#define GENIE_SLIST_FOR_EACH_CONTAINER_SAFE(__sl, __cn, __cns, __n) \
for (__cn = GENIE_SLIST_PEEK_HEAD_CONTAINER(__sl, __cn, __n), \
__cns = GENIE_SLIST_PEEK_NEXT_CONTAINER(__cn, __n); __cn; \
__cn = __cns, __cns = GENIE_SLIST_PEEK_NEXT_CONTAINER(__cn, __n))
/**
* @brief Initialize a list
*
* @param list A pointer on the list to initialize
*/
static inline void genie_slist_init(genie_slist_t *list)
{
list->head = NULL;
list->tail = NULL;
}
#define GENIE_SLIST_STATIC_INIT(ptr_to_list) {NULL, NULL}
/**
* @brief Test if the given list is empty
*
* @param list A pointer on the list to test
*
* @return a boolean, true if it's empty, false otherwise
*/
static inline bool genie_slist_is_empty(genie_slist_t *list)
{
return (!list->head);
}
/**
* @brief Peek the first node from the list
*
* @param list A point on the list to peek the first node from
*
* @return A pointer on the first node of the list (or NULL if none)
*/
static inline genie_snode_t *genie_slist_peek_head(genie_slist_t *list)
{
return list->head;
}
/**
* @brief Peek the last node from the list
*
* @param list A point on the list to peek the last node from
*
* @return A pointer on the last node of the list (or NULL if none)
*/
static inline genie_snode_t *genie_slist_peek_tail(genie_slist_t *list)
{
return list->tail;
}
/**
* @brief Peek the next node from current node, node is not NULL
*
* Faster then genie_slist_peek_next() if node is known not to be NULL.
*
* @param node A pointer on the node where to peek the next node
*
* @return a pointer on the next node (or NULL if none)
*/
static inline genie_snode_t *genie_slist_peek_next_no_check(genie_snode_t *node)
{
return node->next;
}
/**
* @brief Peek the next node from current node
*
* @param node A pointer on the node where to peek the next node
*
* @return a pointer on the next node (or NULL if none)
*/
static inline genie_snode_t *genie_slist_peek_next(genie_snode_t *node)
{
return node ? genie_slist_peek_next_no_check(node) : NULL;
}
/**
* @brief Prepend a node to the given list
*
* This and other sys_slist_*() functions are not thread safe.
*
* @param list A pointer on the list to affect
* @param node A pointer on the node to prepend
*/
static inline void genie_slist_prepend(genie_slist_t *list,
genie_snode_t *node)
{
node->next = list->head;
list->head = node;
if (!list->tail) {
list->tail = list->head;
}
}
/**
* @brief Append a node to the given list
*
* This and other sys_slist_*() functions are not thread safe.
*
* @param list A pointer on the list to affect
* @param node A pointer on the node to append
*/
static inline void genie_slist_append(genie_slist_t *list,
genie_snode_t *node)
{
node->next = NULL;
if (!list->tail) {
list->tail = node;
list->head = node;
} else {
list->tail->next = node;
list->tail = node;
}
}
/**
* @brief Append a list to the given list
*
* Append a singly-linked, NULL-terminated list consisting of nodes containing
* the pointer to the next node as the first element of a node, to @a list.
* This and other sys_slist_*() functions are not thread safe.
*
* @param list A pointer on the list to affect
* @param head A pointer to the first element of the list to append
* @param tail A pointer to the last element of the list to append
*/
static inline void genie_slist_append_list(genie_slist_t *list,
void *head, void *tail)
{
if (!list->tail) {
list->head = (genie_snode_t *)head;
list->tail = (genie_snode_t *)tail;
} else {
list->tail->next = (genie_snode_t *)head;
list->tail = (genie_snode_t *)tail;
}
}
/**
* @brief merge two slists, appending the second one to the first
*
* When the operation is completed, the appending list is empty.
* This and other sys_slist_*() functions are not thread safe.
*
* @param list A pointer on the list to affect
* @param list_to_append A pointer to the list to append.
*/
static inline void genie_slist_merge_slist(genie_slist_t *list,
genie_slist_t *list_to_append)
{
genie_slist_append_list(list, list_to_append->head,
list_to_append->tail);
genie_slist_init(list_to_append);
}
/**
* @brief Insert a node to the given list
*
* This and other sys_slist_*() functions are not thread safe.
*
* @param list A pointer on the list to affect
* @param prev A pointer on the previous node
* @param node A pointer on the node to insert
*/
static inline void genie_slist_insert(genie_slist_t *list,
genie_snode_t *prev,
genie_snode_t *node)
{
if (!prev) {
genie_slist_prepend(list, node);
} else if (!prev->next) {
genie_slist_append(list, node);
} else {
node->next = prev->next;
prev->next = node;
}
}
/**
* @brief Fetch and remove the first node of the given list
*
* List must be known to be non-empty.
* This and other sys_slist_*() functions are not thread safe.
*
* @param list A pointer on the list to affect
*
* @return A pointer to the first node of the list
*/
static inline genie_snode_t *genie_slist_get_not_empty(genie_slist_t *list)
{
genie_snode_t *node = list->head;
list->head = node->next;
if (list->tail == node) {
list->tail = list->head;
}
return node;
}
/**
* @brief Fetch and remove the first node of the given list
*
* This and other sys_slist_*() functions are not thread safe.
*
* @param list A pointer on the list to affect
*
* @return A pointer to the first node of the list (or NULL if empty)
*/
static inline genie_snode_t *genie_slist_get(genie_slist_t *list)
{
return genie_slist_is_empty(list) ? NULL : genie_slist_get_not_empty(list);
}
/**
* @brief Remove a node
*
* This and other sys_slist_*() functions are not thread safe.
*
* @param list A pointer on the list to affect
* @param prev_node A pointer on the previous node
* (can be NULL, which means the node is the list's head)
* @param node A pointer on the node to remove
*/
static inline void genie_slist_remove(genie_slist_t *list,
genie_snode_t *prev_node,
genie_snode_t *node)
{
if (!prev_node) {
list->head = node->next;
/* Was node also the tail? */
if (list->tail == node) {
list->tail = list->head;
}
} else {
prev_node->next = node->next;
/* Was node the tail? */
if (list->tail == node) {
list->tail = prev_node;
}
}
node->next = NULL;
}
/**
* @brief Find and remove a node from a list
*
* This and other sys_slist_*() functions are not thread safe.
*
* @param list A pointer on the list to affect
* @param node A pointer on the node to remove from the list
*
* @return true if node was removed
*/
static inline bool genie_slist_find_and_remove(genie_slist_t *list,
genie_snode_t *node)
{
genie_snode_t *prev = NULL;
genie_snode_t *test;
GENIE_SLIST_FOR_EACH_NODE(list, test) {
if (test == node) {
genie_slist_remove(list, prev, node);
return true;
}
prev = test;
}
return false;
}
#ifdef __cplusplus
}
#endif
#endif /* _GENIE_SLIST_H_ */

View File

@ -0,0 +1,175 @@
// Copyright (C) 2018-2020 Alibaba Group Holding Limited
// Adaptations to ESP-IDF Copyright (c) 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _GENIE_TIMER_H_
#define _GENIE_TIMER_H_
#ifdef __cplusplus
extern "C" {
#endif /**< __cplusplus */
// #define GENIE_VENDOR_TIMER_STORE 1 // store timing data/operation
#define GENIE_STORE_VENDOR_TIMER "vendor_timer"
#pragma pack(1)
typedef struct {
uint16_t type;
uint8_t para;
} genie_timer_attr_data_t;
#pragma pack()
typedef struct {
volatile uint16_t year; // 2019+
volatile uint8_t month; // 0-11
volatile uint8_t day; // 1-31
volatile uint8_t seconds; // 0-59
volatile uint8_t minutes; // 0-59
volatile uint8_t hour; // 0-23
volatile uint8_t weekday; // 0 means sunday
} utc_time_t;
typedef enum {
GENIE_TIMER_ERR_OK = 0,
GENIE_TIMER_ERR_INIT,
GENIE_TIMER_ERR_LOCALTIME_NOTSET,
GENIE_TIMER_ERR_INDEX,
GENIE_TIMER_ERR_PARAM,
GENIE_TIMER_ERR_NORESOURCE,
GENIE_TIMER_ERR_OTHER,
} genie_timer_error_t;
typedef enum {
GENIE_TIME_EVT_TIMEOUT = 0,
GENIE_TIME_EVT_TIMING_SYNC = 1,
} genie_timer_event_t;
/**
* @brief
*
*/
typedef void *genie_timer_handle_t;
/**
* @brief genie timer event handle callback function.
*
*/
typedef int (*genie_timer_event_func_t)(uint8_t event, uint8_t index, genie_timer_attr_data_t *data);
/**
* @brief Genie time init.
*
* @param cb genie timer event handle callback function.
*
* @return int
*/
int genie_timer_init(genie_timer_event_func_t cb);
/**
* @brief Local time update.
*
* @param unix_time
*
* @return int
*/
int genie_timer_local_time_update(uint32_t unix_time);
/**
* @brief Get local unix time.
*
* @return uint32_t
*/
uint32_t genie_timer_local_unixtime_get(void);
/**
* @brief Update local timezone.
*
* @param timezone
*
* @return int
*/
int genie_timer_timezone_update(int8_t timezone);
/**
* @brief Get local timezone.
*
* @return int8_t
*/
int8_t genie_timer_timezone_get(void);
/**
* @brief
*
* @param period_time
* @param retry_delay
* @param retry_times
*
* @return int
*/
int genie_timer_time_sync_get(uint16_t *period_time, uint8_t *retry_delay, uint8_t *retry_times);
/**
* @brief
*
* @param period_time
* @param retry_delay
* @param retry_times
*
* @return int
*/
int genie_timer_time_sync_set(uint16_t period_time, uint8_t retry_delay, uint8_t retry_times);
/**
* @brief Start timer specified by index.
*
* @param index
* @param unix_time
* @param attr_data
*
* @return int
*/
int genie_timer_start(uint8_t index, uint32_t unix_time, genie_timer_attr_data_t *attr_data);
/**
* @brief Start periodic timer specified by index.
*
* @param index
* @param periodic_time
* @param schedule
* @param attr_data
*
* @return int
*/
int genie_timer_periodic_start(uint8_t index, uint16_t periodic_time, uint8_t schedule, genie_timer_attr_data_t *attr_data);
/**
* @brief Remove genie timer.
*
* @param index
*
* @return int
*/
int genie_timer_remove(uint8_t index);
/**
* @brief show local time.
*
*/
void genie_timer_local_time_show(void);
#ifdef __cplusplus
}
#endif /**< __cplusplus */
#endif /* _GENIE_TIMER_H_ */

View File

@ -0,0 +1,194 @@
// Copyright (C) 2018-2020 Alibaba Group Holding Limited
// Adaptations to ESP-IDF Copyright (c) 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _GENIE_UTIL_H_
#define _GENIE_UTIL_H_
#ifdef __cplusplus
extern "C" {
#endif /**< __cplusplus */
/**
* p_elem: pointer to the element which the messsage want to be sent to
* retry: retry counts before desired confirm message received
* * fill negative value if retransmission is not necessary
* * fill positive value if retransmission is needed
* * * will be round in this scope - [GENIE_MODEL_MSG_DFT_RETRY_TIMES, GENIE_MODEL_MSG_MAX_RETRY_TIMES]
* retry_period: wait for retry_period before retransmit the message, in unit of ms
* opid: hightest byte in Opcode defined in the vendor model spec designed by Alibaba IoT Group
* * e.g. for Vendor Message Attr Get message, Opcode is 0xD001A8, corresponding opid is 0xD0
* * refer to the marco named VENDOR_OP_ATTR_xxxx
* tid:
* * if the message is with type of GENIE_OP_ATTR_CONFIME or GENIE_OP_ATTR_CONFIME_TG,
* * tid should be filled with the replied message's tid
* len: payload length
* data: pointer to the genie vendor message's payload
* */
typedef struct {
esp_ble_mesh_elem_t *p_elem;
int8_t retry;
uint16_t retry_period;
uint8_t opid;
uint8_t tid;
uint16_t len;
uint8_t *data;
} genie_model_msg_t;
typedef void (*util_timer_handler_t)(void *args);
typedef struct {
esp_timer_handle_t timer;
util_timer_handler_t handler;
void *args;
uint32_t timeout;
uint32_t start_ms;
} util_timer_t;
/* semaphore define */
typedef struct {
SemaphoreHandle_t sem;
} util_semaphore_t;
/**
* @brief Initialize a timer.
*
* This routine initializes a timer, prior to its first use.
*
* @param timer Address of timer.
* @param handle Function to invoke each time the timer expires.
* @param args Arguments sent to handle.
*
* @return N/A
*/
void util_timer_init(util_timer_t *timer, util_timer_handler_t handle, void *args);
/**
* @brief Start a timer.
*
* @param timer Address of timer.
* @param timeout time before timeout happen(in milliseconds).
*
* @return N/A
*/
void util_timer_start(util_timer_t *timer, uint32_t timeout);
/**
* @brief Stop a timer.
*
* @param timer Address of timer.
*
* @return N/A
*/
void util_timer_stop(util_timer_t *timer);
/**
* @brief check wether a timer is started or not.
*
* @param timer Address of timer.
*
* @return true if timer is started; otherwise false.
*/
bool util_timer_is_started(util_timer_t *timer);
/**
* @brief Initialize a semaphore.
*
* This routine initializes a semaphore object, prior to its first use.
*
* @param sem Address of the semaphore.
* @param initial_count Initial semaphore count.
* @param limit Maximum permitted semaphore count.
*
* @return 0 Creat a semaphore succcess
*/
int util_semaphore_init(util_semaphore_t *sem, uint32_t initial_count, uint32_t limit);
/**
* @brief Take a semaphore.
*
* This routine takes @a sem.
*
* @note Can be called by ISRs, but @a timeout must be set to K_NO_WAIT.
*
* @param sem Address of the semaphore.
* @param timeout Waiting period to take the semaphore (in milliseconds),
* or one of the special values K_NO_WAIT and K_FOREVER.
*
* @note When porting code from the nanokernel legacy API to the new API, be
* careful with the return value of this function. The return value is the
* reverse of the one of nano_sem_take family of APIs: 0 means success, and
* non-zero means failure, while the nano_sem_take family returns 1 for success
* and 0 for failure.
*
* @retval 0 Semaphore taken.
* @retval -EBUSY Returned without waiting.
* @retval -EAGAIN Waiting period timed out.
*/
int util_semaphore_take(util_semaphore_t *sem, uint32_t timeout);
/**
* @brief Give a semaphore.
*
* This routine gives @a sem, unless the semaphore is already at its maximum
* permitted count.
*
* @note Can be called by ISRs.
*
* @param sem Address of the semaphore.
*
* @return 0 Give semaphore success
*/
int util_semaphore_give(util_semaphore_t *sem);
/**
* @brief Delete a semaphore.
*
* This routine delete @a sem,
*
* @note Can be called by ISRs.
*
* @param sem Address of the semaphore.
*
* @return 0 delete semaphore success
*/
int util_semaphore_delete(util_semaphore_t *sem);
/**
* @brief Get a semaphore's count.
*
* This routine returns the current count of @a sem.
*
* @param sem Address of the semaphore.
*
* @return Current semaphore count.
*/
uint32_t util_semaphore_count_get(util_semaphore_t *sem);
/**
* @brief Get a semaphore's count.
*
* This routine returns the current count of @a sem.
*
* @param sem Address of the semaphore.
*
* @return Current semaphore count.
*/
const char *util_hex2str(const void *buf, size_t len);
#ifdef __cplusplus
}
#endif /**< __cplusplus */
#endif /* _GENIE_UTIL_H_ */

View File

@ -0,0 +1,7 @@
set(COMPONENT_SRCS "board.c"
"aligenie_demo.c")
set(COMPONENT_ADD_INCLUDEDIRS ". include")
register_component()

View File

@ -0,0 +1,88 @@
menu "AliGenie Example Configuration"
menu "AliGenie Triples Configuration"
config TRIPLES_PRODUCT_ID
int "Product ID"
default 0
config TRIPLES_DEVICE_NAME
string "Device Name"
default "Device Name"
help
Please use lower case
config TRIPLES_DEVICE_SECRET
string "Device Secret"
default "Device Secret"
help
Please use lower case
endmenu
menu "light driver config"
config LIGHT_GPIO_RED
int "Light red pin GPIO number"
range -1 33
default 25
help
There are more enumerations like that
up to GPIO39, excluding GPIO20, GPIO24 and GPIO28..31.
They are not shown here to reduce redundant information.
@note GPIO34..39 are input mode only.
config LIGHT_GPIO_GREEN
int "Light green pin GPIO number"
range -1 33
default 26
help
There are more enumerations like that
up to GPIO39, excluding GPIO20, GPIO24 and GPIO28..31.
They are not shown here to reduce redundant information.
@note GPIO34..39 are input mode only.
config LIGHT_GPIO_BLUE
int "Light blue pin GPIO number"
range -1 33
default 27
help
There are more enumerations like that
up to GPIO39, excluding GPIO20, GPIO24 and GPIO28..31.
They are not shown here to reduce redundant information.
@note GPIO34..39 are input mode only.
config LIGHT_GPIO_COLD
int "Light cold colors pin GPIO number"
range -1 33
default -1
help
There are more enumerations like that
up to GPIO39, excluding GPIO20, GPIO24 and GPIO28..31.
They are not shown here to reduce redundant information.
@note GPIO34..39 are input mode only.
config LIGHT_GPIO_WARM
int "Light warm color pin GPIO number"
range -1 33
default -1
help
There are more enumerations like that
up to GPIO39, excluding GPIO20, GPIO24 and GPIO28..31.
They are not shown here to reduce redundant information.
@note GPIO34..39 are input mode only.
config LIGHT_FADE_PERIOD_MS
int "The time from the current color to the next color"
default 500
help
The time from the current color to the next color.
config LIGHT_BLINK_PERIOD_MS
int "Period of blinking lights"
default 2000
help
Period of blinking lights.
endmenu
endmenu

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,227 @@
/* AliGenie - Example
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 <stdio.h>
#include "driver/gpio.h"
#include "esp_log.h"
#include "iot_button.h"
#include "light_driver.h"
#include "genie_event.h"
#define BUTTON_ON_OFF 0 /* on/off button */
#define BUTTON_ACTIVE_LEVEL 0
static const char *TAG = "board";
static uint32_t dev_on_btn_num = BUTTON_ON_OFF;
extern void user_genie_event_handle(genie_event_t event, void *p_arg);
void button_tap_cb(void* arg)
{
user_genie_event_handle(GENIE_EVT_BUTTON_TAP, NULL);
}
static void board_led_init(void)
{
/**
* NOTE:
* If the module has SPI flash, GPIOs 6-11 are connected to the modules integrated SPI flash and PSRAM.
* If the module has PSRAM, GPIOs 16 and 17 are connected to the modules integrated PSRAM.
*/
light_driver_config_t driver_config = {
.gpio_red = CONFIG_LIGHT_GPIO_RED,
.gpio_green = CONFIG_LIGHT_GPIO_GREEN,
.gpio_blue = CONFIG_LIGHT_GPIO_BLUE,
.gpio_cold = CONFIG_LIGHT_GPIO_COLD,
.gpio_warm = CONFIG_LIGHT_GPIO_WARM,
.fade_period_ms = CONFIG_LIGHT_FADE_PERIOD_MS,
.blink_period_ms = CONFIG_LIGHT_BLINK_PERIOD_MS,
};
/**
* @brief Light driver initialization
*/
ESP_ERROR_CHECK(light_driver_init(&driver_config));
light_driver_set_mode(MODE_HSL);
// light_driver_set_switch(true);
button_handle_t dev_on_off_btn = iot_button_create(BUTTON_ON_OFF, BUTTON_ACTIVE_LEVEL);
iot_button_set_evt_cb(dev_on_off_btn, BUTTON_CB_TAP, button_tap_cb, &dev_on_btn_num);
}
void board_init(void)
{
board_led_init();
}
/**
* hsl
*/
void board_led_hsl(uint8_t elem_index, uint16_t hue, uint16_t saturation, uint16_t lightness)
{
static uint16_t last_hue = 0xFFFF;
static uint16_t last_saturation = 0xFFFF;
static uint16_t last_lightness = 0xFFFF;
ESP_LOGD(TAG, "hue last state %d, state %d", last_hue, hue);
ESP_LOGD(TAG, "saturation last state %d, state %d", last_saturation, saturation);
ESP_LOGD(TAG, "lightness last state %d, state %d", last_lightness, lightness);
if(last_hue != hue || last_saturation != saturation || last_lightness != lightness ) {
last_hue = hue;
last_saturation = saturation;
last_lightness = lightness;
uint16_t actual_hue = (float)last_hue / (UINT16_MAX / 360.0);
uint8_t actual_saturation = (float)last_saturation / (UINT16_MAX / 100.0);
uint8_t actual_lightness = (float)last_lightness / (UINT16_MAX / 100.0);
ESP_LOGD(TAG, "hsl: %d, %d, %d operation", actual_hue, actual_saturation, actual_lightness);
light_driver_set_hsl(actual_hue, actual_saturation, actual_lightness);
}
}
/**
* temperature light temp
*/
void board_led_temperature(uint8_t elem_index, uint16_t temperature)
{
static uint16_t last_temperature = 0xFFFF;
ESP_LOGD(TAG, "temperature last state %d, state %d", last_temperature, temperature);
if(last_temperature != temperature) {
last_temperature = temperature;
uint16_t actual_temperature = (float)last_temperature / (UINT16_MAX / 100.0);
ESP_LOGD(TAG, "temperature %d %%%d operation", last_temperature, actual_temperature);
light_driver_set_color_temperature(actual_temperature);
}
}
/**
* actual lightness
*/
void board_led_lightness(uint8_t elem_index, uint16_t actual)
{
static uint16_t last_acual = 0xFFFF;
ESP_LOGD(TAG, "actual last state %d, state %d", last_acual, actual);
if(last_acual != actual) {
last_acual = actual;
uint16_t actual_lightness = (float)last_acual / (UINT16_MAX / 100.0);
ESP_LOGD(TAG, "lightness %d %%%d operation", last_acual, actual_lightness);
light_driver_set_lightness(actual_lightness);
}
}
/**
* onoff on/off
*/
void board_led_switch(uint8_t elem_index, uint8_t onoff)
{
static uint8_t last_onoff = 0xFF;
ESP_LOGD(TAG, "onoff last state %d, state %d", last_onoff, onoff);
if(last_onoff != onoff) {
last_onoff = onoff;
if (last_onoff) {
ESP_LOGD(TAG, "onoff %d operation", last_onoff);
light_driver_set_switch(true);
} else {
ESP_LOGD(TAG, "onoff %d operation", last_onoff);
light_driver_set_switch(false);
}
}
}
#define MINDIFF (2.25e-308)
static float bt_mesh_sqrt(float square)
{
float root, last, diff;
root = square / 3.0;
diff = 1;
if (square <= 0) {
return 0;
}
do {
last = root;
root = (root + square / root) / 2.0;
diff = root - last;
} while (diff > MINDIFF || diff < -MINDIFF);
return root;
}
static int32_t bt_mesh_ceiling(float num)
{
int32_t inum = (int32_t)num;
if (num == (float)inum) {
return inum;
}
return inum + 1;
}
uint16_t convert_lightness_actual_to_linear(uint16_t actual)
{
float tmp = ((float) actual / UINT16_MAX);
return bt_mesh_ceiling(UINT16_MAX * tmp * tmp);
}
uint16_t convert_lightness_linear_to_actual(uint16_t linear)
{
return (uint16_t)(UINT16_MAX * bt_mesh_sqrt(((float) linear / UINT16_MAX)));
}
int16_t convert_temperature_to_level(uint16_t temp, uint16_t min, uint16_t max)
{
float tmp = (temp - min) * UINT16_MAX / (max - min);
return (int16_t) (tmp + INT16_MIN);
}
uint16_t covert_level_to_temperature(int16_t level, uint16_t min, uint16_t max)
{
float diff = (float) (max - min) / UINT16_MAX;
uint16_t tmp = (uint16_t) ((level - INT16_MIN) * diff);
return (uint16_t) (min + tmp);
}
/* swap octets */
void swap_buf(uint8_t *dst, const uint8_t *src, int len)
{
int i;
for (i = 0; i < len; i++) {
dst[len - 1 - i] = src[i];
}
}
uint8_t *mac_str2hex(const char *mac_str, uint8_t *mac_hex)
{
uint32_t mac_data[6] = {0};
sscanf(mac_str, "%02x%02x%02x%02x%02x%02x",
mac_data, mac_data + 1, mac_data + 2, mac_data + 3, mac_data + 4, mac_data + 5);
for (int i = 0; i < 6; i++) {
mac_hex[i] = mac_data[i];
}
return mac_hex;
}

View File

@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@ -0,0 +1,85 @@
/* AliGenie - Example
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.
*/
#ifndef _BOARD_H_
#define _BOARD_H_
#include "driver/gpio.h"
#include "light_driver.h"
#ifdef __cplusplus
extern "C" {
#endif /**< __cplusplus */
#define LED_ON 1
#define LED_OFF 0
#define POWERUP_KEY "powerup"
/**
* @brief
*/
void board_init(void);
/**
* @brief
*/
void board_led_hsl(uint8_t elem_index, uint16_t hue, uint16_t saturation, uint16_t lightness);
/**
* @brief
*/
void board_led_temperature(uint8_t elem_index, uint16_t temperature);
/**
* @brief
*/
void board_led_lightness(uint8_t elem_index, uint16_t actual);
/**
* @brief
*/
void board_led_switch(uint8_t elem_index, uint8_t onoff);
/**
* @brief
*/
uint16_t convert_lightness_actual_to_linear(uint16_t actual);
/**
* @brief
*/
uint16_t convert_lightness_linear_to_actual(uint16_t linear);
/**
* @brief
*/
int16_t convert_temperature_to_level(uint16_t temp, uint16_t min, uint16_t max);
/**
* @brief
*/
uint16_t covert_level_to_temperature(int16_t level, uint16_t min, uint16_t max);
/**
* @brief
*/
void swap_buf(uint8_t *dst, const uint8_t *src, int len);
/**
* @brief
*/
uint8_t *mac_str2hex(const char *mac_str, uint8_t *mac_hex);
#ifdef __cplusplus
}
#endif /**< __cplusplus */
#endif

View File

@ -0,0 +1,8 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 16k
otadata, data, ota, 0xd000, 8k
phy_init, data, phy, 0xf000, 4k
ota_0, app, ota_0, 0x10000, 1920k
ota_1, app, ota_1, , 1920k
coredump, data, coredump, , 64K
reserved, data, 0xfe, , 128K
1 # Name, Type, SubType, Offset, Size, Flags
2 nvs, data, nvs, 0x9000, 16k
3 otadata, data, ota, 0xd000, 8k
4 phy_init, data, phy, 0xf000, 4k
5 ota_0, app, ota_0, 0x10000, 1920k
6 ota_1, app, ota_1, , 1920k
7 coredump, data, coredump, , 64K
8 reserved, data, 0xfe, , 128K

View File

@ -0,0 +1,37 @@
#
# Partition Table
#
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
#
# Serial flasher config
#
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
# Override some defaults so BT stack is enabled
# by default in this example
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BTDM_MODEM_SLEEP=n
CONFIG_BTDM_SCAN_DUPL_TYPE_DATA_DEVICE=y
CONFIG_BTDM_BLE_MESH_SCAN_DUPL_EN=y
CONFIG_BT_GATTS_SEND_SERVICE_CHANGE_MANUAL=y
CONFIG_BT_BTU_TASK_STACK_SIZE=4512
# Override some defaults of ESP BLE Mesh
CONFIG_BLE_MESH=y
CONFIG_BLE_MESH_NODE=y
CONFIG_BLE_MESH_PB_GATT=y
CONFIG_BLE_MESH_TX_SEG_MSG_COUNT=10
CONFIG_BLE_MESH_RX_SEG_MSG_COUNT=10
CONFIG_BLE_MESH_SETTINGS=y
CONFIG_BLE_MESH_SUBNET_COUNT=5
CONFIG_BLE_MESH_APP_KEY_COUNT=5
CONFIG_BLE_MESH_MODEL_KEY_COUNT=5
CONFIG_BLE_MESH_MODEL_GROUP_COUNT=5
CONFIG_BLE_MESH_MSG_CACHE_SIZE=20
CONFIG_BLE_MESH_ADV_BUF_COUNT=256

View File

@ -0,0 +1,111 @@
| Supported Targets | ESP32 |
| ----------------- | ----- |
ESP BLE Mesh AliGenie Example
=============================
This demo shows how to use AliGenie to control the LED state. The basic procedures are as follows:
1. Go to the Ali IoT platform to apply for device triples.
2. Configure triples.
3. Download and run this demo.
4. Use AliGenie speakers or AliGenie app for BLE Mesh to provision.
5. After devices are provisioned, use voice interaction or app to control the LED state with AliGenie speakers.
# Basic Procedures
## 1. Apply Triples
## 1.1 Create Project
The first step in using the Ali IoT platform is to create a project. Data between projects is isolated from each other. Steps:
1. Log in to the console of the [Ali IoT platform](https://living.aliyun.com/?spm=a2c4g.11186623.2.13.4d219931TRSWJO).
2. Click Create Project.
3. Configure the project name.
![Create Project](images/create_project.png)
## 1.2 Create Product
In each project, you can create multiple products. After the product is created, you can abstract the actual product into a data model composed of attributes, services, and events by defining product functions to facilitate cloud management and data interaction.
1. On the main project page, click Create New Product.
2. Configure product parameters
3. Click Finish. After the product is successfully created, it will automatically enter the product function definition page.
![Create Product](images/create_product.png)
![Create Product](images/create_product_01.png)
## 1.3 Definition Features
The platform provides default standard functions for each category. After the product is created, the function definition page automatically displays the default standard functions of the product.
You can add or modify features according to the following steps. On the product function definition page, click Add function corresponding to the standard function.
![Create Product](images/create_product_02.png)
## 1.4 Configure App
The life IoT platform provides App services, which simplifies App development tasks. You can realize the data communication between the App and the platform through simple configuration, and achieve the effect of human-computer interaction.
1. Click Next: Human-Computer Interaction to enter the product-human-computer interaction page.
2. Turn on the switch to control the product using the public version App.
3. Click the selection panel. Select a device panel in the Theme Panel tab in the Select Product Panel dialog box or design a panel by yourself in the custom panel.
4. Install the public version App "Cloud Intelligence". Click Install now, select the development version, and then scan the code to download the development version of the public version of the App.
5. Configure the boot page for device network configuration.
![Create Product](images/create_product_03.png)
## 1.2 Add Debug Device
The device must use the unique device certificate (ProductID, DeviceName, DeviceSecret) issued by the platform to access the Ali IoT platform.
1. Click Next: Device debugging, enter the product-device debugging page.
2. Select the corresponding module in the selection of authentication module/chip, and it is recommended to use the module that has been certified by Ali.
3. Click Add Test Device in Test Device.
4. Configure the device name. DeviceName can also be left unconfigured, and the system automatically generates the device name.
5. Click OK, the interface displays the device certificate of the test device. The activation credentials (ProductID, DeviceName, DeviceSecret) that need to be burned into the device are shown in the red box in the figure below.
![Create Product](images/create_product_04.png)
![Create Product](images/create_product_05.png)
![Create Product](images/create_product_06.png)
## 2. Configure triples
1. Enter the examples/bluetooth/esp_ble_mesh/aligenie_demo directory and run `idf.py menuconfig`
2. Configure device triples information, save and exit after modification.
![Configure triples](images/configure_triples_00.png)
![Configure triples](images/configure_triples_01.png)
![Configure triples](images/configure_triples_02.png)
## 3. Config LED GPIO
1. To use color-adjustable RGB lights, three GPIOs, RED, GREEN, and BLUE, need to be configured.
![Configure LED](images/configure_led.png)
## 4. Download and run this demo
1. After all configurations are completed, you can run `idf.py flash monitor` to download and monitor.
2. After the download is complete, you can see this log.
![Configure LED](images/flash_monitor.png)
## 5. Provision
1. When the device is powered on for the first time, the GREEN light will blink, indicating that the device has not been provisioned yet.
2. We can say "天猫精灵,发现智能设备" to the AliGenie speaker.
3. When the speaker finds the device and prompts whether to connect, we say "连接" to the AliGenie speaker, and you can start provisioning the device.
4. Next we can use voice to control the lights, for example:"天猫精灵,开灯"、"天猫精灵,关灯"、"天猫精灵,把灯调整成蓝色"、"天猫精灵,把灯调整成绿色"…………
## 6. Reset
1. The device can be reset by repeatedly resetting the device three times.
2. Each reset operation needs to wait until the light is on before proceeding.

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -0,0 +1,10 @@
set(COMPONENT_SRCS "light_driver.c"
"iot_led.c")
set(COMPONENT_ADD_INCLUDEDIRS ". include")
# requirements can't depend on config
set(COMPONENT_REQUIRES example_nvs)
register_component()

View File

@ -0,0 +1,22 @@
# Component: Light
* This component defines a light as a well encapsulated object.
* A light device is defined by:
* ledc timer which is used to control the pwm channels of light
* mode of the ledc timer
* frequency of the ledc timer
* pwm channel number of the light
* bit number of the ledc timer
* A light device can provide:
* iot_light_channel_regist function to add channel to corresponding channel id
* iot_light_duty_write function to set the duty of corresponding channel and it support setting duty directly or gradually
* iot_light_breath_write function to set the corresponding channel to breath mode and breath period can be set
* iot_light_blink_starte and iot_light_blink_stop function to make some of channels to blink in appointed period. Note that if any channel works in blink mode, all the other channels would be turned off.
* To use the light device, you need to:
* create a light object returned by iot_light_create()
* regist the light channels according the channel number by iot_light_channel_regist()
* To free the object, you can call iot_light_delete to delete the button object and free the memory.
### NOTE:
> If any channel(s) work(s) in blink mode, all the other channels would be turned off. iot_light_blink_stop() must be called before setting any channel to other mode(write duty or breath).

View File

@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@ -0,0 +1,179 @@
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef __IOT_LED_H__
#define __IOT_LED_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "driver/ledc.h"
#define HW_TIMER_GROUP (0) /**< Hardware timer group */
#define HW_TIMER_ID (0) /**< Hardware timer number */
#define HW_TIMER_DIVIDER (16) /**< Hardware timer clock divider */
#define HW_TIMER_SCALE (TIMER_BASE_CLK / HW_TIMER_DIVIDER) /**< Convert counter value to seconds */
#define GAMMA_CORRECTION 0.8 /**< Gamma curve parameter */
#define GAMMA_TABLE_SIZE 256 /**< Gamma table size, used for led fade*/
#define DUTY_SET_CYCLE (20) /**< Set duty cycle */
/**
* Macro which can be used to check the error code,
* and terminate the program in case the code is not ESP_OK.
* Prints the error code, error location, and the failed statement to serial output.
*
* Disabled if assertions are disabled.
*/
#define LIGHT_ERROR_CHECK(con, err, format, ...) do { \
if (con) { \
if(*format != '\0') \
ESP_LOGW(TAG, "<%s> " format, esp_err_to_name(err), ##__VA_ARGS__); \
return err; \
} \
} while(0)
#define LIGHT_PARAM_CHECK(con) do { \
if (!(con)) { \
ESP_LOGE(TAG, "<ESP_ERR_INVALID_ARG> !(%s)", #con); \
return ESP_ERR_INVALID_ARG; \
} \
} while(0)
/**
* @brief Initialize and set the ledc timer for the iot led
*
* @param timer_num The timer index of ledc timer group used for iot led
* This parameter can be one of LEDC_TIMER_x where x can be (0 .. 3)
*
* @param speed_mode speed mode of ledc timer
* This parameter can be one of LEDC_x_SPEED_MODE where x can be (LOW, HIGH)
*
* @param freq_hz frequency of ledc timer
* This parameter must be less than 5000
*
* @return
* - ESP_OK if sucess
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_FAIL Can not find a proper pre-divider number base on the given frequency
* and the current duty_resolution.
*/
esp_err_t iot_led_init(ledc_timer_t timer_num, ledc_mode_t speed_mode, uint32_t freq_hz);
/**
* @brief DeInitializes the iot led and free resource
*
* @return
* - ESP_OK if sucess
*/
esp_err_t iot_led_deinit(void);
/**
* @brief Set the ledc channel used by iot led and associate the gpio port used
* for output
*
* @param channel The ledc channel
* This parameter can be LEDC_CHANNEL_x where x can be (0 .. 15)
* @param gpio_num the ledc output gpio_num
* This parameter can be GPIO_NUM_x where x can be (0, 33)
*
* @note If the operation of esp32 depends on SPI FLASH or PSRAM, then these related
* pins should not be set to output.
*
* @return
* - ESP_OK if sucess
* - ESP_ERR_INVALID_ARG Parameter error
*/
esp_err_t iot_led_regist_channel(ledc_channel_t channel, gpio_num_t gpio_num);
/**
* @brief Returns the channel value
* @note before calling this function, you need to call iot_led_regist_channel() to
* set the channel
*
* @param channel The ledc channel
* This parameter can be LEDC_CHANNEL_x where x can be (0 .. 15)
* @param dst The address where the channel value is stored
* @return
* - ESP_OK if sucess
* - ESP_ERR_INVALID_ARG if dst is NULL
*/
esp_err_t iot_led_get_channel(ledc_channel_t channel, uint8_t *dst);
/**
* @brief Set the fade state for the specified channel
* @note before calling this function, you need to call iot_led_regist_channel() to
* set the channel
*
* @param channel The ledc channel
* This parameter can be LEDC_CHANNEL_x where x can be (0 .. 15)
* @param value The target output brightness of iot led
* This parameter can be (0 .. 255)
* @param fade_ms The time from the current value to the target value
* @return
* - ESP_OK if sucess
*/
esp_err_t iot_led_set_channel(ledc_channel_t channel, uint8_t value, uint32_t fade_ms);
/**
* @brief Set the blink state or loop fade for the specified channel
* @note before calling this function, you need to call iot_led_regist_channel() to
* set the channel
*
* @param channel The ledc channel
* This parameter can be LEDC_CHANNEL_x where x can be (0 .. 15)
* @param value The output brightness of iot led
* This parameter can be (0 .. 255)
* @param period_ms Blink cycle
* @param fade_flag select loop fade or blink
* 1 for loop fade
* 0 for blink
* @return
* - ESP_OK if sucess
*/
esp_err_t iot_led_start_blink(ledc_channel_t channel, uint8_t value, uint32_t period_ms, bool fade_flag);
/**
* @brief Stop the blink state or loop fade for the specified channel
*
* @param channel The ledc channel
* This parameter can be LEDC_CHANNEL_x where x can be (0 .. 15)
* @return
* - ESP_OK if sucess
*/
esp_err_t iot_led_stop_blink(ledc_channel_t channel);
/**
* @brief Set the specified gamma_table to control the fade effect, usually
* no need to set
*
* @param gamma_table[GAMMA_TABLE_SIZE] Expected gamma table value
*
* @note Gamma_table is the dimming curve used by the iot_led driver.
* The element type is uint16_t. Each element is treated as a binary
* fixed-point number. The decimal point is before the eighth bit
* and after the ninth bit, so the range of expressions can be
* 0x00.00 ~ 0xff.ff.
* @note default gamma_table is created in iot_led_init()
*
* @return
* - ESP_OK if sucess
*/
esp_err_t iot_led_set_gamma_table(const uint16_t gamma_table[GAMMA_TABLE_SIZE]);
#ifdef __cplusplus
}
#endif
#endif /**< __IOT_LED_H__ */

View File

@ -0,0 +1,162 @@
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef __IOT_LIGHT_H__
#define __IOT_LIGHT_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "driver/ledc.h"
/********************************** NOTE *********************************/
/* When we create a light object, a hardware timer will be enabled, this */
/* timer is used to realize fade and blink operation. The default timer */
/* occupied is timer 0 of timer group 0, user can change this config in */
/* menuconfig. */
/*************************************************************************/
typedef void *light_handle_t;
#define HW_TIMER_GROUP (0) /**< Hardware timer group */
#define HW_TIMER_ID (0) /**< Hardware timer number */
#define HW_TIMER_DIVIDER (16) /**< Hardware timer clock divider */
#define HW_TIMER_SCALE (TIMER_BASE_CLK / HW_TIMER_DIVIDER) /**< Convert counter value to seconds */
#define DUTY_SET_CYCLE (20) /**< Set duty cycle */
#define DUTY_SET_GAMMA (0.6) /**< Set the Gamma value for the fade curve, default value is 0.6 */
#define LIGHT_MAX_CHANNEL_NUM (5)
/**
* @brief light initialize
*
* @param timer the LEDC timer used by light
* @param speed_mode speed mode of LEDC timer
* @param freq_hz frequency of LEDC timer
* @param channel_num decide how many channels the light contains
* @param timer_bit LEDC PWM duty resolution
*
* @return the handle of light
*/
light_handle_t iot_light_create(ledc_timer_t timer, ledc_mode_t speed_mode, uint32_t freq_hz, uint8_t channel_num, ledc_timer_bit_t timer_bit);
/**
* @brief add an output channel to light
*
* @param light_handle light handle
* @param channel_idx the id of channel (0 ~ channel_num-1)
* @param io_num the IO number use to output LEDC PWM
* @param channel the ledc channel you want to use
*
* @return
* - ESP_OK: succeed
* - others: fail
*/
esp_err_t iot_light_channel_regist(light_handle_t light_handle, uint8_t channel_idx, gpio_num_t io_num, ledc_channel_t channel);
/**
* @brief free the memory of light
*
* @param light_handle light handle
*
* @return
* - ESP_OK: succeed
* - others: fail
*/
esp_err_t iot_light_delete(light_handle_t light_handle);
/**
* @brief get channel duty
*
* @param light_handle light handle
* @param channel_id the id of channel (0 ~ channel_num-1)
*
* @return
* - LEDC_ERR_DUTY if parameter error
* - Others Current LEDC duty
*/
uint32_t iot_light_duty_get(light_handle_t light_handle, uint8_t channel_id);
/**
* @brief set light fade with time. if set fade_period_ms as 0, set the duty directly.
*
* @param light_handle light handle
* @param channel_id the id of channel (0 ~ channel_num-1)
* @param duty target duty
* @param fade_period_ms fade time (uint: ms)
*
* @return
* - ESP_OK: succeed
* - others: fail
*/
esp_err_t iot_light_fade_with_time(light_handle_t light_handle, uint8_t channel_id, uint32_t duty, uint32_t fade_period_ms);
/**
* @brief set breath config of a light channel, call `iot_light_operate_start` to start breath operation
*
* @param light_handle light handle
* @param channel_id the id of channel (0 ~ channel_num-1)
* @param duty the maximum duty when breath
* @param breath_period_ms breath period (uint: ms)
*
* @return
* - ESP_OK: succeed
* - others: fail
*/
esp_err_t iot_light_breath_config(light_handle_t light_handle, uint8_t channel_id, uint32_t duty, uint32_t breath_period_ms);
/**
* @brief set blink config of a light channel, call `iot_light_operate_start` to start blink operation
*
* @param light_handle light handle
* @param channel_id the id of channel (0 ~ channel_num-1)
* @param blink_period_ms blink period (uint: ms)
*
* @return
* - ESP_OK: succeed
* - others: fail
*/
esp_err_t iot_light_blink_config(light_handle_t light_handle, uint8_t channel_id, uint32_t blink_period_ms);
/**
* @brief start breath or blink operation, user need to set breath or blink config before call this API
*
* @param light_handle light handle
* @param channel_id the id of channel (0 ~ channel_num-1)
*
* @return
* - ESP_OK: succeed
* - others: fail
*/
esp_err_t iot_light_operate_start(light_handle_t light_handle, uint8_t channel_id);
/**
* @brief stop breath or blink operation
*
* @param light_handle light handle
* @param channel_id the id of channel (0 ~ channel_num-1)
*
* @return
* - ESP_OK: succeed
* - others: fail
*/
esp_err_t iot_light_operate_stop(light_handle_t light_handle, uint8_t channel_id);
#ifdef __cplusplus
}
#endif
#endif /**< __IOT_LIGHT_H__ */

View File

@ -0,0 +1,166 @@
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef __LIGHT_DRIVER_H__
#define __LIGHT_DRIVER_H__
#include "iot_led.h"
#ifdef __cplusplus
extern "C" {
#endif
#define LIGHT_STATUS_STORE_KEY "light_status"
/**
* @brief The mode of the five-color light
*/
typedef enum light_mode {
MODE_NONE = 0,
MODE_RGB = 1,
MODE_HSV = 2,
MODE_CTB = 3,
MODE_ON = 4,
MODE_OFF = 5,
MODE_HUE_INCREASE = 4,
MODE_HUE_DECREASE = 5,
MODE_WARM_INCREASE = 6,
MODE_WARM_DECREASE = 7,
MODE_BRIGHTNESS_INCREASE = 8,
MODE_BRIGHTNESS_DECREASE = 9,
MODE_HSL = 10,
} light_mode_t;
/**
* @brief Light driven configuration
*/
typedef struct {
gpio_num_t gpio_red; /**< Red corresponds to GPIO */
gpio_num_t gpio_green; /**< Green corresponds to GPIO */
gpio_num_t gpio_blue; /**< Blue corresponds to GPIO */
gpio_num_t gpio_cold; /**< Cool corresponds to GPIO */
gpio_num_t gpio_warm; /**< Warm corresponds to GPIO */
uint32_t fade_period_ms; /**< The time from the current color to the next color */
uint32_t blink_period_ms; /**< Period of flashing lights */
} light_driver_config_t;
/**
* @brief Light initialize
*
* @param config [description]
*
* @return
* - ESP_OK
* - ESP_ERR_INVALID_ARG
*/
esp_err_t light_driver_init(light_driver_config_t *config);
/**
* @brief Light deinitialize
*
* @return
* - ESP_OK
* - ESP_ERR_INVALID_ARG
*/
esp_err_t light_driver_deinit(void);
/**
* @brief Set the fade time of the light
*
* @param fade_period_ms The time from the current color to the next color
* @param blink_period_ms Light flashing frequency
*
* @return
* - ESP_OK
* - ESP_FAIL
*/
esp_err_t light_driver_config(uint32_t fade_period_ms, uint32_t blink_period_ms);
/**@{*/
/**
* @brief Set the status of the light
*
*
* @return
* - ESP_OK
* - ESP_ERR_INVALID_ARG
*/
esp_err_t light_driver_set_hue(uint16_t hue);
esp_err_t light_driver_set_saturation(uint8_t saturation);
esp_err_t light_driver_set_value(uint8_t value);
esp_err_t light_driver_set_color_temperature(uint8_t color_temperature);
esp_err_t light_driver_set_brightness(uint8_t brightness);
esp_err_t light_driver_set_hsv(uint16_t hue, uint8_t saturation, uint8_t value);
esp_err_t light_driver_set_hsl(uint16_t hue, uint8_t saturation, uint8_t lightness);
esp_err_t light_driver_set_lightness(uint8_t lightness);
esp_err_t light_driver_set_ctb(uint8_t color_temperature, uint8_t brightness);
esp_err_t light_driver_set_switch(bool status);
esp_err_t light_driver_set_mode(light_mode_t mode);
/**@}*/
/**@{*/
/**
* @brief Set the status of the light
*/
uint16_t light_driver_get_hue(void);
uint8_t light_driver_get_saturation(void);
uint8_t light_driver_get_value(void);
esp_err_t light_driver_get_hsv(uint16_t *hue, uint8_t *saturation, uint8_t *value);
uint8_t light_driver_get_lightness(void);
esp_err_t light_driver_get_hsl(uint16_t *hue, uint8_t *saturation, uint8_t *lightness);
uint8_t light_driver_get_color_temperature(void);
uint8_t light_driver_get_brightness(void);
esp_err_t light_driver_get_ctb(uint8_t *color_temperature, uint8_t *brightness);
bool light_driver_get_switch(void);
uint8_t light_driver_get_mode(void);
/**@}*/
/**@{*/
/**
* @brief Used to indicate the operating mode, such as configuring the network mode, upgrading mode
*
* @note The state of the light is not saved in nvs
*
* @return
* - ESP_OK
* - ESP_ERR_INVALID_ARG
*/
esp_err_t light_driver_set_rgb(uint8_t red, uint8_t green, uint8_t blue);
esp_err_t light_driver_breath_start(uint8_t red, uint8_t green, uint8_t blue);
esp_err_t light_driver_breath_stop(void);
esp_err_t light_driver_blink_start(uint8_t red, uint8_t green, uint8_t blue);
esp_err_t light_driver_blink_stop(void);
/**@}*/
/**@{*/
/**
* @brief Color gradient
*
* @return
* - ESP_OK
* - ESP_ERR_INVALID_ARG
*/
esp_err_t light_driver_fade_brightness(uint8_t brightness);
esp_err_t light_driver_fade_hue(uint16_t hue);
esp_err_t light_driver_fade_warm(uint8_t color_temperature);
esp_err_t light_driver_fade_stop(void);
/**@}*/
#ifdef __cplusplus
}
#endif
#endif/**< __LIGHT_DRIVER_H__ */

View File

@ -0,0 +1,511 @@
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "errno.h"
#include "math.h"
#include "soc/ledc_reg.h"
#include "soc/timer_group_struct.h"
#include "soc/ledc_struct.h"
#include "driver/timer.h"
#include "driver/ledc.h"
#include "iot_led.h"
#include "esp_log.h"
#define LEDC_FADE_MARGIN (10)
#define LEDC_VALUE_TO_DUTY(value) (value * ((1 << LEDC_TIMER_13_BIT) - 1) / UINT16_MAX)
#define LEDC_DUTY_TO_VALUE(value) (value * UINT16_MAX / ((1 << LEDC_TIMER_13_BIT) - 1) )
#define LEDC_FIXED_Q (8)
#define FLOATINT_2_FIXED(X, Q) ((int)((X)*(0x1U << Q)))
#define FIXED_2_FLOATING(X, Q) ((int)((X)/(0x1U << Q)))
#define GET_FIXED_INTEGER_PART(X, Q) (X >> Q)
#define GET_FIXED_DECIMAL_PART(X, Q) (X & ((0x1U << Q) - 1))
typedef struct {
int cur;
int final;
int step;
int cycle;
size_t num;
} ledc_fade_data_t;
typedef struct {
timer_group_t timer_group;
timer_idx_t timer_id;
} hw_timer_idx_t;
typedef struct {
ledc_fade_data_t fade_data[LEDC_CHANNEL_MAX];
ledc_mode_t speed_mode;
ledc_timer_t timer_num;
hw_timer_idx_t timer_id;
} iot_light_t;
static const char *TAG = "iot_light";
static DRAM_ATTR iot_light_t *g_light_config = NULL;
static DRAM_ATTR uint16_t *g_gamma_table = NULL;
static DRAM_ATTR bool g_hw_timer_started = false;
static DRAM_ATTR timg_dev_t *TG[2] = {&TIMERG0, &TIMERG1};
static IRAM_ATTR esp_err_t _timer_pause(timer_group_t group_num, timer_idx_t timer_num)
{
TG[group_num]->hw_timer[timer_num].config.enable = 0;
return ESP_OK;
}
static void iot_timer_create(hw_timer_idx_t *timer_id, bool auto_reload,
uint32_t timer_interval_ms, void *isr_handle)
{
/* Select and initialize basic parameters of the timer */
timer_config_t config = {0x00};
config.divider = HW_TIMER_DIVIDER;
config.counter_dir = TIMER_COUNT_UP;
config.counter_en = TIMER_PAUSE;
config.alarm_en = TIMER_ALARM_EN;
config.intr_type = TIMER_INTR_LEVEL;
config.auto_reload = auto_reload;
timer_init(timer_id->timer_group, timer_id->timer_id, &config);
/* Timer's counter will initially start from value below.
Also, if auto_reload is set, this value will be automatically reload on alarm */
timer_set_counter_value(timer_id->timer_group, timer_id->timer_id, 0x00000000ULL);
/* Configure the alarm value and the interrupt on alarm. */
timer_set_alarm_value(timer_id->timer_group, timer_id->timer_id, timer_interval_ms * HW_TIMER_SCALE / 1000);
timer_enable_intr(timer_id->timer_group, timer_id->timer_id);
timer_isr_register(timer_id->timer_group, timer_id->timer_id, isr_handle,
(void *) timer_id->timer_id, ESP_INTR_FLAG_IRAM, NULL);
}
static void iot_timer_start(hw_timer_idx_t *timer_id)
{
timer_start(timer_id->timer_group, timer_id->timer_id);
g_hw_timer_started = true;
}
static IRAM_ATTR void iot_timer_stop(hw_timer_idx_t *timer_id)
{
_timer_pause(timer_id->timer_group, timer_id->timer_id);
g_hw_timer_started = false;
}
static IRAM_ATTR esp_err_t iot_ledc_duty_config(ledc_mode_t speed_mode, ledc_channel_t channel, int hpoint_val, int duty_val,
uint32_t duty_direction, uint32_t duty_num, uint32_t duty_cycle, uint32_t duty_scale)
{
if (hpoint_val >= 0) {
LEDC.channel_group[speed_mode].channel[channel].hpoint.hpoint = hpoint_val & LEDC_HPOINT_HSCH1_V;
}
if (duty_val >= 0) {
LEDC.channel_group[speed_mode].channel[channel].duty.duty = duty_val;
}
LEDC.channel_group[speed_mode].channel[channel].conf1.val = ((duty_direction & LEDC_DUTY_INC_HSCH0_V) << LEDC_DUTY_INC_HSCH0_S) |
((duty_num & LEDC_DUTY_NUM_HSCH0_V) << LEDC_DUTY_NUM_HSCH0_S) |
((duty_cycle & LEDC_DUTY_CYCLE_HSCH0_V) << LEDC_DUTY_CYCLE_HSCH0_S) |
((duty_scale & LEDC_DUTY_SCALE_HSCH0_V) << LEDC_DUTY_SCALE_HSCH0_S);
LEDC.channel_group[speed_mode].channel[channel].conf0.sig_out_en = 1;
LEDC.channel_group[speed_mode].channel[channel].conf1.duty_start = 1;
if (speed_mode == LEDC_LOW_SPEED_MODE) {
LEDC.channel_group[speed_mode].channel[channel].conf0.low_speed_update = 1;
}
return ESP_OK;
}
static IRAM_ATTR esp_err_t _iot_set_fade_with_step(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t target_duty, int scale, int cycle_num)
{
uint32_t duty_cur = LEDC.channel_group[speed_mode].channel[channel].duty_rd.duty_read >> 4;
int step_num = 0;
int dir = LEDC_DUTY_DIR_DECREASE;
if (scale > 0) {
if (duty_cur > target_duty) {
step_num = (duty_cur - target_duty) / scale;
step_num = step_num > 1023 ? 1023 : step_num;
scale = (step_num == 1023) ? (duty_cur - target_duty) / step_num : scale;
} else {
dir = LEDC_DUTY_DIR_INCREASE;
step_num = (target_duty - duty_cur) / scale;
step_num = step_num > 1023 ? 1023 : step_num;
scale = (step_num == 1023) ? (target_duty - duty_cur) / step_num : scale;
}
}
if (scale > 0 && step_num > 0) {
iot_ledc_duty_config(speed_mode, channel, -1, duty_cur << 4, dir, step_num, cycle_num, scale);
} else {
iot_ledc_duty_config(speed_mode, channel, -1, target_duty << 4, dir, 0, 1, 0);
}
return ESP_OK;
}
static IRAM_ATTR esp_err_t _iot_set_fade_with_time(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t target_duty, int max_fade_time_ms)
{
uint32_t freq = 0;
uint32_t duty_cur = LEDC.channel_group[speed_mode].channel[channel].duty_rd.duty_read >> 4;
uint32_t duty_delta = target_duty > duty_cur ? target_duty - duty_cur : duty_cur - target_duty;
uint32_t timer_source_clk = LEDC.timer_group[speed_mode].timer[g_light_config->timer_num].conf.tick_sel;
uint32_t duty_resolution = LEDC.timer_group[speed_mode].timer[g_light_config->timer_num].conf.duty_resolution;
uint32_t clock_divider = LEDC.timer_group[speed_mode].timer[g_light_config->timer_num].conf.clock_divider;
uint32_t precision = (0x1U << duty_resolution);
if (timer_source_clk == LEDC_APB_CLK) {
freq = ((uint64_t)LEDC_APB_CLK_HZ << 8) / precision / clock_divider;
} else {
freq = ((uint64_t)LEDC_REF_CLK_HZ << 8) / precision / clock_divider;
}
if (duty_delta == 0) {
return _iot_set_fade_with_step(speed_mode, channel, target_duty, 0, 0);
}
int total_cycles = max_fade_time_ms * freq / 1000;
if (total_cycles == 0) {
return _iot_set_fade_with_step(speed_mode, channel, target_duty, 0, 0);
}
int scale, cycle_num;
if (total_cycles > duty_delta) {
scale = 1;
cycle_num = total_cycles / duty_delta;
if (cycle_num > LEDC_DUTY_NUM_HSCH0_V) {
cycle_num = LEDC_DUTY_NUM_HSCH0_V;
}
} else {
cycle_num = 1;
scale = duty_delta / total_cycles;
if (scale > LEDC_DUTY_SCALE_HSCH0_V) {
scale = LEDC_DUTY_SCALE_HSCH0_V;
}
}
return _iot_set_fade_with_step(speed_mode, channel, target_duty, scale, cycle_num);
}
static IRAM_ATTR esp_err_t _iot_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel)
{
LEDC.channel_group[speed_mode].channel[channel].conf0.sig_out_en = 1;
LEDC.channel_group[speed_mode].channel[channel].conf1.duty_start = 1;
if (speed_mode == LEDC_LOW_SPEED_MODE) {
LEDC.channel_group[speed_mode].channel[channel].conf0.low_speed_update = 1;
}
return ESP_OK;
}
static IRAM_ATTR esp_err_t iot_ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty)
{
return iot_ledc_duty_config(speed_mode,
channel, // uint32_t chan_num,
-1,
duty << 4, // uint32_t duty_val,the least 4 bits are decimal part
1, // uint32_t increase,
1, // uint32_t duty_num,
1, // uint32_t duty_cycle,
0 // uint32_t duty_scale
);
}
static void gamma_table_create(uint16_t *gamma_table, float correction)
{
float value_tmp = 0;
/**
* @brief gamma curve formula: y=a*x^(1/gm)
* x (0,(GAMMA_TABLE_SIZE-1)/GAMMA_TABLE_SIZE)
* a = GAMMA_TABLE_SIZE
*/
for (int i = 0; i < GAMMA_TABLE_SIZE; i++) {
value_tmp = (float)(i) / GAMMA_TABLE_SIZE;
value_tmp = powf(value_tmp, 1.0f / correction);
gamma_table[i] = (uint16_t)FLOATINT_2_FIXED((value_tmp * GAMMA_TABLE_SIZE), LEDC_FIXED_Q);
}
}
static IRAM_ATTR uint32_t gamma_value_to_duty(int value)
{
uint32_t tmp_q = GET_FIXED_INTEGER_PART(value, LEDC_FIXED_Q);
uint32_t tmp_r = GET_FIXED_DECIMAL_PART(value, LEDC_FIXED_Q);
uint16_t cur = LEDC_VALUE_TO_DUTY(g_gamma_table[tmp_q]);
uint16_t next = LEDC_VALUE_TO_DUTY(g_gamma_table[tmp_q + 1]);
return (cur + (next - cur) * tmp_r / (0x1U << LEDC_FIXED_Q));
}
static IRAM_ATTR void fade_timercb(void *para)
{
int timer_idx = (int) para;
int idle_channel_num = 0;
if (HW_TIMER_GROUP == TIMER_GROUP_0) {
/* Retrieve the interrupt status */
uint32_t intr_status = TIMERG0.int_st_timers.val;
TIMERG0.hw_timer[timer_idx].update = 1;
/* Clear the interrupt */
if ((intr_status & BIT(timer_idx)) && timer_idx == TIMER_0) {
TIMERG0.int_clr_timers.t0 = 1;
} else if ((intr_status & BIT(timer_idx)) && timer_idx == TIMER_1) {
TIMERG0.int_clr_timers.t1 = 1;
}
/* After the alarm has been triggered
we need enable it again, so it is triggered the next time */
TIMERG0.hw_timer[timer_idx].config.alarm_en = TIMER_ALARM_EN;
} else if (HW_TIMER_GROUP == TIMER_GROUP_1) {
uint32_t intr_status = TIMERG1.int_st_timers.val;
TIMERG1.hw_timer[timer_idx].update = 1;
if ((intr_status & BIT(timer_idx)) && timer_idx == TIMER_0) {
TIMERG1.int_clr_timers.t0 = 1;
} else if ((intr_status & BIT(timer_idx)) && timer_idx == TIMER_1) {
TIMERG1.int_clr_timers.t1 = 1;
}
TIMERG1.hw_timer[timer_idx].config.alarm_en = TIMER_ALARM_EN;
}
for (int channel = 0; channel < LEDC_CHANNEL_MAX; channel++) {
ledc_fade_data_t *fade_data = g_light_config->fade_data + channel;
if (fade_data->num > 0) {
fade_data->num--;
if (fade_data->step) {
fade_data->cur += fade_data->step;
if (fade_data->num != 0) {
_iot_set_fade_with_time(g_light_config->speed_mode, channel,
gamma_value_to_duty(fade_data->cur),
DUTY_SET_CYCLE - LEDC_FADE_MARGIN);
} else {
iot_ledc_set_duty(g_light_config->speed_mode, channel, gamma_value_to_duty(fade_data->cur));
}
_iot_update_duty(g_light_config->speed_mode, channel);
} else {
iot_ledc_set_duty(g_light_config->speed_mode,channel,gamma_value_to_duty(fade_data->cur));
_iot_update_duty(g_light_config->speed_mode, channel);
}
} else if (fade_data->cycle) {
fade_data->num = fade_data->cycle - 1;
if (fade_data->step) {
fade_data->step *= -1;
fade_data->cur += fade_data->step;
} else {
fade_data->cur = (fade_data->cur == fade_data->final) ? 0 : fade_data->final;
}
_iot_set_fade_with_time(g_light_config->speed_mode, channel,
gamma_value_to_duty(fade_data->cur),
DUTY_SET_CYCLE - LEDC_FADE_MARGIN);
_iot_update_duty(g_light_config->speed_mode, channel);
} else {
idle_channel_num++;
}
}
if (idle_channel_num >= LEDC_CHANNEL_MAX) {
iot_timer_stop(&g_light_config->timer_id);
}
}
esp_err_t iot_led_init(ledc_timer_t timer_num, ledc_mode_t speed_mode, uint32_t freq_hz)
{
esp_err_t ret = ESP_OK;
const ledc_timer_config_t ledc_time_config = {
.speed_mode = speed_mode,
.timer_num = timer_num,
.freq_hz = freq_hz,
.duty_resolution = LEDC_TIMER_13_BIT,
};
ret = ledc_timer_config(&ledc_time_config);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "LEDC timer configuration");
return ret;
}
if (g_gamma_table == NULL) {
g_gamma_table = calloc(GAMMA_TABLE_SIZE, sizeof(uint16_t));
gamma_table_create(g_gamma_table, GAMMA_CORRECTION);
} else {
ESP_LOGE(TAG, "gamma_table has been initialized");
}
if (g_light_config == NULL) {
g_light_config = calloc(1, sizeof(iot_light_t));
g_light_config->timer_num = timer_num;
g_light_config->speed_mode = speed_mode;
hw_timer_idx_t hw_timer = {
.timer_group = HW_TIMER_GROUP,
.timer_id = HW_TIMER_ID,
};
g_light_config->timer_id = hw_timer;
iot_timer_create(&hw_timer, 1, DUTY_SET_CYCLE, fade_timercb);
} else {
ESP_LOGE(TAG, "g_light_config has been initialized");
}
return ESP_OK;
}
esp_err_t iot_led_deinit(void)
{
if (g_gamma_table) {
free(g_gamma_table);
}
if (g_light_config) {
free(g_light_config);
}
timer_disable_intr(g_light_config->timer_id.timer_group, g_light_config->timer_id.timer_id);
return ESP_OK;
}
esp_err_t iot_led_regist_channel(ledc_channel_t channel, gpio_num_t gpio_num)
{
esp_err_t ret = ESP_OK;
if (g_light_config == NULL) {
ESP_LOGW(TAG, "iot_led_init() must be called first");
return ESP_ERR_NOT_FOUND;
}
#ifdef CONFIG_SPIRAM_SUPPORT
if (gpio_num != GPIO_NUM_16 || gpio_num != GPIO_NUM_17) {
ESP_LOGW(TAG, "gpio_num must not conflict to PSRAM(IO16 && IO17)");
return ESP_ERR_INVALID_ARG;
}
#endif
const ledc_channel_config_t ledc_ch_config = {
.gpio_num = gpio_num,
.channel = channel,
.intr_type = LEDC_INTR_DISABLE,
.speed_mode = g_light_config->speed_mode,
.timer_sel = g_light_config->timer_num,
};
ret = ledc_channel_config(&ledc_ch_config);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "LEDC channel configuration");
return ret;
}
return ESP_OK;
}
esp_err_t iot_led_get_channel(ledc_channel_t channel, uint8_t *dst)
{
if (g_light_config == NULL || dst == NULL) {
ESP_LOGW(TAG, "iot_led_init() must be called first or dst should not be NULL");
return ESP_ERR_INVALID_ARG;
}
int cur = g_light_config->fade_data[channel].cur;
*dst = FIXED_2_FLOATING(cur, LEDC_FIXED_Q);
return ESP_OK;
}
esp_err_t iot_led_set_channel(ledc_channel_t channel, uint8_t value, uint32_t fade_ms)
{
if (g_light_config == NULL) {
ESP_LOGW(TAG, "iot_led_init() must be called first");
return ESP_ERR_INVALID_ARG;
}
ledc_fade_data_t *fade_data = g_light_config->fade_data + channel;
fade_data->final = FLOATINT_2_FIXED(value, LEDC_FIXED_Q);
if (fade_ms < DUTY_SET_CYCLE) {
fade_data->num = 1;
} else {
fade_data->num = fade_ms / DUTY_SET_CYCLE;
}
fade_data->step = abs(fade_data->cur - fade_data->final) / fade_data->num;
if (fade_data->cur > fade_data->final) {
fade_data->step *= -1;
}
if (fade_data->cycle != 0){
fade_data->cycle = 0;
}
if (g_hw_timer_started != true) {
iot_timer_start(&g_light_config->timer_id);
}
return ESP_OK;
}
esp_err_t iot_led_start_blink(ledc_channel_t channel, uint8_t value, uint32_t period_ms, bool fade_flag)
{
if (g_light_config == NULL) {
ESP_LOGW(TAG, "iot_led_init() must be called first");
return ESP_ERR_INVALID_ARG;
}
ledc_fade_data_t *fade_data = g_light_config->fade_data + channel;
fade_data->final = fade_data->cur = FLOATINT_2_FIXED(value, LEDC_FIXED_Q);
fade_data->cycle = period_ms / 2 / DUTY_SET_CYCLE;
fade_data->num = (fade_flag) ? period_ms / 2 / DUTY_SET_CYCLE : 0;
fade_data->step = (fade_flag) ? fade_data->cur / fade_data->num * -1 : 0;
if (g_hw_timer_started != true) {
iot_timer_start(&g_light_config->timer_id);
}
return ESP_OK;
}
esp_err_t iot_led_stop_blink(ledc_channel_t channel)
{
if (g_light_config == NULL) {
ESP_LOGW(TAG, "iot_led_init() must be called first");
return ESP_ERR_INVALID_ARG;
}
ledc_fade_data_t *fade_data = g_light_config->fade_data + channel;
fade_data->cycle = fade_data->num = 0;
return ESP_OK;
}
esp_err_t iot_led_set_gamma_table(const uint16_t gamma_table[GAMMA_TABLE_SIZE])
{
if (g_gamma_table == NULL) {
ESP_LOGW(TAG, "iot_led_init() must be called first");
return ESP_ERR_INVALID_ARG;
}
memcpy(g_gamma_table, gamma_table, GAMMA_TABLE_SIZE * sizeof(uint16_t));
return ESP_OK;
}

View File

@ -0,0 +1,518 @@
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include "math.h"
#include "sys/time.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "esp_log.h"
#include "soc/ledc_reg.h"
#include "soc/timer_group_struct.h"
#include "soc/ledc_struct.h"
#include "driver/periph_ctrl.h"
#include "driver/timer.h"
#include "driver/ledc.h"
#include "iot_light.h"
static const char *TAG = "light";
#define IOT_CHECK(tag, a, ret) if(!(a)) { \
return (ret); \
}
#define ERR_ASSERT(tag, param, ret) IOT_CHECK(tag, (param) == ESP_OK, ret)
#define POINT_ASSERT(tag, param) IOT_CHECK(tag, (param) != NULL, ESP_FAIL)
#define LIGHT_NUM_MAX 4
typedef enum {
LIGHT_CH_NUM_1 = 1, /*!< Light channel number */
LIGHT_CH_NUM_2 = 2, /*!< Light channel number */
LIGHT_CH_NUM_3 = 3, /*!< Light channel number */
LIGHT_CH_NUM_4 = 4, /*!< Light channel number */
LIGHT_CH_NUM_5 = 5, /*!< Light channel number */
LIGHT_CH_NUM_MAX, /*!< user shouldn't use this */
} light_channel_num_t;
typedef struct {
timer_group_t timer_group;
timer_idx_t timer_id;
} hw_timer_idx_t;
typedef struct {
gpio_num_t io_num;
ledc_mode_t mode;
ledc_channel_t channel;
int breath_period;
int blink_period;
uint32_t fade_step_num;
uint32_t *fade_duty_step;
int fade_duty_counter;
bool fade_step_up;
bool fade_once;
bool fade_start;
} light_channel_t;
typedef struct {
uint8_t channel_num;
ledc_mode_t mode;
ledc_timer_t ledc_timer;
uint32_t full_duty;
uint32_t freq_hz;
ledc_timer_bit_t timer_bit;
hw_timer_idx_t hw_timer;
light_channel_t *channel_group[0];
} light_t;
static bool g_fade_installed = false;
static bool g_hw_timer_started = false;
static light_t *g_light_group[LIGHT_NUM_MAX] = {NULL};
static esp_err_t iot_light_duty_set(light_handle_t light_handle, uint8_t channel_id, uint32_t duty);
static void iot_timer_create(hw_timer_idx_t *timer_id, bool auto_reload, double timer_interval_sec, timer_isr_handle_t *isr_handle)
{
/* Select and initialize basic parameters of the timer */
timer_config_t config;
config.divider = HW_TIMER_DIVIDER;
config.counter_dir = TIMER_COUNT_UP;
config.counter_en = TIMER_PAUSE;
config.alarm_en = TIMER_ALARM_EN;
config.intr_type = TIMER_INTR_LEVEL;
config.auto_reload = auto_reload;
timer_init(timer_id->timer_group, timer_id->timer_id, &config);
/* Timer's counter will initially start from value below.
Also, if auto_reload is set, this value will be automatically reload on alarm */
timer_set_counter_value(timer_id->timer_group, timer_id->timer_id, 0x00000000ULL);
/* Configure the alarm value and the interrupt on alarm. */
timer_set_alarm_value(timer_id->timer_group, timer_id->timer_id, timer_interval_sec * HW_TIMER_SCALE);
timer_enable_intr(timer_id->timer_group, timer_id->timer_id);
timer_isr_register(timer_id->timer_group, timer_id->timer_id, (void *)isr_handle,
(void *) timer_id->timer_id, ESP_INTR_FLAG_IRAM, NULL);
}
static void iot_timer_start(hw_timer_idx_t *timer_id)
{
timer_start(timer_id->timer_group, timer_id->timer_id);
g_hw_timer_started = true;
}
static void iot_timer_stop(hw_timer_idx_t *timer_id)
{
timer_disable_intr(timer_id->timer_group, timer_id->timer_id);
timer_pause(timer_id->timer_group, timer_id->timer_id);
g_hw_timer_started = false;
}
static IRAM_ATTR void iot_ledc_ls_channel_update(ledc_mode_t speed_mode, ledc_channel_t channel_num)
{
if (speed_mode == LEDC_LOW_SPEED_MODE) {
LEDC.channel_group[speed_mode].channel[channel_num].conf0.low_speed_update = 1;
}
}
static IRAM_ATTR esp_err_t iot_ledc_duty_config(ledc_mode_t speed_mode, ledc_channel_t channel_num, int hpoint_val, int duty_val,
uint32_t duty_direction, uint32_t duty_num, uint32_t duty_cycle, uint32_t duty_scale)
{
if (hpoint_val >= 0) {
LEDC.channel_group[speed_mode].channel[channel_num].hpoint.hpoint = hpoint_val & LEDC_HPOINT_HSCH1_V;
}
if (duty_val >= 0) {
LEDC.channel_group[speed_mode].channel[channel_num].duty.duty = duty_val;
}
LEDC.channel_group[speed_mode].channel[channel_num].conf1.val = ((duty_direction & LEDC_DUTY_INC_HSCH0_V) << LEDC_DUTY_INC_HSCH0_S) |
((duty_num & LEDC_DUTY_NUM_HSCH0_V) << LEDC_DUTY_NUM_HSCH0_S) |
((duty_cycle & LEDC_DUTY_CYCLE_HSCH0_V) << LEDC_DUTY_CYCLE_HSCH0_S) |
((duty_scale & LEDC_DUTY_SCALE_HSCH0_V) << LEDC_DUTY_SCALE_HSCH0_S);
iot_ledc_ls_channel_update(speed_mode, channel_num);
return ESP_OK;
}
static IRAM_ATTR esp_err_t iot_ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty)
{
return iot_ledc_duty_config(speed_mode,
channel, //uint32_t chan_num,
-1,
duty << 4, //uint32_t duty_val,the least 4 bits are decimal part
1, //uint32_t increase,
1, //uint32_t duty_num,
1, //uint32_t duty_cycle,
0 //uint32_t duty_scale
);
}
static IRAM_ATTR esp_err_t iot_ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel)
{
LEDC.channel_group[speed_mode].channel[channel].conf0.sig_out_en = 1;
LEDC.channel_group[speed_mode].channel[channel].conf1.duty_start = 1;
iot_ledc_ls_channel_update(speed_mode, channel);
return ESP_OK;
}
static IRAM_ATTR void breath_timer_callback(void *para)
{
int timer_idx = (int) para;
if (HW_TIMER_GROUP == TIMER_GROUP_0) {
/* Retrieve the interrupt status */
uint32_t intr_status = TIMERG0.int_st_timers.val;
TIMERG0.hw_timer[timer_idx].update = 1;
/* Clear the interrupt */
if ((intr_status & BIT(timer_idx)) && timer_idx == TIMER_0) {
TIMERG0.int_clr_timers.t0 = 1;
} else if ((intr_status & BIT(timer_idx)) && timer_idx == TIMER_1) {
TIMERG0.int_clr_timers.t1 = 1;
}
/* After the alarm has been triggered
we need enable it again, so it is triggered the next time */
TIMERG0.hw_timer[timer_idx].config.alarm_en = TIMER_ALARM_EN;
} else if (HW_TIMER_GROUP == TIMER_GROUP_1) {
uint32_t intr_status = TIMERG1.int_st_timers.val;
TIMERG1.hw_timer[timer_idx].update = 1;
if ((intr_status & BIT(timer_idx)) && timer_idx == TIMER_0) {
TIMERG1.int_clr_timers.t0 = 1;
} else if ((intr_status & BIT(timer_idx)) && timer_idx == TIMER_1) {
TIMERG1.int_clr_timers.t1 = 1;
}
TIMERG1.hw_timer[timer_idx].config.alarm_en = TIMER_ALARM_EN;
}
for (int i = 0; i < LIGHT_NUM_MAX; i++) {
if (g_light_group[i] != NULL) {
light_t *light = g_light_group[i];
for (int j = 0; j < light->channel_num; j++) {
light_channel_t *l_chn = light->channel_group[j];
if (l_chn->fade_start == true) {
if (l_chn->fade_step_up == true) {
l_chn->fade_duty_counter++;
} else {
l_chn->fade_duty_counter--;
}
if ((l_chn->fade_duty_counter >= 0) && (l_chn->fade_duty_counter < l_chn->fade_step_num)) {
iot_ledc_set_duty(l_chn->mode, l_chn->channel, l_chn->fade_duty_step[l_chn->fade_duty_counter]);
iot_ledc_update_duty(l_chn->mode, l_chn->channel);
} else {
if (l_chn->fade_once != true) {
l_chn->fade_step_up = 1 - l_chn->fade_step_up;
} else {
l_chn->fade_start = false;
}
}
}
}
}
}
}
static light_channel_t *light_channel_create(gpio_num_t io_num, ledc_channel_t channel, ledc_mode_t mode, ledc_timer_t timer)
{
ledc_channel_config_t ledc_channel = {
.channel = channel,
.duty = 0,
.gpio_num = io_num,
.intr_type = LEDC_INTR_FADE_END,
.speed_mode = mode,
.timer_sel = timer
};
ERR_ASSERT(TAG, ledc_channel_config(&ledc_channel), NULL);
light_channel_t *pwm = (light_channel_t *)calloc(1, sizeof(light_channel_t));
pwm->io_num = io_num;
pwm->channel = channel;
pwm->mode = mode;
pwm->breath_period = 0;
pwm->blink_period = 0;
pwm->fade_step_num = 0;
pwm->fade_duty_step = NULL;
pwm->fade_duty_counter = 0;
pwm->fade_step_up = true;
pwm->fade_once = false;
pwm->fade_start = false;
return pwm;
}
static esp_err_t light_channel_delete(light_channel_t *light_channel)
{
POINT_ASSERT(TAG, light_channel);
if (light_channel->fade_duty_step != NULL) {
heap_caps_free(light_channel->fade_duty_step);
light_channel->fade_duty_step = NULL;
}
free(light_channel);
return ESP_OK;
}
static esp_err_t light_channel_fade_clear(light_channel_t *light_channel)
{
light_channel->fade_start = false;
light_channel->fade_step_num = 0;
light_channel->fade_duty_counter = 0;
light_channel->fade_step_up = true;
light_channel->fade_once = false;
if (light_channel->fade_duty_step != NULL) {
heap_caps_free(light_channel->fade_duty_step);
light_channel->fade_duty_step = NULL;
}
return ESP_OK;
}
light_handle_t iot_light_create(ledc_timer_t timer, ledc_mode_t speed_mode, uint32_t freq_hz, uint8_t channel_num, ledc_timer_bit_t timer_bit)
{
IOT_CHECK(TAG, channel_num != 0, NULL);
ledc_timer_config_t timer_conf = {
.timer_num = timer,
.speed_mode = speed_mode,
.freq_hz = freq_hz,
.duty_resolution = timer_bit
};
ERR_ASSERT(TAG, ledc_timer_config(&timer_conf), NULL);
light_t *light_ptr = (light_t *)calloc(1, sizeof(light_t) + sizeof(light_channel_t *) * channel_num);
light_ptr->channel_num = channel_num;
light_ptr->ledc_timer = timer;
light_ptr->full_duty = (1 << timer_bit) - 1;
light_ptr->freq_hz = freq_hz;
light_ptr->mode = speed_mode;
light_ptr->timer_bit = timer_bit;
light_ptr->hw_timer.timer_group = HW_TIMER_GROUP;
light_ptr->hw_timer.timer_id = HW_TIMER_ID;
if (g_hw_timer_started == false) {
iot_timer_create(&(light_ptr->hw_timer), 1, (double)DUTY_SET_CYCLE / 1000, (void *)breath_timer_callback);
iot_timer_start(&(light_ptr->hw_timer));
}
for (int i = 0; i < channel_num; i++) {
light_ptr->channel_group[i] = NULL;
}
for (int i = 0; i < LIGHT_NUM_MAX; i++) {
if (g_light_group[i] == NULL) {
g_light_group[i] = light_ptr;
break;
}
}
return (light_handle_t)light_ptr;
}
esp_err_t iot_light_delete(light_handle_t light_handle)
{
light_t *light = (light_t *)light_handle;
POINT_ASSERT(TAG, light_handle);
for (int i = 0; i < light->channel_num; i++) {
if (light->channel_group[i] != NULL) {
iot_light_duty_set(light, i, 0);
light_channel_delete(light->channel_group[i]);
}
}
for (int i = 0; i < LIGHT_NUM_MAX; i++) {
if (g_light_group[i] == light) {
g_light_group[i] = NULL;
break;
}
}
for (int i = 0; i < LIGHT_NUM_MAX; i++) {
if (g_light_group[i] != NULL) {
goto FREE_MEM;
}
}
ledc_fade_func_uninstall();
g_fade_installed = false;
iot_timer_stop(&(light->hw_timer));
FREE_MEM:
free(light_handle);
return ESP_OK;
}
esp_err_t iot_light_channel_regist(light_handle_t light_handle, uint8_t channel_idx, gpio_num_t io_num, ledc_channel_t channel)
{
light_t *light = (light_t *)light_handle;
POINT_ASSERT(TAG, light_handle);
IOT_CHECK(TAG, channel_idx < light->channel_num, FAIL);
if (light->channel_group[channel_idx] != NULL) {
ESP_LOGE(TAG, "this channel index has been registered");
return ESP_FAIL;
}
light->channel_group[channel_idx] = light_channel_create(io_num, channel, light->mode, light->ledc_timer);
if (g_fade_installed == false) {
ledc_fade_func_install(0);
g_fade_installed = true;
}
return ESP_OK;
}
static esp_err_t iot_light_duty_set(light_handle_t light_handle, uint8_t channel_id, uint32_t duty)
{
light_t *light = (light_t *)light_handle;
POINT_ASSERT(TAG, light_handle);
IOT_CHECK(TAG, channel_id < light->channel_num, ESP_FAIL);
POINT_ASSERT(TAG, light->channel_group[channel_id]);
light_channel_t *l_chn = light->channel_group[channel_id];
light_channel_fade_clear(l_chn);
iot_ledc_set_duty(l_chn->mode, l_chn->channel, duty);
iot_ledc_update_duty(l_chn->mode, l_chn->channel);
return ESP_OK;
}
uint32_t iot_light_duty_get(light_handle_t light_handle, uint8_t channel_id)
{
light_t *light = (light_t *)light_handle;
POINT_ASSERT(TAG, light_handle);
IOT_CHECK(TAG, channel_id < light->channel_num, ESP_FAIL);
POINT_ASSERT(TAG, light->channel_group[channel_id]);
light_channel_t *l_chn = light->channel_group[channel_id];
return ledc_get_duty(l_chn->mode, l_chn->channel);
}
esp_err_t iot_light_fade_with_time(light_handle_t light_handle, uint8_t channel_id, uint32_t duty, uint32_t fade_period_ms)
{
light_t *light = (light_t *)light_handle;
POINT_ASSERT(TAG, light_handle);
IOT_CHECK(TAG, channel_id < light->channel_num, ESP_FAIL);
POINT_ASSERT(TAG, light->channel_group[channel_id]);
light_channel_t *l_chn = light->channel_group[channel_id];
if (fade_period_ms == 0) {
return iot_light_duty_set(light, channel_id, duty);
}
light_channel_fade_clear(l_chn);
l_chn->fade_step_num = fade_period_ms / DUTY_SET_CYCLE + 1;
l_chn->fade_duty_step = (uint32_t *)heap_caps_malloc(l_chn->fade_step_num * sizeof(uint32_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
uint32_t duty_cur = iot_light_duty_get(light, channel_id);
int duty_delta = duty - duty_cur;
if (duty_delta > 0) {
float coe = (duty_delta / pow((double)l_chn->fade_step_num, (double)1 / DUTY_SET_GAMMA));
for (int i = 0; i < l_chn->fade_step_num; i++) {
l_chn->fade_duty_step[i] = duty_cur + (int)(coe * pow((double)i, (double)1 / DUTY_SET_GAMMA));
}
l_chn->fade_step_up = true;
} else {
duty_delta = 0 - duty_delta;
float coe = (duty_delta / pow((double)l_chn->fade_step_num, (double)1 / DUTY_SET_GAMMA));
for (int i = 0; i < l_chn->fade_step_num; i++) {
l_chn->fade_duty_step[i] = duty + (int)(coe * pow((double)i, (double)1 / DUTY_SET_GAMMA));
}
l_chn->fade_duty_counter = l_chn->fade_step_num;
l_chn->fade_step_up = false;
}
l_chn->fade_once = true;
l_chn->fade_start = true;
return ESP_OK;
}
esp_err_t iot_light_breath_config(light_handle_t light_handle, uint8_t channel_id, uint32_t duty, uint32_t breath_period_ms)
{
light_t *light = (light_t *)light_handle;
POINT_ASSERT(TAG, light_handle);
IOT_CHECK(TAG, channel_id < light->channel_num, ESP_FAIL);
POINT_ASSERT(TAG, light->channel_group[channel_id]);
light_channel_t *l_chn = light->channel_group[channel_id];
light_channel_fade_clear(l_chn);
/**
* @brief control light with nonlinearity fade, the duty and the fade step
* conform to gamma curve. gamma curve formula: y=a*x^(1/gm) firstly,
* use the target duty and step number to calculate coefficient `a`,
* secondly, calculate the duty for every step
*/
l_chn->fade_step_num = (breath_period_ms / 2) / DUTY_SET_CYCLE + 1;
float coe = (duty / pow((double)l_chn->fade_step_num, (double)1 / DUTY_SET_GAMMA));
l_chn->fade_duty_step = (uint32_t *)heap_caps_malloc(l_chn->fade_step_num * sizeof(uint32_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
for (int i = 0; i < l_chn->fade_step_num; i++) {
l_chn->fade_duty_step[i] = (int)(coe * pow((double)i, (double)1 / DUTY_SET_GAMMA));
}
return ESP_OK;
}
esp_err_t iot_light_blink_config(light_handle_t light_handle, uint8_t channel_id, uint32_t blink_period_ms)
{
light_t *light = (light_t *)light_handle;
POINT_ASSERT(TAG, light_handle);
IOT_CHECK(TAG, channel_id < light->channel_num, ESP_FAIL);
light_channel_t *l_chn = light->channel_group[channel_id];
light_channel_fade_clear(l_chn);
l_chn->fade_step_num = (blink_period_ms / 2) / DUTY_SET_CYCLE;
l_chn->fade_duty_step = (uint32_t *)heap_caps_malloc(l_chn->fade_step_num * sizeof(uint32_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
int i = 0;
for (i = 0; i < (l_chn->fade_step_num / 2); i++) {
l_chn->fade_duty_step[i] = 0;
}
for (; i < l_chn->fade_step_num; i++) {
l_chn->fade_duty_step[i] = light->full_duty;
}
return ESP_OK;
}
esp_err_t iot_light_operate_start(light_handle_t light_handle, uint8_t channel_id)
{
light_t *light = (light_t *)light_handle;
POINT_ASSERT(TAG, light_handle);
IOT_CHECK(TAG, channel_id < light->channel_num, ESP_FAIL);
light->channel_group[channel_id]->fade_start = true;
return ESP_OK;
}
esp_err_t iot_light_operate_stop(light_handle_t light_handle, uint8_t channel_id)
{
light_t *light = (light_t *)light_handle;
POINT_ASSERT(TAG, light_handle);
IOT_CHECK(TAG, channel_id < light->channel_num, ESP_FAIL);
light->channel_group[channel_id]->fade_start = false;
// iot_light_duty_set(light, channel_id, 0);
return ESP_OK;
}

View File

@ -0,0 +1,895 @@
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "errno.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/event_groups.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_partition.h"
#include "lwip/sockets.h"
#include "lwip/netdb.h"
#include "lwip/sockets.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "driver/i2c.h"
#include "sys/param.h"
#include "driver/gpio.h"
#include "light_driver.h"
#include "ble_mesh_example_nvs.h"
/**
* @brief The state of the five-color light
*/
typedef struct {
uint8_t mode;
uint8_t on;
uint16_t hue;
uint8_t saturation;
uint8_t value;
uint8_t lightness;
uint8_t color_temperature;
uint8_t brightness;
uint32_t fade_period_ms;
uint32_t blink_period_ms;
} light_status_t;
/**
* @brief The channel of the five-color light
*/
enum light_channel {
CHANNEL_ID_RED = 0,
CHANNEL_ID_GREEN,
CHANNEL_ID_BLUE,
CHANNEL_ID_WARM,
CHANNEL_ID_COLD,
};
#define LIGHT_FADE_PERIOD_MAX_MS (3 * 1000)
static const char *TAG = "light_driver";
static light_status_t g_light_status = {0};
static bool g_light_blink_flag = false;
static TimerHandle_t g_fade_timer = NULL;
static int g_fade_mode = MODE_NONE;
static uint16_t g_fade_hue = 0;
extern nvs_handle_t NVS_HANDLE;
esp_err_t light_driver_init(light_driver_config_t *config)
{
LIGHT_PARAM_CHECK(config);
bool exist = false;
if (ble_mesh_nvs_restore(NVS_HANDLE, LIGHT_STATUS_STORE_KEY, &g_light_status, sizeof(light_status_t), &exist) != ESP_OK) {
memset(&g_light_status, 0, sizeof(light_status_t));
g_light_status.mode = MODE_HSV;
g_light_status.on = 1;
g_light_status.hue = 360;
g_light_status.saturation = 0;
g_light_status.value = 100;
g_light_status.lightness = 100;
g_light_status.color_temperature = 0;
g_light_status.brightness = 30;
}
iot_led_init(LEDC_TIMER_0, LEDC_HIGH_SPEED_MODE, 1000);
g_light_status.fade_period_ms = config->fade_period_ms;
g_light_status.blink_period_ms = config->blink_period_ms;
iot_led_regist_channel(CHANNEL_ID_RED, config->gpio_red);
iot_led_regist_channel(CHANNEL_ID_GREEN, config->gpio_green);
iot_led_regist_channel(CHANNEL_ID_BLUE, config->gpio_blue);
iot_led_regist_channel(CHANNEL_ID_WARM, config->gpio_warm);
iot_led_regist_channel(CHANNEL_ID_COLD, config->gpio_cold);
ESP_LOGI(TAG, "hue: %d, saturation: %d, value: %d, lightness: %d",
g_light_status.hue, g_light_status.saturation, g_light_status.value, g_light_status.lightness);
ESP_LOGI(TAG, "brightness: %d, color_temperature: %d",
g_light_status.brightness, g_light_status.color_temperature);
return ESP_OK;
}
esp_err_t light_driver_deinit(void)
{
esp_err_t ret = ESP_OK;
iot_led_deinit();
return ret;
}
esp_err_t light_driver_config(uint32_t fade_period_ms, uint32_t blink_period_ms)
{
g_light_status.fade_period_ms = fade_period_ms;
g_light_status.blink_period_ms = blink_period_ms;
return ESP_OK;
}
esp_err_t light_driver_set_rgb(uint8_t red, uint8_t green, uint8_t blue)
{
esp_err_t ret = 0;
ret = iot_led_set_channel(CHANNEL_ID_RED, red, 0);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_GREEN, green, 0);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_BLUE, blue, 0);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_WARM, 0, 0);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_COLD, 0, 0);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
return ESP_OK;
}
static esp_err_t light_driver_hsv2rgb(uint16_t hue, uint8_t saturation, uint8_t value,
uint8_t *red, uint8_t *green, uint8_t *blue)
{
uint16_t hi = (hue / 60) % 6;
uint16_t F = 100 * hue / 60 - 100 * hi;
uint16_t P = value * (100 - saturation) / 100;
uint16_t Q = value * (10000 - F * saturation) / 10000;
uint16_t T = value * (10000 - saturation * (100 - F)) / 10000;
switch (hi) {
case 0:
*red = value;
*green = T;
*blue = P;
break;
case 1:
*red = Q;
*green = value;
*blue = P;
break;
case 2:
*red = P;
*green = value;
*blue = T;
break;
case 3:
*red = P;
*green = Q;
*blue = value;
break;
case 4:
*red = T;
*green = P;
*blue = value;
break;
case 5:
*red = value;
*green = P;
*blue = Q;
break;
default:
return ESP_FAIL;
}
*red = *red * 255 / 100;
*green = *green * 255 / 100;
*blue = *blue * 255 / 100;
return ESP_OK;
}
static void light_driver_rgb2hsv(uint16_t red, uint16_t green, uint16_t blue,
uint16_t *h, uint8_t *s, uint8_t *v)
{
double hue, saturation, value;
double m_max = MAX(red, MAX(green, blue));
double m_min = MIN(red, MIN(green, blue));
double m_delta = m_max - m_min;
value = m_max / 255.0;
if (m_delta == 0) {
hue = 0;
saturation = 0;
} else {
saturation = m_delta / m_max;
if (red == m_max) {
hue = (green - blue) / m_delta;
} else if (green == m_max) {
hue = 2 + (blue - red) / m_delta;
} else {
hue = 4 + (red - green) / m_delta;
}
hue = hue * 60;
if (hue < 0) {
hue = hue + 360;
}
}
*h = (int)(hue + 0.5);
*s = (int)(saturation * 100 + 0.5);
*v = (int)(value * 100 + 0.5);
}
// refence: https://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
static esp_err_t light_driver_hsl2rgb(uint16_t hue, uint8_t saturation, uint8_t lightness,
uint8_t *red, uint8_t *green, uint8_t *blue)
{
uint16_t hi = (hue / 60) % 6;
uint16_t C = (100 - abs(2 * lightness - 100)) * saturation / 100;
uint16_t M = 100 * (lightness - 0.5 * C) / 100;
uint16_t X = C * (100 - abs((hue * 100 / 60 ) % 200 - 100)) / 100;
switch (hi) {
case 0: /* hue 0~60 */
*red = C + M;
*green = X + M;
*blue = M;
break;
case 1: /* hue 60~120 */
*red = X + M;
*green = C + M;
*blue = M;
break;
case 2: /* hue 120~180 */
*red = M;
*green = C + M;
*blue = X + M;
break;
case 3: /* hue 180~240 */
*red = M;
*green = X + M;
*blue = C + M;
break;
case 4: /* hue 240~300 */
*red = X + M;
*green = M;
*blue = C + M;
break;
case 5: /* hue 300~360 */
*red = C + M;
*green = M;
*blue = X + M;
break;
default:
return ESP_FAIL;
}
*red = *red * 255 / 100;
*green = *green * 255 / 100;
*blue = *blue * 255 / 100;
return ESP_OK;
}
esp_err_t light_driver_set_hsv(uint16_t hue, uint8_t saturation, uint8_t value)
{
LIGHT_PARAM_CHECK(hue <= 360);
LIGHT_PARAM_CHECK(saturation <= 100);
LIGHT_PARAM_CHECK(value <= 100);
esp_err_t ret = ESP_OK;
uint8_t red = 0;
uint8_t green = 0;
uint8_t blue = 0;
ret = light_driver_hsv2rgb(hue, saturation, value, &red, &green, &blue);
LIGHT_ERROR_CHECK(ret < 0, ret, "light_driver_hsv2rgb, ret: %d", ret);
ESP_LOGV(TAG, "red: %d, green: %d, blue: %d", red, green, blue);
ret = iot_led_set_channel(CHANNEL_ID_RED, red, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_GREEN, green, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_BLUE, blue, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
if (g_light_status.mode != MODE_HSV) {
ret = iot_led_set_channel(CHANNEL_ID_WARM, 0, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_COLD, 0, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
}
g_light_status.mode = MODE_HSV;
g_light_status.on = 1;
g_light_status.hue = hue;
g_light_status.value = value;
g_light_status.saturation = saturation;
ret = ble_mesh_nvs_store(NVS_HANDLE, LIGHT_STATUS_STORE_KEY, &g_light_status, sizeof(light_status_t));
LIGHT_ERROR_CHECK(ret < 0, ret, "ble_mesh_nvs_store, ret: %d", ret);
return ESP_OK;
}
esp_err_t light_driver_set_hue(uint16_t hue)
{
if (g_light_status.mode == MODE_HSV) {
return light_driver_set_hsv(hue, g_light_status.saturation, g_light_status.value);
} else if (g_light_status.mode == MODE_HSL) {
return light_driver_set_hsl(hue, g_light_status.saturation, g_light_status.lightness);
} else {
return ESP_FAIL;
}
}
esp_err_t light_driver_set_saturation(uint8_t saturation)
{
if (g_light_status.mode == MODE_HSV) {
return light_driver_set_hsv(g_light_status.hue, saturation, g_light_status.value);
} else if (g_light_status.mode == MODE_HSL) {
return light_driver_set_hsl(g_light_status.hue, saturation, g_light_status.lightness);
} else {
return ESP_FAIL;
}
}
esp_err_t light_driver_set_value(uint8_t value)
{
return light_driver_set_hsv(g_light_status.hue, g_light_status.saturation, value);
}
esp_err_t light_driver_get_hsv(uint16_t *hue, uint8_t *saturation, uint8_t *value)
{
LIGHT_PARAM_CHECK(hue);
LIGHT_PARAM_CHECK(saturation);
LIGHT_PARAM_CHECK(value);
*hue = g_light_status.hue;
*saturation = g_light_status.saturation;
*value = g_light_status.value;
return ESP_OK;
}
uint16_t light_driver_get_hue(void)
{
return g_light_status.hue;
}
uint8_t light_driver_get_saturation(void)
{
return g_light_status.saturation;
}
uint8_t light_driver_get_value(void)
{
return g_light_status.value;
}
uint8_t light_driver_get_mode(void)
{
return g_light_status.mode;
}
esp_err_t light_driver_set_ctb(uint8_t color_temperature, uint8_t brightness)
{
LIGHT_PARAM_CHECK(brightness <= 100);
LIGHT_PARAM_CHECK(color_temperature <= 100);
esp_err_t ret = ESP_OK;
uint8_t warm_tmp = color_temperature * brightness / 100;
uint8_t cold_tmp = (100 - color_temperature) * brightness / 100;
warm_tmp = warm_tmp < 15 ? warm_tmp : 14 + warm_tmp * 86 / 100;
cold_tmp = cold_tmp < 15 ? cold_tmp : 14 + cold_tmp * 86 / 100;
ret = iot_led_set_channel(CHANNEL_ID_COLD,
cold_tmp * 255 / 100, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_WARM,
warm_tmp * 255 / 100, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
if (g_light_status.mode != MODE_CTB) {
ret = iot_led_set_channel(CHANNEL_ID_RED, 0, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_GREEN, 0, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_BLUE, 0, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
}
g_light_status.mode = MODE_CTB;
g_light_status.on = 1;
g_light_status.brightness = brightness;
g_light_status.color_temperature = color_temperature;
ret = ble_mesh_nvs_store(NVS_HANDLE, LIGHT_STATUS_STORE_KEY, &g_light_status, sizeof(light_status_t));
LIGHT_ERROR_CHECK(ret < 0, ret, "ble_mesh_nvs_store, ret: %d", ret);
return ESP_OK;
}
esp_err_t light_driver_set_color_temperature(uint8_t color_temperature)
{
return light_driver_set_ctb(color_temperature, g_light_status.brightness);
}
esp_err_t light_driver_set_brightness(uint8_t brightness)
{
return light_driver_set_ctb(g_light_status.color_temperature, brightness);
}
esp_err_t light_driver_get_ctb(uint8_t *color_temperature, uint8_t *brightness)
{
LIGHT_PARAM_CHECK(color_temperature);
LIGHT_PARAM_CHECK(brightness);
*brightness = g_light_status.brightness;
*color_temperature = g_light_status.color_temperature;
return ESP_OK;
}
uint8_t light_driver_get_color_temperature(void)
{
return g_light_status.color_temperature;
}
uint8_t light_driver_get_brightness(void)
{
return g_light_status.brightness;
}
esp_err_t light_driver_set_hsl(uint16_t hue, uint8_t saturation, uint8_t lightness)
{
LIGHT_PARAM_CHECK(hue <= 360);
LIGHT_PARAM_CHECK(saturation <= 100);
LIGHT_PARAM_CHECK(lightness <= 100);
esp_err_t ret = ESP_OK;
uint8_t red = 0;
uint8_t green = 0;
uint8_t blue = 0;
ret = light_driver_hsl2rgb(hue, saturation, lightness, &red, &green, &blue);
LIGHT_ERROR_CHECK(ret < 0, ret, "light_driver_hsl2rgb, ret: %d", ret);
ESP_LOGV(TAG, "red: %d, green: %d, blue: %d", red, green, blue);
ret = iot_led_set_channel(CHANNEL_ID_RED, red, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_GREEN, green, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_BLUE, blue, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
if (g_light_status.mode != MODE_HSL) {
ret = iot_led_set_channel(CHANNEL_ID_WARM, 0, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_COLD, 0, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
}
g_light_status.mode = MODE_HSL;
g_light_status.on = 1;
g_light_status.hue = hue;
g_light_status.saturation = saturation;
g_light_status.lightness = lightness;
ret = ble_mesh_nvs_store(NVS_HANDLE, LIGHT_STATUS_STORE_KEY, &g_light_status, sizeof(light_status_t));
LIGHT_ERROR_CHECK(ret < 0, ret, "ble_mesh_nvs_store, ret: %d", ret);
return ESP_OK;
}
esp_err_t light_driver_set_lightness(uint8_t lightness)
{
return light_driver_set_hsl(g_light_status.hue, g_light_status.saturation, lightness);
}
uint8_t light_driver_get_lightness(void)
{
return g_light_status.lightness;
}
esp_err_t light_driver_get_hsl(uint16_t *hue, uint8_t *saturation, uint8_t *lightness)
{
LIGHT_PARAM_CHECK(hue);
LIGHT_PARAM_CHECK(saturation);
LIGHT_PARAM_CHECK(lightness);
*hue = g_light_status.hue;
*saturation = g_light_status.saturation;
*lightness = g_light_status.lightness;
return ESP_OK;
}
esp_err_t light_driver_set_switch(bool on)
{
esp_err_t ret = ESP_OK;
g_light_status.on = on;
if (!g_light_status.on) {
ret = iot_led_set_channel(CHANNEL_ID_RED, 0, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_GREEN, 0, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_BLUE, 0, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_COLD, 0, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_WARM, 0, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_set_channel, ret: %d", ret);
} else {
switch (g_light_status.mode) {
case MODE_HSV:
g_light_status.value = (g_light_status.value) ? g_light_status.value : 100;
ret = light_driver_set_hsv(g_light_status.hue, g_light_status.saturation, g_light_status.value);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "light_driver_set_hsv, ret: %d", ret);
break;
case MODE_HSL:
g_light_status.lightness = (g_light_status.lightness) ? g_light_status.lightness : 100;
ret = light_driver_set_hsl(g_light_status.hue, g_light_status.saturation, g_light_status.lightness);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "light_driver_set_hsl, ret: %d", ret);
break;
case MODE_CTB:
g_light_status.brightness = (g_light_status.brightness) ? g_light_status.brightness : 100;
ret = light_driver_set_ctb(g_light_status.color_temperature, g_light_status.brightness);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "light_driver_set_ctb, ret: %d", ret);
break;
default:
ESP_LOGW(TAG, "This operation is not supported");
break;
}
}
ret = ble_mesh_nvs_store(NVS_HANDLE, LIGHT_STATUS_STORE_KEY, &g_light_status, sizeof(light_status_t));
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "ble_mesh_nvs_store, ret: %d", ret);
return ESP_OK;
}
esp_err_t light_driver_set_mode(light_mode_t mode)
{
g_light_status.mode = mode;
return ESP_OK;
}
bool light_driver_get_switch(void)
{
return g_light_status.on;
}
esp_err_t light_driver_breath_start(uint8_t red, uint8_t green, uint8_t blue)
{
esp_err_t ret = ESP_OK;
ret = iot_led_start_blink(CHANNEL_ID_RED,
red, g_light_status.blink_period_ms, true);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_start_blink, ret: %d", ret);
ret = iot_led_start_blink(CHANNEL_ID_GREEN,
green, g_light_status.blink_period_ms, true);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_start_blink, ret: %d", ret);
ret = iot_led_start_blink(CHANNEL_ID_BLUE,
blue, g_light_status.blink_period_ms, true);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_start_blink, ret: %d", ret);
g_light_blink_flag = true;
return ESP_OK;
}
esp_err_t light_driver_breath_stop(void)
{
esp_err_t ret = ESP_OK;
if (g_light_blink_flag == false) {
return ESP_OK;
}
ret = iot_led_stop_blink(CHANNEL_ID_RED);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_stop_blink, ret: %d", ret);
ret = iot_led_stop_blink(CHANNEL_ID_GREEN);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_stop_blink, ret: %d", ret);
ret = iot_led_stop_blink(CHANNEL_ID_BLUE);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_stop_blink, ret: %d", ret);
light_driver_set_switch(true);
return ESP_OK;
}
esp_err_t light_driver_fade_brightness(uint8_t brightness)
{
esp_err_t ret = ESP_OK;
g_fade_mode = MODE_ON;
uint32_t fade_period_ms = 0;
if (g_light_status.mode == MODE_HSV) {
uint8_t red = 0;
uint8_t green = 0;
uint8_t blue = 0;
ret = light_driver_hsv2rgb(g_light_status.hue, g_light_status.saturation, g_light_status.value, &red, &green, &blue);
LIGHT_ERROR_CHECK(ret < 0, ret, "light_driver_hsv2rgb, ret: %d", ret);
if (brightness != 0) {
ret = iot_led_get_channel((ledc_channel_t)CHANNEL_ID_RED, &red);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_get_channel, ret: %d", ret);
ret = iot_led_get_channel((ledc_channel_t)CHANNEL_ID_GREEN, &green);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_get_channel, ret: %d", ret);
ret = iot_led_get_channel((ledc_channel_t)CHANNEL_ID_BLUE, &blue);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_get_channel, ret: %d", ret);
uint8_t max_color = MAX(MAX(red, green), blue);
uint8_t change_value = brightness * 255 / 100 - max_color;
fade_period_ms = LIGHT_FADE_PERIOD_MAX_MS * change_value / 255;
} else {
fade_period_ms = LIGHT_FADE_PERIOD_MAX_MS * MAX(MAX(red, green), blue) / 255;
red = 0;
}
g_light_status.value = brightness;
light_driver_hsv2rgb(g_light_status.hue, g_light_status.saturation, g_light_status.value, &red, &green, &blue);
ret = iot_led_set_channel(CHANNEL_ID_RED, red, fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_GREEN, green, fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_BLUE, blue, fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
} else if (g_light_status.mode == MODE_CTB) {
uint8_t warm_tmp = 0;
uint8_t cold_tmp = 0;
fade_period_ms = LIGHT_FADE_PERIOD_MAX_MS * g_light_status.brightness / 100;
if (brightness != 0) {
uint8_t change_value = brightness - g_light_status.brightness;
warm_tmp = g_light_status.color_temperature;
cold_tmp = (brightness - g_light_status.color_temperature);
fade_period_ms = LIGHT_FADE_PERIOD_MAX_MS * change_value / 100;
}
ret = iot_led_set_channel(CHANNEL_ID_COLD,
cold_tmp * 255 / 100, fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_WARM,
warm_tmp * 255 / 100, fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
g_light_status.brightness = brightness;
}
ret = ble_mesh_nvs_store(NVS_HANDLE, LIGHT_STATUS_STORE_KEY, &g_light_status, sizeof(light_status_t));
LIGHT_ERROR_CHECK(ret < 0, ret, "ble_mesh_nvs_store, ret: %d", ret);
return ESP_OK;
}
static void light_fade_timer_stop(void)
{
if (!g_fade_timer) {
return ;
}
if (!xTimerStop(g_fade_timer, portMAX_DELAY)) {
ESP_LOGW(TAG, "xTimerStop timer: %p", g_fade_timer);
}
if (!xTimerDelete(g_fade_timer, portMAX_DELAY)) {
ESP_LOGW(TAG, "xTimerDelete timer: %p", g_fade_timer);
}
g_fade_timer = NULL;
}
static void light_fade_timer_cb(void *timer)
{
uint8_t red = 0;
uint8_t green = 0;
uint8_t blue = 0;
uint32_t fade_period_ms = LIGHT_FADE_PERIOD_MAX_MS * 2 / 6;
int variety = (g_fade_hue > 180) ? 60 : -60;
if (g_light_status.hue >= 360 || g_light_status.hue <= 0) {
light_fade_timer_stop();
}
g_light_status.hue = g_light_status.hue >= 360 ? 360 : g_light_status.hue + variety;
g_light_status.hue = g_light_status.hue <= 60 ? 0 : g_light_status.hue + variety;
light_driver_hsv2rgb(g_light_status.hue, g_light_status.saturation, g_light_status.value, &red, &green, &blue);
iot_led_set_channel(CHANNEL_ID_RED, red, fade_period_ms);
iot_led_set_channel(CHANNEL_ID_GREEN, green, fade_period_ms);
iot_led_set_channel(CHANNEL_ID_BLUE, blue, fade_period_ms);
}
esp_err_t light_driver_fade_hue(uint16_t hue)
{
esp_err_t ret = ESP_OK;
g_fade_mode = MODE_HSV;
g_fade_hue = hue;
light_fade_timer_stop();
if (g_light_status.mode != MODE_HSV) {
ret = iot_led_set_channel(CHANNEL_ID_WARM, 0, 0);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_COLD, 0, 0);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
}
g_light_status.mode = MODE_HSV;
g_light_status.value = (g_light_status.value == 0) ? 100 : g_light_status.value;
uint32_t fade_period_ms = LIGHT_FADE_PERIOD_MAX_MS * 2 / 6;
light_fade_timer_cb(NULL);
g_fade_timer = xTimerCreate("light_timer", fade_period_ms,
true, NULL, light_fade_timer_cb);
xTimerStart(g_fade_timer, 0);
return ESP_OK;
}
esp_err_t light_driver_fade_warm(uint8_t color_temperature)
{
esp_err_t ret = ESP_OK;
g_fade_mode = MODE_CTB;
if (g_light_status.mode != MODE_CTB) {
ret = iot_led_set_channel(CHANNEL_ID_RED, 0, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_GREEN, 0, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_BLUE, 0, g_light_status.fade_period_ms);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
}
uint8_t warm_tmp = color_temperature * g_light_status.brightness / 100;
uint8_t cold_tmp = (100 - color_temperature) * g_light_status.brightness / 100;
ret = iot_led_set_channel(CHANNEL_ID_COLD, cold_tmp * 255 / 100, LIGHT_FADE_PERIOD_MAX_MS);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
ret = iot_led_set_channel(CHANNEL_ID_WARM, warm_tmp * 255 / 100, LIGHT_FADE_PERIOD_MAX_MS);
LIGHT_ERROR_CHECK(ret < 0, ret, "iot_led_set_channel, ret: %d", ret);
g_light_status.mode = MODE_CTB;
g_light_status.color_temperature = color_temperature;
ret = ble_mesh_nvs_store(NVS_HANDLE, LIGHT_STATUS_STORE_KEY, &g_light_status, sizeof(light_status_t));
LIGHT_ERROR_CHECK(ret < 0, ret, "ble_mesh_nvs_store, ret: %d", ret);
return ESP_OK;
}
esp_err_t light_driver_fade_stop(void)
{
esp_err_t ret = ESP_OK;
light_fade_timer_stop();
if (g_light_status.mode != MODE_CTB) {
uint16_t hue = 0;
uint8_t saturation = 0;
uint8_t value = 0;
ret = iot_led_stop_blink(CHANNEL_ID_RED);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_stop_blink, ret: %d", ret);
ret = iot_led_stop_blink(CHANNEL_ID_GREEN);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_stop_blink, ret: %d", ret);
ret = iot_led_stop_blink(CHANNEL_ID_BLUE);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_stop_blink, ret: %d", ret);
uint8_t red, green, blue;
ret = iot_led_get_channel(CHANNEL_ID_RED, &red);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_get_channel, ret: %d", ret);
ret = iot_led_get_channel(CHANNEL_ID_GREEN, &green);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_get_channel, ret: %d", ret);
ret = iot_led_get_channel(CHANNEL_ID_BLUE, &blue);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_get_channel, ret: %d", ret);
light_driver_rgb2hsv(red, green, blue, &hue, &saturation, &value);
g_light_status.hue = (g_fade_mode == MODE_HSV) ? hue : g_light_status.hue;
g_light_status.value = (g_fade_mode == MODE_OFF || g_fade_mode == MODE_ON) ? value : g_light_status.value;
} else {
uint8_t color_temperature = 0;
uint8_t brightness = 0;
ret = iot_led_stop_blink(CHANNEL_ID_COLD);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_stop_blink, ret: %d", ret);
ret = iot_led_stop_blink(CHANNEL_ID_WARM);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_stop_blink, ret: %d", ret);
uint8_t warm_tmp, cold_tmp;
uint8_t tmp;
ret = iot_led_get_channel(CHANNEL_ID_WARM, &tmp);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_get_channel, ret: %d", ret);
warm_tmp = (int32_t)tmp * 100 / 255;
ret = iot_led_get_channel(CHANNEL_ID_COLD, &tmp);
LIGHT_ERROR_CHECK(ret < 0, ESP_FAIL, "iot_led_get_channel, ret: %d", ret);
cold_tmp = (int32_t)tmp * 100 / 255;
color_temperature = (!warm_tmp) ? 0 : 100 / (cold_tmp / warm_tmp + 1);
brightness = (!color_temperature) ? cold_tmp : warm_tmp * 100 / color_temperature;
g_light_status.brightness = (g_fade_mode == MODE_OFF || g_fade_mode == MODE_ON) ? brightness : g_light_status.brightness;
g_light_status.color_temperature = (g_fade_mode == MODE_CTB) ? color_temperature : g_light_status.color_temperature;
}
ret = ble_mesh_nvs_store(NVS_HANDLE, LIGHT_STATUS_STORE_KEY, &g_light_status, sizeof(light_status_t));
LIGHT_ERROR_CHECK(ret < 0, ret, "ble_mesh_nvs_store, ret: %d", ret);
g_fade_mode = MODE_NONE;
return ESP_OK;
}