diff --git a/examples/lighting-app/efr32/src/AppTask.cpp b/examples/lighting-app/efr32/src/AppTask.cpp index 53a7963fd95f9a..98299c5fc6b990 100644 --- a/examples/lighting-app/efr32/src/AppTask.cpp +++ b/examples/lighting-app/efr32/src/AppTask.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -84,12 +85,16 @@ StaticQueue_t sAppEventQueueStruct; StackType_t appStack[APP_TASK_STACK_SIZE / sizeof(StackType_t)]; StaticTask_t appTaskStruct; -inline void OnTriggerEffectCompleted(chip::System::Layer * systemLayer, void * appState) +/********************************************************** + * Identify Callbacks + *********************************************************/ + +inline void OnTriggerIdentifyEffectCompleted(chip::System::Layer * systemLayer, void * appState) { sIdentifyEffect = EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_STOP_EFFECT; } -void OnTriggerEffect(Identify * identify) +void OnTriggerIdentifyEffect(Identify * identify) { sIdentifyEffect = identify->mCurrentEffectIdentifier; @@ -105,14 +110,16 @@ void OnTriggerEffect(Identify * identify) case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_BLINK: case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_BREATHE: case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_OKAY: - (void) chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds16(5), OnTriggerEffectCompleted, identify); + (void) chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds16(5), OnTriggerIdentifyEffectCompleted, + identify); break; case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_FINISH_EFFECT: - (void) chip::DeviceLayer::SystemLayer().CancelTimer(OnTriggerEffectCompleted, identify); - (void) chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds16(1), OnTriggerEffectCompleted, identify); + (void) chip::DeviceLayer::SystemLayer().CancelTimer(OnTriggerIdentifyEffectCompleted, identify); + (void) chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds16(1), OnTriggerIdentifyEffectCompleted, + identify); break; case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_STOP_EFFECT: - (void) chip::DeviceLayer::SystemLayer().CancelTimer(OnTriggerEffectCompleted, identify); + (void) chip::DeviceLayer::SystemLayer().CancelTimer(OnTriggerIdentifyEffectCompleted, identify); sIdentifyEffect = EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_STOP_EFFECT; break; default: @@ -125,7 +132,53 @@ Identify gIdentify = { [](Identify *) { ChipLogProgress(Zcl, "onIdentifyStart"); }, [](Identify *) { ChipLogProgress(Zcl, "onIdentifyStop"); }, EMBER_ZCL_IDENTIFY_IDENTIFY_TYPE_VISIBLE_LED, - OnTriggerEffect, + OnTriggerIdentifyEffect, +}; + +/********************************************************** + * OffWithEffect Callbacks + *********************************************************/ + +void OnTriggerOffWithEffect(OnOffEffect * effect) +{ + uint8_t effectId = effect->mEffectIdentifier; + uint8_t effectVariant = effect->mEffectVariant; + + // Uses print outs 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) + { + ChipLogProgress(Zcl, "EMBER_ZCL_ON_OFF_DELAYED_ALL_OFF_EFFECT_VARIANT_FADE_TO_OFF_IN_0P8_SECONDS"); + } + else if (effectVariant == EMBER_ZCL_ON_OFF_DELAYED_ALL_OFF_EFFECT_VARIANT_NO_FADE) + { + ChipLogProgress(Zcl, "EMBER_ZCL_ON_OFF_DELAYED_ALL_OFF_EFFECT_VARIANT_NO_FADE"); + } + else if (effectVariant == + EMBER_ZCL_ON_OFF_DELAYED_ALL_OFF_EFFECT_VARIANT_50_PERCENT_DIM_DOWN_IN_0P8_SECONDS_THEN_FADE_TO_OFF_IN_12_SECONDS) + { + ChipLogProgress(Zcl, + "EMBER_ZCL_ON_OFF_DELAYED_ALL_OFF_EFFECT_VARIANT_50_PERCENT_DIM_DOWN_IN_0P8_SECONDS_THEN_FADE_TO_OFF_" + "IN_12_SECONDS"); + } + } + else if (effectId == EMBER_ZCL_ON_OFF_EFFECT_IDENTIFIER_DYING_LIGHT) + { + if (effectVariant == + EMBER_ZCL_ON_OFF_DYING_LIGHT_EFFECT_VARIANT_20_PERCENTER_DIM_UP_IN_0P5_SECONDS_THEN_FADE_TO_OFF_IN_1_SECOND) + { + ChipLogProgress( + Zcl, "EMBER_ZCL_ON_OFF_DYING_LIGHT_EFFECT_VARIANT_20_PERCENTER_DIM_UP_IN_0P5_SECONDS_THEN_FADE_TO_OFF_IN_1_SECOND"); + } + } +} + +OnOffEffect gEffect = { + chip::EndpointId{ 1 }, + OnTriggerOffWithEffect, + static_cast(EMBER_ZCL_ON_OFF_EFFECT_IDENTIFIER_DELAYED_ALL_OFF), + static_cast(EMBER_ZCL_ON_OFF_DELAYED_ALL_OFF_EFFECT_VARIANT_FADE_TO_OFF_IN_0P8_SECONDS), }; } // namespace @@ -586,8 +639,8 @@ void AppTask::UpdateClusterState(void) uint8_t newValue = LightMgr().IsLightOn(); // write the new on/off value - EmberAfStatus status = emberAfWriteAttribute(1, ZCL_ON_OFF_CLUSTER_ID, ZCL_ON_OFF_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, - (uint8_t *) &newValue, ZCL_BOOLEAN_ATTRIBUTE_TYPE); + 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/examples/lighting-app/lighting-common/lighting-app.zap b/examples/lighting-app/lighting-common/lighting-app.zap index d1593e1580efa7..6d5bf22aea4d76 100644 --- a/examples/lighting-app/lighting-common/lighting-app.zap +++ b/examples/lighting-app/lighting-common/lighting-app.zap @@ -4405,7 +4405,7 @@ "code": 64, "mfgCode": null, "source": "client", - "incoming": 0, + "incoming": 1, "outgoing": 0 }, { @@ -4413,7 +4413,7 @@ "code": 65, "mfgCode": null, "source": "client", - "incoming": 0, + "incoming": 1, "outgoing": 0 }, { @@ -4421,7 +4421,7 @@ "code": 66, "mfgCode": null, "source": "client", - "incoming": 0, + "incoming": 1, "outgoing": 0 } ], @@ -6045,6 +6045,30 @@ "source": "client", "incoming": 0, "outgoing": 1 + }, + { + "name": "OffWithEffect", + "code": 64, + "mfgCode": null, + "source": "client", + "incoming": 0, + "outgoing": 1 + }, + { + "name": "OnWithRecallGlobalScene", + "code": 65, + "mfgCode": null, + "source": "client", + "incoming": 0, + "outgoing": 1 + }, + { + "name": "OnWithTimedOff", + "code": 66, + "mfgCode": null, + "source": "client", + "incoming": 0, + "outgoing": 1 } ], "attributes": [ diff --git a/src/app/clusters/level-control/level-control.cpp b/src/app/clusters/level-control/level-control.cpp index b1b739203610fb..e7c5bf07c9035b 100644 --- a/src/app/clusters/level-control/level-control.cpp +++ b/src/app/clusters/level-control/level-control.cpp @@ -310,7 +310,7 @@ static void setOnOffValue(EndpointId endpoint, bool onOff) if (emberAfContainsServer(endpoint, OnOff::Id)) { emberAfLevelControlClusterPrintln("Setting on/off to %p due to level change", onOff ? "ON" : "OFF"); - emberAfOnOffClusterSetValueCallback(endpoint, (onOff ? OnOff::Commands::On::Id : OnOff::Commands::Off::Id), true); + OnOffServer::Instance().setOnOffValue(endpoint, (onOff ? OnOff::Commands::On::Id : OnOff::Commands::Off::Id), true); } #endif // EMBER_AF_PLUGIN_ON_OFF } diff --git a/src/app/clusters/on-off-server/on-off-server.cpp b/src/app/clusters/on-off-server/on-off-server.cpp index 551a29a0b812bb..28edded4ee923c 100644 --- a/src/app/clusters/on-off-server/on-off-server.cpp +++ b/src/app/clusters/on-off-server/on-off-server.cpp @@ -41,7 +41,7 @@ #include "on-off-server.h" #include -#include +#include #include #include @@ -64,11 +64,38 @@ using namespace chip; using namespace chip::app::Clusters; using namespace chip::app::Clusters::OnOff; -#ifdef ZCL_USING_ON_OFF_CLUSTER_START_UP_ON_OFF_ATTRIBUTE -static bool areStartUpOnOffServerAttributesTokenized(EndpointId endpoint); -#endif +/********************************************************** + * Attributes Definition + *********************************************************/ + +static std::array instances = { 0 }; +OnOffServer OnOffServer::instance; + +/********************************************************** + * Function definition + *********************************************************/ + +static OnOffEffect * inst(EndpointId endpoint); -EmberAfStatus emberAfOnOffClusterSetValueCallback(EndpointId endpoint, uint8_t command, bool initiatedByLevelChange) +/********************************************************** + * OnOff Implementation + *********************************************************/ + +OnOffServer & OnOffServer::Instance() +{ + return instance; +} + +/** @brief On/off Cluster Set Value + * + * This function is called when the on/off value needs to be set, either through + * normal channels or as a result of a level change. + * + * @param endpoint Ver.: always + * @param command Ver.: always + * @param initiatedByLevelChange Ver.: always + */ +EmberAfStatus OnOffServer::setOnOffValue(chip::EndpointId endpoint, uint8_t command, bool initiatedByLevelChange) { EmberAfStatus status; bool currentValue, newValue; @@ -100,8 +127,23 @@ EmberAfStatus emberAfOnOffClusterSetValueCallback(EndpointId endpoint, uint8_t c // should update the on/off attribute before kicking off level change, if we are // turning off the light, we should do the opposite, that is kick off level change // before updating the on/off attribute. - if (newValue) + if (newValue) // Set On { + uint16_t onTime = 0; + Attributes::OnTime::Get(endpoint, &onTime); + + if (onTime == 0) + { + emberAfOnOffClusterPrintln("On Command - OffWaitTime : 0"); + Attributes::OffWaitTime::Set(endpoint, 0); + + // Stop timer on the endpoint + emberEventControlSetInactive(getEventControl(endpoint)); + emberAfOnOffClusterPrintln("On/Toggle Command - Stop Timer"); + } + + Attributes::GlobalSceneControl::Set(endpoint, true); + // write the new on/off value status = Attributes::OnOff::Set(endpoint, newValue); if (status != EMBER_ZCL_STATUS_SUCCESS) @@ -119,8 +161,11 @@ EmberAfStatus emberAfOnOffClusterSetValueCallback(EndpointId endpoint, uint8_t c } #endif // EMBER_AF_PLUGIN_LEVEL_CONTROL } - else + else // Set Off { + emberAfOnOffClusterPrintln("Off Command - OnTime : 0"); + Attributes::OnTime::Set(endpoint, 0); // Reset onTime + #ifdef EMBER_AF_PLUGIN_LEVEL_CONTROL // If initiatedByLevelChange is false, then we assume that the level change // ZCL stuff has not happened and we do it here @@ -161,49 +206,7 @@ EmberAfStatus emberAfOnOffClusterSetValueCallback(EndpointId endpoint, uint8_t c return EMBER_ZCL_STATUS_SUCCESS; } -bool emberAfOnOffClusterOffCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, - const Commands::Off::DecodableType & commandData) -{ - EmberAfStatus status = emberAfOnOffClusterSetValueCallback(emberAfCurrentEndpoint(), Commands::Off::Id, false); -#ifdef EMBER_AF_PLUGIN_ZLL_ON_OFF_SERVER - if (status == EMBER_ZCL_STATUS_SUCCESS) - { - emberAfPluginZllOnOffServerOffZllExtensions(emberAfCurrentCommand()); - } -#endif - emberAfSendImmediateDefaultResponse(status); - return true; -} - -bool emberAfOnOffClusterOnCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, - const Commands::On::DecodableType & commandData) -{ - EmberAfStatus status = emberAfOnOffClusterSetValueCallback(emberAfCurrentEndpoint(), Commands::On::Id, false); -#ifdef EMBER_AF_PLUGIN_ZLL_ON_OFF_SERVER - if (status == EMBER_ZCL_STATUS_SUCCESS) - { - emberAfPluginZllOnOffServerOnZllExtensions(emberAfCurrentCommand()); - } -#endif - emberAfSendImmediateDefaultResponse(status); - return true; -} - -bool emberAfOnOffClusterToggleCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, - const Commands::Toggle::DecodableType & commandData) -{ - EmberAfStatus status = emberAfOnOffClusterSetValueCallback(emberAfCurrentEndpoint(), Commands::Toggle::Id, false); -#ifdef EMBER_AF_PLUGIN_ZLL_ON_OFF_SERVER - if (status == EMBER_ZCL_STATUS_SUCCESS) - { - emberAfPluginZllOnOffServerToggleZllExtensions(emberAfCurrentCommand()); - } -#endif - emberAfSendImmediateDefaultResponse(status); - return true; -} - -void emberAfOnOffClusterServerInitCallback(EndpointId endpoint) +void OnOffServer::initOnOffServer(chip::EndpointId endpoint) { #ifdef ZCL_USING_ON_OFF_CLUSTER_START_UP_ON_OFF_ATTRIBUTE // StartUp behavior relies on OnOff and StartUpOnOff attributes being tokenized. @@ -262,8 +265,228 @@ void emberAfOnOffClusterServerInitCallback(EndpointId endpoint) emberAfPluginOnOffClusterServerPostInitCallback(endpoint); } +bool OnOffServer::offCommand() +{ + EmberAfStatus status = setOnOffValue(emberAfCurrentEndpoint(), Commands::Off::Id, false); +#ifdef EMBER_AF_PLUGIN_ZLL_ON_OFF_SERVER + if (status == EMBER_ZCL_STATUS_SUCCESS) + { + emberAfPluginZllOnOffServerOffZllExtensions(emberAfCurrentCommand()); + } +#endif + emberAfSendImmediateDefaultResponse(status); + return true; +} + +bool OnOffServer::onCommand() +{ + EmberAfStatus status = setOnOffValue(emberAfCurrentEndpoint(), Commands::On::Id, false); + +#ifdef EMBER_AF_PLUGIN_ZLL_ON_OFF_SERVER + if (status == EMBER_ZCL_STATUS_SUCCESS) + { + emberAfPluginZllOnOffServerOnZllExtensions(emberAfCurrentCommand()); + } +#endif + + emberAfSendImmediateDefaultResponse(status); + return true; +} + +bool OnOffServer::toggleCommand() +{ + EmberAfStatus status = setOnOffValue(emberAfCurrentEndpoint(), Commands::Toggle::Id, false); +#ifdef EMBER_AF_PLUGIN_ZLL_ON_OFF_SERVER + if (status == EMBER_ZCL_STATUS_SUCCESS) + { + emberAfPluginZllOnOffServerToggleZllExtensions(emberAfCurrentCommand()); + } +#endif + emberAfSendImmediateDefaultResponse(status); + return true; +} + +bool OnOffServer::offWithEffectCommand(uint8_t effectId, uint8_t effectVariant) +{ + chip::EndpointId endpoint = emberAfCurrentEndpoint(); + EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS; + + bool globalSceneControl = false; + OnOff::Attributes::GlobalSceneControl::Get(endpoint, &globalSceneControl); + + bool isOnBeforeCommand = false; + OnOff::Attributes::OnOff::Get(endpoint, &isOnBeforeCommand); + + if (globalSceneControl) + { + OnOff::Attributes::GlobalSceneControl::Set(endpoint, false); + + status = setOnOffValue(endpoint, Commands::Off::Id, false); + Attributes::OnTime::Set(endpoint, 0); + } + else + { + status = setOnOffValue(endpoint, Commands::Off::Id, false); + } + + // Only apply effect if OnOff is on + if (isOnBeforeCommand) + { + OnOffEffect * effect = inst(endpoint); + + if (effect != nullptr && effect->mOffWithEffectTrigger != nullptr) + { + effect->mEffectIdentifier = effectId; + effect->mEffectVariant = effectVariant; + + effect->mOffWithEffectTrigger(effect); + } + } + + emberAfSendImmediateDefaultResponse(status); + return true; +} + +bool OnOffServer::OnWithRecallGlobalSceneCommand() +{ + chip::EndpointId endpoint = emberAfCurrentEndpoint(); + EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS; + + bool globalSceneControl = false; + OnOff::Attributes::GlobalSceneControl::Get(endpoint, &globalSceneControl); + + if (globalSceneControl) + { + emberAfSendImmediateDefaultResponse(status); + return true; + } + + OnOff::Attributes::GlobalSceneControl::Set(endpoint, true); + setOnOffValue(endpoint, Commands::On::Id, false); + + emberAfSendImmediateDefaultResponse(status); + return true; +} + +bool OnOffServer::OnWithTimedOffCommand(BitFlags onOffControl, uint16_t onTime, uint16_t offWaitTime) +{ + EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS; + chip::EndpointId endpoint = emberAfCurrentEndpoint(); + + bool isOn = false; + OnOff::Attributes::OnOff::Get(endpoint, &isOn); + + // OnOff is off and the commands is only accepted if on + if (onOffControl.Has(OnOffControl::kAcceptOnlyWhenOn) && !isOn) + { + emberAfSendImmediateDefaultResponse(status); + return true; + } + + uint16_t currentOffWaitTime = MAX_TIME_VALUE; + OnOff::Attributes::OffWaitTime::Get(endpoint, ¤tOffWaitTime); + + uint16_t currentOnTime = 0; + OnOff::Attributes::OnTime::Get(endpoint, ¤tOnTime); + + if (currentOffWaitTime > 0 && !isOn) + { + uint16_t newOffWaitTime = currentOffWaitTime < offWaitTime ? currentOffWaitTime : offWaitTime; + OnOff::Attributes::OffWaitTime::Set(endpoint, newOffWaitTime); + + currentOffWaitTime = newOffWaitTime; + } + else + { + uint16_t newOnTime = currentOnTime > onTime ? currentOnTime : onTime; + OnOff::Attributes::OnTime::Set(endpoint, newOnTime); + + OnOff::Attributes::OffWaitTime::Set(endpoint, offWaitTime); + setOnOffValue(endpoint, Commands::On::Id, false); + + currentOnTime = newOnTime; + currentOffWaitTime = offWaitTime; + } + + emberAfOnOffClusterPrintln("On Time: %d | off wait Time: %d", currentOnTime, currentOffWaitTime); + + if (currentOnTime < MAX_TIME_VALUE && currentOffWaitTime < MAX_TIME_VALUE) + { + emberEventControlSetDelayMS(configureEventControl(endpoint), UPDATE_TIME_MS); + } + + emberAfSendImmediateDefaultResponse(status); + return true; +} + +/** + * @brief Updates OnOff values after timer is finished + * + * @param[in] endpoint endpoint associated with the finished timer + */ +void OnOffServer::updateOnOffTimeCommand(chip::EndpointId endpoint) +{ + emberAfOnOffClusterPrintln("Timer callback - Entering callbackc"); + + bool isOn = false; + OnOff::Attributes::OnOff::Get(endpoint, &isOn); + + if (isOn) // OnOff On case + { + // Restart Timer + emberEventControlSetDelayMS(configureEventControl(endpoint), UPDATE_TIME_MS); + + // Update onTime values + uint16_t onTime = MIN_TIME_VALUE; + OnOff::Attributes::OnTime::Get(endpoint, &onTime); + emberAfOnOffClusterPrintln("Timer callback - On Time: %d", onTime); + + if (onTime > 0) + { + onTime--; + OnOff::Attributes::OnTime::Set(endpoint, onTime); + } + + if (onTime == 0) + { + emberAfOnOffClusterPrintln("Timer callback - Turning off OnOff"); + + OnOff::Attributes::OffWaitTime::Set(endpoint, 0); + setOnOffValue(endpoint, Commands::Off::Id, false); + } + } + else // OnOff Off Case + { + uint16_t offWaitTime = 0; + OnOff::Attributes::OffWaitTime::Get(endpoint, &offWaitTime); + + // Validate before decreasing counter + if (offWaitTime > 0) + { + offWaitTime--; + OnOff::Attributes::OffWaitTime::Set(endpoint, offWaitTime); + } + + emberAfOnOffClusterPrintln("Timer Callback - wait Off Time: %d", offWaitTime); + + // Validate if necessary to restart timer + if (offWaitTime > 0) + { + // Restart Timer + emberEventControlSetDelayMS(configureEventControl(endpoint), UPDATE_TIME_MS); + } + else + { + emberAfOnOffClusterPrintln("Timer Callback - wait Off Time cycle finished"); + + // Stop timer on the endpoint + emberEventControlSetInactive(getEventControl(endpoint)); + } + } +} + #ifdef ZCL_USING_ON_OFF_CLUSTER_START_UP_ON_OFF_ATTRIBUTE -static bool areStartUpOnOffServerAttributesTokenized(EndpointId endpoint) +bool OnOffServer::areStartUpOnOffServerAttributesTokenized(EndpointId endpoint) { EmberAfAttributeMetadata * metadata; @@ -283,7 +506,146 @@ static bool areStartUpOnOffServerAttributesTokenized(EndpointId endpoint) return true; } -#endif +#endif // ZCL_USING_ON_OFF_CLUSTER_START_UP_ON_OFF_ATTRIBUTE + +/** + * @brief event control object for an endpoint + * + * @param[in] endpoint + * @return EmberEventControl* configured event control + */ +EmberEventControl * OnOffServer::getEventControl(EndpointId endpoint) +{ + uint16_t index = emberAfFindClusterServerEndpointIndex(endpoint, OnOff::Id); + return &eventControls[index]; +} + +/** + * @brief Configures EnventControl callback when using XY colors + * + * @param[in] endpoint endpoint to start timer for + * @return EmberEventControl* configured event control + */ +EmberEventControl * OnOffServer::configureEventControl(EndpointId endpoint) +{ + EmberEventControl * controller = getEventControl(endpoint); + + controller->endpoint = endpoint; + controller->callback = &onOffWaitTimeOffEventHandler; + + return controller; +} + +/********************************************************** + * OnOffEffect Implementation + *********************************************************/ + +static OnOffEffect * inst(EndpointId endpoint) +{ + for (size_t i = 0; i < instances.size(); i++) + { + if (nullptr != instances[i] && endpoint == instances[i]->mEndpoint) + { + return instances[i]; + } + } + + return nullptr; +} + +static inline void reg(OnOffEffect * inst) +{ + for (size_t i = 0; i < instances.size(); i++) + { + if (nullptr == instances[i]) + { + instances[i] = inst; + break; + } + } +} + +static inline void unreg(OnOffEffect * inst) +{ + for (size_t i = 0; i < instances.size(); i++) + { + if (inst == instances[i]) + { + instances[i] = nullptr; + } + } +} + +OnOffEffect::OnOffEffect(chip::EndpointId endpoint, OffWithEffectTriggerCommand offWithEffectTrigger, uint8_t effectIdentifier, + uint8_t effectVariant) : + mEndpoint(endpoint), + mOffWithEffectTrigger(offWithEffectTrigger), mEffectIdentifier(effectIdentifier), mEffectVariant(effectVariant) +{ + reg(this); +}; + +OnOffEffect::~OnOffEffect() +{ + unreg(this); +}; + +/********************************************************** + * Callbacks Implementation + *********************************************************/ + +bool emberAfOnOffClusterOffCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, + const Commands::Off::DecodableType & commandData) +{ + return OnOffServer::Instance().offCommand(); +} + +bool emberAfOnOffClusterOnCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, + const Commands::On::DecodableType & commandData) +{ + return OnOffServer::Instance().onCommand(); +} + +bool emberAfOnOffClusterToggleCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, + const Commands::Toggle::DecodableType & commandData) +{ + return OnOffServer::Instance().toggleCommand(); +} + +bool emberAfOnOffClusterOffWithEffectCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, + const Commands::OffWithEffect::DecodableType & commandData) +{ + auto & effectId = commandData.effectId; + auto & effectVariant = commandData.effectVariant; + + return OnOffServer::Instance().offWithEffectCommand(effectId, effectVariant); +} + +bool emberAfOnOffClusterOnWithRecallGlobalSceneCallback(app::CommandHandler * commandObj, + const app::ConcreteCommandPath & commandPath, + const Commands::OnWithRecallGlobalScene::DecodableType & commandData) +{ + return OnOffServer::Instance().OnWithRecallGlobalSceneCommand(); +} + +bool emberAfOnOffClusterOnWithTimedOffCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, + const Commands::OnWithTimedOff::DecodableType & commandData) +{ + auto & onOffControl = commandData.onOffControl; + auto & onTime = commandData.onTime; + auto & offWaitTime = commandData.offWaitTime; + + return OnOffServer::Instance().OnWithTimedOffCommand(onOffControl, onTime, offWaitTime); +} + +void emberAfOnOffClusterServerInitCallback(chip::EndpointId endpoint) +{ + OnOffServer::Instance().initOnOffServer(endpoint); +} + +void onOffWaitTimeOffEventHandler(chip::EndpointId endpoint) +{ + OnOffServer::Instance().updateOnOffTimeCommand(endpoint); +} void emberAfPluginOnOffClusterServerPostInitCallback(EndpointId endpoint) {} diff --git a/src/app/clusters/on-off-server/on-off-server.h b/src/app/clusters/on-off-server/on-off-server.h index b9eab9eaf4c89b..2465517e7591be 100644 --- a/src/app/clusters/on-off-server/on-off-server.h +++ b/src/app/clusters/on-off-server/on-off-server.h @@ -17,19 +17,83 @@ #pragma once +#include #include #include -/** @brief On/off Cluster Set Value - * - * This function is called when the on/off value needs to be set, either through - * normal channels or as a result of a level change. +/********************************************************** + * Defines and Macros + *********************************************************/ + +static constexpr uint8_t UPDATE_TIME_MS = 100; +static constexpr uint16_t TRANSITION_TIME_1S = 10; + +static constexpr uint16_t MAX_TIME_VALUE = 0xFFFF; +static constexpr uint8_t MIN_TIME_VALUE = 1; + +/** + * @brief * - * @param endpoint Ver.: always - * @param command Ver.: always - * @param initiatedByLevelChange Ver.: always */ -EmberAfStatus emberAfOnOffClusterSetValueCallback(chip::EndpointId endpoint, uint8_t command, bool initiatedByLevelChange); +class OnOffServer +{ +public: + /********************************************************** + * Functions Definitions + *********************************************************/ + + static OnOffServer & Instance(); + + bool offCommand(); + bool onCommand(); + bool toggleCommand(); + void initOnOffServer(chip::EndpointId endpoint); + bool offWithEffectCommand(uint8_t effectId, uint8_t effectVariant); + bool OnWithRecallGlobalSceneCommand(); + bool OnWithTimedOffCommand(chip::BitFlags onOffControl, uint16_t onTime, + uint16_t offWaitTime); + void updateOnOffTimeCommand(chip::EndpointId endpoint); + EmberAfStatus setOnOffValue(chip::EndpointId endpoint, uint8_t command, bool initiatedByLevelChange); + +private: + /********************************************************** + * Functions Definitions + *********************************************************/ + +#ifdef ZCL_USING_ON_OFF_CLUSTER_START_UP_ON_OFF_ATTRIBUTE + bool areStartUpOnOffServerAttributesTokenized(chip::EndpointId endpoint); +#endif // ZCL_USING_ON_OFF_CLUSTER_START_UP_ON_OFF_ATTRIBUTE + EmberEventControl * getEventControl(chip::EndpointId endpoint); + EmberEventControl * configureEventControl(chip::EndpointId endpoint); + + /********************************************************** + * Attributes Decleration + *********************************************************/ + + static OnOffServer instance; + EmberEventControl eventControls[EMBER_AF_ON_OFF_CLUSTER_SERVER_ENDPOINT_COUNT]; +}; + +struct OnOffEffect +{ + using OffWithEffectTriggerCommand = void (*)(OnOffEffect *); + + chip::EndpointId mEndpoint; + OffWithEffectTriggerCommand mOffWithEffectTrigger = nullptr; + uint8_t mEffectIdentifier; + uint8_t mEffectVariant; + bool mActive = false; + + OnOffEffect( + chip::EndpointId endpoint, OffWithEffectTriggerCommand offWithEffectTrigger, + uint8_t effectIdentifier = static_cast(EMBER_ZCL_ON_OFF_EFFECT_IDENTIFIER_DELAYED_ALL_OFF), + uint8_t effectVariant = static_cast(EMBER_ZCL_ON_OFF_DELAYED_ALL_OFF_EFFECT_VARIANT_FADE_TO_OFF_IN_0P8_SECONDS)); + ~OnOffEffect(); +}; + +/********************************************************** + * Global + *********************************************************/ /** @brief On/off Cluster Level Control Effect * @@ -42,6 +106,10 @@ EmberAfStatus emberAfOnOffClusterSetValueCallback(chip::EndpointId endpoint, uin */ void emberAfOnOffClusterLevelControlEffectCallback(chip::EndpointId endpoint, bool newValue); +/********************************************************** + * Callbacks + *********************************************************/ + /** @brief On/off Cluster Server Post Init * * Following resolution of the On/Off state at startup for this endpoint, @@ -51,3 +119,5 @@ void emberAfOnOffClusterLevelControlEffectCallback(chip::EndpointId endpoint, bo * @param endpoint Endpoint that is being initialized Ver.: always */ void emberAfPluginOnOffClusterServerPostInitCallback(chip::EndpointId endpoint); + +void onOffWaitTimeOffEventHandler(chip::EndpointId endpoint); diff --git a/src/app/clusters/zll-on-off-server/zll-on-off-server.c b/src/app/clusters/zll-on-off-server/zll-on-off-server.c index cdda633bf6591e..aab25f9fed728b 100644 --- a/src/app/clusters/zll-on-off-server/zll-on-off-server.c +++ b/src/app/clusters/zll-on-off-server/zll-on-off-server.c @@ -218,7 +218,7 @@ bool emberAfOnOffClusterOffWithEffectCallback(app::CommandHandler * commandObj, // If the application handled the effect, the endpoint shall enter its // "off" state, update the OnOff attribute accordingly, and set the OnTime // attribute to 0x0000. - status = emberAfOnOffClusterSetValueCallback(endpoint, ZCL_OFF_COMMAND_ID, false); + status = OnOffServer::Instance().setOnOffValue(endpoint, ZCL_OFF_COMMAND_ID, false); if (status == EMBER_ZCL_STATUS_SUCCESS) { status = writeOnTime(endpoint, 0x0000); diff --git a/zzz_generated/lighting-app/zap-generated/IMClusterCommandHandler.cpp b/zzz_generated/lighting-app/zap-generated/IMClusterCommandHandler.cpp index 8d4bca0ff085b6..6e9b21e99a8dd0 100644 --- a/zzz_generated/lighting-app/zap-generated/IMClusterCommandHandler.cpp +++ b/zzz_generated/lighting-app/zap-generated/IMClusterCommandHandler.cpp @@ -922,6 +922,15 @@ void DispatchServerCommand(CommandHandler * apCommandObj, const ConcreteCommandP } break; } + case Commands::OffWithEffect::Id: { + Commands::OffWithEffect::DecodableType commandData; + TLVError = DataModel::Decode(aDataTlv, commandData); + if (TLVError == CHIP_NO_ERROR) + { + wasHandled = emberAfOnOffClusterOffWithEffectCallback(apCommandObj, aCommandPath, commandData); + } + break; + } case Commands::On::Id: { Commands::On::DecodableType commandData; TLVError = DataModel::Decode(aDataTlv, commandData); @@ -931,6 +940,24 @@ void DispatchServerCommand(CommandHandler * apCommandObj, const ConcreteCommandP } break; } + case Commands::OnWithRecallGlobalScene::Id: { + Commands::OnWithRecallGlobalScene::DecodableType commandData; + TLVError = DataModel::Decode(aDataTlv, commandData); + if (TLVError == CHIP_NO_ERROR) + { + wasHandled = emberAfOnOffClusterOnWithRecallGlobalSceneCallback(apCommandObj, aCommandPath, commandData); + } + break; + } + case Commands::OnWithTimedOff::Id: { + Commands::OnWithTimedOff::DecodableType commandData; + TLVError = DataModel::Decode(aDataTlv, commandData); + if (TLVError == CHIP_NO_ERROR) + { + wasHandled = emberAfOnOffClusterOnWithTimedOffCallback(apCommandObj, aCommandPath, commandData); + } + break; + } case Commands::Toggle::Id: { Commands::Toggle::DecodableType commandData; TLVError = DataModel::Decode(aDataTlv, commandData);