From 6648ef2e659287631db0a50d625ac649d13a2228 Mon Sep 17 00:00:00 2001 From: Alex Tsitsiura Date: Thu, 19 Jan 2023 23:55:31 +0200 Subject: [PATCH] [Telink] Add lighting color feature & Move LightingManager into PWMDevice (#24378) * [Telink] Moved LightingManager into PWMDevice * Restyled by clang-format * [Telink] Adding lighting color feature (#59) * [Telink] Added lighting color feature * [Telink] Added tlsr9518adk80d.overlay to lighting-app * [Telink] Updated RGB functionality * [Telink] Updated RGB PWM pins Co-authored-by: Alex Tsitsiura * Restyled by whitespace * Restyled by clang-format * [Telink] Minor changes * [Telink] Add 'telink reboot' shell CLI command (#63) * [Telink] Restyled Co-authored-by: Serhii Salamakha Co-authored-by: Restyled.io Co-authored-by: Dmytro Huz <75682372+interfer@users.noreply.github.com> --- .../all-clusters-app/telink/src/AppTask.cpp | 17 ++ .../telink/src/AppTask.cpp | 18 ++ .../light-switch-app/telink/src/AppTask.cpp | 18 ++ .../telink/src/binding-handler.cpp | 14 +- examples/lighting-app/telink/CMakeLists.txt | 5 +- .../lighting-app/telink/include/AppConfig.h | 9 +- .../lighting-app/telink/include/AppTask.h | 16 +- examples/lighting-app/telink/src/AppTask.cpp | 218 +++++++++++++++--- .../lighting-app/telink/src/ZclCallbacks.cpp | 77 ++++++- .../ota-requestor-app/telink/src/AppTask.cpp | 18 ++ .../telink/util/include/ColorFormat.h | 50 ++++ .../telink/util/include/PWMDevice.h} | 32 +-- .../platform/telink/util/src/ColorFormat.cpp | 187 +++++++++++++++ .../telink/util/src/PWMDevice.cpp} | 41 ++-- examples/thermostat/telink/src/AppTask.cpp | 18 ++ 15 files changed, 642 insertions(+), 96 deletions(-) create mode 100755 examples/platform/telink/util/include/ColorFormat.h rename examples/{lighting-app/telink/include/LightingManager.h => platform/telink/util/include/PWMDevice.h} (68%) create mode 100644 examples/platform/telink/util/src/ColorFormat.cpp rename examples/{lighting-app/telink/src/LightingManager.cpp => platform/telink/util/src/PWMDevice.cpp} (70%) diff --git a/examples/all-clusters-app/telink/src/AppTask.cpp b/examples/all-clusters-app/telink/src/AppTask.cpp index 9f831beecfaf9b..bc05a343618354 100644 --- a/examples/all-clusters-app/telink/src/AppTask.cpp +++ b/examples/all-clusters-app/telink/src/AppTask.cpp @@ -43,6 +43,23 @@ #include #include +#if CONFIG_CHIP_LIB_SHELL +#include +#include + +static int cmd_telink_reboot(const struct shell * shell, size_t argc, char ** argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + shell_print(shell, "Performing board reboot..."); + sys_reboot(); +} + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_telink, SHELL_CMD(reboot, NULL, "Reboot board command", cmd_telink_reboot), + SHELL_SUBCMD_SET_END); +SHELL_CMD_REGISTER(telink, &sub_telink, "Telink commands", NULL); +#endif // CONFIG_CHIP_LIB_SHELL LOG_MODULE_DECLARE(app); diff --git a/examples/all-clusters-minimal-app/telink/src/AppTask.cpp b/examples/all-clusters-minimal-app/telink/src/AppTask.cpp index 4d0f29cb87efea..2b5a51b524f879 100644 --- a/examples/all-clusters-minimal-app/telink/src/AppTask.cpp +++ b/examples/all-clusters-minimal-app/telink/src/AppTask.cpp @@ -41,6 +41,24 @@ #include #include +#if CONFIG_CHIP_LIB_SHELL +#include +#include + +static int cmd_telink_reboot(const struct shell * shell, size_t argc, char ** argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + shell_print(shell, "Performing board reboot..."); + sys_reboot(); +} + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_telink, SHELL_CMD(reboot, NULL, "Reboot board command", cmd_telink_reboot), + SHELL_SUBCMD_SET_END); +SHELL_CMD_REGISTER(telink, &sub_telink, "Telink commands", NULL); +#endif // CONFIG_CHIP_LIB_SHELL + LOG_MODULE_DECLARE(app); namespace { diff --git a/examples/light-switch-app/telink/src/AppTask.cpp b/examples/light-switch-app/telink/src/AppTask.cpp index 5b7817831ed68a..0c8013067fc9d7 100644 --- a/examples/light-switch-app/telink/src/AppTask.cpp +++ b/examples/light-switch-app/telink/src/AppTask.cpp @@ -45,6 +45,24 @@ #include +#if CONFIG_CHIP_LIB_SHELL +#include +#include + +static int cmd_telink_reboot(const struct shell * shell, size_t argc, char ** argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + shell_print(shell, "Performing board reboot..."); + sys_reboot(); +} + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_telink, SHELL_CMD(reboot, NULL, "Reboot board command", cmd_telink_reboot), + SHELL_SUBCMD_SET_END); +SHELL_CMD_REGISTER(telink, &sub_telink, "Telink commands", NULL); +#endif // CONFIG_CHIP_LIB_SHELL + LOG_MODULE_DECLARE(app); using namespace ::chip; diff --git a/examples/light-switch-app/telink/src/binding-handler.cpp b/examples/light-switch-app/telink/src/binding-handler.cpp index ed6b07f8df467e..60efbcf8857f5c 100755 --- a/examples/light-switch-app/telink/src/binding-handler.cpp +++ b/examples/light-switch-app/telink/src/binding-handler.cpp @@ -26,15 +26,15 @@ #include #include -#if defined(ENABLE_CHIP_SHELL) +#if defined(CONFIG_CHIP_LIB_SHELL) #include "lib/shell/Engine.h" #include "lib/shell/commands/Help.h" -#endif // ENABLE_CHIP_SHELL +#endif // CONFIG_CHIP_LIB_SHELL using namespace chip; using namespace chip::app; -#if defined(ENABLE_CHIP_SHELL) +#if defined(CONFIG_CHIP_LIB_SHELL) using Shell::Engine; using Shell::shell_command_t; using Shell::streamer_get; @@ -47,7 +47,7 @@ Engine sShellSwitchGroupsSubCommands; Engine sShellSwitchGroupsOnOffSubCommands; Engine sShellSwitchBindingSubCommands; -#endif // defined(ENABLE_CHIP_SHELL) +#endif // defined(CONFIG_CHIP_LIB_SHELL) namespace { @@ -140,7 +140,7 @@ void LightSwitchContextReleaseHandler(void * context) Platform::Delete(static_cast(context)); } -#ifdef ENABLE_CHIP_SHELL +#ifdef CONFIG_CHIP_LIB_SHELL /******************************************************** * Switch shell functions @@ -384,7 +384,7 @@ static void RegisterSwitchCommands() Engine::Root().RegisterCommands(&sSwitchCommand, 1); } -#endif // ENABLE_CHIP_SHELL +#endif // CONFIG_CHIP_LIB_SHELL void InitBindingHandlerInternal(intptr_t arg) { @@ -439,7 +439,7 @@ CHIP_ERROR InitBindingHandler() // so it requires the Server instance to be correctly initialized. Post the init function to // the event queue so that everything is ready when initialization is conducted. chip::DeviceLayer::PlatformMgr().ScheduleWork(InitBindingHandlerInternal); -#if defined(ENABLE_CHIP_SHELL) +#if defined(CONFIG_CHIP_LIB_SHELL) RegisterSwitchCommands(); #endif return CHIP_NO_ERROR; diff --git a/examples/lighting-app/telink/CMakeLists.txt b/examples/lighting-app/telink/CMakeLists.txt index a917c8d6ea6b36..628e10410e816c 100644 --- a/examples/lighting-app/telink/CMakeLists.txt +++ b/examples/lighting-app/telink/CMakeLists.txt @@ -47,12 +47,13 @@ add_definitions( target_sources(app PRIVATE src/AppTask.cpp - src/LightingManager.cpp src/main.cpp src/ZclCallbacks.cpp ${TELINK_COMMON}/util/src/LEDWidget.cpp ${TELINK_COMMON}/util/src/ButtonManager.cpp - ${TELINK_COMMON}/util/src/ThreadUtil.cpp) + ${TELINK_COMMON}/util/src/ThreadUtil.cpp + ${TELINK_COMMON}/util/src/PWMDevice.cpp + ${TELINK_COMMON}/util/src/ColorFormat.cpp) chip_configure_data_model(app INCLUDE_SERVER diff --git a/examples/lighting-app/telink/include/AppConfig.h b/examples/lighting-app/telink/include/AppConfig.h index 2d7aec52b295d2..3c2430643eb2ee 100644 --- a/examples/lighting-app/telink/include/AppConfig.h +++ b/examples/lighting-app/telink/include/AppConfig.h @@ -34,5 +34,10 @@ #define SYSTEM_STATE_LED_PIN 7 // Lighting LED config -#define LIGHTING_PWM_DEVICE DEVICE_DT_GET(DT_PWMS_CTLR(DT_ALIAS(pwm_led0))) -#define LIGHTING_PWM_CHANNEL DT_PWMS_CHANNEL(DT_ALIAS(pwm_led0)) +#define USE_RGB_PWM 0 + +#define LIGHTING_PWM_SPEC_BLUE PWM_DT_SPEC_GET(DT_ALIAS(pwm_led0)) +#if USE_RGB_PWM +#define LIGHTING_PWM_SPEC_GREEN PWM_DT_SPEC_GET(DT_ALIAS(pwm_led1)) +#define LIGHTING_PWM_SPEC_RED PWM_DT_SPEC_GET(DT_ALIAS(pwm_led2)) +#endif diff --git a/examples/lighting-app/telink/include/AppTask.h b/examples/lighting-app/telink/include/AppTask.h index 26575805d70a5d..e0b37739915d4e 100644 --- a/examples/lighting-app/telink/include/AppTask.h +++ b/examples/lighting-app/telink/include/AppTask.h @@ -20,7 +20,7 @@ #include "AppEvent.h" #include "LEDWidget.h" -#include "LightingManager.h" +#include "PWMDevice.h" #include #if CONFIG_CHIP_FACTORY_DATA @@ -40,9 +40,10 @@ class AppTask public: CHIP_ERROR StartApp(); - void PostLightingActionRequest(LightingManager::Action_t aAction); - void PostEvent(AppEvent * event); + void SetInitiateAction(PWMDevice::Action_t aAction, int32_t aActor, uint8_t * value); + void PostEvent(AppEvent * aEvent); void UpdateClusterState(); + PWMDevice & GetPWMDevice() { return mBluePwmLed; } enum ButtonId_t { @@ -59,8 +60,8 @@ class AppTask friend AppTask & GetAppTask(void); CHIP_ERROR Init(); - static void ActionInitiated(LightingManager::Action_t aAction, int32_t aActor); - static void ActionCompleted(LightingManager::Action_t aAction, int32_t aActor); + static void ActionInitiated(PWMDevice::Action_t aAction, int32_t aActor); + static void ActionCompleted(PWMDevice::Action_t aAction, int32_t aActor); void DispatchEvent(AppEvent * event); @@ -88,6 +89,11 @@ class AppTask static void ThreadProvisioningHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); static AppTask sAppTask; + PWMDevice mBluePwmLed; +#if USE_RGB_PWM + PWMDevice mGreenPwmLed; + PWMDevice mRedPwmLed; +#endif #if CONFIG_CHIP_FACTORY_DATA // chip::DeviceLayer::FactoryDataProvider mFactoryDataProvider; diff --git a/examples/lighting-app/telink/src/AppTask.cpp b/examples/lighting-app/telink/src/AppTask.cpp index f7866db456c370..0dd80c267c668e 100644 --- a/examples/lighting-app/telink/src/AppTask.cpp +++ b/examples/lighting-app/telink/src/AppTask.cpp @@ -21,6 +21,7 @@ #include "AppConfig.h" #include "AppEvent.h" #include "ButtonManager.h" +#include "ColorFormat.h" #include "ThreadUtil.h" @@ -45,6 +46,24 @@ #include +#if CONFIG_CHIP_LIB_SHELL +#include +#include + +static int cmd_telink_reboot(const struct shell * shell, size_t argc, char ** argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + shell_print(shell, "Performing board reboot..."); + sys_reboot(); +} + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_telink, SHELL_CMD(reboot, NULL, "Reboot board command", cmd_telink_reboot), + SHELL_SUBCMD_SET_END); +SHELL_CMD_REGISTER(telink, &sub_telink, "Telink commands", NULL); +#endif // CONFIG_CHIP_LIB_SHELL + LOG_MODULE_DECLARE(app); using namespace ::chip; @@ -60,15 +79,29 @@ constexpr uint8_t kButtonReleaseEvent = 0; constexpr uint8_t kDefaultMinLevel = 0; constexpr uint8_t kDefaultMaxLevel = 254; +const struct pwm_dt_spec sBluePwmLed = LIGHTING_PWM_SPEC_BLUE; +#if USE_RGB_PWM +const struct pwm_dt_spec sGreenPwmLed = LIGHTING_PWM_SPEC_GREEN; +const struct pwm_dt_spec sRedPwmLed = LIGHTING_PWM_SPEC_RED; +#endif + +#if CONFIG_CHIP_FACTORY_DATA // NOTE! This key is for test/certification only and should not be available in production devices! -// If CONFIG_CHIP_FACTORY_DATA is enabled, this value is read from the factory data. uint8_t sTestEventTriggerEnableKey[TestEventTriggerDelegate::kEnableKeyLength] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; +#endif K_MSGQ_DEFINE(sAppEventQueue, sizeof(AppEvent), kAppEventQueueSize, alignof(AppEvent)); k_timer sFactoryResetTimer; LEDWidget sStatusLED; +#if USE_RGB_PWM +uint8_t sBrightness; +PWMDevice::Action_t sColorAction = PWMDevice::INVALID_ACTION; +XyColor_t sXY; +HsvColor_t sHSV; +CtColor_t sCT; +#endif Button sFactoryResetButton; Button sLightingButton; @@ -153,13 +186,28 @@ CHIP_ERROR AppTask::Init() uint8_t maxLightLevel = kDefaultMaxLevel; Clusters::LevelControl::Attributes::MaxLevel::Get(1, &maxLightLevel); - CHIP_ERROR err = LightingMgr().Init(LIGHTING_PWM_DEVICE, LIGHTING_PWM_CHANNEL, minLightLevel, maxLightLevel, maxLightLevel); + CHIP_ERROR err = sAppTask.mBluePwmLed.Init(&sBluePwmLed, minLightLevel, maxLightLevel, maxLightLevel); + if (err != CHIP_NO_ERROR) + { + LOG_ERR("Blue PWM Device Init fail"); + return err; + } +#if USE_RGB_PWM + err = sAppTask.mRedPwmLed.Init(&sRedPwmLed, minLightLevel, maxLightLevel, maxLightLevel); if (err != CHIP_NO_ERROR) { - LOG_ERR("LightingMgr Init fail"); + LOG_ERR("Red PWM Device Init fail"); return err; } - LightingMgr().SetCallbacks(ActionInitiated, ActionCompleted); + + err = sAppTask.mGreenPwmLed.Init(&sGreenPwmLed, minLightLevel, maxLightLevel, maxLightLevel); + if (err != CHIP_NO_ERROR) + { + LOG_ERR("Green PWM Device Init fail"); + return err; + } +#endif + sAppTask.mBluePwmLed.SetCallbacks(ActionInitiated, ActionCompleted); // Initialize CHIP server #if CONFIG_CHIP_FACTORY_DATA @@ -253,21 +301,38 @@ void AppTask::LightingActionButtonEventHandler(void) void AppTask::LightingActionEventHandler(AppEvent * aEvent) { - LightingManager::Action_t action = LightingManager::INVALID_ACTION; - int32_t actor = 0; + PWMDevice::Action_t action = PWMDevice::INVALID_ACTION; + int32_t actor = 0; if (aEvent->Type == AppEvent::kEventType_Lighting) { - action = static_cast(aEvent->LightingEvent.Action); + action = static_cast(aEvent->LightingEvent.Action); actor = aEvent->LightingEvent.Actor; } else if (aEvent->Type == AppEvent::kEventType_Button) { - action = LightingMgr().IsTurnedOn() ? LightingManager::OFF_ACTION : LightingManager::ON_ACTION; - actor = AppEvent::kEventType_Button; +#if USE_RGB_PWM + if (sAppTask.mRedPwmLed.IsTurnedOn() || sAppTask.mGreenPwmLed.IsTurnedOn() || sAppTask.mBluePwmLed.IsTurnedOn()) + { + action = PWMDevice::OFF_ACTION; + } + else + { + action = PWMDevice::ON_ACTION; + } +#else + action = sAppTask.mBluePwmLed.IsTurnedOn() ? PWMDevice::OFF_ACTION : PWMDevice::ON_ACTION; +#endif + actor = AppEvent::kEventType_Button; } - if (action != LightingManager::INVALID_ACTION && !LightingMgr().InitiateAction(action, actor, 0, NULL)) + if (action != PWMDevice::INVALID_ACTION && + ( +#if USE_RGB_PWM + !sAppTask.mRedPwmLed.InitiateAction(action, actor, NULL) || + !sAppTask.mGreenPwmLed.InitiateAction(action, actor, NULL) || +#endif + !sAppTask.mBluePwmLed.InitiateAction(action, actor, NULL))) { LOG_INF("Action is in progress or active"); } @@ -419,33 +484,33 @@ void AppTask::ChipEventHandler(const ChipDeviceEvent * event, intptr_t /* arg */ } } -void AppTask::ActionInitiated(LightingManager::Action_t aAction, int32_t aActor) +void AppTask::ActionInitiated(PWMDevice::Action_t aAction, int32_t aActor) { - if (aAction == LightingManager::ON_ACTION) + if (aAction == PWMDevice::ON_ACTION) { LOG_INF("ON_ACTION initiated"); } - else if (aAction == LightingManager::OFF_ACTION) + else if (aAction == PWMDevice::OFF_ACTION) { LOG_INF("OFF_ACTION initiated"); } - else if (aAction == LightingManager::LEVEL_ACTION) + else if (aAction == PWMDevice::LEVEL_ACTION) { LOG_INF("LEVEL_ACTION initiated"); } } -void AppTask::ActionCompleted(LightingManager::Action_t aAction, int32_t aActor) +void AppTask::ActionCompleted(PWMDevice::Action_t aAction, int32_t aActor) { - if (aAction == LightingManager::ON_ACTION) + if (aAction == PWMDevice::ON_ACTION) { LOG_INF("ON_ACTION completed"); } - else if (aAction == LightingManager::OFF_ACTION) + else if (aAction == PWMDevice::OFF_ACTION) { LOG_INF("OFF_ACTION completed"); } - else if (aAction == LightingManager::LEVEL_ACTION) + else if (aAction == PWMDevice::LEVEL_ACTION) { LOG_INF("LEVEL_ACTION completed"); } @@ -456,15 +521,6 @@ void AppTask::ActionCompleted(LightingManager::Action_t aAction, int32_t aActor) } } -void AppTask::PostLightingActionRequest(LightingManager::Action_t aAction) -{ - AppEvent event; - event.Type = AppEvent::kEventType_Lighting; - event.LightingEvent.Action = aAction; - event.Handler = LightingActionEventHandler; - PostEvent(&event); -} - void AppTask::PostEvent(AppEvent * aEvent) { if (k_msgq_put(&sAppEventQueue, aEvent, K_NO_WAIT) != 0) @@ -487,16 +543,34 @@ void AppTask::DispatchEvent(AppEvent * aEvent) void AppTask::UpdateClusterState() { +#if USE_RGB_PWM + bool isTurnedOn = sAppTask.mRedPwmLed.IsTurnedOn() || sAppTask.mGreenPwmLed.IsTurnedOn() || sAppTask.mBluePwmLed.IsTurnedOn(); +#else + bool isTurnedOn = sAppTask.mBluePwmLed.IsTurnedOn(); +#endif // write the new on/off value - EmberAfStatus status = Clusters::OnOff::Attributes::OnOff::Set(1, LightingMgr().IsTurnedOn()); + EmberAfStatus status = Clusters::OnOff::Attributes::OnOff::Set(1, isTurnedOn); if (status != EMBER_ZCL_STATUS_SUCCESS) { LOG_ERR("Update OnOff fail: %x", status); } - status = Clusters::LevelControl::Attributes::CurrentLevel::Set(1, LightingMgr().GetLevel()); - +#if USE_RGB_PWM + uint8_t setLevel; + if (sColorAction == PWMDevice::COLOR_ACTION_XY || sColorAction == PWMDevice::COLOR_ACTION_HSV || + sColorAction == PWMDevice::COLOR_ACTION_CT) + { + setLevel = sBrightness; + } + else + { + setLevel = sAppTask.mBluePwmLed.GetLevel(); + } +#else + uint8_t setLevel = sAppTask.mBluePwmLed.GetLevel(); +#endif + status = Clusters::LevelControl::Attributes::CurrentLevel::Set(1, setLevel); if (status != EMBER_ZCL_STATUS_SUCCESS) { LOG_ERR("Update CurrentLevel fail: %x", status); @@ -564,3 +638,87 @@ void AppTask::InitButtons(void) ButtonManagerInst().AddButton(sThreadStartButton); ButtonManagerInst().AddButton(sBleAdvStartButton); } + +void AppTask::SetInitiateAction(PWMDevice::Action_t aAction, int32_t aActor, uint8_t * value) +{ +#if USE_RGB_PWM + bool setRgbAction = false; + RgbColor_t rgb; +#endif + + if (aAction == PWMDevice::ON_ACTION || aAction == PWMDevice::OFF_ACTION) + { + sAppTask.mBluePwmLed.InitiateAction(aAction, aActor, value); +#if USE_RGB_PWM + sAppTask.mRedPwmLed.InitiateAction(aAction, aActor, value); + sAppTask.mGreenPwmLed.InitiateAction(aAction, aActor, value); +#endif + } + else if (aAction == PWMDevice::LEVEL_ACTION) + { +#if USE_RGB_PWM + // Save a new brightness for ColorControl + sBrightness = *value; + + if (sColorAction == PWMDevice::COLOR_ACTION_XY) + { + rgb = XYToRgb(sBrightness, sXY.x, sXY.y); + } + else if (sColorAction == PWMDevice::COLOR_ACTION_HSV) + { + sHSV.v = sBrightness; + rgb = HsvToRgb(sHSV); + } + else + { + rgb.r = sBrightness; + rgb.g = sBrightness; + rgb.b = sBrightness; + } + + ChipLogProgress(Zcl, "New brightness: %u | R: %u, G: %u, B: %u", sBrightness, rgb.r, rgb.g, rgb.b); + setRgbAction = true; +#else + sAppTask.mBluePwmLed.InitiateAction(aAction, aActor, value); +#endif + } + +#if USE_RGB_PWM + else if (aAction == PWMDevice::COLOR_ACTION_XY) + { + sXY = *reinterpret_cast(value); + rgb = XYToRgb(sBrightness, sXY.x, sXY.y); + ChipLogProgress(Zcl, "XY to RGB: X: %u, Y: %u, Level: %u | R: %u, G: %u, B: %u", sXY.x, sXY.y, sBrightness, rgb.r, rgb.g, + rgb.b); + setRgbAction = true; + sColorAction = PWMDevice::COLOR_ACTION_XY; + } + else if (aAction == PWMDevice::COLOR_ACTION_HSV) + { + sHSV = *reinterpret_cast(value); + sHSV.v = sBrightness; + rgb = HsvToRgb(sHSV); + ChipLogProgress(Zcl, "HSV to RGB: H: %u, S: %u, V: %u | R: %u, G: %u, B: %u", sHSV.h, sHSV.s, sHSV.v, rgb.r, rgb.g, rgb.b); + setRgbAction = true; + sColorAction = PWMDevice::COLOR_ACTION_HSV; + } + else if (aAction == PWMDevice::COLOR_ACTION_CT) + { + sCT = *reinterpret_cast(value); + if (sCT.ctMireds) + { + rgb = CTToRgb(sCT); + ChipLogProgress(Zcl, "ColorTemp to RGB: CT: %u | R: %u, G: %u, B: %u", sCT.ctMireds, rgb.r, rgb.g, rgb.b); + setRgbAction = true; + sColorAction = PWMDevice::COLOR_ACTION_CT; + } + } + + if (setRgbAction) + { + sAppTask.mRedPwmLed.InitiateAction(aAction, aActor, &rgb.r); + sAppTask.mGreenPwmLed.InitiateAction(aAction, aActor, &rgb.g); + sAppTask.mBluePwmLed.InitiateAction(aAction, aActor, &rgb.b); + } +#endif +} diff --git a/examples/lighting-app/telink/src/ZclCallbacks.cpp b/examples/lighting-app/telink/src/ZclCallbacks.cpp index 83a5d7a0a67ba9..5a0a6dbec4c07f 100644 --- a/examples/lighting-app/telink/src/ZclCallbacks.cpp +++ b/examples/lighting-app/telink/src/ZclCallbacks.cpp @@ -17,7 +17,8 @@ */ #include "AppTask.h" -#include "LightingManager.h" +#include "ColorFormat.h" +#include "PWMDevice.h" #include #include @@ -32,28 +33,88 @@ using namespace chip::app::Clusters::OnOff; void MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath & attributePath, uint8_t type, uint16_t size, uint8_t * value) { + static HsvColor_t hsv; + static XyColor_t xy; ClusterId clusterId = attributePath.mClusterId; AttributeId attributeId = attributePath.mAttributeId; if (clusterId == OnOff::Id && attributeId == OnOff::Attributes::OnOff::Id) { ChipLogProgress(Zcl, "Cluster OnOff: attribute OnOff set to %u", *value); - LightingMgr().InitiateAction(*value ? LightingManager::ON_ACTION : LightingManager::OFF_ACTION, - AppEvent::kEventType_Lighting, size, value); + GetAppTask().SetInitiateAction(*value ? PWMDevice::ON_ACTION : PWMDevice::OFF_ACTION, + static_cast(AppEvent::kEventType_Lighting), value); } else if (clusterId == LevelControl::Id && attributeId == LevelControl::Attributes::CurrentLevel::Id) { - ChipLogProgress(Zcl, "Cluster LevelControl: attribute CurrentLevel set to %u", *value); - - if (LightingMgr().IsTurnedOn()) + if (GetAppTask().GetPWMDevice().IsTurnedOn()) { - LightingMgr().InitiateAction(LightingManager::LEVEL_ACTION, AppEvent::kEventType_Lighting, size, value); + ChipLogProgress(Zcl, "Cluster LevelControl: attribute CurrentLevel set to %u", *value); + GetAppTask().SetInitiateAction(PWMDevice::LEVEL_ACTION, static_cast(AppEvent::kEventType_Lighting), value); } else { ChipLogDetail(Zcl, "LED is off. Try to use move-to-level-with-on-off instead of move-to-level"); } } + else if (clusterId == ColorControl::Id) + { + /* Ignore several attributes that are currently not processed */ + if ((attributeId == ColorControl::Attributes::RemainingTime::Id) || + (attributeId == ColorControl::Attributes::EnhancedColorMode::Id) || + (attributeId == ColorControl::Attributes::ColorMode::Id)) + { + return; + } + + /* XY color space */ + if (attributeId == ColorControl::Attributes::CurrentX::Id || attributeId == ColorControl::Attributes::CurrentY::Id) + { + if (attributeId == ColorControl::Attributes::CurrentX::Id) + { + xy.x = *reinterpret_cast(value); + } + else if (attributeId == ColorControl::Attributes::CurrentY::Id) + { + xy.y = *reinterpret_cast(value); + } + + ChipLogProgress(Zcl, "New XY color: %u|%u", xy.x, xy.y); + GetAppTask().SetInitiateAction(PWMDevice::COLOR_ACTION_XY, static_cast(AppEvent::kEventType_Lighting), + (uint8_t *) &xy); + } + /* HSV color space */ + else if (attributeId == ColorControl::Attributes::CurrentHue::Id || + attributeId == ColorControl::Attributes::CurrentSaturation::Id || + attributeId == ColorControl::Attributes::EnhancedCurrentHue::Id) + { + if (attributeId == ColorControl::Attributes::EnhancedCurrentHue::Id) + { + hsv.h = (uint8_t)(((*reinterpret_cast(value)) & 0xFF00) >> 8); + hsv.s = (uint8_t)((*reinterpret_cast(value)) & 0xFF); + } + else if (attributeId == ColorControl::Attributes::CurrentHue::Id) + { + hsv.h = *value; + } + else if (attributeId == ColorControl::Attributes::CurrentSaturation::Id) + { + hsv.s = *value; + } + ChipLogProgress(Zcl, "New HSV color: hue = %u| saturation = %u", hsv.h, hsv.s); + GetAppTask().SetInitiateAction(PWMDevice::COLOR_ACTION_HSV, static_cast(AppEvent::kEventType_Lighting), + (uint8_t *) &hsv); + } + /* Temperature Mireds color space */ + else if (attributeId == ColorControl::Attributes::ColorTemperatureMireds::Id) + { + ChipLogProgress(Zcl, "New Temperature Mireds color = %u", *(uint16_t *) value); + GetAppTask().SetInitiateAction(PWMDevice::COLOR_ACTION_CT, static_cast(AppEvent::kEventType_Lighting), value); + } + else + { + ChipLogProgress(Zcl, "Ignore ColorControl attribute (%u) that is not currently processed!", attributeId); + } + } } /** @brief OnOff Cluster Init @@ -75,7 +136,7 @@ void emberAfOnOffClusterInitCallback(EndpointId endpoint) if (status == EMBER_ZCL_STATUS_SUCCESS) { // Set actual state to stored before reboot - LightingMgr().Set(storedValue); + GetAppTask().GetPWMDevice().Set(storedValue); } GetAppTask().UpdateClusterState(); diff --git a/examples/ota-requestor-app/telink/src/AppTask.cpp b/examples/ota-requestor-app/telink/src/AppTask.cpp index 15f3c6f17ff274..af2bc6c906a811 100644 --- a/examples/ota-requestor-app/telink/src/AppTask.cpp +++ b/examples/ota-requestor-app/telink/src/AppTask.cpp @@ -52,6 +52,24 @@ #include +#if CONFIG_CHIP_LIB_SHELL +#include +#include + +static int cmd_telink_reboot(const struct shell * shell, size_t argc, char ** argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + shell_print(shell, "Performing board reboot..."); + sys_reboot(); +} + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_telink, SHELL_CMD(reboot, NULL, "Reboot board command", cmd_telink_reboot), + SHELL_SUBCMD_SET_END); +SHELL_CMD_REGISTER(telink, &sub_telink, "Telink commands", NULL); +#endif // CONFIG_CHIP_LIB_SHELL + LOG_MODULE_DECLARE(app); namespace { diff --git a/examples/platform/telink/util/include/ColorFormat.h b/examples/platform/telink/util/include/ColorFormat.h new file mode 100755 index 00000000000000..1ad0cb0e75f4f4 --- /dev/null +++ b/examples/platform/telink/util/include/ColorFormat.h @@ -0,0 +1,50 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +#pragma once + +#include + +struct RgbColor_t +{ + uint8_t r; + uint8_t g; + uint8_t b; +}; + +struct HsvColor_t +{ + uint8_t h; + uint8_t s; + uint8_t v; +}; + +struct XyColor_t +{ + uint16_t x; + uint16_t y; +}; + +struct CtColor_t +{ + uint16_t ctMireds; +}; + +RgbColor_t XYToRgb(uint8_t Level, uint16_t currentX, uint16_t currentY); +RgbColor_t HsvToRgb(HsvColor_t hsv); +RgbColor_t CTToRgb(CtColor_t ct); diff --git a/examples/lighting-app/telink/include/LightingManager.h b/examples/platform/telink/util/include/PWMDevice.h similarity index 68% rename from examples/lighting-app/telink/include/LightingManager.h rename to examples/platform/telink/util/include/PWMDevice.h index 5ac8187f001e7f..8950884f3c106e 100644 --- a/examples/lighting-app/telink/include/LightingManager.h +++ b/examples/platform/telink/util/include/PWMDevice.h @@ -1,5 +1,4 @@ /* - * * Copyright (c) 2022 Project CHIP Authors * All rights reserved. * @@ -18,14 +17,13 @@ #pragma once -#include "AppEvent.h" - #include #include #include +#include -class LightingManager +class PWMDevice { public: enum Action_t : uint8_t @@ -33,6 +31,9 @@ class LightingManager ON_ACTION = 0, OFF_ACTION, LEVEL_ACTION, + COLOR_ACTION_XY, + COLOR_ACTION_HSV, + COLOR_ACTION_CT, INVALID_ACTION }; @@ -43,36 +44,27 @@ class LightingManager kState_Off, }; - using LightingCallback_fn = void (*)(Action_t, int32_t); + using PWMCallback_fn = void (*)(Action_t, int32_t); - CHIP_ERROR Init(const device * pwmDevice, uint32_t pwmChannel, uint8_t aMinLevel, uint8_t aMaxLevel, uint8_t aDefaultLevel = 0); + CHIP_ERROR Init(const pwm_dt_spec * pwmDevice, uint8_t aMinLevel, uint8_t aMaxLevel, uint8_t aDefaultLevel = 0); void Set(bool aOn); bool IsTurnedOn() const { return mState == kState_On; } uint8_t GetLevel() const { return mLevel; } uint8_t GetMinLevel() const { return mMinLevel; } uint8_t GetMaxLevel() const { return mMaxLevel; } - bool InitiateAction(Action_t aAction, int32_t aActor, uint8_t size, uint8_t * value); - void SetCallbacks(LightingCallback_fn aActionInitiated_CB, LightingCallback_fn aActionCompleted_CB); + bool InitiateAction(Action_t aAction, int32_t aActor, uint8_t * value); + void SetCallbacks(PWMCallback_fn aActionInitiated_CB, PWMCallback_fn aActionCompleted_CB); private: - friend LightingManager & LightingMgr(); State_t mState; uint8_t mMinLevel; uint8_t mMaxLevel; uint8_t mLevel; - const device * mPwmDevice; - uint32_t mPwmChannel; + const pwm_dt_spec * mPwmDevice; - LightingCallback_fn mActionInitiated_CB; - LightingCallback_fn mActionCompleted_CB; + PWMCallback_fn mActionInitiated_CB; + PWMCallback_fn mActionCompleted_CB; void SetLevel(uint8_t aLevel); void UpdateLight(); - - static LightingManager sLight; }; - -inline LightingManager & LightingMgr(void) -{ - return LightingManager::sLight; -} diff --git a/examples/platform/telink/util/src/ColorFormat.cpp b/examples/platform/telink/util/src/ColorFormat.cpp new file mode 100644 index 00000000000000..b68b6ca926e009 --- /dev/null +++ b/examples/platform/telink/util/src/ColorFormat.cpp @@ -0,0 +1,187 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * 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 "ColorFormat.h" + +#include + +// define a clamp macro to substitute the std::clamp macro which is available from C++17 onwards +#define clamp(a, min, max) ((a) < (min) ? (min) : ((a) > (max) ? (max) : (a))) + +RgbColor_t HsvToRgb(HsvColor_t hsv) +{ + RgbColor_t rgb; + + uint8_t region, p, q, t; + uint32_t h, s, v, remainder; + + if (hsv.s == 0) + { + rgb.r = rgb.g = rgb.b = hsv.v; + } + else + { + h = hsv.h; + s = hsv.s; + v = hsv.v; + + region = h / 43; + remainder = (h - (region * 43)) * 6; + p = (v * (255 - s)) >> 8; + q = (v * (255 - ((s * remainder) >> 8))) >> 8; + t = (v * (255 - ((s * (255 - remainder)) >> 8))) >> 8; + switch (region) + { + case 0: + rgb.r = v, rgb.g = t, rgb.b = p; + break; + case 1: + rgb.r = q, rgb.g = v, rgb.b = p; + break; + case 2: + rgb.r = p, rgb.g = v, rgb.b = t; + break; + case 3: + rgb.r = p, rgb.g = q, rgb.b = v; + break; + case 4: + rgb.r = t, rgb.g = p, rgb.b = v; + break; + case 5: + default: + rgb.r = v, rgb.g = p, rgb.b = q; + break; + } + } + + return rgb; +} + +RgbColor_t XYToRgb(uint8_t Level, uint16_t currentX, uint16_t currentY) +{ + // convert xyY color space to RGB + + // https://www.easyrgb.com/en/math.php + // https://en.wikipedia.org/wiki/SRGB + // refer https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_xy_chromaticity_diagram_and_the_CIE_xyY_color_space + + // The currentX/currentY attribute contains the current value of the normalized chromaticity value of x/y. + // The value of x/y shall be related to the currentX/currentY attribute by the relationship + // x = currentX/65536 + // y = currentY/65536 + // z = 1-x-y + + RgbColor_t rgb; + + float x, y, z; + float X, Y, Z; + float r, g, b; + + x = ((float) currentX) / 65535.0f; + y = ((float) currentY) / 65535.0f; + + z = 1.0f - x - y; + + // Calculate XYZ values + + // Y - given brightness in 0 - 1 range + Y = ((float) Level) / 254.0f; + X = (Y / y) * x; + Z = (Y / y) * z; + + // X, Y and Z input refer to a D65/2° standard illuminant. + // sR, sG and sB (standard RGB) output range = 0 ÷ 255 + // convert XYZ to RGB - CIE XYZ to sRGB + X = X / 100.0f; + Y = Y / 100.0f; + Z = Z / 100.0f; + + r = (X * 3.2406f) - (Y * 1.5372f) - (Z * 0.4986f); + g = -(X * 0.9689f) + (Y * 1.8758f) + (Z * 0.0415f); + b = (X * 0.0557f) - (Y * 0.2040f) + (Z * 1.0570f); + + // apply gamma 2.2 correction + r = (r <= 0.0031308f ? 12.92f * r : (1.055f) * pow(r, (1.0f / 2.4f)) - 0.055f); + g = (g <= 0.0031308f ? 12.92f * g : (1.055f) * pow(g, (1.0f / 2.4f)) - 0.055f); + b = (b <= 0.0031308f ? 12.92f * b : (1.055f) * pow(b, (1.0f / 2.4f)) - 0.055f); + + // Round off + r = clamp(r, 0, 1); + g = clamp(g, 0, 1); + b = clamp(b, 0, 1); + + // these rgb values are in the range of 0 to 1, convert to limit of HW specific LED + rgb.r = (uint8_t)(r * 255); + rgb.g = (uint8_t)(g * 255); + rgb.b = (uint8_t)(b * 255); + + return rgb; +} + +RgbColor_t CTToRgb(CtColor_t ct) +{ + RgbColor_t rgb; + float r, g, b; + + // Algorithm credits to Tanner Helland: https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html + + // Convert Mireds to centiKelvins. k = 1,000,000/mired + float ctCentiKelvin = 10000 / ct.ctMireds; + + // Red + if (ctCentiKelvin <= 66) + { + r = 255; + } + else + { + r = 329.698727446f * pow(ctCentiKelvin - 60, -0.1332047592f); + } + + // Green + if (ctCentiKelvin <= 66) + { + g = 99.4708025861f * log(ctCentiKelvin) - 161.1195681661f; + } + else + { + g = 288.1221695283f * pow(ctCentiKelvin - 60, -0.0755148492f); + } + + // Blue + if (ctCentiKelvin >= 66) + { + b = 255; + } + else + { + if (ctCentiKelvin <= 19) + { + b = 0; + } + else + { + b = 138.5177312231 * log(ctCentiKelvin - 10) - 305.0447927307; + } + } + rgb.r = (uint8_t) clamp(r, 0, 255); + rgb.g = (uint8_t) clamp(g, 0, 255); + rgb.b = (uint8_t) clamp(b, 0, 255); + + return rgb; +} diff --git a/examples/lighting-app/telink/src/LightingManager.cpp b/examples/platform/telink/util/src/PWMDevice.cpp similarity index 70% rename from examples/lighting-app/telink/src/LightingManager.cpp rename to examples/platform/telink/util/src/PWMDevice.cpp index 1c0cea74bb5cce..6a23b399be7576 100644 --- a/examples/lighting-app/telink/src/LightingManager.cpp +++ b/examples/platform/telink/util/src/PWMDevice.cpp @@ -1,5 +1,4 @@ /* - * * Copyright (c) 2022 Project CHIP Authors * All rights reserved. * @@ -16,7 +15,7 @@ * limitations under the License. */ -#include "LightingManager.h" +#include "PWMDevice.h" #include "AppConfig.h" @@ -28,24 +27,20 @@ LOG_MODULE_DECLARE(app); -LightingManager LightingManager::sLight; - -CHIP_ERROR LightingManager::Init(const device * pwmDevice, uint32_t pwmChannel, uint8_t aMinLevel, uint8_t aMaxLevel, - uint8_t aDefaultLevel) +CHIP_ERROR PWMDevice::Init(const pwm_dt_spec * pwmDevice, uint8_t aMinLevel, uint8_t aMaxLevel, uint8_t aDefaultLevel) { // We use a gpioPin instead of a LEDWidget here because we want to use PWM // and other features instead of just on/off. - mState = kState_On; - mMinLevel = aMinLevel; - mMaxLevel = aMaxLevel; - mLevel = aDefaultLevel; - mPwmDevice = pwmDevice; - mPwmChannel = pwmChannel; + mState = kState_On; + mMinLevel = aMinLevel; + mMaxLevel = aMaxLevel; + mLevel = aDefaultLevel; + mPwmDevice = pwmDevice; - if (!device_is_ready(mPwmDevice)) + if (!device_is_ready(mPwmDevice->dev)) { - LOG_ERR("PWM device %s is not ready", mPwmDevice->name); + LOG_ERR("PWM device %s is not ready", mPwmDevice->dev->name); return CHIP_ERROR_INCORRECT_STATE; } @@ -53,13 +48,13 @@ CHIP_ERROR LightingManager::Init(const device * pwmDevice, uint32_t pwmChannel, return CHIP_NO_ERROR; } -void LightingManager::SetCallbacks(LightingCallback_fn aActionInitiated_CB, LightingCallback_fn aActionCompleted_CB) +void PWMDevice::SetCallbacks(PWMCallback_fn aActionInitiated_CB, PWMCallback_fn aActionCompleted_CB) { mActionInitiated_CB = aActionInitiated_CB; mActionCompleted_CB = aActionCompleted_CB; } -bool LightingManager::InitiateAction(Action_t aAction, int32_t aActor, uint8_t size, uint8_t * value) +bool PWMDevice::InitiateAction(Action_t aAction, int32_t aActor, uint8_t * value) { bool action_initiated = false; State_t new_state; @@ -75,7 +70,8 @@ bool LightingManager::InitiateAction(Action_t aAction, int32_t aActor, uint8_t s action_initiated = true; new_state = kState_Off; } - else if (aAction == LEVEL_ACTION && *value != mLevel) + else if ((aAction == LEVEL_ACTION || aAction == COLOR_ACTION_XY || aAction == COLOR_ACTION_HSV || aAction == COLOR_ACTION_CT) && + *value != mLevel) { action_initiated = true; if (*value == 0) @@ -99,7 +95,7 @@ bool LightingManager::InitiateAction(Action_t aAction, int32_t aActor, uint8_t s { Set(new_state == kState_On); } - else if (aAction == LEVEL_ACTION) + else if (aAction == LEVEL_ACTION || aAction == COLOR_ACTION_XY || aAction == COLOR_ACTION_HSV || aAction == COLOR_ACTION_CT) { SetLevel(*value); } @@ -113,24 +109,25 @@ bool LightingManager::InitiateAction(Action_t aAction, int32_t aActor, uint8_t s return action_initiated; } -void LightingManager::SetLevel(uint8_t aLevel) +void PWMDevice::SetLevel(uint8_t aLevel) { LOG_INF("Setting brightness level to %u", aLevel); mLevel = aLevel; UpdateLight(); } -void LightingManager::Set(bool aOn) +void PWMDevice::Set(bool aOn) { mState = aOn ? kState_On : kState_Off; UpdateLight(); } -void LightingManager::UpdateLight() +void PWMDevice::UpdateLight() { constexpr uint32_t kPwmWidthUs = 20000u; const uint8_t maxEffectiveLevel = mMaxLevel - mMinLevel; const uint8_t effectiveLevel = mState == kState_On ? chip::min(mLevel - mMinLevel, maxEffectiveLevel) : 0; - pwm_set(mPwmDevice, mPwmChannel, PWM_USEC(kPwmWidthUs), PWM_USEC(kPwmWidthUs * effectiveLevel / maxEffectiveLevel), 0); + pwm_set(mPwmDevice->dev, mPwmDevice->channel, PWM_USEC(kPwmWidthUs), PWM_USEC(kPwmWidthUs * effectiveLevel / maxEffectiveLevel), + 0); } diff --git a/examples/thermostat/telink/src/AppTask.cpp b/examples/thermostat/telink/src/AppTask.cpp index 96caac3e06e2bd..28df4af294a3bc 100644 --- a/examples/thermostat/telink/src/AppTask.cpp +++ b/examples/thermostat/telink/src/AppTask.cpp @@ -44,6 +44,24 @@ #include +#if CONFIG_CHIP_LIB_SHELL +#include +#include + +static int cmd_telink_reboot(const struct shell * shell, size_t argc, char ** argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + shell_print(shell, "Performing board reboot..."); + sys_reboot(); +} + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_telink, SHELL_CMD(reboot, NULL, "Reboot board command", cmd_telink_reboot), + SHELL_SUBCMD_SET_END); +SHELL_CMD_REGISTER(telink, &sub_telink, "Telink commands", NULL); +#endif // CONFIG_CHIP_LIB_SHELL + LOG_MODULE_DECLARE(app); using namespace ::chip;