diff --git a/silabs_examples/sl-newLight/efr32/BUILD.gn b/silabs_examples/sl-newLight/efr32/BUILD.gn index 7b0474b2988ee5..421f7bfc2711fa 100644 --- a/silabs_examples/sl-newLight/efr32/BUILD.gn +++ b/silabs_examples/sl-newLight/efr32/BUILD.gn @@ -60,6 +60,9 @@ declare_args() { # Enables LCD Qr Code on supported devices show_qr_code = true + + # Enables RGB LEDs on supported devices + rgb_led = false } # Sanity check @@ -75,9 +78,16 @@ if (efr32_board == "BRD4166A" || efr32_board == "BRD4180A") { show_qr_code = false } +if (efr32_board == "BRD4166A" || efr32_board == "BRD4160A" || efr32_board == "BRD2207A") { + rgb_led = true +} + # Enables LCD on supported devices lcd_on = show_qr_code +# Enables RGB LEDs on supported devices +rgb_on = rgb_led + # WiFi settings if (chip_enable_wifi) { wifi_sdk_dir = "${chip_root}/third_party/efr32_sdk/repo/matter/wifi" @@ -109,6 +119,7 @@ efr32_sdk("sdk") { include_dirs = [ "${chip_root}/src/platform/EFR32", "${efr32_project_dir}/include", + "${efr32_project_dir}/light_modules", "${examples_plat_dir}", "${chip_root}/src/lib", ] @@ -168,6 +179,7 @@ efr32_executable("newLight_app") { "src/AppTask.cpp", "src/ZclCallbacks.cpp", "src/main.cpp", + "light_modules/LightingManager.cpp", ] if (chip_enable_pw_rpc || chip_build_libshell || enable_openthread_cli) { @@ -245,6 +257,11 @@ efr32_executable("newLight_app") { } } + if (rgb_on) { + sources += [ "light_modules/led_widget_rgb.cpp" ] + defines += [ "RGB_LED_ENABLED" ] + } + if (chip_enable_pw_rpc) { defines += [ "PW_RPC_ENABLED", diff --git a/silabs_examples/sl-newLight/efr32/include/AppConfig.h b/silabs_examples/sl-newLight/efr32/include/AppConfig.h index b4bcbd1a834447..7d2ae22dce2f90 100644 --- a/silabs_examples/sl-newLight/efr32/include/AppConfig.h +++ b/silabs_examples/sl-newLight/efr32/include/AppConfig.h @@ -21,9 +21,9 @@ #include "efr32_utils.h" -// ---- Template Example App Config ---- +// ---- newLight Example App Config ---- -#define APP_TASK_NAME "TEMPLATE" +#define APP_TASK_NAME "NEWLIGHT" // Time it takes in ms for the simulated actuator to move from one // state to another. diff --git a/silabs_examples/sl-newLight/efr32/include/AppTask.h b/silabs_examples/sl-newLight/efr32/include/AppTask.h index 79c28fe51e6d50..d465ea81b1c7e1 100644 --- a/silabs_examples/sl-newLight/efr32/include/AppTask.h +++ b/silabs_examples/sl-newLight/efr32/include/AppTask.h @@ -24,6 +24,7 @@ #include "AppEvent.h" #include "FreeRTOS.h" +#include "LightingManager.h" #include "sl_simple_button_instances.h" #include "timers.h" // provides FreeRTOS timer support @@ -45,6 +46,7 @@ class AppTask CHIP_ERROR StartAppTask(); static void AppTaskMain(void * pvParameter); + void PostLightActionRequest(int32_t aActor, LightingManager::Action_t aAction); void PostEvent(const AppEvent * event); void ButtonEventHandler(const sl_button_t * buttonHandle, uint8_t btnAction); @@ -54,12 +56,17 @@ class AppTask CHIP_ERROR Init(); + static void ActionInitiated(LightingManager::Action_t aAction, int32_t aActor); + static void ActionCompleted(LightingManager::Action_t aAction); + static void ActionChangeLight(LightingManager::Action_t aAction, uint16_t endpoint, uint8_t value); + void CancelTimer(void); void DispatchEvent(AppEvent * event); static void FunctionTimerEventHandler(AppEvent * aEvent); static void FunctionHandler(AppEvent * aEvent); + static void LightActionEventHandler(AppEvent * aEvent); static void TimerEventHandler(TimerHandle_t xTimer); static void UpdateClusterState(intptr_t context); diff --git a/silabs_examples/sl-newLight/efr32/light_modules/LightingManager.cpp b/silabs_examples/sl-newLight/efr32/light_modules/LightingManager.cpp new file mode 100644 index 00000000000000..3fbfc6a8405482 --- /dev/null +++ b/silabs_examples/sl-newLight/efr32/light_modules/LightingManager.cpp @@ -0,0 +1,256 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2019 Google LLC. + * 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 "LightingManager.h" + +#include "AppConfig.h" +#include "AppTask.h" +#include + +#include + +using namespace chip; +using namespace ::chip::DeviceLayer; + +LightingManager LightingManager::sLight; + +TimerHandle_t sLightTimer; + +CHIP_ERROR LightingManager::Init() +{ + // Create FreeRTOS sw timer for light timer. + sLightTimer = xTimerCreate("lightTmr", // Just a text name, not used by the RTOS kernel + 1, // == default timer period (mS) + false, // no timer reload (==one-shot) + (void *) this, // init timer id = light obj context + TimerEventHandler // timer callback handler + ); + + if (sLightTimer == NULL) + { + EFR32_LOG("sLightTimer timer create failed"); + return APP_ERROR_CREATE_TIMER_FAILED; + } + + bool currentLedState; + // read current on/off value on endpoint one. + chip::DeviceLayer::PlatformMgr().LockChipStack(); + OnOffServer::Instance().getOnOffValue(1, ¤tLedState); + chip::DeviceLayer::PlatformMgr().UnlockChipStack(); + + mState = currentLedState ? kState_OnCompleted : kState_OffCompleted; + mAutoTurnOffTimerArmed = false; + mAutoTurnOff = false; + mAutoTurnOffDuration = 0; + + return CHIP_NO_ERROR; +} + +void LightingManager::SetCallbacks(Callback_fn_initiated aActionInitiated_CB, Callback_fn_completed aActionCompleted_CB) +{ + mActionInitiated_CB = aActionInitiated_CB; + mActionCompleted_CB = aActionCompleted_CB; +} + +void LightingManager::SetLightCallbacks(Callback_fn_set_light aChangeLight_CB) +{ + mChangeLight_CB = aChangeLight_CB; +} + +bool LightingManager::IsActionInProgress() +{ + return (mState == kState_OffInitiated || mState == kState_OnInitiated); +} + +bool LightingManager::IsLightOn() +{ + return (mState == kState_OnCompleted); +} + +void LightingManager::EnableAutoTurnOff(bool aOn) +{ + mAutoTurnOff = aOn; +} + +void LightingManager::SetAutoTurnOffDuration(uint32_t aDurationInSecs) +{ + mAutoTurnOffDuration = aDurationInSecs; +} + +bool LightingManager::InitiateAction(int32_t aActor, Action_t aAction) +{ + bool action_initiated = false; + State_t new_state; + + // Initiate Turn On/Off Action only when the previous one is complete. + if (mState == kState_OffCompleted && aAction == ON_ACTION) + { + action_initiated = true; + + new_state = kState_OnInitiated; + } + else if (mState == kState_OnCompleted && aAction == OFF_ACTION) + { + action_initiated = true; + + new_state = kState_OffInitiated; + } + + if (action_initiated) + { + if (mAutoTurnOffTimerArmed && new_state == kState_OffInitiated) + { + // If auto turn off timer has been armed and someone initiates turning off, + // cancel the timer and continue as normal. + mAutoTurnOffTimerArmed = false; + + CancelTimer(); + } + + StartTimer(ACTUATOR_MOVEMENT_PERIOS_MS); + + // Since the timer started successfully, update the state and trigger callback + mState = new_state; + + if (mActionInitiated_CB) + { + mActionInitiated_CB(aAction, aActor); + } + } + + return action_initiated; +} + +bool LightingManager::InitiateActionLight(int32_t aActor, Action_t aAction, uint16_t endpoint, uint8_t value) +{ + bool action_initiated = false; + bool onoff_complete = (mState == kState_OnCompleted || mState == kState_OffCompleted); + bool led_action = ((aAction == MOVE_TO_LEVEL) || (aAction == MOVE_TO_HUE) || (aAction == MOVE_TO_SAT)); + + if (onoff_complete && led_action) + { + action_initiated = true; + mChangeLight_CB(aAction, endpoint, value); + } + + return action_initiated; +} + +void LightingManager::StartTimer(uint32_t aTimeoutMs) +{ + if (xTimerIsTimerActive(sLightTimer)) + { + EFR32_LOG("app timer already started!"); + CancelTimer(); + } + + // timer is not active, change its period to required value (== restart). + // FreeRTOS- Block for a maximum of 100 ticks if the change period command + // cannot immediately be sent to the timer command queue. + if (xTimerChangePeriod(sLightTimer, (aTimeoutMs / portTICK_PERIOD_MS), 100) != pdPASS) + { + EFR32_LOG("sLightTimer timer start() failed"); + appError(APP_ERROR_START_TIMER_FAILED); + } +} + +void LightingManager::CancelTimer(void) +{ + if (xTimerStop(sLightTimer, 0) == pdFAIL) + { + EFR32_LOG("sLightTimer stop() failed"); + appError(APP_ERROR_STOP_TIMER_FAILED); + } +} + +void LightingManager::TimerEventHandler(TimerHandle_t xTimer) +{ + // Get light obj context from timer id. + LightingManager * light = static_cast(pvTimerGetTimerID(xTimer)); + + // The timer event handler will be called in the context of the timer task + // once sLightTimer expires. Post an event to apptask queue with the actual handler + // so that the event can be handled in the context of the apptask. + AppEvent event; + event.Type = AppEvent::kEventType_Timer; + event.TimerEvent.Context = light; + if (light->mAutoTurnOffTimerArmed) + { + event.Handler = AutoTurnOffTimerEventHandler; + } + else + { + event.Handler = ActuatorMovementTimerEventHandler; + } + GetAppTask().PostEvent(&event); +} + +void LightingManager::AutoTurnOffTimerEventHandler(AppEvent * aEvent) +{ + LightingManager * light = static_cast(aEvent->TimerEvent.Context); + int32_t actor = 0; + + // Make sure auto turn off timer is still armed. + if (!light->mAutoTurnOffTimerArmed) + { + return; + } + + light->mAutoTurnOffTimerArmed = false; + + EFR32_LOG("Auto Turn Off has been triggered!"); + + light->InitiateAction(actor, OFF_ACTION); +} + +void LightingManager::ActuatorMovementTimerEventHandler(AppEvent * aEvent) +{ + Action_t actionCompleted = INVALID_ACTION; + + LightingManager * light = static_cast(aEvent->TimerEvent.Context); + + if (light->mState == kState_OffInitiated) + { + light->mState = kState_OffCompleted; + actionCompleted = OFF_ACTION; + } + else if (light->mState == kState_OnInitiated) + { + light->mState = kState_OnCompleted; + actionCompleted = ON_ACTION; + } + + if (actionCompleted != INVALID_ACTION) + { + if (light->mActionCompleted_CB) + { + light->mActionCompleted_CB(actionCompleted); + } + + if (light->mAutoTurnOff && actionCompleted == ON_ACTION) + { + // Start the timer for auto turn off + light->StartTimer(light->mAutoTurnOffDuration * 1000); + + light->mAutoTurnOffTimerArmed = true; + + EFR32_LOG("Auto Turn off enabled. Will be triggered in %u seconds", light->mAutoTurnOffDuration); + } + } +} diff --git a/silabs_examples/sl-newLight/efr32/light_modules/LightingManager.h b/silabs_examples/sl-newLight/efr32/light_modules/LightingManager.h new file mode 100644 index 00000000000000..8d7a8ee0b34774 --- /dev/null +++ b/silabs_examples/sl-newLight/efr32/light_modules/LightingManager.h @@ -0,0 +1,93 @@ +/* + * + * Copyright (c) 2019 Google LLC. + * 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 +#include + +#include "AppEvent.h" + +#include "FreeRTOS.h" +#include "timers.h" // provides FreeRTOS timer support + +#include + +class LightingManager +{ +public: + enum Action_t + { + OFF_ACTION = 0, + ON_ACTION, + MOVE_TO_LEVEL, + MOVE_TO_HUE, + MOVE_TO_SAT, + + IGNORE_ACTION, + INVALID_ACTION + } Action; + + enum State_t + { + kState_OffInitiated = 0, + kState_OffCompleted, + kState_OnInitiated, + kState_OnCompleted, + } State; + + CHIP_ERROR Init(); + bool IsLightOn(); + void EnableAutoTurnOff(bool aOn); + void SetAutoTurnOffDuration(uint32_t aDurationInSecs); + bool IsActionInProgress(); + bool InitiateAction(int32_t aActor, Action_t aAction); + bool InitiateActionLight(int32_t aActor, Action_t aAction, uint16_t endpoint, uint8_t value); + + typedef void (*Callback_fn_initiated)(Action_t, int32_t aActor); + typedef void (*Callback_fn_completed)(Action_t); + typedef void (*Callback_fn_set_light)(Action_t, uint16_t endpoint, uint8_t value); + void SetCallbacks(Callback_fn_initiated aActionInitiated_CB, Callback_fn_completed aActionCompleted_CB); + void SetLightCallbacks(Callback_fn_set_light aChangeLight_CB); + +private: + friend LightingManager & LightMgr(void); + State_t mState; + + Callback_fn_initiated mActionInitiated_CB; + Callback_fn_completed mActionCompleted_CB; + Callback_fn_set_light mChangeLight_CB; + + bool mAutoTurnOff; + uint32_t mAutoTurnOffDuration; + bool mAutoTurnOffTimerArmed; + + void CancelTimer(void); + void StartTimer(uint32_t aTimeoutMs); + + static void TimerEventHandler(TimerHandle_t xTimer); + static void AutoTurnOffTimerEventHandler(AppEvent * aEvent); + static void ActuatorMovementTimerEventHandler(AppEvent * aEvent); + + static LightingManager sLight; +}; + +inline LightingManager & LightMgr(void) +{ + return LightingManager::sLight; +} diff --git a/silabs_examples/sl-newLight/efr32/light_modules/led_widget_rgb.cpp b/silabs_examples/sl-newLight/efr32/light_modules/led_widget_rgb.cpp new file mode 100644 index 00000000000000..d70710af292afd --- /dev/null +++ b/silabs_examples/sl-newLight/efr32/light_modules/led_widget_rgb.cpp @@ -0,0 +1,375 @@ +/***************************************************************************//** + * @file + * @brief RGB Led controller. + ******************************************************************************* + * # License + * Copyright 2018 Silicon Laboratories Inc. www.silabs.com + ******************************************************************************* + * + * SPDX-License-Identifier: Zlib + * + * The licensor of this software is Silicon Laboratories Inc. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + ******************************************************************************/ + +#include "led_widget_rgb.h" + +// cmath is needed only for HueToRGB. +#include +#include +#include + +#include "AppConfig.h" +#include "em_gpio.h" +#include "sl_led.h" +#include "sl_pwm_led.h" +#include "sl_simple_rgb_pwm_led.h" + + +/** + * @brief Red led instance. + */ +sl_led_pwm_t led_pwm_red = { + .set_color = sl_pwm_led_set_color, + .get_color = sl_pwm_led_get_color, + .channel = 0, + .port = gpioPortD, + .pin = 11, + .location = 19, + .level = INITIAL_RGB, + .polarity = LED_RGB_POLARITY, + .state = LED_RGB_INITIAL_STATE, + .timer = TIMER1, + .frequency = PWM_FREQUENCY, + .resolution = PWM_RESOLUTION_LEVELS, +}; + +/** + * @brief Green led instance. + */ +sl_led_pwm_t led_pwm_green = { + .set_color = sl_pwm_led_set_color, + .get_color = sl_pwm_led_get_color, + .channel = 1, + .port = gpioPortD, + .pin = 12, + .location = 19, + .level = INITIAL_RGB, + .polarity = LED_RGB_POLARITY, + .state = LED_RGB_INITIAL_STATE, + .timer = TIMER1, + .frequency = PWM_FREQUENCY, + .resolution = PWM_RESOLUTION_LEVELS, +}; + +/** + * @brief Blue led instance. + */ +sl_led_pwm_t led_pwm_blue = { + .set_color = sl_pwm_led_set_color, + .get_color = sl_pwm_led_get_color, + .channel = 2, + .port = gpioPortD, + .pin = 13, + .location = 19, + .level = INITIAL_RGB, + .polarity = LED_RGB_POLARITY, + .state = LED_RGB_INITIAL_STATE, + .timer = TIMER1, + .frequency = PWM_FREQUENCY, + .resolution = PWM_RESOLUTION_LEVELS, +}; + +/** + * @brief Context of the envelopping RGB led instance. + */ +sl_simple_rgb_pwm_led_context_t led_rgb_context = { + .red = &led_pwm_red, + .green = &led_pwm_green, + .blue = &led_pwm_blue, + .timer = TIMER1, + .frequency = PWM_FREQUENCY, + .resolution = PWM_RESOLUTION_LEVELS, + .state = LED_RGB_INITIAL_STATE, +}; + +/** + * @brief Structure holding the driver functions for the LED. + */ +sl_led_t sl_led_rgb = { + .context = &led_rgb_context, + .init = sl_simple_rgb_pwm_led_init, + .turn_on = sl_simple_rgb_pwm_led_turn_on, + .turn_off = sl_simple_rgb_pwm_led_turn_off, + .toggle = sl_simple_rgb_pwm_led_toggle, + .get_state = sl_simple_rgb_pwm_led_get_state, +}; + +/** + * @brief The envelopping RGB led instance. + */ +const sl_led_rgb_pwm_t sl_led_rgb_pwm = { + .led_common = sl_led_rgb, + .set_rgb_color = sl_simple_rgb_pwm_led_set_color, + .get_rgb_color = sl_simple_rgb_pwm_led_get_color, +}; + + +void LEDWidgetRGB::InitGpioRGB() +{ + sl_pwm_led_init(&led_pwm_red); + sl_pwm_led_init(&led_pwm_green); + sl_pwm_led_init(&led_pwm_blue); + sl_led_init(&sl_led_rgb); +} + + +void LEDWidgetRGB::Init(const sl_led_rgb_pwm_t* led) +{ + /* 1. Initialize the value of class variables. */ + sl_simple_rgb_pwm_led_context_t* led_context; + led_context = reinterpret_cast(led->led_common.context); + this->level_resolution_ = led_context->resolution; + this->current_hue_ = INITIAL_HUE; + this->current_saturation_ = INITIAL_SATURATION; + this->led_rgb_ = led; + + /* 2. Set the RGB values to INITIAL_RGB. This does not affect the driver. */ + for (uint8_t i = 0; i < 3; i++) + { + this->current_rgb_[i] = INITIAL_RGB; + } + + /* 3. Initialize the value of the base class member variables. */ + LEDWidget::Init(&(led->led_common)); + + /* 4. Turn on the RGB LED pins. */ + GPIO_PinOutSet(gpioPortJ, 14); + GPIO_PinOutSet(gpioPortI, 0); + GPIO_PinOutSet(gpioPortI, 1); + GPIO_PinOutSet(gpioPortI, 2); + GPIO_PinOutSet(gpioPortI, 3); +} + + +void LEDWidgetRGB::Set(bool state) +{ + this->Set(state, LED_ENDPOINT_DEFAULT); +} + + +void LEDWidgetRGB::Set(bool state, uint16_t led_endpoint) +{ + uint8_t rgb[3] = {0, 0, 0}; + bool save_values = false; + + if (state == true) + { + save_values = true; + bool turned_on = false; + + for (int i = 0; i < 3; i++) + { + if (this->current_rgb_[i] > 1) + { + turned_on = true; + } + } + + if (turned_on == true) + { + for (int i = 0; i < 3; i++) + { + rgb[i] = current_rgb_[i]; + } + } + else + { + for (int i = 0; i < 3; i++) + { + rgb[i] = PWM_MAX_VALUE; + } + } + } + + this->SetRGB(rgb, led_endpoint, save_values); +} + + +void LEDWidgetRGB::SetLevel(uint8_t level, uint16_t led_endpoint) +{ + if(led_endpoint == 1) + { + /* 1. Adjust the color level. */ + // The Levelcontrol cluster takes values from 0 to 254. + // However, the PWM driver accepts values from 0 to 100. + // See appclusters, section 1.6.6.1. + uint8_t adjusted_level = (level * PWM_MAX_VALUE) / ATTRIBUTE_LEVEL_MAX; + + /* 2. Set the colors. */ + uint8_t rgb[3] = {adjusted_level, adjusted_level, adjusted_level}; + this->SetRGB(rgb, led_endpoint, true); + } +} + + +void LEDWidgetRGB::GetLevel(uint8_t* rgb, uint32_t size) +{ + /* 1. Create a buffer and assign it to the pointer. */ + if (rgb == nullptr || rgb == NULL || size < 3) + { + EFR32_LOG(" Error in led_widget_rgb.cpp. Array refused."); + return; + } + + /* 2. Get the current RGB value from the driver. */ + uint16_t red = 0; + uint16_t green = 0; + uint16_t blue = 0; + void* led_rgb_context = this->led_rgb_->led_common.context; + this->led_rgb_->get_rgb_color(led_rgb_context, &red, &green, &blue); + + /* 3. Assign the colors in the array. */ + rgb[0] = static_cast(red); + rgb[1] = static_cast(green); + rgb[2] = static_cast(blue); +} + + +void LEDWidgetRGB::SetHue(uint8_t hue, uint16_t led_endpoint) +{ + // Hue takes a value [0, 360] and is expressed in degrees. + // See appclusters, section 3.2.7.1. + this->current_hue_ = static_cast((hue * 360) / ATTRIBUTE_LEVEL_MAX); + this->SetColor(this->current_hue_, this->current_saturation_, led_endpoint); +} + + +void LEDWidgetRGB::SetSaturation(uint8_t sat, uint16_t led_endpoint) +{ + // Saturation takes a value [0, 1] representing a percentage. + // The Color Control cluster accepts saturation values 0 to 254. + // See appclusters, section 3.2.7.2. + this->current_saturation_ = sat/254.0; + this->SetColor(this->current_hue_, this->current_saturation_, led_endpoint); +} + + +void LEDWidgetRGB::SetColor(uint8_t* rgb, uint16_t led_endpoint) +{ + /* 1. Set the color values. */ + this->SetRGB(rgb, led_endpoint, true); +} + + +void LEDWidgetRGB::SetColor(uint8_t hue, uint8_t saturation, uint16_t led_endpoint) +{ + /* 1. Convert the hue and saturation input to RGB values. (HSV to RGB conversion) */ + uint8_t rgb[3] = {0, 0, 0}; + HueToRGB(this->current_hue_, this->current_saturation_, rgb, PWM_MAX_VALUE); + + /* 2. Update the LEDs RGB values. */ + this->SetRGB(rgb, led_endpoint, true); +} + + +void LEDWidgetRGB::SetRGB(uint8_t* rgb, uint16_t led_endpoint, bool memorize) +{ + /* 1. Call the PWM driver to set the new values. */ + void* led_rgb_context = this->led_rgb_->led_common.context; + this->led_rgb_->set_rgb_color(led_rgb_context, rgb[0], rgb[1], rgb[2]); + + /* 2. Save the current RGB values. */ + if (memorize == true) + { + memcpy(this->current_rgb_, rgb, sizeof(this->current_rgb_)); + } +} + + +/** + * @note This code is based on the HSV to RGB mathematical formula + * which was retrieved from: https://en.wikipedia.org/wiki/HSL_and_HSV + */ +void LEDWidgetRGB::HueToRGB(uint16_t hue, float saturation, uint8_t* rgb, uint8_t max_value) +{ + /* 1. Normalize the values. */ + uint16_t hue_degrees = hue; + float saturation_decimal = saturation; + float hsv_value = 1.0; + + if (hue_degrees == 360) + { + hue_degrees = 0; + } + + /* 2. Calculate the formula parameters. */ + float chroma = hsv_value * saturation_decimal; + float hue_prime = hue_degrees / 60.0; + float hue_modulo = fmod(hue_prime, 2.0); + float hue_diff = hue_modulo - 1.0; + float hue_abs = fabs(hue_diff); + float median_value = chroma * (1.0 - hue_abs); + float m = hsv_value - chroma; + + /* 3. Determine the points R', G' and B'. */ + float r_prime, g_prime, b_prime = 0; + + if ( hue_degrees < 60 ) + { + r_prime = chroma; + g_prime = median_value; + b_prime = 0; + } + else if ( (hue_degrees >= 60) && (hue_degrees < 120) ) + { + r_prime = median_value; + g_prime = chroma; + b_prime = 0; + } + else if ( (hue_degrees >= 120) && (hue_degrees < 180) ) + { + r_prime = 0; + g_prime = chroma; + b_prime = median_value; + } + else if ( (hue_degrees >= 180) && (hue_degrees < 240) ) + { + r_prime = 0; + g_prime = median_value; + b_prime = chroma; + } + else if ( (hue_degrees >= 240) && (hue_degrees < 300) ) + { + r_prime = median_value; + g_prime = 0; + b_prime = chroma; + } + else if ( (hue_degrees >= 300) && (hue_degrees < 360) ) + { + r_prime = chroma; + g_prime = 0; + b_prime = median_value; + } + + /* 4. Calculate the final values of RGB. */ + rgb[0] = (uint8_t) ( (r_prime + m) * max_value ); + rgb[1] = (uint8_t) ( (g_prime + m) * max_value ); + rgb[2] = (uint8_t) ( (b_prime + m) * max_value ); +} \ No newline at end of file diff --git a/silabs_examples/sl-newLight/efr32/light_modules/led_widget_rgb.h b/silabs_examples/sl-newLight/efr32/light_modules/led_widget_rgb.h new file mode 100644 index 00000000000000..80c873ba6d68dd --- /dev/null +++ b/silabs_examples/sl-newLight/efr32/light_modules/led_widget_rgb.h @@ -0,0 +1,80 @@ +/***************************************************************************//** + * @file + * @brief Header file for RGB PWM Led controller. + ******************************************************************************* + * # License + * Copyright 2018 Silicon Laboratories Inc. www.silabs.com + ******************************************************************************* + * + * SPDX-License-Identifier: Zlib + * + * The licensor of this software is Silicon Laboratories Inc. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + ******************************************************************************/ + + +#pragma once + +#include "LEDWidget.h" + +#include "sl_simple_rgb_pwm_led.h" + +#define ATTRIBUTE_LEVEL_MAX 254 +// Default Saturation in the cluster is zero. +#define INITIAL_SATURATION 0.0f +// Default Hue in the color control cluster is zero. +#define INITIAL_HUE 0 +#define INITIAL_RGB 0 + +#define LED_ENDPOINT_DEFAULT 1 +#define LED_RGB_INITIAL_STATE 1 +#define LED_RGB_POLARITY 0U +#define PWM_FREQUENCY 10000 +#define PWM_MAX_VALUE 100 +#define PWM_RESOLUTION_LEVELS 100 + + +// RGB PWM Led instance with preset parameters. +extern const sl_led_rgb_pwm_t sl_led_rgb_pwm; + + +class LEDWidgetRGB : public LEDWidget +{ +public: + static void InitGpioRGB(); + void Init(const sl_led_rgb_pwm_t* led); + void Set(bool state); + void Set(bool state, uint16_t led_endpoint); + void SetLevel(uint8_t level, uint16_t led_endpoint); + void GetLevel(uint8_t rgb[], uint32_t size); + void SetHue(uint8_t hue, uint16_t led_endpoint); + void SetSaturation(uint8_t sat, uint16_t led_endpoint); + void SetColor(uint8_t hue, uint8_t saturation, uint16_t led_endpoint); + void SetColor(uint8_t* rgb, uint16_t led_endpoint); + +private: + void HueToRGB(uint16_t hue, float saturation, uint8_t* rgb, uint8_t max_value); + void SetRGB(uint8_t* rgb, uint16_t led_endpoint, bool memorize); + + uint16_t current_hue_; + uint8_t current_rgb_[3]; + float current_saturation_; + const sl_led_rgb_pwm_t* led_rgb_; + uint32_t level_resolution_; +}; diff --git a/silabs_examples/sl-newLight/efr32/src/AppTask.cpp b/silabs_examples/sl-newLight/efr32/src/AppTask.cpp index 74f2c429e6ddb1..97663f34ab5913 100644 --- a/silabs_examples/sl-newLight/efr32/src/AppTask.cpp +++ b/silabs_examples/sl-newLight/efr32/src/AppTask.cpp @@ -21,6 +21,9 @@ #include "AppConfig.h" #include "AppEvent.h" #include "LEDWidget.h" +#ifdef RGB_LED_ENABLED +#include "led_widget_rgb.h" +#endif //RGB_LED_ENABLED #ifdef DISPLAY_ENABLED #include "lcd.h" #include "qrcodegen.h" @@ -81,7 +84,12 @@ TaskHandle_t sAppTaskHandle; QueueHandle_t sAppEventQueue; LEDWidget sStatusLED; +#ifdef RGB_LED_ENABLED +#define LIGHT_LED_RGB &sl_led_rgb_pwm +LEDWidgetRGB sLightLED; +#else LEDWidget sLightLED; +#endif #ifdef SL_WIFI bool sIsWiFiProvisioned = false; @@ -168,7 +176,7 @@ void OnTriggerOffWithEffect(OnOffEffect * effect) chip::app::Clusters::OnOff::OnOffEffectIdentifier effectId = effect->mEffectIdentifier; uint8_t effectVariant = effect->mEffectVariant; - // Uses print outs until we can support the effects + // Uses printouts until we can support the effects if (effectId == EMBER_ZCL_ON_OFF_EFFECT_IDENTIFIER_DELAYED_ALL_OFF) { if (effectVariant == EMBER_ZCL_ON_OFF_DELAYED_ALL_OFF_EFFECT_VARIANT_FADE_TO_OFF_IN_0P8_SECONDS) @@ -266,10 +274,26 @@ CHIP_ERROR AppTask::Init() EFR32_LOG("Current Software Version: %s", CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING); + err = LightMgr().Init(); + if (err != CHIP_NO_ERROR) + { + EFR32_LOG("LightMgr().Init() failed"); + appError(err); + } + + LightMgr().SetCallbacks(ActionInitiated, ActionCompleted); + LightMgr().SetLightCallbacks(ActionChangeLight); + // Initialize LEDs LEDWidget::InitGpio(); - sStatusLED.Init(SYSTEM_STATE_LED); + sStatusLED.Init(SYSTEM_STATE_LED); +#if defined(RGB_LED_ENABLED) + LEDWidgetRGB::InitGpioRGB(); + sLightLED.Init(LIGHT_LED_RGB); +#else sLightLED.Init(LIGHT_LED); +#endif //RGB_LED_ENABLED + sLightLED.Set(LightMgr().IsLightOn()); ConfigurationMgr().LogDeviceConfig(); @@ -386,6 +410,46 @@ void AppTask::AppTaskMain(void * pvParameter) } } +void AppTask::LightActionEventHandler(AppEvent * aEvent) +{ + bool initiated = false; + LightingManager::Action_t action; + int32_t actor; + CHIP_ERROR err = CHIP_NO_ERROR; + + if (aEvent->Type == AppEvent::kEventType_Light) + { + action = static_cast(aEvent->LightEvent.Action); + actor = aEvent->LightEvent.Actor; + } + else if (aEvent->Type == AppEvent::kEventType_Button) + { + if (LightMgr().IsLightOn()) + { + action = LightingManager::OFF_ACTION; + } + else + { + action = LightingManager::ON_ACTION; + } + actor = AppEvent::kEventType_Button; + } + else + { + err = APP_ERROR_UNHANDLED_EVENT; + } + + if (err == CHIP_NO_ERROR) + { + initiated = LightMgr().InitiateAction(actor, action); + + if (!initiated) + { + EFR32_LOG("Action is already in progress or active."); + } + } +} + void AppTask::ButtonEventHandler(const sl_button_t * buttonHandle, uint8_t btnAction) { if (buttonHandle == NULL) @@ -397,7 +461,12 @@ void AppTask::ButtonEventHandler(const sl_button_t * buttonHandle, uint8_t btnAc button_event.Type = AppEvent::kEventType_Button; button_event.ButtonEvent.Action = btnAction; - if (buttonHandle == APP_FUNCTION_BUTTON) + if (buttonHandle == APP_LIGHT_SWITCH && btnAction == SL_SIMPLE_BUTTON_PRESSED) + { + button_event.Handler = LightActionEventHandler; + sAppTask.PostEvent(&button_event); + } + else if (buttonHandle == APP_FUNCTION_BUTTON) { button_event.Handler = FunctionHandler; sAppTask.PostEvent(&button_event); @@ -487,6 +556,8 @@ void AppTask::FunctionHandler(AppEvent * aEvent) } else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset) { + // Set Light status LED back to show state of light. + sLightLED.Set(LightMgr().IsLightOn()); sAppTask.CancelTimer(); @@ -530,6 +601,78 @@ void AppTask::StartTimer(uint32_t aTimeoutInMs) mFunctionTimerActive = true; } +void AppTask::ActionInitiated(LightingManager::Action_t aAction, int32_t aActor) +{ + // Action initiated, update the light led + if (aAction == LightingManager::ON_ACTION) + { + EFR32_LOG("Turning light ON") + sLightLED.Set(true); + } + else if (aAction == LightingManager::OFF_ACTION) + { + EFR32_LOG("Turning light OFF") + sLightLED.Set(false); + } + + if (aActor == AppEvent::kEventType_Button) + { + sAppTask.mSyncClusterToButtonAction = true; + } +} + +void AppTask::ActionCompleted(LightingManager::Action_t aAction) +{ + // action has been completed on the light + if (aAction == LightingManager::ON_ACTION) + { + EFR32_LOG("Light ON") + } + else if (aAction == LightingManager::OFF_ACTION) + { + EFR32_LOG("Light OFF") + } + + if (sAppTask.mSyncClusterToButtonAction) + { + chip::DeviceLayer::PlatformMgr().ScheduleWork(UpdateClusterState, reinterpret_cast(nullptr)); + sAppTask.mSyncClusterToButtonAction = false; + } +} + +void AppTask::ActionChangeLight(LightingManager::Action_t aAction, uint16_t endpoint, uint8_t value) +{ +#ifdef RGB_LED_ENABLED + if (aAction == LightingManager::MOVE_TO_LEVEL) + { + sLightLED.SetLevel(value, endpoint); + EFR32_LOG("Light LED Level set to: %d.", value); + } + else if (aAction == LightingManager::MOVE_TO_HUE) + { + sLightLED.SetHue(value, endpoint); + EFR32_LOG("Light LED hue set."); + } + else if (aAction == LightingManager::MOVE_TO_SAT) + { + sLightLED.SetSaturation(value, endpoint); + EFR32_LOG("Light LED saturation set."); + } +#else + EFR32_LOG(" Level Control and Color control are unavailable on your board."); +#endif //RGB_LED_ENABLED +} + +void AppTask::PostLightActionRequest(int32_t aActor, LightingManager::Action_t aAction) +{ + AppEvent event; + event.Type = AppEvent::kEventType_Light; + event.LightEvent.Actor = aActor; + event.LightEvent.Action = aAction; + event.Handler = LightActionEventHandler; + PostEvent(&event); +} + void AppTask::PostEvent(const AppEvent * aEvent) { if (sAppEventQueue != NULL) @@ -573,3 +716,16 @@ void AppTask::DispatchEvent(AppEvent * aEvent) EFR32_LOG("Event received with no handler. Dropping event."); } } + +void AppTask::UpdateClusterState(intptr_t context) +{ + uint8_t newValue = LightMgr().IsLightOn(); + + // write the new on/off value + EmberAfStatus status = OnOffServer::Instance().setOnOffValue(1, newValue, false); + + if (status != EMBER_ZCL_STATUS_SUCCESS) + { + EFR32_LOG("ERR: updating on/off %x", status); + } +} diff --git a/silabs_examples/sl-newLight/efr32/src/ZclCallbacks.cpp b/silabs_examples/sl-newLight/efr32/src/ZclCallbacks.cpp index 66ecdca623fa56..a3dd8bef632cf1 100644 --- a/silabs_examples/sl-newLight/efr32/src/ZclCallbacks.cpp +++ b/silabs_examples/sl-newLight/efr32/src/ZclCallbacks.cpp @@ -21,6 +21,7 @@ */ #include "AppConfig.h" +#include "LightingManager.h" #include #include @@ -30,21 +31,59 @@ using namespace ::chip; using namespace ::chip::app::Clusters; + void MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath & attributePath, uint8_t type, uint16_t size, uint8_t * value) { ClusterId clusterId = attributePath.mClusterId; - // AttributeId attributeId = attributePath.mAttributeId; + AttributeId attributeId = attributePath.mAttributeId; + EndpointId endpoint = attributePath.mEndpointId; + LightingManager::Action_t action_type = LightingManager::IGNORE_ACTION; + ChipLogProgress(Zcl, "Cluster callback: " ChipLogFormatMEI, ChipLogValueMEI(clusterId)); + if (clusterId == OnOff::Id && attributeId == OnOff::Attributes::OnOff::Id) + { + LightMgr().InitiateAction(AppEvent::kEventType_Light, *value ? LightingManager::ON_ACTION : LightingManager::OFF_ACTION); + } + else if (clusterId == LevelControl::Id) + { + ChipLogProgress(Zcl, "Level Control attribute ID: " ChipLogFormatMEI " Type: %u Value: %u, length %u", + ChipLogValueMEI(attributeId), type, *value, size); + + if (attributeId == LevelControl::Attributes::CurrentLevel::Id) + { + action_type = LightingManager::MOVE_TO_LEVEL; + } + + LightMgr().InitiateActionLight(AppEvent::kEventType_Light, action_type, endpoint, *value); + } + else if (clusterId == ColorControl::Id) + { + ChipLogProgress(Zcl, "Color Control attribute ID: " ChipLogFormatMEI " Type: %u Value: %u, length %u", + ChipLogValueMEI(attributeId), type, *value, size); - // Template TODO, fill with cluster configuration - // switch (clusterId) - // { - // case : - // default : + if (attributeId == ColorControl::Attributes::CurrentHue::Id) + { + action_type = LightingManager::MOVE_TO_HUE; + } + else if (attributeId == ColorControl::Attributes::CurrentSaturation::Id) + { + action_type = LightingManager::MOVE_TO_SAT; + } - // } + LightMgr().InitiateActionLight(AppEvent::kEventType_Light, action_type, endpoint, *value); + } + else if (clusterId == OnOffSwitchConfiguration::Id) + { + ChipLogProgress(Zcl, "OnOff Switch Configuration attribute ID: " ChipLogFormatMEI " Type: %u Value: %u, length %u", + ChipLogValueMEI(attributeId), type, *value, size); + } + else if (clusterId == Identify::Id) + { + ChipLogProgress(Zcl, "Identify attribute ID: " ChipLogFormatMEI " Type: %u Value: %u, length %u", + ChipLogValueMEI(attributeId), type, *value, size); + } } /** @brief OnOff Cluster Init