blob: c4fff306f3ea3e6276818a9ab4b13be519385fdb [file] [log] [blame]
/*
*
* Copyright (c) 2021-2023 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ScenesManagementCluster.h"
#include <app/clusters/scenes-server/Constants.h>
#include <app/clusters/scenes-server/SceneTable.h>
#include <app/server-cluster/AttributeListBuilder.h>
#include <app/server-cluster/DefaultServerCluster.h>
#include <clusters/ScenesManagement/Metadata.h>
#include <lib/core/CHIPError.h>
#include <lib/support/CodeUtils.h>
#include <tracing/macros.h>
#include <optional>
using SceneTable = chip::scenes::SceneTable<chip::scenes::ExtensionFieldSetsImpl>;
using SceneTableEntry = SceneTable::SceneTableEntry;
using SceneStorageId = SceneTable::SceneStorageId;
using SceneData = SceneTable::SceneData;
using ExtensionFieldSet = chip::scenes::ExtensionFieldSet;
using GroupDataProvider = chip::Credentials::GroupDataProvider;
using AuthMode = chip::Access::AuthMode;
using chip::Protocols::InteractionModel::Status;
using namespace chip::app::Clusters::ScenesManagement::Attributes;
using namespace chip::app::Clusters::ScenesManagement::Commands;
using namespace chip::app::Clusters::ScenesManagement::Structs;
using namespace chip::app::Clusters::ScenesManagement;
namespace chip::app::Clusters {
namespace {
constexpr Protocols::InteractionModel::Status ResponseStatus(CHIP_ERROR err)
{
// TODO : Properly fix mapping between error types (issue https://github.com/project-chip/connectedhomeip/issues/26885)
if (CHIP_ERROR_NOT_FOUND == err)
{
return Status::NotFound;
}
if (CHIP_ERROR_NO_MEMORY == err)
{
return Status::ResourceExhausted;
}
if (CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute) == err)
{
// TODO: Confirm if we need to add UnsupportedAttribute status as a return for Scene Commands
return Status::InvalidCommand;
}
return StatusIB(err).mStatus;
}
/// RAII for a scenes management table provider:
/// - does a `Take()` on a scene at creation
/// - ensures `Release()` is called on destruction
class ScopedSceneTable
{
public:
ScopedSceneTable(const ScopedSceneTable &) = delete;
ScopedSceneTable & operator=(const ScopedSceneTable &) = delete;
ScopedSceneTable(ScenesManagementTableProvider & provider) : mProvider(provider), mTable(provider.Take()) {}
~ScopedSceneTable() { mProvider.Release(mTable); }
SceneTable * operator->() { return mTable; }
const SceneTable * operator->() const { return mTable; }
operator bool() const { return mTable != nullptr; }
private:
ScenesManagementTableProvider & mProvider;
SceneTable * mTable;
};
/// A very common pattern of:
/// - if error (i.e. NOT CHIP_NO_ERROR), then set response status and return response
#define SuccessOrReturnWithFailureStatus(err_expr, response) \
do \
{ \
if (CHIP_ERROR __err = err_expr; __err != CHIP_NO_ERROR) \
{ \
response.status = to_underlying(ResponseStatus(__err)); \
return response; \
} \
} while (0)
} // namespace
CHIP_ERROR ScenesManagementCluster::StoreCurrentGlobalScene(FabricIndex fabricIndex)
{
return StoreCurrentScene(fabricIndex, scenes::kGlobalSceneGroupId, scenes::kGlobalSceneId);
}
CHIP_ERROR ScenesManagementCluster::RecallGlobalScene(FabricIndex fabricIndex)
{
return RecallScene(fabricIndex, scenes::kGlobalSceneGroupId, scenes::kGlobalSceneId);
}
CHIP_ERROR ScenesManagementCluster::UpdateFabricSceneInfo(FabricIndex fabric, Optional<GroupId> group, Optional<SceneId> scene,
Optional<bool> sceneValid)
{
VerifyOrReturnError(kUndefinedFabricIndex != fabric, CHIP_ERROR_INVALID_ARGUMENT);
ScopedSceneTable sceneTable(mSceneTableProvider);
VerifyOrReturnError(sceneTable, CHIP_ERROR_INTERNAL);
SceneInfoStruct::Type * sceneInfo = GetSceneInfoStruct(fabric);
if (nullptr != sceneInfo)
{
if (group.HasValue())
{
sceneInfo->currentGroup = group.Value();
}
if (scene.HasValue())
{
sceneInfo->currentScene = scene.Value();
}
if (sceneValid.HasValue())
{
sceneInfo->sceneValid = sceneValid.Value();
}
ReturnErrorOnFailure(sceneTable->GetFabricSceneCount(fabric, sceneInfo->sceneCount));
ReturnErrorOnFailure(sceneTable->GetRemainingCapacity(fabric, sceneInfo->remainingCapacity));
}
else
{
// If we couldn't find a SceneInfoStruct for the fabric, create one
SceneInfoStruct::Type newSceneInfo;
newSceneInfo.fabricIndex = fabric;
newSceneInfo.currentGroup = group.ValueOr(0);
newSceneInfo.currentScene = scene.ValueOr(0);
newSceneInfo.sceneValid = sceneValid.ValueOr(false);
ReturnErrorOnFailure(sceneTable->GetFabricSceneCount(fabric, newSceneInfo.sceneCount));
ReturnErrorOnFailure(sceneTable->GetRemainingCapacity(fabric, newSceneInfo.remainingCapacity));
ReturnErrorOnFailure(SetSceneInfoStruct(fabric, newSceneInfo));
}
NotifyAttributeChanged(ScenesManagement::Attributes::FabricSceneInfo::Id);
return CHIP_NO_ERROR;
}
SceneInfoStruct::Type * ScenesManagementCluster::FabricSceneInfo::GetSceneInfoStruct(FabricIndex fabric)
{
uint8_t sceneInfoStructIndex = 0;
VerifyOrReturnValue(CHIP_NO_ERROR == FindSceneInfoStructIndex(fabric, sceneInfoStructIndex), nullptr);
return &mSceneInfoStructs[sceneInfoStructIndex];
}
/// @brief Sets the SceneInfoStruct for a specific fabric for a specific endpoint
/// @param endpoint target endpoint
/// @param fabric target fabric
/// @param [in] sceneInfoStruct SceneInfoStruct to set
/// @return CHIP_NO_ERROR, CHIP_ERROR_NOT_FOUND if the endpoint is not found, CHIP_ERROR_NO_MEMORY if the number of fabrics is
/// exceeded, CHIP_ERROR_INVALID_ARGUMENT if invalid fabric or endpoint
CHIP_ERROR ScenesManagementCluster::FabricSceneInfo::SetSceneInfoStruct(FabricIndex fabric,
const SceneInfoStruct::Type & sceneInfoStruct)
{
VerifyOrReturnError(kUndefinedFabricIndex != fabric, CHIP_ERROR_INVALID_ARGUMENT);
uint8_t sceneInfoStructIndex = 0;
if (CHIP_ERROR_NOT_FOUND == FindSceneInfoStructIndex(fabric, sceneInfoStructIndex))
{
VerifyOrReturnError(mSceneInfoStructsCount < MATTER_ARRAY_SIZE(mSceneInfoStructs), CHIP_ERROR_NO_MEMORY);
sceneInfoStructIndex = mSceneInfoStructsCount;
mSceneInfoStructsCount++;
}
mSceneInfoStructs[sceneInfoStructIndex] = sceneInfoStruct;
return CHIP_NO_ERROR;
}
void ScenesManagementCluster::FabricSceneInfo::ClearSceneInfoStruct(FabricIndex fabric)
{
uint8_t sceneInfoStructIndex = 0;
ReturnOnFailure(FindSceneInfoStructIndex(fabric, sceneInfoStructIndex));
uint8_t nextIndex = static_cast<uint8_t>(sceneInfoStructIndex + 1);
uint8_t moveNum = static_cast<uint8_t>(MATTER_ARRAY_SIZE(mSceneInfoStructs) - nextIndex);
// Compress the endpoint's SceneInfoStruct array
if (moveNum)
{
for (size_t i = 0; i < moveNum; ++i)
{
mSceneInfoStructs[sceneInfoStructIndex + i] = mSceneInfoStructs[nextIndex + i];
}
}
// Decrement the SceneInfoStruct count
mSceneInfoStructsCount--;
// Clear the last populated SceneInfoStruct
mSceneInfoStructs[mSceneInfoStructsCount].fabricIndex = kUndefinedFabricIndex;
mSceneInfoStructs[mSceneInfoStructsCount].sceneCount = 0;
mSceneInfoStructs[mSceneInfoStructsCount].currentScene = 0;
mSceneInfoStructs[mSceneInfoStructsCount].currentGroup = 0;
mSceneInfoStructs[mSceneInfoStructsCount].remainingCapacity = 0;
}
CHIP_ERROR ScenesManagementCluster::FabricSceneInfo::FindSceneInfoStructIndex(FabricIndex fabric, uint8_t & index)
{
VerifyOrReturnError(kUndefinedFabricIndex != fabric, CHIP_ERROR_INVALID_ARGUMENT);
index = 0;
for (auto & info : mSceneInfoStructs)
{
if (info.fabricIndex == fabric)
{
return CHIP_NO_ERROR;
}
index++;
}
return CHIP_ERROR_NOT_FOUND;
}
CHIP_ERROR ScenesManagementCluster::Attributes(const ConcreteClusterPath & path,
ReadOnlyBufferBuilder<DataModel::AttributeEntry> & builder)
{
AttributeListBuilder listBuilder(builder);
return listBuilder.Append(Span(kMandatoryMetadata), {}, {});
}
CHIP_ERROR ScenesManagementCluster::AcceptedCommands(const ConcreteClusterPath & path,
ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & builder)
{
if (mSupportCopyScenes)
{
ReturnErrorOnFailure(builder.EnsureAppendCapacity(1));
ReturnErrorOnFailure(builder.Append(CopyScene::kMetadataEntry));
}
static constexpr DataModel::AcceptedCommandEntry kCommands[] = {
AddScene::kMetadataEntry, //
ViewScene::kMetadataEntry, //
RemoveScene::kMetadataEntry, //
RemoveAllScenes::kMetadataEntry, //
StoreScene::kMetadataEntry, //
RecallScene::kMetadataEntry, //
GetSceneMembership::kMetadataEntry, //
};
return builder.ReferenceExisting(kCommands);
}
CHIP_ERROR ScenesManagementCluster::GeneratedCommands(const ConcreteClusterPath & path, ReadOnlyBufferBuilder<CommandId> & builder)
{
if (mSupportCopyScenes)
{
ReturnErrorOnFailure(builder.EnsureAppendCapacity(1));
ReturnErrorOnFailure(builder.Append(CopySceneResponse::Id));
}
static constexpr CommandId kCommands[] = {
AddSceneResponse::Id, //
ViewSceneResponse::Id, //
RemoveSceneResponse::Id, //
RemoveAllScenesResponse::Id, //
StoreSceneResponse::Id, //
GetSceneMembershipResponse::Id, //
};
return builder.ReferenceExisting(kCommands);
}
void ScenesManagementCluster::OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex)
{
ScopedSceneTable sceneTable(mSceneTableProvider);
VerifyOrReturn(sceneTable);
// The implementation of SceneTable::RemoveFabric() must not call back into the FabricTable
LogErrorOnFailure(sceneTable->RemoveFabric(fabricIndex));
mFabricSceneInfo.ClearSceneInfoStruct(fabricIndex);
}
CHIP_ERROR ScenesManagementCluster::StoreSceneParse(const FabricIndex & fabricIdx, const GroupId & groupID, const SceneId & sceneID)
{
// Make the current fabric's SceneValid false before storing a scene
ReturnErrorOnFailure(MakeSceneInvalid(fabricIdx));
ScopedSceneTable sceneTable(mSceneTableProvider);
// Verify Endpoint in group
VerifyOrReturnError(nullptr != mGroupProvider, CHIP_ERROR_INTERNAL);
if (0 != groupID && !mGroupProvider->HasEndpoint(fabricIdx, groupID, mPath.mEndpointId))
{
return CHIP_IM_GLOBAL_STATUS(InvalidCommand);
}
// Scene Table interface data
SceneTableEntry scene(SceneStorageId(sceneID, groupID));
VerifyOrReturnError(sceneTable, CHIP_ERROR_INTERNAL);
CHIP_ERROR err = sceneTable->GetSceneTableEntry(fabricIdx, scene.mStorageId, scene);
if (CHIP_NO_ERROR != err && CHIP_ERROR_NOT_FOUND != err)
{
return err;
}
if (CHIP_ERROR_NOT_FOUND == err)
{
scene.mStorageData.SetName(CharSpan());
scene.mStorageData.mSceneTransitionTimeMs = 0;
}
else
{
// Check if we still support scenes name in case an OTA changed that, if we don't, set name to empty
if (!mFeatures.Has(ScenesManagement::Feature::kSceneNames))
{
scene.mStorageData.SetName(CharSpan());
}
scene.mStorageData.mExtensionFieldSets.Clear();
}
// Gets the EFS
ReturnErrorOnFailure(sceneTable->SceneSaveEFS(scene));
// Insert in Scene Table
ReturnErrorOnFailure(sceneTable->SetSceneTableEntry(fabricIdx, scene));
// Update SceneInfo Attribute
return UpdateFabricSceneInfo(fabricIdx, MakeOptional(groupID), MakeOptional(sceneID), MakeOptional(static_cast<bool>(true)));
}
CHIP_ERROR ScenesManagementCluster::MakeSceneInvalidForAllFabrics()
{
for (auto & info : *mFabricTable)
{
ReturnErrorOnFailure(MakeSceneInvalid(info.GetFabricIndex()));
}
return CHIP_NO_ERROR;
}
CHIP_ERROR ScenesManagementCluster::RecallSceneParse(const FabricIndex & fabricIdx, const GroupId & groupID,
const SceneId & sceneID,
const Optional<DataModel::Nullable<uint32_t>> & transitionTime)
{
// Make SceneValid false for all fabrics before recalling a scene
ReturnErrorOnFailure(MakeSceneInvalidForAllFabrics());
// Get Scene Table Instance
ScopedSceneTable sceneTable(mSceneTableProvider);
// Verify Endpoint in group
VerifyOrReturnError(nullptr != mGroupProvider, CHIP_ERROR_INTERNAL);
if (0 != groupID && !mGroupProvider->HasEndpoint(fabricIdx, groupID, mPath.mEndpointId))
{
return CHIP_IM_GLOBAL_STATUS(InvalidCommand);
}
// Scene Table interface data
SceneTableEntry scene(SceneStorageId(sceneID, groupID));
VerifyOrReturnError(sceneTable, CHIP_ERROR_INTERNAL);
ReturnErrorOnFailure(sceneTable->GetSceneTableEntry(fabricIdx, scene.mStorageId, scene));
// Check for optional
if (transitionTime.HasValue())
{
// Check for nullable
if (!transitionTime.Value().IsNull())
{
scene.mStorageData.mSceneTransitionTimeMs = transitionTime.Value().Value();
}
}
ReturnErrorOnFailure(sceneTable->SceneApplyEFS(scene));
// Update FabricSceneInfo, at this point the scene is considered valid
return UpdateFabricSceneInfo(fabricIdx, Optional<GroupId>(groupID), Optional<SceneId>(sceneID), Optional<bool>(true));
}
std::optional<DataModel::ActionReturnStatus> ScenesManagementCluster::InvokeCommand(const DataModel::InvokeRequest & request,
chip::TLV::TLVReader & input_arguments,
CommandHandler * handler)
{
switch (request.path.mCommandId)
{
case AddScene::Id: {
AddScene::DecodableType request_data;
ReturnErrorOnFailure(request_data.Decode(input_arguments, request.GetAccessingFabricIndex()));
handler->AddResponse(request.path, HandleAddScene(handler->GetAccessingFabricIndex(), request_data));
return std::nullopt;
}
case ViewScene::Id: {
ViewScene::DecodableType request_data;
SceneTableEntry scene;
std::array<ExtensionFieldSetStruct::Type, scenes::kMaxClustersPerScene> responseEFSBuffer;
ReturnErrorOnFailure(request_data.Decode(input_arguments, request.GetAccessingFabricIndex()));
handler->AddResponse(request.path,
HandleViewScene(handler->GetAccessingFabricIndex(), request_data, scene, responseEFSBuffer));
return std::nullopt;
}
case RemoveScene::Id: {
RemoveScene::DecodableType request_data;
ReturnErrorOnFailure(request_data.Decode(input_arguments, request.GetAccessingFabricIndex()));
handler->AddResponse(request.path, HandleRemoveScene(handler->GetAccessingFabricIndex(), request_data));
return std::nullopt;
}
case RemoveAllScenes::Id: {
RemoveAllScenes::DecodableType request_data;
ReturnErrorOnFailure(request_data.Decode(input_arguments, request.GetAccessingFabricIndex()));
handler->AddResponse(request.path, HandleRemoveAllScenes(handler->GetAccessingFabricIndex(), request_data));
return std::nullopt;
}
case StoreScene::Id: {
StoreScene::DecodableType request_data;
ReturnErrorOnFailure(request_data.Decode(input_arguments, request.GetAccessingFabricIndex()));
handler->AddResponse(request.path, HandleStoreScene(handler->GetAccessingFabricIndex(), request_data));
return std::nullopt;
}
case RecallScene::Id: {
RecallScene::DecodableType request_data;
ReturnErrorOnFailure(request_data.Decode(input_arguments, request.GetAccessingFabricIndex()));
return HandleRecallScene(handler->GetAccessingFabricIndex(), request_data);
}
case GetSceneMembership::Id: {
GetSceneMembership::DecodableType request_data;
std::array<SceneId, scenes::kMaxScenesPerFabric> scenesInGroup;
ReturnErrorOnFailure(request_data.Decode(input_arguments, request.GetAccessingFabricIndex()));
handler->AddResponse(request.path,
HandleGetSceneMembership(handler->GetAccessingFabricIndex(), request_data, scenesInGroup));
return std::nullopt;
}
case CopyScene::Id: {
CopyScene::DecodableType request_data;
ReturnErrorOnFailure(request_data.Decode(input_arguments, request.GetAccessingFabricIndex()));
handler->AddResponse(request.path, HandleCopyScene(handler->GetAccessingFabricIndex(), request_data));
return std::nullopt;
}
default:
return Status::UnsupportedCommand;
}
}
DataModel::ActionReturnStatus ScenesManagementCluster::ReadAttribute(const DataModel::ReadAttributeRequest & request,
AttributeValueEncoder & encoder)
{
switch (request.path.mAttributeId)
{
case ClusterRevision::Id:
return encoder.Encode(ScenesManagement::kRevision);
case FeatureMap::Id:
return encoder.Encode(mFeatures);
case SceneTableSize::Id: {
ScopedSceneTable sceneTable(mSceneTableProvider);
VerifyOrReturnError(sceneTable, Status::Failure);
return encoder.Encode(sceneTable->GetTableSize());
}
case ScenesManagement::Attributes::FabricSceneInfo::Id: {
ScopedSceneTable sceneTable(mSceneTableProvider);
return encoder.EncodeList([&](const auto & listEncoder) -> CHIP_ERROR {
Span<SceneInfoStruct::Type> fabricSceneInfoSpan = mFabricSceneInfo.GetFabricSceneInfo();
for (auto & info : fabricSceneInfoSpan)
{
// Update the SceneInfoStruct's Capacity in case it's capacity was limited by other fabrics
ReturnErrorOnFailure(sceneTable->GetRemainingCapacity(info.fabricIndex, info.remainingCapacity));
ReturnErrorOnFailure(listEncoder.Encode(info));
}
return CHIP_NO_ERROR;
});
}
default:
return Status::UnsupportedAttribute;
}
}
CHIP_ERROR ScenesManagementCluster::Startup(ServerClusterContext & context)
{
ScopedSceneTable sceneTable(mSceneTableProvider);
// NOTE: this re-sets the storage delegate and provider on a SHARED GLOBAL member.
// Generally safe(the same values should be used within an entire cluster) but will not work well
// if multiple stacks work in parallel.
ReturnErrorOnFailure(sceneTable->Init(context.storage, context.provider));
ReturnErrorOnFailure(mFabricTable->AddFabricDelegate(this));
for (const FabricInfo & info : *mFabricTable)
{
FabricIndex fabric = info.GetFabricIndex();
LogErrorOnFailure(UpdateFabricSceneInfo(fabric, Optional<GroupId>(), Optional<SceneId>(), Optional<bool>()));
}
return DefaultServerCluster::Startup(context);
}
CHIP_ERROR ScenesManagementCluster::ClearPersistentData()
{
// For the persistent storage to be likely correct, we enforce cluster to be started up
VerifyOrReturnError(mContext != nullptr, CHIP_ERROR_INCORRECT_STATE);
ScopedSceneTable sceneTable(mSceneTableProvider);
VerifyOrReturnError(sceneTable, CHIP_ERROR_INCORRECT_STATE);
return sceneTable->RemoveEndpoint();
}
void ScenesManagementCluster::Shutdown(ClusterShutdownType shutdownType)
{
mFabricTable->RemoveFabricDelegate(this);
if (shutdownType == ClusterShutdownType::kPermanentRemove)
{
LogErrorOnFailure(ClearPersistentData());
}
DefaultServerCluster::Shutdown(shutdownType);
}
CHIP_ERROR ScenesManagementCluster::GroupWillBeRemoved(FabricIndex aFabricIdx, GroupId aGroupId)
{
// Get Scene Table Instance
ScopedSceneTable sceneTable(mSceneTableProvider);
VerifyOrReturnError(sceneTable, CHIP_ERROR_INTERNAL);
SceneInfoStruct::Type * sceneInfo = mFabricSceneInfo.GetSceneInfoStruct(aFabricIdx);
chip::GroupId currentGroup = (nullptr != sceneInfo) ? sceneInfo->currentGroup : 0x0000;
// If currentGroup is what is being removed, we can't possibly still have a valid scene,
// because the scene we have (if any) will also be removed.
if (aGroupId == currentGroup)
{
ReturnErrorOnFailure(MakeSceneInvalid(aFabricIdx));
}
VerifyOrReturnError(nullptr != mGroupProvider, CHIP_ERROR_INCORRECT_STATE);
if (0 != aGroupId && !mGroupProvider->HasEndpoint(aFabricIdx, aGroupId, mPath.mEndpointId))
{
return CHIP_NO_ERROR;
}
return sceneTable->DeleteAllScenesInGroup(aFabricIdx, aGroupId);
}
CHIP_ERROR ScenesManagementCluster::MakeSceneInvalid(FabricIndex aFabricIdx)
{
return UpdateFabricSceneInfo(aFabricIdx, Optional<GroupId>(), Optional<SceneId>(), Optional<bool>(false));
}
CHIP_ERROR ScenesManagementCluster::StoreCurrentScene(FabricIndex aFabricIx, GroupId aGroupId, SceneId aSceneId)
{
return StoreSceneParse(aFabricIx, aGroupId, aSceneId);
}
CHIP_ERROR ScenesManagementCluster::RecallScene(FabricIndex aFabricIx, GroupId aGroupId, SceneId aSceneId)
{
Optional<DataModel::Nullable<uint32_t>> transitionTime;
return RecallSceneParse(aFabricIx, aGroupId, aSceneId, transitionTime);
}
CHIP_ERROR ScenesManagementCluster::RemoveFabric(FabricIndex aFabricIndex)
{
ScopedSceneTable sceneTable(mSceneTableProvider);
VerifyOrReturnError(sceneTable, CHIP_ERROR_INTERNAL);
ReturnErrorOnFailure(sceneTable->RemoveFabric(aFabricIndex));
mFabricSceneInfo.ClearSceneInfoStruct(aFabricIndex);
return CHIP_NO_ERROR;
}
AddSceneResponse::Type ScenesManagementCluster::HandleAddScene(FabricIndex fabricIndex,
const ScenesManagement::Commands::AddScene::DecodableType & req)
{
AddSceneResponse::Type response;
response.groupID = req.groupID;
response.sceneID = req.sceneID;
// Get Scene Table Instance
ScopedSceneTable sceneTable(mSceneTableProvider);
if (!sceneTable)
{
response.status = to_underlying(Status::Failure);
return response;
}
// Verify the attributes are respecting constraints
if (req.transitionTime > scenes::kScenesMaxTransitionTime || req.sceneName.size() > scenes::kSceneNameMaxLength ||
req.sceneID == scenes::kUndefinedSceneId)
{
response.status = to_underlying(Status::ConstraintError);
return response;
}
// Verify Endpoint in group
if (nullptr == mGroupProvider)
{
response.status = to_underlying(Status::UnsupportedCommand);
return response;
}
if (0 != req.groupID && !mGroupProvider->HasEndpoint(fabricIndex, req.groupID, mPath.mEndpointId))
{
response.status = to_underlying(Status::InvalidCommand);
return response;
}
SceneData storageData(CharSpan(), req.transitionTime);
if (mFeatures.Has(ScenesManagement::Feature::kSceneNames))
{
storageData.SetName(req.sceneName);
}
auto fieldSetIter = req.extensionFieldSetStructs.begin();
uint8_t EFSCount = 0;
// Goes through all EFS in command
while (fieldSetIter.Next() && EFSCount++ < scenes::kMaxClustersPerScene)
{
scenes::ExtensionFieldSet tempEFS;
tempEFS.mID = fieldSetIter.GetValue().clusterID;
MutableByteSpan buff_span(tempEFS.mBytesBuffer);
// Check if a handler is registered for the EFS's cluster
for (auto & handler : sceneTable->mHandlerList)
{
if (handler.SupportsCluster(mPath.mEndpointId, tempEFS.mID))
{
SuccessOrReturnWithFailureStatus(handler.SerializeAdd(mPath.mEndpointId, fieldSetIter.GetValue(), buff_span),
response);
break;
}
}
static_assert(sizeof(tempEFS.mBytesBuffer) <= UINT8_MAX, "Serialized EFS number of bytes must fit in a uint8");
tempEFS.mUsedBytes = static_cast<uint8_t>(buff_span.size());
if (!tempEFS.IsEmpty())
{
LogErrorOnFailure(storageData.mExtensionFieldSets.InsertFieldSet(tempEFS));
}
}
SuccessOrReturnWithFailureStatus(fieldSetIter.GetStatus(), response);
// Create scene from data and ID
SceneTableEntry scene(SceneStorageId(req.sceneID, req.groupID), storageData);
// Get Capacity
uint8_t capacity = 0;
SuccessOrReturnWithFailureStatus(sceneTable->GetRemainingCapacity(fabricIndex, capacity), response);
if (0 == capacity)
{
response.status = to_underlying(Status::ResourceExhausted);
return response;
}
// Insert in table
SuccessOrReturnWithFailureStatus(sceneTable->SetSceneTableEntry(fabricIndex, scene), response);
// Update FabricSceneInfo
SuccessOrReturnWithFailureStatus(UpdateFabricSceneInfo(fabricIndex, Optional<GroupId>(), Optional<SceneId>(), Optional<bool>()),
response);
// Write response
response.status = to_underlying(Status::Success);
return response;
}
ViewSceneResponse::Type ScenesManagementCluster::HandleViewScene(
FabricIndex fabricIndex, const ScenesManagement::Commands::ViewScene::DecodableType & req, SceneTableEntry & scene,
std::array<ScenesManagement::Structs::ExtensionFieldSetStruct::Type, scenes::kMaxClustersPerScene> & responseEFSBuffer)
{
MATTER_TRACE_SCOPE("ViewScene", "Scenes");
ViewSceneResponse::Type response;
response.groupID = req.groupID;
response.sceneID = req.sceneID;
// Get Scene Table Instance
ScopedSceneTable sceneTable(mSceneTableProvider);
if (!sceneTable)
{
response.status = to_underlying(Status::Failure);
return response;
}
// Verify the attributes are respecting constraints
if (req.sceneID == scenes::kUndefinedSceneId)
{
response.status = to_underlying(Status::ConstraintError);
return response;
}
// Verify Endpoint in group
if (nullptr == mGroupProvider)
{
response.status = to_underlying(Status::Failure);
return response;
}
if (0 != req.groupID && !mGroupProvider->HasEndpoint(fabricIndex, req.groupID, mPath.mEndpointId))
{
response.status = to_underlying(Status::InvalidCommand);
return response;
}
// Gets the scene from the table
SuccessOrReturnWithFailureStatus(sceneTable->GetSceneTableEntry(fabricIndex, SceneStorageId(req.sceneID, req.groupID), scene),
response);
// Response Extension Field Sets buffer
uint8_t deserializedEFSCount = 0;
// Adds extension field sets to the scene
for (uint8_t i = 0; i < scene.mStorageData.mExtensionFieldSets.GetFieldSetCount(); i++)
{
// gets data from the field in the scene
ExtensionFieldSet tempField;
SuccessOrReturnWithFailureStatus(scene.mStorageData.mExtensionFieldSets.GetFieldSetAtPosition(tempField, i), response);
ByteSpan efsSpan(tempField.mBytesBuffer, tempField.mUsedBytes);
// This should only find one handle per cluster
for (auto & handler : sceneTable->mHandlerList)
{
if (handler.SupportsCluster(mPath.mEndpointId, tempField.mID))
{
SuccessOrReturnWithFailureStatus(
handler.Deserialize(mPath.mEndpointId, tempField.mID, efsSpan, responseEFSBuffer[i]), response);
deserializedEFSCount++;
break;
}
}
}
response.status = to_underlying(Status::Success);
response.transitionTime.SetValue(scene.mStorageData.mSceneTransitionTimeMs);
response.sceneName.SetValue({ scene.mStorageData.mName, scene.mStorageData.mNameLength });
response.extensionFieldSetStructs.SetValue({ responseEFSBuffer.data(), deserializedEFSCount });
return response;
}
RemoveSceneResponse::Type
ScenesManagementCluster::HandleRemoveScene(FabricIndex fabricIndex,
const ScenesManagement::Commands::RemoveScene::DecodableType & req)
{
MATTER_TRACE_SCOPE("RemoveScene", "Scenes");
// Write response
RemoveSceneResponse::Type response;
response.groupID = req.groupID;
response.sceneID = req.sceneID;
// Get Scene Table Instance
ScopedSceneTable sceneTable(mSceneTableProvider);
if (!sceneTable)
{
response.status = to_underlying(Status::Failure);
return response;
}
// Verify the attributes are respecting constraints
if (req.sceneID == scenes::kUndefinedSceneId)
{
response.status = to_underlying(Status::ConstraintError);
return response;
}
// Scene Table interface data
SceneTableEntry scene(SceneStorageId(req.sceneID, req.groupID));
// Verify Endpoint in group
if (nullptr == mGroupProvider)
{
response.status = to_underlying(Status::Failure);
return response;
}
if (0 != req.groupID && !mGroupProvider->HasEndpoint(fabricIndex, req.groupID, mPath.mEndpointId))
{
response.status = to_underlying(Status::InvalidCommand);
return response;
}
// Gets the scene from the table
SuccessOrReturnWithFailureStatus(sceneTable->GetSceneTableEntry(fabricIndex, scene.mStorageId, scene), response);
// Remove the scene from the scene table
SuccessOrReturnWithFailureStatus(sceneTable->RemoveSceneTableEntry(fabricIndex, scene.mStorageId), response);
// Update SceneInfoStruct Attributes
SceneInfoStruct::Type * sceneInfo = GetSceneInfoStruct(fabricIndex);
Optional<bool> sceneValid;
if (nullptr != sceneInfo && req.groupID == sceneInfo->currentGroup && req.sceneID == sceneInfo->currentScene)
{
sceneValid.Emplace(false);
}
SuccessOrReturnWithFailureStatus(UpdateFabricSceneInfo(fabricIndex, Optional<GroupId>(), Optional<SceneId>(), sceneValid),
response);
response.status = to_underlying(Status::Success);
return response;
}
RemoveAllScenesResponse::Type
ScenesManagementCluster::HandleRemoveAllScenes(FabricIndex fabricIndex,
const ScenesManagement::Commands::RemoveAllScenes::DecodableType & req)
{
MATTER_TRACE_SCOPE("RemoveAllScenes", "Scenes");
// Response data
RemoveAllScenesResponse::Type response;
response.groupID = req.groupID;
// Get Scene Table Instance
ScopedSceneTable sceneTable(mSceneTableProvider);
if (!sceneTable)
{
response.status = to_underlying(Status::Failure);
return response;
}
// Verify Endpoint in group
if (nullptr == mGroupProvider)
{
response.status = to_underlying(Status::Failure);
return response;
}
if (0 != req.groupID && !mGroupProvider->HasEndpoint(fabricIndex, req.groupID, mPath.mEndpointId))
{
response.status = to_underlying(Status::InvalidCommand);
return response;
}
SuccessOrReturnWithFailureStatus(sceneTable->DeleteAllScenesInGroup(fabricIndex, req.groupID), response);
// Update Attributes
SceneInfoStruct::Type * sceneInfo = GetSceneInfoStruct(fabricIndex);
Optional<bool> sceneValid;
if (nullptr != sceneInfo && req.groupID == sceneInfo->currentGroup)
{
sceneValid.Emplace(false);
}
SuccessOrReturnWithFailureStatus(UpdateFabricSceneInfo(fabricIndex, Optional<GroupId>(), Optional<SceneId>(), sceneValid),
response);
response.status = to_underlying(Status::Success);
return response;
}
ScenesManagement::Commands::StoreSceneResponse::Type
ScenesManagementCluster::HandleStoreScene(FabricIndex fabricIndex,
const ScenesManagement::Commands::StoreScene::DecodableType & req)
{
MATTER_TRACE_SCOPE("StoreScene", "Scenes");
StoreSceneResponse::Type response;
response.groupID = req.groupID;
response.sceneID = req.sceneID;
// Verify the attributes are respecting constraints
if (req.sceneID == scenes::kUndefinedSceneId)
{
response.status = to_underlying(Status::ConstraintError);
return response;
}
SuccessOrReturnWithFailureStatus(StoreSceneParse(fabricIndex, req.groupID, req.sceneID), response);
response.status = to_underlying(Status::Success);
return response;
}
Protocols::InteractionModel::Status
ScenesManagementCluster::HandleRecallScene(FabricIndex fabricIndex,
const ScenesManagement::Commands::RecallScene::DecodableType & req)
{
MATTER_TRACE_SCOPE("RecallScene", "Scenes");
// Verify the attributes are respecting constraints
VerifyOrReturnError(req.sceneID != scenes::kUndefinedSceneId, Status::ConstraintError);
CHIP_ERROR err = RecallSceneParse(fabricIndex, req.groupID, req.sceneID, req.transitionTime);
// TODO : implement proper mapping between CHIP_ERROR and IM Status
if (CHIP_NO_ERROR == err)
{
return Status::Success;
}
if (CHIP_ERROR_NOT_FOUND == err)
{
return Status::NotFound;
}
return StatusIB(err).mStatus;
}
ScenesManagement::Commands::GetSceneMembershipResponse::Type
ScenesManagementCluster::HandleGetSceneMembership(FabricIndex fabricIndex,
const ScenesManagement::Commands::GetSceneMembership::DecodableType & req,
std::array<SceneId, scenes::kMaxScenesPerFabric> & scenesInGroup)
{
MATTER_TRACE_SCOPE("GetSceneMembership", "Scenes");
GetSceneMembershipResponse::Type response;
response.groupID = req.groupID;
// Verify Endpoint in group
if (nullptr == mGroupProvider)
{
response.status = to_underlying(Status::Failure);
return response;
}
if (0 != req.groupID && !mGroupProvider->HasEndpoint(fabricIndex, req.groupID, mPath.mEndpointId))
{
response.status = to_underlying(Status::InvalidCommand);
return response;
}
uint8_t capacity = 0;
ScopedSceneTable sceneTable(mSceneTableProvider);
if (!sceneTable)
{
response.status = to_underlying(Status::Failure);
return response;
}
// Get Capacity
SuccessOrReturnWithFailureStatus(sceneTable->GetRemainingCapacity(fabricIndex, capacity), response);
response.capacity.SetNonNull(capacity);
// populate scene list
auto sceneList = Span<SceneId>(scenesInGroup);
SuccessOrReturnWithFailureStatus(sceneTable->GetAllSceneIdsInGroup(fabricIndex, req.groupID, sceneList), response);
response.sceneList.SetValue(sceneList);
// Write response
response.status = to_underlying(Status::Success);
return response;
}
ScenesManagement::Commands::CopySceneResponse::Type
ScenesManagementCluster::HandleCopyScene(FabricIndex fabricIndex, const ScenesManagement::Commands::CopyScene::DecodableType & req)
{
MATTER_TRACE_SCOPE("CopyScene", "Scenes");
CopySceneResponse::Type response;
response.groupIdentifierFrom = req.groupIdentifierFrom;
response.sceneIdentifierFrom = req.sceneIdentifierFrom;
// Verify the attributes are respecting constraints
if (req.sceneIdentifierFrom == scenes::kUndefinedSceneId || req.sceneIdentifierTo == scenes::kUndefinedSceneId)
{
response.status = to_underlying(Status::ConstraintError);
return response;
}
// Verify Endpoint in group
if (nullptr == mGroupProvider)
{
response.status = to_underlying(Status::Failure);
return response;
}
if ((0 != req.groupIdentifierFrom && !mGroupProvider->HasEndpoint(fabricIndex, req.groupIdentifierFrom, mPath.mEndpointId)) ||
(0 != req.groupIdentifierTo && !mGroupProvider->HasEndpoint(fabricIndex, req.groupIdentifierTo, mPath.mEndpointId)))
{
response.status = to_underlying(Status::InvalidCommand);
return response;
}
ScopedSceneTable sceneTable(mSceneTableProvider);
if (!sceneTable)
{
response.status = to_underlying(Status::Failure);
return response;
}
// Copying a scene over an existing one should be ok. At this point we:
// - check if we overwrite a scene (then capacity is ok)
// - adding a new slot (in this case we have to check capacity)
// Check if the destination scene already exists
const SceneStorageId destStorageId(req.sceneIdentifierTo, req.groupIdentifierTo);
SceneTableEntry destScene;
CHIP_ERROR err = sceneTable->GetSceneTableEntry(fabricIndex, destStorageId, destScene);
if (err == CHIP_ERROR_NOT_FOUND)
{
// Only check capacity if the destination scene does not exist, as an overwrite doesn't consume a new slot.
uint8_t capacity = 0;
SuccessOrReturnWithFailureStatus(sceneTable->GetRemainingCapacity(fabricIndex, capacity), response);
if (0 == capacity)
{
response.status = to_underlying(Status::ResourceExhausted);
return response;
}
}
else if (err != CHIP_NO_ERROR)
{
// Some other error occurred when trying to check if the scene exists.
response.status = to_underlying(ResponseStatus(err));
return response;
}
// Checks if we copy a single scene or all of them
if (req.mode.GetField(app::Clusters::ScenesManagement::CopyModeBitmap::kCopyAllScenes))
{
// Scene Table interface data
SceneId scenesInGroup[scenes::kMaxScenesPerFabric];
Span<SceneId> sceneList = Span<SceneId>(scenesInGroup);
// populate scene list
SuccessOrReturnWithFailureStatus(sceneTable->GetAllSceneIdsInGroup(fabricIndex, req.groupIdentifierFrom, sceneList),
response);
for (auto & sceneId : sceneList)
{
SceneTableEntry scene(SceneStorageId(sceneId, req.groupIdentifierFrom));
// Insert in table
SuccessOrReturnWithFailureStatus(sceneTable->GetSceneTableEntry(fabricIndex, scene.mStorageId, scene), response);
scene.mStorageId = SceneStorageId(sceneId, req.groupIdentifierTo);
SuccessOrReturnWithFailureStatus(sceneTable->SetSceneTableEntry(fabricIndex, scene), response);
// Update SceneInfoStruct Attributes after each insert in case we hit max capacity in the middle of the loop
SuccessOrReturnWithFailureStatus(
UpdateFabricSceneInfo(fabricIndex, Optional<GroupId>(), Optional<SceneId>(), Optional<bool>() /* = sceneValid*/),
response);
}
response.status = to_underlying(Status::Success);
return response;
}
SceneTableEntry scene(SceneStorageId(req.sceneIdentifierFrom, req.groupIdentifierFrom));
SuccessOrReturnWithFailureStatus(sceneTable->GetSceneTableEntry(fabricIndex, scene.mStorageId, scene), response);
scene.mStorageId = destStorageId;
SuccessOrReturnWithFailureStatus(sceneTable->SetSceneTableEntry(fabricIndex, scene), response);
// Update Attributes
SuccessOrReturnWithFailureStatus(UpdateFabricSceneInfo(fabricIndex, Optional<GroupId>(), Optional<SceneId>(), Optional<bool>()),
response);
response.status = to_underlying(Status::Success);
return response;
}
} // namespace chip::app::Clusters