/* * * 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 "scenes-server.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using SceneTableEntry = chip::scenes::DefaultSceneTableImpl::SceneTableEntry; using SceneStorageId = chip::scenes::DefaultSceneTableImpl::SceneStorageId; using SceneData = chip::scenes::DefaultSceneTableImpl::SceneData; using HandlerContext = chip::app::CommandHandlerInterface::HandlerContext; using ExtensionFieldSet = chip::scenes::ExtensionFieldSet; using GroupDataProvider = chip::Credentials::GroupDataProvider; using SceneTable = chip::scenes::SceneTable; using AuthMode = chip::Access::AuthMode; using ScenesServer = chip::app::Clusters::ScenesManagement::ScenesServer; using chip::Protocols::InteractionModel::Status; namespace chip { namespace app { namespace Clusters { namespace ScenesManagement { namespace { /// @brief Generate and add a response to a command handler context if err parameter is not CHIP_NO_ERROR /// @tparam ResponseType Type of response, depends on the command /// @param ctx Command Handler context where to add reponse /// @param resp Response to add in ctx /// @param status Status to verify /// @return CHIP_ERROR template CHIP_ERROR AddResponseOnError(CommandHandlerInterface::HandlerContext & ctx, ResponseType & resp, CHIP_ERROR err) { if (CHIP_NO_ERROR != err) { // TODO : Properly fix mapping between error types (issue https://github.com/project-chip/connectedhomeip/issues/26885) if (CHIP_ERROR_NOT_FOUND == err) { resp.status = to_underlying(Protocols::InteractionModel::Status::NotFound); } else if (CHIP_ERROR_NO_MEMORY == err) { resp.status = to_underlying(Protocols::InteractionModel::Status::ResourceExhausted); } else if (CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute) == err) { // TODO: Confirm if we need to add UnsupportedAttribute status as a return for Scene Commands resp.status = to_underlying(Protocols::InteractionModel::Status::InvalidCommand); } else { resp.status = to_underlying(StatusIB(err).mStatus); } ctx.mCommandHandler.AddResponse(ctx.mRequestPath, resp); } return err; } /// @brief Generate and add a response to a command handler context depending on an InteractionModel::Status /// @tparam ResponseType Type of response, depends on the command /// @param ctx Command Handler context where to add reponse /// @param resp Response to add in ctx /// @param status Status to verify /// @return InteractionModel::Status -> CHIP_ERROR template CHIP_ERROR AddResponseOnError(CommandHandlerInterface::HandlerContext & ctx, ResponseType & resp, Status status) { return AddResponseOnError(ctx, resp, StatusIB(status).ToChipError()); } template CHIP_ERROR UpdateLastConfiguredBy(HandlerContext & ctx, ResponseType resp) { Access::SubjectDescriptor descriptor = ctx.mCommandHandler.GetSubjectDescriptor(); Status status = Status::Success; if (AuthMode::kCase == descriptor.authMode) { status = Attributes::LastConfiguredBy::Set(ctx.mRequestPath.mEndpointId, descriptor.subject); } else { status = Attributes::LastConfiguredBy::SetNull(ctx.mRequestPath.mEndpointId); } // LastConfiguredBy is optional, so we don't want to fail the command if it fails to update VerifyOrReturnValue(!(Status::Success == status || Status::UnsupportedAttribute == status), CHIP_NO_ERROR); return AddResponseOnError(ctx, resp, status); } /// @brief Helper function to update the FabricSceneInfo attribute for a given Endpoint and fabric /// @param endpoint Endpoint to update /// @param fabric Fabric to update /// @param group Group to update, if not provided, will be assigned 0 for a new SceneInfoStruct or keep previous value for an /// existing one /// @param scene Scene to update, if not provided, will be assigned 0 for a new SceneInfoStruct or keep previous value for an /// existing one /// @param sceneValid sceneValid status, if not provided, will be assigned false for a new SceneInfoStruct or keep previous /// value for an existing one /// @return CHIP_ERROR UpdateFabricSceneInfo(EndpointId endpoint, FabricIndex fabric, Optional group, Optional scene, Optional sceneValid) { VerifyOrReturnError(kInvalidEndpointId != endpoint, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(kUndefinedFabricIndex != fabric, CHIP_ERROR_INVALID_ARGUMENT); SceneTable * sceneTable = scenes::GetSceneTableImpl(endpoint); Structs::SceneInfoStruct::Type * sceneInfo = ScenesServer::Instance().GetSceneInfoStruct(endpoint, 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 Structs::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(ScenesServer::Instance().SetSceneInfoStruct(endpoint, fabric, newSceneInfo)); } MatterReportingAttributeChangeCallback(endpoint, Id, Attributes::FabricSceneInfo::Id); return CHIP_NO_ERROR; } } // namespace /// @brief Gets the SceneInfoStruct array associated to an endpoint /// @param endpoint target endpoint /// @return Optional with no value not found, Span of SceneInfoStruct Span ScenesServer::FabricSceneInfo::GetFabricSceneInfo(EndpointId endpoint) { size_t endpointIndex = 0; Span fabricSceneInfoSpan; CHIP_ERROR status = FindFabricSceneInfoIndex(endpoint, endpointIndex); if (CHIP_NO_ERROR == status) { fabricSceneInfoSpan = Span(&mSceneInfoStructs[endpointIndex][0], mSceneInfoStructsCount[endpointIndex]); } return fabricSceneInfoSpan; } /// @brief Gets the SceneInfoStruct for a specific fabric for a specific endpoint /// @param endpoint target endpoint /// @param fabric target fabric /// @param index /// @return Nullptr if not found, pointer to the SceneInfoStruct otherwise Structs::SceneInfoStruct::Type * ScenesServer::FabricSceneInfo::GetSceneInfoStruct(EndpointId endpoint, FabricIndex fabric) { size_t endpointIndex = 0; VerifyOrReturnValue(CHIP_NO_ERROR == FindFabricSceneInfoIndex(endpoint, endpointIndex), nullptr); uint8_t sceneInfoStructIndex = 0; VerifyOrReturnValue(CHIP_NO_ERROR == FindSceneInfoStructIndex(fabric, endpointIndex, sceneInfoStructIndex), nullptr); return &mSceneInfoStructs[endpointIndex][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 ScenesServer::FabricSceneInfo::SetSceneInfoStruct(EndpointId endpoint, FabricIndex fabric, Structs::SceneInfoStruct::Type & sceneInfoStruct) { VerifyOrReturnError(kInvalidEndpointId != endpoint, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(kUndefinedFabricIndex != fabric, CHIP_ERROR_INVALID_ARGUMENT); size_t endpointIndex = 0; ReturnErrorOnFailure(FindFabricSceneInfoIndex(endpoint, endpointIndex)); uint8_t sceneInfoStructIndex = 0; if (CHIP_ERROR_NOT_FOUND == FindSceneInfoStructIndex(fabric, endpointIndex, sceneInfoStructIndex)) { VerifyOrReturnError(mSceneInfoStructsCount[endpointIndex] < ArraySize(mSceneInfoStructs[endpointIndex]), CHIP_ERROR_NO_MEMORY); sceneInfoStructIndex = mSceneInfoStructsCount[endpointIndex]; // Increment number of populated ScenesInfoStructs mSceneInfoStructsCount[endpointIndex]++; } mSceneInfoStructs[endpointIndex][sceneInfoStructIndex] = sceneInfoStruct; return CHIP_NO_ERROR; } /// @brief Clears the SceneInfoStruct associated to a fabric and compresses the array to leave uninitialised structs at the end /// @param[in] endpoint target endpoint /// @param[in] fabric target fabric void ScenesServer::FabricSceneInfo::ClearSceneInfoStruct(EndpointId endpoint, FabricIndex fabric) { size_t endpointIndex = 0; ReturnOnFailure(FindFabricSceneInfoIndex(endpoint, endpointIndex)); uint8_t sceneInfoStructIndex = 0; ReturnOnFailure(FindSceneInfoStructIndex(fabric, endpointIndex, sceneInfoStructIndex)); uint8_t nextIndex = static_cast(sceneInfoStructIndex + 1); uint8_t moveNum = static_cast(ArraySize(mSceneInfoStructs[endpointIndex]) - nextIndex); // Compress the endpoint's SceneInfoStruct array if (moveNum) { for (size_t i = 0; i < moveNum; ++i) { mSceneInfoStructs[endpointIndex][sceneInfoStructIndex + i] = mSceneInfoStructs[endpointIndex][nextIndex + i]; } } // Decrement the SceneInfoStruct count mSceneInfoStructsCount[endpointIndex]--; // Clear the last populated SceneInfoStruct mSceneInfoStructs[endpointIndex][mSceneInfoStructsCount[endpointIndex]].fabricIndex = kUndefinedFabricIndex; mSceneInfoStructs[endpointIndex][mSceneInfoStructsCount[endpointIndex]].sceneCount = 0; mSceneInfoStructs[endpointIndex][mSceneInfoStructsCount[endpointIndex]].currentScene = 0; mSceneInfoStructs[endpointIndex][mSceneInfoStructsCount[endpointIndex]].currentGroup = 0; mSceneInfoStructs[endpointIndex][mSceneInfoStructsCount[endpointIndex]].remainingCapacity = 0; } /// @brief Returns the index of the FabricSceneInfo associated to an endpoint /// @param[in] endpoint target endpoint /// @param[out] endpointIndex index of the corresponding FabricSceneInfo for an endpoint, corresponds to a row in the /// mSceneInfoStructs array, /// @return CHIP_NO_ERROR or CHIP_ERROR_NOT_FOUND, CHIP_ERROR_INVALID_ARGUMENT if invalid endpoint CHIP_ERROR ScenesServer::FabricSceneInfo::FindFabricSceneInfoIndex(EndpointId endpoint, size_t & endpointIndex) { VerifyOrReturnError(kInvalidEndpointId != endpoint, CHIP_ERROR_INVALID_ARGUMENT); uint16_t index = emberAfGetClusterServerEndpointIndex(endpoint, ScenesManagement::Id, MATTER_DM_SCENES_CLUSTER_SERVER_ENDPOINT_COUNT); if (index < ArraySize(mSceneInfoStructs)) { endpointIndex = index; return CHIP_NO_ERROR; } return CHIP_ERROR_NOT_FOUND; } /// @brief Returns the SceneInfoStruct associated to a fabric /// @param[in] fabric target fabric index /// @param[in] endpointIndex index of the corresponding FabricSceneInfo for an endpoint, corresponds to a row in the /// mSceneInfoStructs array /// @param[out] index index of the corresponding SceneInfoStruct if found, otherwise the index value will be invalid and /// should not be used. This is safe to store in a uint8_t because the index is guaranteed to be smaller than /// CHIP_CONFIG_MAX_FABRICS. /// @return CHIP_NO_ERROR or CHIP_ERROR_NOT_FOUND, CHIP_ERROR_INVALID_ARGUMENT if invalid fabric or endpointIndex are provided CHIP_ERROR ScenesServer::FabricSceneInfo::FindSceneInfoStructIndex(FabricIndex fabric, size_t endpointIndex, uint8_t & index) { VerifyOrReturnError(endpointIndex < ArraySize(mSceneInfoStructs), CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(kUndefinedFabricIndex != fabric, CHIP_ERROR_INVALID_ARGUMENT); index = 0; for (auto & info : mSceneInfoStructs[endpointIndex]) { if (info.fabricIndex == fabric) { return CHIP_NO_ERROR; } index++; } return CHIP_ERROR_NOT_FOUND; } ScenesServer ScenesServer::mInstance; ScenesServer & ScenesServer::Instance() { return mInstance; } void ReportAttributeOnAllEndpoints(AttributeId attribute) {} class ScenesClusterFabricDelegate : public chip::FabricTable::Delegate { void OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex) override { SceneTable * sceneTable = scenes::GetSceneTableImpl(); VerifyOrReturn(nullptr != sceneTable); // The implementation of SceneTable::RemoveFabric() must not call back into the FabricTable sceneTable->RemoveFabric(fabricIndex); } }; static ScenesClusterFabricDelegate gFabricDelegate; CHIP_ERROR ScenesServer::Init() { // Prevents re-initializing VerifyOrReturnError(!mIsInitialized, CHIP_ERROR_INCORRECT_STATE); ReturnErrorOnFailure(chip::app::CommandHandlerInterfaceRegistry::Instance().RegisterCommandHandler(this)); VerifyOrReturnError(AttributeAccessInterfaceRegistry::Instance().Register(this), CHIP_ERROR_INCORRECT_STATE); mGroupProvider = Credentials::GetGroupDataProvider(); SceneTable * sceneTable = scenes::GetSceneTableImpl(); ReturnErrorOnFailure(sceneTable->Init(&chip::Server::GetInstance().GetPersistentStorage())); ReturnErrorOnFailure(chip::Server::GetInstance().GetFabricTable().AddFabricDelegate(&gFabricDelegate)); mIsInitialized = true; return CHIP_NO_ERROR; } void ScenesServer::Shutdown() { chip::app::CommandHandlerInterfaceRegistry::Instance().UnregisterCommandHandler(this); mGroupProvider = nullptr; mIsInitialized = false; } template void AddSceneParse(CommandHandlerInterface::HandlerContext & ctx, const CommandData & req, GroupDataProvider * groupProvider) { ResponseType response; uint16_t endpointTableSize = 0; ReturnOnFailure( AddResponseOnError(ctx, response, Attributes::SceneTableSize::Get(ctx.mRequestPath.mEndpointId, &endpointTableSize))); // Get Scene Table Instance SceneTable * sceneTable = scenes::GetSceneTableImpl(ctx.mRequestPath.mEndpointId, endpointTableSize); // Response data response.groupID = req.groupID; response.sceneID = req.sceneID; // Verify the attributes are respecting constraints if (req.transitionTime > scenes::kScenesMaxTransitionTime || req.sceneName.size() > scenes::kSceneNameMaxLength || req.sceneID == scenes::kUndefinedSceneId) { response.status = to_underlying(Protocols::InteractionModel::Status::ConstraintError); ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); return; } // Verify Endpoint in group VerifyOrReturn(nullptr != groupProvider); if (0 != req.groupID && !groupProvider->HasEndpoint(ctx.mCommandHandler.GetAccessingFabricIndex(), req.groupID, ctx.mRequestPath.mEndpointId)) { response.status = to_underlying(Protocols::InteractionModel::Status::InvalidCommand); ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); return; } uint32_t featureMap = 0; ReturnOnFailure(AddResponseOnError(ctx, response, Attributes::FeatureMap::Get(ctx.mRequestPath.mEndpointId, &featureMap))); SceneData storageData(CharSpan(), req.transitionTime); if (featureMap & to_underlying(Feature::kSceneNames)) { storageData.SetName(req.sceneName); } auto fieldSetIter = req.extensionFieldSets.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(ctx.mRequestPath.mEndpointId, tempEFS.mID)) { ReturnOnFailure(AddResponseOnError( ctx, response, handler.SerializeAdd(ctx.mRequestPath.mEndpointId, fieldSetIter.GetValue(), buff_span))); break; } } static_assert(sizeof(tempEFS.mBytesBuffer) <= UINT8_MAX, "Serialized EFS number of bytes must fit in a uint8"); tempEFS.mUsedBytes = static_cast(buff_span.size()); if (!tempEFS.IsEmpty()) { storageData.mExtensionFieldSets.InsertFieldSet(tempEFS); } } ReturnOnFailure(AddResponseOnError(ctx, response, fieldSetIter.GetStatus())); // Create scene from data and ID SceneTableEntry scene(SceneStorageId(req.sceneID, req.groupID), storageData); // Get Capacity VerifyOrReturn(nullptr != sceneTable); uint8_t capacity = 0; ReturnOnFailure(AddResponseOnError(ctx, response, sceneTable->GetRemainingCapacity(ctx.mCommandHandler.GetAccessingFabricIndex(), capacity))); if (0 == capacity) { response.status = to_underlying(Protocols::InteractionModel::Status::ResourceExhausted); ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); return; } // Insert in table ReturnOnFailure( AddResponseOnError(ctx, response, sceneTable->SetSceneTableEntry(ctx.mCommandHandler.GetAccessingFabricIndex(), scene))); // Update FabricSceneInfo ReturnOnFailure( AddResponseOnError(ctx, response, UpdateFabricSceneInfo(ctx.mRequestPath.mEndpointId, ctx.mCommandHandler.GetAccessingFabricIndex(), Optional(), Optional(), Optional()))); ReturnOnFailure(UpdateLastConfiguredBy(ctx, response)); // Write response response.status = to_underlying(Protocols::InteractionModel::Status::Success); ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); } template void ViewSceneParse(HandlerContext & ctx, const CommandData & req, GroupDataProvider * groupProvider) { ResponseType response; uint16_t endpointTableSize = 0; ReturnOnFailure( AddResponseOnError(ctx, response, Attributes::SceneTableSize::Get(ctx.mRequestPath.mEndpointId, &endpointTableSize))); // Get Scene Table Instance SceneTable * sceneTable = scenes::GetSceneTableImpl(ctx.mRequestPath.mEndpointId, endpointTableSize); // Response data response.groupID = req.groupID; response.sceneID = req.sceneID; // Verify the attributes are respecting constraints if (req.sceneID == scenes::kUndefinedSceneId) { response.status = to_underlying(Protocols::InteractionModel::Status::ConstraintError); ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); return; } // Verify Endpoint in group VerifyOrReturn(nullptr != groupProvider); if (0 != req.groupID && !groupProvider->HasEndpoint(ctx.mCommandHandler.GetAccessingFabricIndex(), req.groupID, ctx.mRequestPath.mEndpointId)) { response.status = to_underlying(Protocols::InteractionModel::Status::InvalidCommand); ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); return; } SceneTableEntry scene; // Gets the scene from the table ReturnOnFailure(AddResponseOnError(ctx, response, sceneTable->GetSceneTableEntry(ctx.mCommandHandler.GetAccessingFabricIndex(), SceneStorageId(req.sceneID, req.groupID), scene))); // Response Extension Field Sets buffer Structs::ExtensionFieldSet::Type responseEFSBuffer[scenes::kMaxClustersPerScene]; 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; scene.mStorageData.mExtensionFieldSets.GetFieldSetAtPosition(tempField, i); ByteSpan efsSpan(tempField.mBytesBuffer, tempField.mUsedBytes); // This should only find one handle per cluster for (auto & handler : sceneTable->mHandlerList) { if (handler.SupportsCluster(ctx.mRequestPath.mEndpointId, tempField.mID)) { ReturnOnFailure(AddResponseOnError( ctx, response, handler.Deserialize(ctx.mRequestPath.mEndpointId, tempField.mID, efsSpan, responseEFSBuffer[i]))); deserializedEFSCount++; break; } } } response.status = to_underlying(Protocols::InteractionModel::Status::Success); response.transitionTime.SetValue(scene.mStorageData.mSceneTransitionTimeMs); response.sceneName.SetValue(CharSpan(scene.mStorageData.mName, scene.mStorageData.mNameLength)); Span responseEFSSpan(responseEFSBuffer, deserializedEFSCount); response.extensionFieldSets.SetValue(responseEFSSpan); ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); } CHIP_ERROR StoreSceneParse(const FabricIndex & fabricIdx, const EndpointId & endpointID, const GroupId & groupID, const SceneId & sceneID, GroupDataProvider * groupProvider) { // Make the current fabric's SceneValid false before storing a scene ScenesServer::Instance().MakeSceneInvalid(endpointID, fabricIdx); uint16_t endpointTableSize = 0; ReturnErrorOnFailure(StatusIB(Attributes::SceneTableSize::Get(endpointID, &endpointTableSize)).ToChipError()); // Get Scene Table Instance SceneTable * sceneTable = scenes::GetSceneTableImpl(endpointID, endpointTableSize); // Verify Endpoint in group VerifyOrReturnError(nullptr != groupProvider, CHIP_ERROR_INTERNAL); if (0 != groupID && !groupProvider->HasEndpoint(fabricIdx, groupID, endpointID)) { return CHIP_IM_GLOBAL_STATUS(InvalidCommand); } // Scene Table interface data SceneTableEntry scene(SceneStorageId(sceneID, groupID)); VerifyOrReturnError(nullptr != 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 { uint32_t featureMap = 0; ReturnErrorOnFailure(StatusIB(Attributes::FeatureMap::Get(endpointID, &featureMap)).ToChipError()); // Check if we still support scenes name in case an OTA changed that, if we don't, set name to empty if (!(featureMap & to_underlying(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 ReturnErrorOnFailure(UpdateFabricSceneInfo(endpointID, fabricIdx, MakeOptional(groupID), MakeOptional(sceneID), MakeOptional(static_cast(true)))); return CHIP_NO_ERROR; } CHIP_ERROR RecallSceneParse(const FabricIndex & fabricIdx, const EndpointId & endpointID, const GroupId & groupID, const SceneId & sceneID, const Optional> & transitionTime, GroupDataProvider * groupProvider) { // Make SceneValid false for all fabrics before recalling a scene ScenesServer::Instance().MakeSceneInvalidForAllFabrics(endpointID); uint16_t endpointTableSize = 0; ReturnErrorOnFailure(StatusIB(Attributes::SceneTableSize::Get(endpointID, &endpointTableSize)).ToChipError()); // Get Scene Table Instance SceneTable * sceneTable = scenes::GetSceneTableImpl(endpointID, endpointTableSize); // Verify Endpoint in group VerifyOrReturnError(nullptr != groupProvider, CHIP_ERROR_INTERNAL); if (0 != groupID && !groupProvider->HasEndpoint(fabricIdx, groupID, endpointID)) { return CHIP_IM_GLOBAL_STATUS(InvalidCommand); } // Scene Table interface data SceneTableEntry scene(SceneStorageId(sceneID, groupID)); VerifyOrReturnError(nullptr != 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 ReturnErrorOnFailure( UpdateFabricSceneInfo(endpointID, fabricIdx, Optional(groupID), Optional(sceneID), Optional(true))); return CHIP_NO_ERROR; } // CommandHanlerInterface void ScenesServer::InvokeCommand(HandlerContext & ctxt) { switch (ctxt.mRequestPath.mCommandId) { case Commands::AddScene::Id: HandleCommand( ctxt, [this](HandlerContext & ctx, const auto & req) { HandleAddScene(ctx, req); }); return; case Commands::ViewScene::Id: HandleCommand( ctxt, [this](HandlerContext & ctx, const auto & req) { HandleViewScene(ctx, req); }); return; case Commands::RemoveScene::Id: HandleCommand( ctxt, [this](HandlerContext & ctx, const auto & req) { HandleRemoveScene(ctx, req); }); return; case Commands::RemoveAllScenes::Id: HandleCommand( ctxt, [this](HandlerContext & ctx, const auto & req) { HandleRemoveAllScenes(ctx, req); }); return; case Commands::StoreScene::Id: HandleCommand( ctxt, [this](HandlerContext & ctx, const auto & req) { HandleStoreScene(ctx, req); }); return; case Commands::RecallScene::Id: HandleCommand( ctxt, [this](HandlerContext & ctx, const auto & req) { HandleRecallScene(ctx, req); }); return; case Commands::GetSceneMembership::Id: HandleCommand( ctxt, [this](HandlerContext & ctx, const auto & req) { HandleGetSceneMembership(ctx, req); }); return; case Commands::CopyScene::Id: HandleCommand( ctxt, [this](HandlerContext & ctx, const auto & req) { HandleCopyScene(ctx, req); }); return; } } // AttributeAccessInterface CHIP_ERROR ScenesServer::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) { uint16_t endpointTableSize = 0; ReturnErrorOnFailure(StatusIB(Attributes::SceneTableSize::Get(aPath.mEndpointId, &endpointTableSize)).ToChipError()); // Get Scene Table Instance SceneTable * sceneTable = scenes::GetSceneTableImpl(aPath.mEndpointId, endpointTableSize); switch (aPath.mAttributeId) { case Attributes::FabricSceneInfo::Id: { return aEncoder.EncodeList([&, sceneTable](const auto & encoder) -> CHIP_ERROR { Span fabricSceneInfoSpan = mFabricSceneInfo.GetFabricSceneInfo(aPath.mEndpointId); for (auto & info : fabricSceneInfoSpan) { // Update the SceneInfoStruct's Capacity in case it's capacity was limited by other fabrics sceneTable->GetRemainingCapacity(info.fabricIndex, info.remainingCapacity); ReturnErrorOnFailure(encoder.Encode(info)); } return CHIP_NO_ERROR; }); } default: return CHIP_NO_ERROR; } } Structs::SceneInfoStruct::Type * ScenesServer::GetSceneInfoStruct(EndpointId endpoint, FabricIndex fabric) { Structs::SceneInfoStruct::Type * sceneInfoStruct = mFabricSceneInfo.GetSceneInfoStruct(endpoint, fabric); return sceneInfoStruct; } CHIP_ERROR ScenesServer::SetSceneInfoStruct(EndpointId endpoint, FabricIndex fabric, Structs::SceneInfoStruct::Type & sceneInfoStruct) { ReturnErrorOnFailure(mFabricSceneInfo.SetSceneInfoStruct(endpoint, fabric, sceneInfoStruct)); return CHIP_NO_ERROR; } void ScenesServer::GroupWillBeRemoved(FabricIndex aFabricIx, EndpointId aEndpointId, GroupId aGroupId) { // Get Scene Table Instance SceneTable * sceneTable = scenes::GetSceneTableImpl(aEndpointId); VerifyOrReturn(nullptr != sceneTable); Structs::SceneInfoStruct::Type * sceneInfo = mFabricSceneInfo.GetSceneInfoStruct(aEndpointId, aFabricIx); 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) { MakeSceneInvalid(aEndpointId, aFabricIx); } VerifyOrReturn(nullptr != mGroupProvider); if (0 != aGroupId && !mGroupProvider->HasEndpoint(aFabricIx, aGroupId, aEndpointId)) { return; } sceneTable->DeleteAllScenesInGroup(aFabricIx, aGroupId); } void ScenesServer::MakeSceneInvalid(EndpointId aEndpointId, FabricIndex aFabricIx) { UpdateFabricSceneInfo(aEndpointId, aFabricIx, Optional(), Optional(), Optional(false)); } void ScenesServer::MakeSceneInvalidForAllFabrics(EndpointId aEndpointId) { for (auto & info : chip::Server::GetInstance().GetFabricTable()) { MakeSceneInvalid(aEndpointId, info.GetFabricIndex()); } } void ScenesServer::StoreCurrentScene(FabricIndex aFabricIx, EndpointId aEndpointId, GroupId aGroupId, SceneId aSceneId) { StoreSceneParse(aFabricIx, aEndpointId, aGroupId, aSceneId, mGroupProvider); } void ScenesServer::RecallScene(FabricIndex aFabricIx, EndpointId aEndpointId, GroupId aGroupId, SceneId aSceneId) { Optional> transitionTime; RecallSceneParse(aFabricIx, aEndpointId, aGroupId, aSceneId, transitionTime, mGroupProvider); } bool ScenesServer::IsHandlerRegistered(EndpointId aEndpointId, scenes::SceneHandler * handler) { SceneTable * sceneTable = scenes::GetSceneTableImpl(aEndpointId); return sceneTable->mHandlerList.Contains(handler); } void ScenesServer::RegisterSceneHandler(EndpointId aEndpointId, scenes::SceneHandler * handler) { SceneTable * sceneTable = scenes::GetSceneTableImpl(aEndpointId); if (!IsHandlerRegistered(aEndpointId, handler)) { sceneTable->RegisterHandler(handler); } } void ScenesServer::UnregisterSceneHandler(EndpointId aEndpointId, scenes::SceneHandler * handler) { SceneTable * sceneTable = scenes::GetSceneTableImpl(aEndpointId); if (IsHandlerRegistered(aEndpointId, handler)) { sceneTable->UnregisterHandler(handler); } } void ScenesServer::RemoveFabric(EndpointId aEndpointId, FabricIndex aFabricIndex) { SceneTable * sceneTable = scenes::GetSceneTableImpl(aEndpointId); sceneTable->RemoveFabric(aFabricIndex); mFabricSceneInfo.ClearSceneInfoStruct(aEndpointId, aFabricIndex); } void ScenesServer::HandleAddScene(HandlerContext & ctx, const Commands::AddScene::DecodableType & req) { MATTER_TRACE_SCOPE("AddScene", "Scenes"); AddSceneParse(ctx, req, mGroupProvider); } void ScenesServer::HandleViewScene(HandlerContext & ctx, const Commands::ViewScene::DecodableType & req) { MATTER_TRACE_SCOPE("ViewScene", "Scenes"); ViewSceneParse(ctx, req, mGroupProvider); } void ScenesServer::HandleRemoveScene(HandlerContext & ctx, const Commands::RemoveScene::DecodableType & req) { MATTER_TRACE_SCOPE("RemoveScene", "Scenes"); Commands::RemoveSceneResponse::Type response; uint16_t endpointTableSize = 0; ReturnOnFailure( AddResponseOnError(ctx, response, Attributes::SceneTableSize::Get(ctx.mRequestPath.mEndpointId, &endpointTableSize))); // Get Scene Table Instance SceneTable * sceneTable = scenes::GetSceneTableImpl(ctx.mRequestPath.mEndpointId, endpointTableSize); // Response data response.groupID = req.groupID; response.sceneID = req.sceneID; // Verify the attributes are respecting constraints if (req.sceneID == scenes::kUndefinedSceneId) { response.status = to_underlying(Protocols::InteractionModel::Status::ConstraintError); ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); return; } // Scene Table interface data SceneTableEntry scene(SceneStorageId(req.sceneID, req.groupID)); // Verify Endpoint in group VerifyOrReturn(nullptr != mGroupProvider); if (0 != req.groupID && !mGroupProvider->HasEndpoint(ctx.mCommandHandler.GetAccessingFabricIndex(), req.groupID, ctx.mRequestPath.mEndpointId)) { response.status = to_underlying(Protocols::InteractionModel::Status::InvalidCommand); ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); return; } // Gets the scene from the table ReturnOnFailure(AddResponseOnError( ctx, response, sceneTable->GetSceneTableEntry(ctx.mCommandHandler.GetAccessingFabricIndex(), scene.mStorageId, scene))); // Remove the scene from the scene table ReturnOnFailure(AddResponseOnError( ctx, response, sceneTable->RemoveSceneTableEntry(ctx.mCommandHandler.GetAccessingFabricIndex(), scene.mStorageId))); // Update SceneInfoStruct Attributes Structs::SceneInfoStruct::Type * sceneInfo = GetSceneInfoStruct(ctx.mRequestPath.mEndpointId, ctx.mCommandHandler.GetAccessingFabricIndex()); Optional sceneValid; if (nullptr != sceneInfo && req.groupID == sceneInfo->currentGroup && req.sceneID == sceneInfo->currentScene) { sceneValid.Emplace(false); } ReturnOnFailure(UpdateLastConfiguredBy(ctx, response)); ReturnOnFailure( AddResponseOnError(ctx, response, UpdateFabricSceneInfo(ctx.mRequestPath.mEndpointId, ctx.mCommandHandler.GetAccessingFabricIndex(), Optional(), Optional(), sceneValid))); // Write response response.status = to_underlying(Protocols::InteractionModel::Status::Success); ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); } void ScenesServer::HandleRemoveAllScenes(HandlerContext & ctx, const Commands::RemoveAllScenes::DecodableType & req) { MATTER_TRACE_SCOPE("RemoveAllScenes", "Scenes"); Commands::RemoveAllScenesResponse::Type response; uint16_t endpointTableSize = 0; ReturnOnFailure( AddResponseOnError(ctx, response, Attributes::SceneTableSize::Get(ctx.mRequestPath.mEndpointId, &endpointTableSize))); // Get Scene Table Instance SceneTable * sceneTable = scenes::GetSceneTableImpl(ctx.mRequestPath.mEndpointId, endpointTableSize); // Response data response.groupID = req.groupID; // Verify Endpoint in group VerifyOrReturn(nullptr != mGroupProvider); if (0 != req.groupID && !mGroupProvider->HasEndpoint(ctx.mCommandHandler.GetAccessingFabricIndex(), req.groupID, ctx.mRequestPath.mEndpointId)) { response.status = to_underlying(Protocols::InteractionModel::Status::InvalidCommand); ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); return; } ReturnOnFailure(AddResponseOnError( ctx, response, sceneTable->DeleteAllScenesInGroup(ctx.mCommandHandler.GetAccessingFabricIndex(), req.groupID))); // Update Attributes Structs::SceneInfoStruct::Type * sceneInfo = GetSceneInfoStruct(ctx.mRequestPath.mEndpointId, ctx.mCommandHandler.GetAccessingFabricIndex()); Optional sceneValid; if (nullptr != sceneInfo && req.groupID == sceneInfo->currentGroup) { sceneValid.Emplace(false); } ReturnOnFailure( AddResponseOnError(ctx, response, UpdateFabricSceneInfo(ctx.mRequestPath.mEndpointId, ctx.mCommandHandler.GetAccessingFabricIndex(), Optional(), Optional(), sceneValid))); ReturnOnFailure(UpdateLastConfiguredBy(ctx, response)); // Write response response.status = to_underlying(Protocols::InteractionModel::Status::Success); ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); } void ScenesServer::HandleStoreScene(HandlerContext & ctx, const Commands::StoreScene::DecodableType & req) { MATTER_TRACE_SCOPE("StoreScene", "Scenes"); Commands::StoreSceneResponse::Type response; // Response data response.groupID = req.groupID; response.sceneID = req.sceneID; // Verify the attributes are respecting constraints if (req.sceneID == scenes::kUndefinedSceneId) { response.status = to_underlying(Protocols::InteractionModel::Status::ConstraintError); ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); return; } CHIP_ERROR err = StoreSceneParse(ctx.mCommandHandler.GetAccessingFabricIndex(), ctx.mRequestPath.mEndpointId, req.groupID, req.sceneID, mGroupProvider); ReturnOnFailure(AddResponseOnError(ctx, response, err)); ReturnOnFailure(UpdateLastConfiguredBy(ctx, response)); response.status = to_underlying(Protocols::InteractionModel::Status::Success); ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); } void ScenesServer::HandleRecallScene(HandlerContext & ctx, const Commands::RecallScene::DecodableType & req) { MATTER_TRACE_SCOPE("RecallScene", "Scenes"); // Verify the attributes are respecting constraints if (req.sceneID == scenes::kUndefinedSceneId) { ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Protocols::InteractionModel::Status::ConstraintError); return; } CHIP_ERROR err = RecallSceneParse(ctx.mCommandHandler.GetAccessingFabricIndex(), ctx.mRequestPath.mEndpointId, req.groupID, req.sceneID, req.transitionTime, mGroupProvider); if (CHIP_NO_ERROR == err) { ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Protocols::InteractionModel::Status::Success); return; } if (CHIP_ERROR_NOT_FOUND == err) { // TODO : implement proper mapping between CHIP_ERROR and IM Status ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Protocols::InteractionModel::Status::NotFound); return; } ctx.mCommandHandler.AddStatus(ctx.mRequestPath, StatusIB(err).mStatus); } void ScenesServer::HandleGetSceneMembership(HandlerContext & ctx, const Commands::GetSceneMembership::DecodableType & req) { MATTER_TRACE_SCOPE("GetSceneMembership", "Scenes"); Commands::GetSceneMembershipResponse::Type response; uint16_t endpointTableSize = 0; ReturnOnFailure( AddResponseOnError(ctx, response, Attributes::SceneTableSize::Get(ctx.mRequestPath.mEndpointId, &endpointTableSize))); // Get Scene Table Instance SceneTable * sceneTable = scenes::GetSceneTableImpl(ctx.mRequestPath.mEndpointId, endpointTableSize); // Response data response.groupID = req.groupID; // Scene Table interface data SceneId scenesInGroup[scenes::kMaxScenesPerFabric]; Span sceneList = Span(scenesInGroup); SceneTableEntry scene; // Verify Endpoint in group VerifyOrReturn(nullptr != mGroupProvider); if (0 != req.groupID && !mGroupProvider->HasEndpoint(ctx.mCommandHandler.GetAccessingFabricIndex(), req.groupID, ctx.mRequestPath.mEndpointId)) { response.status = to_underlying(Protocols::InteractionModel::Status::InvalidCommand); ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); return; } uint8_t capacity = 0; // Get Capacity ReturnOnFailure(AddResponseOnError(ctx, response, sceneTable->GetRemainingCapacity(ctx.mCommandHandler.GetAccessingFabricIndex(), capacity))); response.capacity.SetNonNull(capacity); // populate scene list ReturnOnFailure(AddResponseOnError( ctx, response, sceneTable->GetAllSceneIdsInGroup(ctx.mCommandHandler.GetAccessingFabricIndex(), req.groupID, sceneList))); response.sceneList.SetValue(sceneList); // Write response response.status = to_underlying(Protocols::InteractionModel::Status::Success); ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); } void ScenesServer::HandleCopyScene(HandlerContext & ctx, const Commands::CopyScene::DecodableType & req) { MATTER_TRACE_SCOPE("CopyScene", "Scenes"); Commands::CopySceneResponse::Type response; uint16_t endpointTableSize = 0; ReturnOnFailure( AddResponseOnError(ctx, response, Attributes::SceneTableSize::Get(ctx.mRequestPath.mEndpointId, &endpointTableSize))); // Get Scene Table Instance SceneTable * sceneTable = scenes::GetSceneTableImpl(ctx.mRequestPath.mEndpointId, endpointTableSize); // Response data 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(Protocols::InteractionModel::Status::ResourceExhausted); ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); return; } // Verify Endpoint in group VerifyOrReturn(nullptr != mGroupProvider); if ((0 != req.groupIdentifierFrom && !mGroupProvider->HasEndpoint(ctx.mCommandHandler.GetAccessingFabricIndex(), req.groupIdentifierFrom, ctx.mRequestPath.mEndpointId)) || (0 != req.groupIdentifierTo && !mGroupProvider->HasEndpoint(ctx.mCommandHandler.GetAccessingFabricIndex(), req.groupIdentifierTo, ctx.mRequestPath.mEndpointId))) { response.status = to_underlying(Protocols::InteractionModel::Status::InvalidCommand); ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); return; } uint8_t capacity = 0; // Get Capacity ReturnOnFailure(AddResponseOnError(ctx, response, sceneTable->GetRemainingCapacity(ctx.mCommandHandler.GetAccessingFabricIndex(), capacity))); if (0 == capacity) { response.status = to_underlying(Protocols::InteractionModel::Status::ResourceExhausted); ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); return; } // 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 sceneList = Span(scenesInGroup); // populate scene list ReturnOnFailure(AddResponseOnError( ctx, response, sceneTable->GetAllSceneIdsInGroup(ctx.mCommandHandler.GetAccessingFabricIndex(), req.groupIdentifierFrom, sceneList))); for (auto & sceneId : sceneList) { SceneTableEntry scene(SceneStorageId(sceneId, req.groupIdentifierFrom)); // Insert in table ReturnOnFailure(AddResponseOnError( ctx, response, sceneTable->GetSceneTableEntry(ctx.mCommandHandler.GetAccessingFabricIndex(), scene.mStorageId, scene))); scene.mStorageId = SceneStorageId(sceneId, req.groupIdentifierTo); ReturnOnFailure(AddResponseOnError( ctx, response, sceneTable->SetSceneTableEntry(ctx.mCommandHandler.GetAccessingFabricIndex(), scene))); // Update SceneInfoStruct Attributes after each insert in case we hit max capacity in the middle of the loop ReturnOnFailure(AddResponseOnError( ctx, response, UpdateFabricSceneInfo(ctx.mRequestPath.mEndpointId, ctx.mCommandHandler.GetAccessingFabricIndex(), Optional(), Optional(), Optional() /* = sceneValid*/))); } ReturnOnFailure(UpdateLastConfiguredBy(ctx, response)); response.status = to_underlying(Protocols::InteractionModel::Status::Success); ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); return; } SceneTableEntry scene(SceneStorageId(req.sceneIdentifierFrom, req.groupIdentifierFrom)); ReturnOnFailure(AddResponseOnError( ctx, response, sceneTable->GetSceneTableEntry(ctx.mCommandHandler.GetAccessingFabricIndex(), scene.mStorageId, scene))); scene.mStorageId = SceneStorageId(req.sceneIdentifierTo, req.groupIdentifierTo); ReturnOnFailure( AddResponseOnError(ctx, response, sceneTable->SetSceneTableEntry(ctx.mCommandHandler.GetAccessingFabricIndex(), scene))); // Update Attributes ReturnOnFailure( AddResponseOnError(ctx, response, UpdateFabricSceneInfo(ctx.mRequestPath.mEndpointId, ctx.mCommandHandler.GetAccessingFabricIndex(), Optional(), Optional(), Optional()))); ReturnOnFailure(UpdateLastConfiguredBy(ctx, response)); response.status = to_underlying(Protocols::InteractionModel::Status::Success); ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); } } // namespace ScenesManagement } // namespace Clusters } // namespace app } // namespace chip using namespace chip; using namespace chip::app::Clusters; using namespace chip::app::Clusters::ScenesManagement; void emberAfScenesManagementClusterServerInitCallback(EndpointId endpoint) { Status status = Attributes::LastConfiguredBy::SetNull(endpoint); if (Status::Success != status) { ChipLogDetail(Zcl, "ERR: setting LastConfiguredBy on Endpoint %hu Status: %x", endpoint, to_underlying(status)); } // Initialize the FabricSceneInfo by getting the number of scenes and the remaining capacity for storing fabric scene data for (auto & info : chip::Server::GetInstance().GetFabricTable()) { auto fabric = info.GetFabricIndex(); UpdateFabricSceneInfo(endpoint, fabric, Optional(), Optional(), Optional()); } } void MatterScenesManagementClusterServerShutdownCallback(EndpointId endpoint) { uint16_t endpointTableSize = 0; VerifyOrReturn(Status::Success == Attributes::SceneTableSize::Get(endpoint, &endpointTableSize)); // Get Scene Table Instance SceneTable * sceneTable = scenes::GetSceneTableImpl(endpoint, endpointTableSize); sceneTable->RemoveEndpoint(); } void MatterScenesManagementPluginServerInitCallback() { CHIP_ERROR err = ScenesServer::Instance().Init(); if (err != CHIP_NO_ERROR) { ChipLogError(Zcl, "ScenesServer::Instance().Init() error: %" CHIP_ERROR_FORMAT, err.Format()); } }