blob: bc71044ae44075ce0fc936baca7c812877b19082 [file] [log] [blame]
/**
*
* Copyright (c) 2020 Project CHIP Authors
*
* 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 "on-off-server.h"
#include <app-common/zap-generated/attributes/Accessors.h>
#include <app/data-model/Nullable.h>
#include <app/reporting/reporting.h>
#include <app/util/attribute-storage.h>
#include <app/util/config.h>
#include <app/util/util.h>
#include <protocols/interaction_model/StatusCode.h>
#include <tracing/macros.h>
#ifdef MATTER_DM_PLUGIN_SCENES_MANAGEMENT
#include <app/clusters/scenes-server/scenes-server.h>
#endif // MATTER_DM_PLUGIN_SCENES_MANAGEMENT
#ifdef MATTER_DM_PLUGIN_LEVEL_CONTROL
#include <app/clusters/level-control/level-control.h>
#endif // MATTER_DM_PLUGIN_LEVEL_CONTROL
#ifdef MATTER_DM_PLUGIN_MODE_BASE
// nogncheck because the gn dependency checker does not understand
// conditional includes, so will fail in an application that has an On/Off
// cluster but no ModeBase-derived cluster.
#include <app/clusters/mode-base-server/mode-base-cluster-objects.h> // nogncheck
#include <app/clusters/mode-base-server/mode-base-server.h> // nogncheck
#endif // MATTER_DM_PLUGIN_MODE_BASE
#include <platform/CHIPDeviceLayer.h>
#include <platform/PlatformManager.h>
using namespace chip;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::OnOff;
using chip::Protocols::InteractionModel::Status;
namespace {
#ifdef MATTER_DM_PLUGIN_MODE_BASE
/**
* For all ModeBase alias clusters on the given endpoint, if the OnOff feature is supported and
* the OnMode attribute is set, update the CurrentMode attribute value to the OnMode value.
* @param endpoint
*/
void UpdateModeBaseCurrentModeToOnMode(EndpointId endpoint)
{
for (auto & modeBaseInstance : ModeBase::GetModeBaseInstanceList())
{
if (modeBaseInstance.GetEndpointId() == endpoint)
{
if (modeBaseInstance.HasFeature(ModeBase::Feature::kOnOff))
{
ModeBase::Attributes::OnMode::TypeInfo::Type onMode = modeBaseInstance.GetOnMode();
if (!onMode.IsNull())
{
Status status = modeBaseInstance.UpdateCurrentMode(onMode.Value());
if (status == Status::Success)
{
ChipLogProgress(Zcl, "Changed the Current Mode to %x", onMode.Value());
}
else
{
ChipLogError(Zcl, "Failed to Changed the Current Mode to %x: %u", onMode.Value(), to_underlying(status));
}
}
}
}
}
}
#endif // MATTER_DM_PLUGIN_MODE_BASE
} // namespace
#ifdef MATTER_DM_PLUGIN_LEVEL_CONTROL
static bool LevelControlWithOnOffFeaturePresent(EndpointId endpoint)
{
if (!emberAfContainsServer(endpoint, LevelControl::Id))
{
return false;
}
return LevelControlHasFeature(endpoint, LevelControl::Feature::kOnOff);
}
#endif // MATTER_DM_PLUGIN_LEVEL_CONTROL
static constexpr size_t kOnOffMaxEnpointCount =
MATTER_DM_ON_OFF_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT;
#if defined(MATTER_DM_PLUGIN_SCENES_MANAGEMENT) && CHIP_CONFIG_SCENES_USE_DEFAULT_HANDLERS
static void sceneOnOffCallback(EndpointId endpoint);
using OnOffEndPointPair = scenes::DefaultSceneHandlerImpl::EndpointStatePair<bool>;
using OnOffTransitionTimeInterface =
scenes::DefaultSceneHandlerImpl::TransitionTimeInterface<kOnOffMaxEnpointCount, MATTER_DM_ON_OFF_CLUSTER_SERVER_ENDPOINT_COUNT>;
class DefaultOnOffSceneHandler : public scenes::DefaultSceneHandlerImpl
{
public:
DefaultSceneHandlerImpl::StatePairBuffer<bool, kOnOffMaxEnpointCount> mSceneEndpointStatePairs;
// As per spec, 1 attribute is scenable in the on off cluster
static constexpr uint8_t scenableAttributeCount = 1;
DefaultOnOffSceneHandler() = default;
~DefaultOnOffSceneHandler() override {}
// Default function for OnOff cluster, only puts the OnOff cluster ID in the span if supported on the given endpoint
virtual void GetSupportedClusters(EndpointId endpoint, Span<ClusterId> & clusterBuffer) override
{
ClusterId * buffer = clusterBuffer.data();
if (emberAfContainsServer(endpoint, OnOff::Id) && clusterBuffer.size() >= 1)
{
buffer[0] = OnOff::Id;
clusterBuffer.reduce_size(1);
}
else
{
clusterBuffer.reduce_size(0);
}
}
// Default function for OnOff cluster, only checks if OnOff is enabled on the endpoint
bool SupportsCluster(EndpointId endpoint, ClusterId cluster) override
{
return (cluster == OnOff::Id) && (emberAfContainsServer(endpoint, OnOff::Id));
}
/// @brief Serialize the Cluster's EFS value
/// @param endpoint target endpoint
/// @param cluster target cluster
/// @param serializedBytes data to serialize into EFS
/// @return CHIP_NO_ERROR if successfully serialized the data, CHIP_ERROR_INVALID_ARGUMENT otherwise
CHIP_ERROR SerializeSave(EndpointId endpoint, ClusterId cluster, MutableByteSpan & serializedBytes) override
{
using AttributeValuePair = ScenesManagement::Structs::AttributeValuePair::Type;
bool currentValue;
// read current on/off value
Status status = Attributes::OnOff::Get(endpoint, &currentValue);
if (status != Status::Success)
{
ChipLogError(Zcl, "ERR: reading on/off %x", to_underlying(status));
return CHIP_ERROR_READ_FAILED;
}
AttributeValuePair pairs[scenableAttributeCount];
pairs[0].attributeID = Attributes::OnOff::Id;
pairs[0].attributeValue = currentValue;
app::DataModel::List<AttributeValuePair> attributeValueList(pairs);
return EncodeAttributeValueList(attributeValueList, serializedBytes);
}
/// @brief Default EFS interaction when applying scene to the OnOff Cluster
/// @param endpoint target endpoint
/// @param cluster target cluster
/// @param serializedBytes Data from nvm
/// @param timeMs transition time in ms
/// @return CHIP_NO_ERROR if value as expected, CHIP_ERROR_INVALID_ARGUMENT otherwise
CHIP_ERROR ApplyScene(EndpointId endpoint, ClusterId cluster, const ByteSpan & serializedBytes,
scenes::TransitionTimeMs timeMs) override
{
app::DataModel::DecodableList<ScenesManagement::Structs::AttributeValuePair::DecodableType> attributeValueList;
VerifyOrReturnError(cluster == OnOff::Id, CHIP_ERROR_INVALID_ARGUMENT);
ReturnErrorOnFailure(DecodeAttributeValueList(serializedBytes, attributeValueList));
size_t attributeCount = 0;
ReturnErrorOnFailure(attributeValueList.ComputeSize(&attributeCount));
VerifyOrReturnError(attributeCount <= scenableAttributeCount, CHIP_ERROR_BUFFER_TOO_SMALL);
auto pair_iterator = attributeValueList.begin();
while (pair_iterator.Next())
{
auto & decodePair = pair_iterator.GetValue();
VerifyOrReturnError(decodePair.attributeID == Attributes::OnOff::Id, CHIP_ERROR_INVALID_ARGUMENT);
ReturnErrorOnFailure(
mSceneEndpointStatePairs.InsertPair(OnOffEndPointPair(endpoint, static_cast<bool>(decodePair.attributeValue))));
}
// Verify that the EFS was completely read
CHIP_ERROR err = pair_iterator.GetStatus();
if (CHIP_NO_ERROR != err)
{
mSceneEndpointStatePairs.RemovePair(endpoint);
return err;
}
// This handler assumes it is being used with the default handler for the level control. Therefore if the level control
// cluster with on off feature is present on the endpoint and the level control handler is registered, it assumes this
// handler will take action on the on-off state. This assumes the level control attributes were also saved in the scene.
// This is to prevent a behavior where the on off state is set by this handler, and then the level control handler or vice
// versa.
#ifdef MATTER_DM_PLUGIN_LEVEL_CONTROL
if (!(LevelControlWithOnOffFeaturePresent(endpoint) &&
ScenesManagement::ScenesServer::Instance().IsHandlerRegistered(endpoint, LevelControlServer::GetSceneHandler())))
#endif
{
VerifyOrReturnError(mTransitionTimeInterface.sceneEventControl(endpoint) != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
OnOffServer::Instance().scheduleTimerCallbackMs(mTransitionTimeInterface.sceneEventControl(endpoint), timeMs);
}
return CHIP_NO_ERROR;
}
private:
OnOffTransitionTimeInterface mTransitionTimeInterface = OnOffTransitionTimeInterface(OnOff::Id, sceneOnOffCallback);
};
static DefaultOnOffSceneHandler sOnOffSceneHandler;
static void sceneOnOffCallback(EndpointId endpoint)
{
OnOffEndPointPair savedState;
ReturnOnFailure(sOnOffSceneHandler.mSceneEndpointStatePairs.GetPair(endpoint, savedState));
CommandId command = (savedState.mValue) ? Commands::On::Id : Commands::Off::Id;
OnOffServer::Instance().setOnOffValue(endpoint, command, false);
ReturnOnFailure(sOnOffSceneHandler.mSceneEndpointStatePairs.RemovePair(endpoint));
}
#endif // defined(MATTER_DM_PLUGIN_SCENES_MANAGEMENT) && CHIP_CONFIG_SCENES_USE_DEFAULT_HANDLERS
/**********************************************************
* Attributes Definition
*********************************************************/
static OnOffEffect * firstEffect = nullptr;
OnOffServer OnOffServer::instance;
static EmberEventControl gEventControls[kOnOffMaxEnpointCount];
/**********************************************************
* Function definition
*********************************************************/
static OnOffEffect * inst(EndpointId endpoint);
/**********************************************************
* Matter timer scheduling glue logic
*********************************************************/
void OnOffServer::timerCallback(System::Layer *, void * callbackContext)
{
auto control = static_cast<EmberEventControl *>(callbackContext);
(control->callback)(control->endpoint);
}
void OnOffServer::scheduleTimerCallbackMs(EmberEventControl * control, uint32_t delayMs)
{
CHIP_ERROR err = DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Milliseconds32(delayMs), timerCallback, control);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "OnOff Server failed to schedule event: %" CHIP_ERROR_FORMAT, err.Format());
}
}
void OnOffServer::cancelEndpointTimerCallback(EmberEventControl * control)
{
DeviceLayer::SystemLayer().CancelTimer(timerCallback, control);
}
void OnOffServer::cancelEndpointTimerCallback(EndpointId endpoint)
{
auto control = OnOffServer::getEventControl(endpoint, Span<EmberEventControl>(gEventControls));
if (control)
{
cancelEndpointTimerCallback(control);
}
}
void MatterOnOffClusterServerShutdownCallback(EndpointId endpoint)
{
ChipLogProgress(Zcl, "Shuting down on/off server cluster on endpoint %d", endpoint);
OnOffServer::Instance().cancelEndpointTimerCallback(endpoint);
}
/**********************************************************
* OnOff Implementation
*********************************************************/
OnOffServer & OnOffServer::Instance()
{
return instance;
}
chip::scenes::SceneHandler * OnOffServer::GetSceneHandler()
{
#if defined(MATTER_DM_PLUGIN_SCENES_MANAGEMENT) && CHIP_CONFIG_SCENES_USE_DEFAULT_HANDLERS
return &sOnOffSceneHandler;
#else
return nullptr;
#endif // defined(MATTER_DM_PLUGIN_SCENES_MANAGEMENT) && CHIP_CONFIG_SCENES_USE_DEFAULT_HANDLERS
}
bool OnOffServer::HasFeature(chip::EndpointId endpoint, Feature feature)
{
bool success;
uint32_t featureMap;
success = (Attributes::FeatureMap::Get(endpoint, &featureMap) == Status::Success);
return success ? ((featureMap & to_underlying(feature)) != 0) : false;
}
Status OnOffServer::getOnOffValue(chip::EndpointId endpoint, bool * currentOnOffValue)
{
// read current on/off value
Status status = Attributes::OnOff::Get(endpoint, currentOnOffValue);
if (status != Status::Success)
{
ChipLogProgress(Zcl, "ERR: reading on/off %x", to_underlying(status));
}
ChipLogProgress(Zcl, "On/Off ep%d value: %d", endpoint, *currentOnOffValue);
return status;
}
/** @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
*/
Status OnOffServer::setOnOffValue(chip::EndpointId endpoint, chip::CommandId command, bool initiatedByLevelChange)
{
MATTER_TRACE_SCOPE("setOnOffValue", "OnOff");
Status status;
bool currentValue, newValue;
// read current on/off value
status = Attributes::OnOff::Get(endpoint, &currentValue);
if (status != Status::Success)
{
ChipLogProgress(Zcl, "ERR: reading on/off %x", to_underlying(status));
return status;
}
// if the value is already what we want to set it to then do nothing
if ((!currentValue && command == Commands::Off::Id) || (currentValue && command == Commands::On::Id))
{
ChipLogProgress(Zcl, "Endpoint %x On/off already set to new value", endpoint);
return Status::Success;
}
// we either got a toggle, or an on when off, or an off when on,
// so we need to swap the value
newValue = !currentValue;
ChipLogProgress(Zcl, "Toggle ep%x on/off from state %x to %x", endpoint, currentValue, newValue);
// the sequence of updating on/off attribute and kick off level change effect should
// be depend on whether we are turning on or off. If we are turning on the light, we
// 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) // Set On
{
if (SupportsLightingApplications(endpoint))
{
uint16_t onTime = 0;
Attributes::OnTime::Get(endpoint, &onTime);
if (onTime == 0)
{
ChipLogProgress(Zcl, "On Command - OffWaitTime : 0");
Attributes::OffWaitTime::Set(endpoint, 0);
// Stop timer on the endpoint
EmberEventControl * event = getEventControl(endpoint, Span<EmberEventControl>(gEventControls));
if (event != nullptr)
{
cancelEndpointTimerCallback(event);
ChipLogProgress(Zcl, "On/Toggle Command - Stop Timer");
}
}
Attributes::GlobalSceneControl::Set(endpoint, true);
}
// write the new on/off value
status = Attributes::OnOff::Set(endpoint, newValue);
if (status != Status::Success)
{
ChipLogProgress(Zcl, "ERR: writing on/off %x", to_underlying(status));
return status;
}
#ifdef MATTER_DM_PLUGIN_LEVEL_CONTROL
// If initiatedByLevelChange is false, then we assume that the level change
// ZCL stuff has not happened and we do it here
if (!initiatedByLevelChange && LevelControlWithOnOffFeaturePresent(endpoint))
{
emberAfOnOffClusterLevelControlEffectCallback(endpoint, newValue);
}
#endif
#ifdef MATTER_DM_PLUGIN_MODE_SELECT
// If OnMode is not a null value, then change the current mode to it.
if (emberAfContainsServer(endpoint, ModeSelect::Id) &&
emberAfContainsAttribute(endpoint, ModeSelect::Id, ModeSelect::Attributes::OnMode::Id))
{
ModeSelect::Attributes::OnMode::TypeInfo::Type onMode;
if (ModeSelect::Attributes::OnMode::Get(endpoint, onMode) == Status::Success && !onMode.IsNull())
{
ChipLogProgress(Zcl, "Changing Current Mode to %x", onMode.Value());
status = ModeSelect::Attributes::CurrentMode::Set(endpoint, onMode.Value());
}
}
#endif
#ifdef MATTER_DM_PLUGIN_MODE_BASE
// If OnMode is not a null value, then change the current mode to it.
UpdateModeBaseCurrentModeToOnMode(endpoint);
#endif
}
else // Set Off
{
#ifdef MATTER_DM_PLUGIN_LEVEL_CONTROL
// If initiatedByLevelChange is false, then we assume that the level change
// ZCL stuff has not happened and we do it here
if (!initiatedByLevelChange && LevelControlWithOnOffFeaturePresent(endpoint))
{
emberAfOnOffClusterLevelControlEffectCallback(endpoint, newValue);
}
else
#endif
{
// write the new on/off value
status = Attributes::OnOff::Set(endpoint, newValue);
if (status != Status::Success)
{
ChipLogProgress(Zcl, "ERR: writing on/off %x", to_underlying(status));
return status;
}
if (SupportsLightingApplications(endpoint))
{
ChipLogProgress(Zcl, "Off completed. reset OnTime to 0");
Attributes::OnTime::Set(endpoint, 0); // Reset onTime
}
}
}
#ifdef MATTER_DM_PLUGIN_SCENES_MANAGEMENT
// the scene has been changed (the value of on/off has changed) so
// the current scene as described in the attribute table is invalid,
// so mark it as invalid (just writes the valid/invalid attribute)
ScenesManagement::ScenesServer::Instance().MakeSceneInvalidForAllFabrics(endpoint);
#endif // MATTER_DM_PLUGIN_SCENES_MANAGEMENT
// The returned status is based solely on the On/Off cluster. Errors in the
// Level Control and/or Scenes cluster are ignored.
return Status::Success;
}
void OnOffServer::initOnOffServer(chip::EndpointId endpoint)
{
#ifndef IGNORE_ON_OFF_CLUSTER_START_UP_ON_OFF
// StartUp behavior relies on OnOff and StartUpOnOff attributes being non-volatile.
if (SupportsLightingApplications(endpoint) && areStartUpOnOffServerAttributesNonVolatile(endpoint))
{
// Read the StartUpOnOff attribute and set the OnOff attribute as per
// following from zcl 7 14-0127-20i-zcl-ch-3-general.doc.
// 3.8.2.2.5 StartUpOnOff Attribute
// The StartUpOnOff attribute SHALL define the desired startup behavior of a
// lamp device when it is supplied with power and this state SHALL be
// reflected in the OnOff attribute. The values of the StartUpOnOff
// attribute are listed below.
// Table 3 46. Values of the StartUpOnOff Attribute
// Value Action on power up
// 0x00 Set the OnOff attribute to 0 (off).
// 0x01 Set the OnOff attribute to 1 (on).
// 0x02 If the previous value of the OnOff attribute is equal to 0,
// set the OnOff attribute to 1.If the previous value of the OnOff
// attribute is equal to 1, set the OnOff attribute to 0 (toggle).
// 0x03-0xfe These values are reserved. No action.
// 0xff This value cannot happen.
// null Set the OnOff attribute to its previous value.
bool onOffValueForStartUp = false;
Status status = getOnOffValueForStartUp(endpoint, onOffValueForStartUp);
if (status == Status::Success)
{
status = setOnOffValue(endpoint, onOffValueForStartUp, true);
}
#if defined(MATTER_DM_PLUGIN_SCENES_MANAGEMENT) && CHIP_CONFIG_SCENES_USE_DEFAULT_HANDLERS
// Registers Scene handlers for the On/Off cluster on the server
app::Clusters::ScenesManagement::ScenesServer::Instance().RegisterSceneHandler(endpoint,
OnOffServer::Instance().GetSceneHandler());
#endif // defined(MATTER_DM_PLUGIN_SCENES_MANAGEMENT) && CHIP_CONFIG_SCENES_USE_DEFAULT_HANDLERS
#ifdef MATTER_DM_PLUGIN_MODE_SELECT
// If OnMode is not a null value, then change the current mode to it.
if (onOffValueForStartUp && emberAfContainsServer(endpoint, ModeSelect::Id) &&
emberAfContainsAttribute(endpoint, ModeSelect::Id, ModeSelect::Attributes::OnMode::Id))
{
ModeSelect::Attributes::OnMode::TypeInfo::Type onMode;
if (ModeSelect::Attributes::OnMode::Get(endpoint, onMode) == Status::Success && !onMode.IsNull())
{
ChipLogProgress(Zcl, "Changing Current Mode to %x", onMode.Value());
status = ModeSelect::Attributes::CurrentMode::Set(endpoint, onMode.Value());
}
}
#endif
}
#endif // IGNORE_ON_OFF_CLUSTER_START_UP_ON_OFF
emberAfPluginOnOffClusterServerPostInitCallback(endpoint);
}
/** @brief Get the OnOff value when server starts.
*
* This function determines how StartUpOnOff affects the OnOff value when the server starts.
*
* @param endpoint Ver.: always
* @param onOffValueForStartUp Ver.: always
*/
Status OnOffServer::getOnOffValueForStartUp(chip::EndpointId endpoint, bool & onOffValueForStartUp)
{
app::DataModel::Nullable<OnOff::StartUpOnOffEnum> startUpOnOff;
Status status = Attributes::StartUpOnOff::Get(endpoint, startUpOnOff);
if (status == Status::Success)
{
// Initialise updated value to 0
bool updatedOnOff = false;
status = Attributes::OnOff::Get(endpoint, &updatedOnOff);
if (status == Status::Success)
{
if (!startUpOnOff.IsNull())
{
switch (startUpOnOff.Value())
{
case OnOff::StartUpOnOffEnum::kOff:
updatedOnOff = false; // Off
break;
case OnOff::StartUpOnOffEnum::kOn:
updatedOnOff = true; // On
break;
case OnOff::StartUpOnOffEnum::kToggle:
updatedOnOff = !updatedOnOff;
break;
default:
// All other values 0x03- 0xFE are reserved - no action.
break;
}
}
onOffValueForStartUp = updatedOnOff;
}
}
return status;
}
bool OnOffServer::offCommand(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath)
{
MATTER_TRACE_SCOPE("OffCommand", "OnOff");
Status status = setOnOffValue(commandPath.mEndpointId, Commands::Off::Id, false);
commandObj->AddStatus(commandPath, status);
return true;
}
bool OnOffServer::onCommand(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath)
{
MATTER_TRACE_SCOPE("OnCommand", "OnOff");
Status status = setOnOffValue(commandPath.mEndpointId, Commands::On::Id, false);
commandObj->AddStatus(commandPath, status);
return true;
}
bool OnOffServer::toggleCommand(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath)
{
MATTER_TRACE_SCOPE("ToggleCommand", "OnOff");
Status status = setOnOffValue(commandPath.mEndpointId, Commands::Toggle::Id, false);
commandObj->AddStatus(commandPath, status);
return true;
}
bool OnOffServer::offWithEffectCommand(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::OffWithEffect::DecodableType & commandData)
{
MATTER_TRACE_SCOPE("offWithEffectCommand", "OnOff");
auto effectId = commandData.effectIdentifier;
auto effectVariant = commandData.effectVariant;
chip::EndpointId endpoint = commandPath.mEndpointId;
Status status = Status::Success;
if (SupportsLightingApplications(endpoint))
{
#ifdef MATTER_DM_PLUGIN_SCENES_MANAGEMENT
FabricIndex fabric = commandObj->GetAccessingFabricIndex();
#endif // MATTER_DM_PLUGIN_SCENES_MANAGEMENT
bool globalSceneControl = false;
OnOff::Attributes::GlobalSceneControl::Get(endpoint, &globalSceneControl);
bool isOnBeforeCommand = false;
OnOff::Attributes::OnOff::Get(endpoint, &isOnBeforeCommand);
if (globalSceneControl)
{
#ifdef MATTER_DM_PLUGIN_SCENES_MANAGEMENT
GroupId groupId = ZCL_SCENES_GLOBAL_SCENE_GROUP_ID;
if (commandObj->GetExchangeContext()->IsGroupExchangeContext())
{
groupId = commandObj->GetExchangeContext()->GetSessionHandle()->AsIncomingGroupSession()->GetGroupId();
}
ScenesManagement::ScenesServer::Instance().StoreCurrentScene(fabric, endpoint, groupId,
ZCL_SCENES_GLOBAL_SCENE_SCENE_ID);
#endif // MATTER_DM_PLUGIN_SCENES_MANAGEMENT
OnOff::Attributes::GlobalSceneControl::Set(endpoint, 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);
}
}
status = setOnOffValue(endpoint, Commands::Off::Id, false);
}
else
{
status = Status::UnsupportedCommand;
}
commandObj->AddStatus(commandPath, status);
return true;
}
bool OnOffServer::OnWithRecallGlobalSceneCommand(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath)
{
MATTER_TRACE_SCOPE("OnWithRecallGlobalSceneCommand", "OnOff");
chip::EndpointId endpoint = commandPath.mEndpointId;
if (!SupportsLightingApplications(endpoint))
{
commandObj->AddStatus(commandPath, Status::UnsupportedCommand);
return true;
}
#ifdef MATTER_DM_PLUGIN_SCENES_MANAGEMENT
FabricIndex fabric = commandObj->GetAccessingFabricIndex();
#endif // MATTER_DM_PLUGIN_SCENES_MANAGEMENT
bool globalSceneControl = false;
OnOff::Attributes::GlobalSceneControl::Get(endpoint, &globalSceneControl);
if (globalSceneControl)
{
commandObj->AddStatus(commandPath, Status::Success);
return true;
}
#ifdef MATTER_DM_PLUGIN_SCENES_MANAGEMENT
GroupId groupId = ZCL_SCENES_GLOBAL_SCENE_GROUP_ID;
if (commandObj->GetExchangeContext()->IsGroupExchangeContext())
{
groupId = commandObj->GetExchangeContext()->GetSessionHandle()->AsIncomingGroupSession()->GetGroupId();
}
ScenesManagement::ScenesServer::Instance().RecallScene(fabric, endpoint, groupId, ZCL_SCENES_GLOBAL_SCENE_SCENE_ID);
#endif // MATTER_DM_PLUGIN_SCENES_MANAGEMENT
OnOff::Attributes::GlobalSceneControl::Set(endpoint, true);
setOnOffValue(endpoint, Commands::On::Id, false);
commandObj->AddStatus(commandPath, Status::Success);
return true;
}
uint32_t OnOffServer::calculateNextWaitTimeMS()
{
const chip::System::Clock::Timestamp currentTime = chip::System::SystemClock().GetMonotonicTimestamp();
chip::System::Clock::Timestamp waitTime = ON_OFF_UPDATE_TIME_MS;
chip::System::Clock::Timestamp latency;
if (currentTime > nextDesiredOnWithTimedOffTimestamp)
{
latency = currentTime - nextDesiredOnWithTimedOffTimestamp;
if (latency >= ON_OFF_UPDATE_TIME_MS)
waitTime = chip::System::Clock::Milliseconds32(1);
else
waitTime -= latency;
}
nextDesiredOnWithTimedOffTimestamp += ON_OFF_UPDATE_TIME_MS;
return (uint32_t) waitTime.count();
}
bool OnOffServer::OnWithTimedOffCommand(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::OnWithTimedOff::DecodableType & commandData)
{
MATTER_TRACE_SCOPE("OnWithTimedOffCommand", "OnOff");
BitFlags<OnOffControlBitmap> onOffControl = commandData.onOffControl;
uint16_t onTime = commandData.onTime;
uint16_t offWaitTime = commandData.offWaitTime;
Status status = Status::Success;
chip::EndpointId endpoint = commandPath.mEndpointId;
bool isOn = false;
uint16_t currentOffWaitTime = MAX_ON_OFF_TIME_VALUE;
uint16_t currentOnTime = 0;
EmberEventControl * event = configureEventControl(endpoint);
VerifyOrExit(event != nullptr, status = Status::UnsupportedEndpoint);
VerifyOrExit(SupportsLightingApplications(endpoint), status = Status::UnsupportedCommand);
OnOff::Attributes::OnOff::Get(endpoint, &isOn);
// OnOff is off and the commands is only accepted if on
if (onOffControl.Has(OnOffControlBitmap::kAcceptOnlyWhenOn) && !isOn)
{
commandObj->AddStatus(commandPath, Status::Success);
return true;
}
OnOff::Attributes::OffWaitTime::Get(endpoint, &currentOffWaitTime);
OnOff::Attributes::OnTime::Get(endpoint, &currentOnTime);
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;
}
ChipLogProgress(Zcl, "On Time: %d | off wait Time: %d", currentOnTime, currentOffWaitTime);
if (currentOnTime < MAX_ON_OFF_TIME_VALUE && currentOffWaitTime < MAX_ON_OFF_TIME_VALUE)
{
nextDesiredOnWithTimedOffTimestamp = chip::System::SystemClock().GetMonotonicTimestamp() + ON_OFF_UPDATE_TIME_MS;
scheduleTimerCallbackMs(configureEventControl(endpoint), ON_OFF_UPDATE_TIME_MS.count());
}
exit:
commandObj->AddStatus(commandPath, 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)
{
ChipLogDetail(Zcl, "Timer callback - Entering callback");
bool isOn = false;
OnOff::Attributes::OnOff::Get(endpoint, &isOn);
if (isOn) // OnOff On case
{
// Restart Timer
scheduleTimerCallbackMs(configureEventControl(endpoint), calculateNextWaitTimeMS());
// Update onTime values
uint16_t onTime = MIN_ON_OFF_TIME_VALUE;
OnOff::Attributes::OnTime::Get(endpoint, &onTime);
ChipLogDetail(Zcl, "Timer callback - On Time: %d", onTime);
if (onTime > 0)
{
onTime--;
OnOff::Attributes::OnTime::Set(endpoint, onTime);
}
if (onTime == 0)
{
ChipLogDetail(Zcl, "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);
}
ChipLogDetail(Zcl, "Timer Callback - wait Off Time: %d", offWaitTime);
// Validate if necessary to restart timer
if (offWaitTime > 0)
{
// Restart Timer
scheduleTimerCallbackMs(configureEventControl(endpoint), calculateNextWaitTimeMS());
}
else
{
ChipLogProgress(Zcl, "Timer Callback - wait Off Time cycle finished");
// Stop timer on the endpoint
cancelEndpointTimerCallback(getEventControl(endpoint, Span<EmberEventControl>(gEventControls)));
}
}
}
#ifndef IGNORE_ON_OFF_CLUSTER_START_UP_ON_OFF
bool OnOffServer::areStartUpOnOffServerAttributesNonVolatile(EndpointId endpoint)
{
return !emberAfIsKnownVolatileAttribute(endpoint, OnOff::Id, Attributes::OnOff::Id) &&
!emberAfIsKnownVolatileAttribute(endpoint, OnOff::Id, Attributes::StartUpOnOff::Id);
}
#endif // IGNORE_ON_OFF_CLUSTER_START_UP_ON_OFF
/**
* @brief event control object for an endpoint
*
* @param[in] endpoint target endpoint
* @param[in] eventControlArray Array where to find the event control
* @param[in] eventControlArraySize Size of the event control array
* @return EmberEventControl* configured event control
*/
EmberEventControl * OnOffServer::getEventControl(EndpointId endpoint, const Span<EmberEventControl> & eventControlArray)
{
uint16_t index = emberAfGetClusterServerEndpointIndex(endpoint, OnOff::Id, MATTER_DM_ON_OFF_CLUSTER_SERVER_ENDPOINT_COUNT);
if (index >= eventControlArray.size())
{
return nullptr;
}
return &eventControlArray[index];
}
/**
* @brief Configures EventControl 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, Span<EmberEventControl>(gEventControls));
VerifyOrReturnError(controller != nullptr, nullptr);
controller->endpoint = endpoint;
controller->callback = &onOffWaitTimeOffEventHandler;
return controller;
}
/**********************************************************
* OnOffEffect Implementation
*********************************************************/
static OnOffEffect * inst(EndpointId endpoint)
{
OnOffEffect * current = firstEffect;
while (current != nullptr && current->mEndpoint != endpoint)
{
current = current->next();
}
return current;
}
static inline void reg(OnOffEffect * inst)
{
inst->setNext(firstEffect);
firstEffect = inst;
}
static inline void unreg(OnOffEffect * inst)
{
if (firstEffect == inst)
{
firstEffect = firstEffect->next();
}
else
{
OnOffEffect * previous = firstEffect;
OnOffEffect * current = firstEffect->next();
while (current != nullptr && current != inst)
{
previous = current;
current = current->next();
}
if (current != nullptr)
{
previous->setNext(current->next());
}
}
}
OnOffEffect::OnOffEffect(chip::EndpointId endpoint, OffWithEffectTriggerCommand offWithEffectTrigger,
EffectIdentifierEnum 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(commandObj, commandPath);
}
bool emberAfOnOffClusterOnCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::On::DecodableType & commandData)
{
return OnOffServer::Instance().onCommand(commandObj, commandPath);
}
bool emberAfOnOffClusterToggleCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::Toggle::DecodableType & commandData)
{
return OnOffServer::Instance().toggleCommand(commandObj, commandPath);
}
bool emberAfOnOffClusterOffWithEffectCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::OffWithEffect::DecodableType & commandData)
{
return OnOffServer::Instance().offWithEffectCommand(commandObj, commandPath, commandData);
}
bool emberAfOnOffClusterOnWithRecallGlobalSceneCallback(app::CommandHandler * commandObj,
const app::ConcreteCommandPath & commandPath,
const Commands::OnWithRecallGlobalScene::DecodableType & commandData)
{
return OnOffServer::Instance().OnWithRecallGlobalSceneCommand(commandObj, commandPath);
}
bool emberAfOnOffClusterOnWithTimedOffCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::OnWithTimedOff::DecodableType & commandData)
{
return OnOffServer::Instance().OnWithTimedOffCommand(commandObj, commandPath, commandData);
}
void emberAfOnOffClusterServerInitCallback(chip::EndpointId endpoint)
{
OnOffServer::Instance().initOnOffServer(endpoint);
}
void onOffWaitTimeOffEventHandler(chip::EndpointId endpoint)
{
OnOffServer::Instance().updateOnOffTimeCommand(endpoint);
}
void emberAfPluginOnOffClusterServerPostInitCallback(EndpointId endpoint) {}
void MatterOnOffPluginServerInitCallback() {}