| /* |
| * |
| * 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 <app-common/zap-generated/attributes/Accessors.h> |
| #include <app-common/zap-generated/cluster-objects.h> |
| #include <app/AttributeAccessInterfaceRegistry.h> |
| #include <app/CommandHandlerInterface.h> |
| #include <app/CommandHandlerInterfaceRegistry.h> |
| #include <app/InteractionModelEngine.h> |
| #include <app/clusters/scenes-server/SceneTableImpl.h> |
| #include <app/reporting/reporting.h> |
| #include <app/server/Server.h> |
| #include <app/util/attribute-storage.h> |
| #include <app/util/config.h> |
| #include <credentials/GroupDataProvider.h> |
| #include <lib/support/CommonIterator.h> |
| #include <lib/support/Span.h> |
| #include <lib/support/logging/CHIPLogging.h> |
| #include <platform/PlatformManager.h> |
| #include <tracing/macros.h> |
| |
| 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<chip::scenes::ExtensionFieldSetsImpl>; |
| 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 <typename ResponseType> |
| 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 <typename ResponseType> |
| CHIP_ERROR AddResponseOnError(CommandHandlerInterface::HandlerContext & ctx, ResponseType & resp, Status status) |
| { |
| return AddResponseOnError(ctx, resp, StatusIB(status).ToChipError()); |
| } |
| |
| template <typename ResponseType> |
| 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<GroupId> group, Optional<SceneId> scene, |
| Optional<bool> 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<Structs::SceneInfoStruct::Type> ScenesServer::FabricSceneInfo::GetFabricSceneInfo(EndpointId endpoint) |
| { |
| size_t endpointIndex = 0; |
| Span<Structs::SceneInfoStruct::Type> fabricSceneInfoSpan; |
| CHIP_ERROR status = FindFabricSceneInfoIndex(endpoint, endpointIndex); |
| if (CHIP_NO_ERROR == status) |
| { |
| fabricSceneInfoSpan = |
| Span<Structs::SceneInfoStruct::Type>(&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<uint8_t>(sceneInfoStructIndex + 1); |
| uint8_t moveNum = static_cast<uint8_t>(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 <typename CommandData, typename ResponseType> |
| 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<uint8_t>(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<GroupId>(), Optional<SceneId>(), Optional<bool>()))); |
| |
| ReturnOnFailure(UpdateLastConfiguredBy(ctx, response)); |
| |
| // Write response |
| response.status = to_underlying(Protocols::InteractionModel::Status::Success); |
| ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); |
| } |
| |
| template <typename CommandData, typename ResponseType> |
| 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<Structs::ExtensionFieldSet::Type> 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<bool>(true)))); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR RecallSceneParse(const FabricIndex & fabricIdx, const EndpointId & endpointID, const GroupId & groupID, |
| const SceneId & sceneID, const Optional<DataModel::Nullable<uint32_t>> & 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>(groupID), Optional<SceneId>(sceneID), Optional<bool>(true))); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| // CommandHanlerInterface |
| void ScenesServer::InvokeCommand(HandlerContext & ctxt) |
| { |
| switch (ctxt.mRequestPath.mCommandId) |
| { |
| case Commands::AddScene::Id: |
| HandleCommand<Commands::AddScene::DecodableType>( |
| ctxt, [this](HandlerContext & ctx, const auto & req) { HandleAddScene(ctx, req); }); |
| return; |
| case Commands::ViewScene::Id: |
| HandleCommand<Commands::ViewScene::DecodableType>( |
| ctxt, [this](HandlerContext & ctx, const auto & req) { HandleViewScene(ctx, req); }); |
| return; |
| case Commands::RemoveScene::Id: |
| HandleCommand<Commands::RemoveScene::DecodableType>( |
| ctxt, [this](HandlerContext & ctx, const auto & req) { HandleRemoveScene(ctx, req); }); |
| return; |
| case Commands::RemoveAllScenes::Id: |
| HandleCommand<Commands::RemoveAllScenes::DecodableType>( |
| ctxt, [this](HandlerContext & ctx, const auto & req) { HandleRemoveAllScenes(ctx, req); }); |
| return; |
| case Commands::StoreScene::Id: |
| HandleCommand<Commands::StoreScene::DecodableType>( |
| ctxt, [this](HandlerContext & ctx, const auto & req) { HandleStoreScene(ctx, req); }); |
| return; |
| case Commands::RecallScene::Id: |
| HandleCommand<Commands::RecallScene::DecodableType>( |
| ctxt, [this](HandlerContext & ctx, const auto & req) { HandleRecallScene(ctx, req); }); |
| return; |
| case Commands::GetSceneMembership::Id: |
| HandleCommand<Commands::GetSceneMembership::DecodableType>( |
| ctxt, [this](HandlerContext & ctx, const auto & req) { HandleGetSceneMembership(ctx, req); }); |
| return; |
| case Commands::CopyScene::Id: |
| HandleCommand<Commands::CopyScene::DecodableType>( |
| 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<Structs::SceneInfoStruct::Type> 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<GroupId>(), Optional<SceneId>(), Optional<bool>(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<DataModel::Nullable<uint32_t>> 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<Commands::AddScene::DecodableType, Commands::AddSceneResponse::Type>(ctx, req, mGroupProvider); |
| } |
| |
| void ScenesServer::HandleViewScene(HandlerContext & ctx, const Commands::ViewScene::DecodableType & req) |
| { |
| MATTER_TRACE_SCOPE("ViewScene", "Scenes"); |
| ViewSceneParse<Commands::ViewScene::DecodableType, Commands::ViewSceneResponse::Type>(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<bool> 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<GroupId>(), Optional<SceneId>(), 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<bool> sceneValid; |
| if (nullptr != sceneInfo && req.groupID == sceneInfo->currentGroup) |
| { |
| sceneValid.Emplace(false); |
| } |
| |
| ReturnOnFailure( |
| AddResponseOnError(ctx, response, |
| UpdateFabricSceneInfo(ctx.mRequestPath.mEndpointId, ctx.mCommandHandler.GetAccessingFabricIndex(), |
| Optional<GroupId>(), Optional<SceneId>(), 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<SceneId> sceneList = Span<SceneId>(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<SceneId> sceneList = Span<SceneId>(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<GroupId>(), Optional<SceneId>(), Optional<bool>() /* = 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<GroupId>(), Optional<SceneId>(), Optional<bool>()))); |
| |
| 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<GroupId>(), Optional<SceneId>(), Optional<bool>()); |
| } |
| } |
| |
| 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()); |
| } |
| } |