diff --git a/src/app/clusters/scenes/ExtensionFieldsSets.h b/src/app/clusters/scenes/ExtensionFieldsSets.h index 3c86a79f08dad3..e2853fa6e09fbd 100644 --- a/src/app/clusters/scenes/ExtensionFieldsSets.h +++ b/src/app/clusters/scenes/ExtensionFieldsSets.h @@ -37,6 +37,7 @@ class ExtensionFieldsSets virtual CHIP_ERROR Deserialize(TLV::TLVReader & reader) = 0; virtual void Clear() = 0; virtual bool IsEmpty() const = 0; + virtual uint8_t GetFieldNum() const = 0; }; } // namespace scenes } // namespace chip diff --git a/src/app/clusters/scenes/ExtensionFieldsSetsImpl.cpp b/src/app/clusters/scenes/ExtensionFieldsSetsImpl.cpp index 77c97516cce2ff..27038353dcec80 100644 --- a/src/app/clusters/scenes/ExtensionFieldsSetsImpl.cpp +++ b/src/app/clusters/scenes/ExtensionFieldsSetsImpl.cpp @@ -25,11 +25,11 @@ ExtensionFieldsSetsImpl::ExtensionFieldsSetsImpl() : ExtensionFieldsSets() {} CHIP_ERROR ExtensionFieldsSetsImpl::Serialize(TLV::TLVWriter & writer) const { TLV::TLVType container; - ReturnErrorOnFailure(writer.StartContainer(TLV::ContextTag(1), TLV::kTLVType_Structure, container)); - ReturnErrorOnFailure(writer.Put(TagFieldNum(), static_cast(this->mFieldNum))); + ReturnErrorOnFailure(writer.StartContainer(TLV::ContextTag(kTagEFSArrayContainer), TLV::kTLVType_Structure, container)); + ReturnErrorOnFailure(writer.Put(TLV::ContextTag(kTagEFSFieldNum), static_cast(this->mFieldNum))); if (!this->IsEmpty()) { - for (uint8_t i = 0; i < kMaxClusterPerScenes; i++) + for (uint8_t i = 0; i < this->mFieldNum; i++) { if (!this->mEFS[i].IsEmpty()) { @@ -44,10 +44,10 @@ CHIP_ERROR ExtensionFieldsSetsImpl::Serialize(TLV::TLVWriter & writer) const CHIP_ERROR ExtensionFieldsSetsImpl::Deserialize(TLV::TLVReader & reader) { TLV::TLVType container; - ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::ContextTag(1))); + ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::ContextTag(kTagEFSArrayContainer))); ReturnErrorOnFailure(reader.EnterContainer(container)); - ReturnErrorOnFailure(reader.Next(TagFieldNum())); + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(kTagEFSFieldNum))); ReturnErrorOnFailure(reader.Get(this->mFieldNum)); if (!this->IsEmpty()) @@ -73,11 +73,6 @@ void ExtensionFieldsSetsImpl::Clear() this->mFieldNum = 0; } -bool ExtensionFieldsSetsImpl::IsEmpty() const -{ - return (this->mFieldNum == 0); -} - /// @brief Inserts a field Set set into the array of extension field Set sets for a scene entry. /// If the same ID is present in the EFS array, it will overwrite it. /// @param fieldSet field set to be inserted @@ -87,6 +82,9 @@ CHIP_ERROR ExtensionFieldsSetsImpl::InsertFieldSet(ExtensionFieldsSet & fieldSet CHIP_ERROR err = CHIP_ERROR_NO_MEMORY; uint8_t idPosition = kInvalidPosition; uint8_t firstEmptyPosition = kInvalidPosition; + + VerifyOrReturnError(fieldSet.mID != kInvalidClusterId, CHIP_ERROR_INVALID_ARGUMENT); + for (uint8_t i = 0; i < kMaxClusterPerScenes; i++) { if (this->mEFS[i].mID == fieldSet.mID) @@ -104,12 +102,12 @@ CHIP_ERROR ExtensionFieldsSetsImpl::InsertFieldSet(ExtensionFieldsSet & fieldSet // if found, replace at found position, otherwise at insert first free position, otherwise return error if (idPosition < kMaxClusterPerScenes) { - ReturnErrorOnFailure(this->mEFS[idPosition] = fieldSet); - err = CHIP_NO_ERROR; + this->mEFS[idPosition] = fieldSet; + err = CHIP_NO_ERROR; } else if (firstEmptyPosition < kMaxClusterPerScenes) { - ReturnErrorOnFailure(this->mEFS[firstEmptyPosition] = fieldSet); + this->mEFS[firstEmptyPosition] = fieldSet; this->mFieldNum++; err = CHIP_NO_ERROR; } @@ -119,23 +117,22 @@ CHIP_ERROR ExtensionFieldsSetsImpl::InsertFieldSet(ExtensionFieldsSet & fieldSet CHIP_ERROR ExtensionFieldsSetsImpl::GetFieldSetAtPosition(ExtensionFieldsSet & fieldSet, uint8_t position) { - if (position < this->mFieldNum) - { - ReturnErrorOnFailure(fieldSet = this->mEFS[position]); - } + VerifyOrReturnError(position < this->mFieldNum, CHIP_ERROR_BUFFER_TOO_SMALL); + + fieldSet = this->mEFS[position]; return CHIP_NO_ERROR; } CHIP_ERROR ExtensionFieldsSetsImpl::RemoveFieldAtPosition(uint8_t position) { - VerifyOrReturnError(position < kMaxClusterPerScenes, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnValue(!this->IsEmpty() && !this->mEFS[position].IsEmpty(), CHIP_NO_ERROR); uint8_t next = position + 1; uint8_t moveNum = kMaxClusterPerScenes - next; + // TODO: Implement general array management methods // Compress array after removal memmove(&this->mEFS[position], &this->mEFS[next], sizeof(ExtensionFieldsSet) * moveNum); diff --git a/src/app/clusters/scenes/ExtensionFieldsSetsImpl.h b/src/app/clusters/scenes/ExtensionFieldsSetsImpl.h index f1fa88daa6544f..a8b24623145dee 100644 --- a/src/app/clusters/scenes/ExtensionFieldsSetsImpl.h +++ b/src/app/clusters/scenes/ExtensionFieldsSetsImpl.h @@ -23,47 +23,64 @@ namespace chip { namespace scenes { +enum EFSTLVTag +{ + kTagEFSArrayContainer = 1, + kTagEFSFieldNum = 1, + kTagEFSContainer, + kTagEFSClusterID, + kTagEFS, +}; + using clusterId = chip::ClusterId; struct ExtensionFieldsSet { - static constexpr TLV::Tag TagClusterId() { return TLV::ContextTag(1); } - static constexpr TLV::Tag TagEFS() { return TLV::ContextTag(2); } clusterId mID = kInvalidClusterId; uint8_t mBytesBuffer[kMaxFieldsPerCluster] = { 0 }; uint8_t mUsedBytes = 0; ExtensionFieldsSet() = default; - ExtensionFieldsSet(clusterId cmID, uint8_t * data, uint8_t dataSize) : mID(cmID), mUsedBytes(dataSize) + ExtensionFieldsSet(clusterId cmID, const uint8_t * data, uint8_t dataSize) : mID(cmID), mUsedBytes(dataSize) { if (dataSize <= kMaxFieldsPerCluster) { memcpy(mBytesBuffer, data, mUsedBytes); } } + + ExtensionFieldsSet(clusterId cmID, ByteSpan bytes) : mID(cmID), mUsedBytes(static_cast(bytes.size())) + { + if (bytes.size() <= kMaxFieldsPerCluster) + { + memcpy(mBytesBuffer, bytes.data(), bytes.size()); + } + } + ~ExtensionFieldsSet() = default; CHIP_ERROR Serialize(TLV::TLVWriter & writer) const { TLV::TLVType container; - ReturnErrorOnFailure(writer.StartContainer(TLV::ContextTag(1), TLV::kTLVType_Structure, container)); + ReturnErrorOnFailure(writer.StartContainer(TLV::ContextTag(kTagEFSContainer), TLV::kTLVType_Structure, container)); - ReturnErrorOnFailure(writer.Put(TagClusterId(), static_cast(this->mID))); - ReturnErrorOnFailure(writer.PutBytes(TagEFS(), mBytesBuffer, mUsedBytes)); + ReturnErrorOnFailure(writer.Put(TLV::ContextTag(kTagEFSClusterID), static_cast(this->mID))); + ReturnErrorOnFailure(writer.PutBytes(TLV::ContextTag(kTagEFS), mBytesBuffer, mUsedBytes)); return writer.EndContainer(container); } + CHIP_ERROR Deserialize(TLV::TLVReader & reader) { ByteSpan buffer; TLV::TLVType container; - ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::ContextTag(1))); + ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::ContextTag(kTagEFSContainer))); ReturnErrorOnFailure(reader.EnterContainer(container)); - ReturnErrorOnFailure(reader.Next(TagClusterId())); + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(kTagEFSClusterID))); ReturnErrorOnFailure(reader.Get(this->mID)); - ReturnErrorOnFailure(reader.Next(TagEFS())); + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(kTagEFS))); ReturnErrorOnFailure(reader.Get(buffer)); VerifyOrReturnError(buffer.size() <= kMaxFieldsPerCluster, CHIP_ERROR_BUFFER_TOO_SMALL); this->mUsedBytes = static_cast(buffer.size()); @@ -86,22 +103,6 @@ struct ExtensionFieldsSet return (this->mID == other.mID && !memcmp(this->mBytesBuffer, other.mBytesBuffer, this->mUsedBytes) && this->mUsedBytes == other.mUsedBytes); } - - CHIP_ERROR operator=(const ExtensionFieldsSet & other) - { - if (other.mUsedBytes <= kMaxFieldsPerCluster) - { - memcpy(this->mBytesBuffer, other.mBytesBuffer, other.mUsedBytes); - } - else - { - return CHIP_ERROR_BUFFER_TOO_SMALL; - } - this->mID = other.mID; - this->mUsedBytes = other.mUsedBytes; - - return CHIP_NO_ERROR; - } }; class ExtensionFieldsSetsImpl : public ExtensionFieldsSets @@ -114,7 +115,8 @@ class ExtensionFieldsSetsImpl : public ExtensionFieldsSets CHIP_ERROR Serialize(TLV::TLVWriter & writer) const override; CHIP_ERROR Deserialize(TLV::TLVReader & reader) override; void Clear() override; - bool IsEmpty() const override; + bool IsEmpty() const override { return (this->mFieldNum == 0); } + uint8_t GetFieldNum() const override { return this->mFieldNum; }; // implementation CHIP_ERROR InsertFieldSet(ExtensionFieldsSet & field); @@ -133,19 +135,18 @@ class ExtensionFieldsSetsImpl : public ExtensionFieldsSets return true; } - CHIP_ERROR operator=(const ExtensionFieldsSetsImpl & other) + ExtensionFieldsSetsImpl & operator=(const ExtensionFieldsSetsImpl & other) { for (uint8_t i = 0; i < kMaxClusterPerScenes; i++) { - ReturnErrorOnFailure(this->mEFS[i] = other.mEFS[i]); + this->mEFS[i] = other.mEFS[i]; } mFieldNum = other.mFieldNum; - return CHIP_NO_ERROR; + return *this; } protected: - static constexpr TLV::Tag TagFieldNum() { return TLV::ContextTag(1); } ExtensionFieldsSet mEFS[kMaxClusterPerScenes]; uint8_t mFieldNum = 0; }; diff --git a/src/app/clusters/scenes/SceneTable.h b/src/app/clusters/scenes/SceneTable.h index 866de234c16762..a1b5bf55e93a38 100644 --- a/src/app/clusters/scenes/SceneTable.h +++ b/src/app/clusters/scenes/SceneTable.h @@ -26,58 +26,114 @@ namespace chip { namespace scenes { +enum SceneTLVTag +{ + kTagSceneStorageIDContainer = 1, + kTagSceneEndpointID, + kTagSceneGroupID, + kTagSceneID, + kTagSceneDataContainer, + kTagSceneName, + kTagSceneDTransitionTime, + kTagSceneDTransitionTime100, +}; + +typedef uint32_t TransitionTimeMs; typedef uint16_t SceneTransitionTime; typedef uint8_t TransitionTime100ms; +static constexpr uint8_t kEFSBufferSize = 128; static constexpr uint8_t kMaxScenePerFabric = CHIP_CONFIG_SCENES_MAX_PER_FABRIC; +static constexpr uint8_t kMaxSceneHandlers = CHIP_CONFIG_SCENES_MAX_CLUSTERS_PER_SCENES; static constexpr size_t kIteratorsMax = CHIP_CONFIG_MAX_SCENES_CONCURRENT_ITERATORS; static constexpr size_t kSceneNameMax = CHIP_CONFIG_SCENES_CLUSTER_MAXIMUM_NAME_LENGTH; +/// @brief Abstract class allowing different Endpoints interactions with the ExtensionFieldSets added to and retrieved from the +/// scene Table +class SceneHandler +{ +public: + SceneHandler(){}; + virtual ~SceneHandler() = default; + + virtual bool SupportsCluster(EndpointId endpoint, ClusterId cluster) = 0; + + /// @brief From command AddScene, allows handler to filter through clusters in command to serialize only the supported ones. + /// @param endpoint Endpoint ID + /// @param cluster Cluster ID to fetch from command + /// @param serialysedBytes Buffer for ExtensionFieldSet in command + /// @param extensionFieldSet ExtensionFieldSets provided by the AddScene Command + /// @return + virtual CHIP_ERROR SerializeAdd(EndpointId endpoint, ClusterId & cluster, MutableByteSpan & serialysedBytes, + app::Clusters::Scenes::Structs::ExtensionFieldSet::DecodableType & extensionFieldSet) = 0; + + /// @brief From command SaveScene, retrieves ExtensionField from nvm + /// @param endpoint + /// @param cluster + /// @param serialysedBytes + /// @return + virtual CHIP_ERROR SerializeSave(EndpointId endpoint, ClusterId cluster, MutableByteSpan & serialysedBytes) = 0; + + /// @brief From stored scene (e.g. ViewScene), deserialize ExtensionFieldSet into a command object + /// @param endpoint Endpoint ID + /// @param cluster Cluster ID to set in command + /// @param serialysedBytes ExtensionFieldSet stored in NVM + /// @param extensionFieldSet ExtensionFieldSet in command format + /// @return + virtual CHIP_ERROR Deserialize(EndpointId endpoint, ClusterId cluster, ByteSpan & serialysedBytes, + app::Clusters::Scenes::Structs::ExtensionFieldSet::Type & extensionFieldSet) = 0; + + /// @brief From stored scene (e.g RecallScene), applies EFS values to cluster at transition time + /// @param endpoint Endpoint ID + /// @param cluster Cluster ID + /// @param serialysedBytes ExtensionFieldSet stored in NVM + /// @param timeMs Transition time in ms to apply the scene + /// @return + virtual CHIP_ERROR ApplyScene(EndpointId endpoint, ClusterId cluster, ByteSpan & serialysedBytes, TransitionTimeMs timeMs) = 0; +}; + class SceneTable { public: /// @brief struct used to identify a scene in storage by 3 ids, endpoint, group and scene struct SceneStorageId { - static constexpr TLV::Tag TagFirstSceneEndpointID() { return TLV::ContextTag(1); } - static constexpr TLV::Tag TagFirstSceneGroupID() { return TLV::ContextTag(2); } - static constexpr TLV::Tag TagFirstSceneID() { return TLV::ContextTag(3); } - // Identifies endpoint to which this scene applies - EndpointId mSceneEndpointId = kInvalidEndpointId; + EndpointId mEndpointId = kInvalidEndpointId; // Identifies group within the scope of the given fabric - SceneGroupID mSceneGroupId = kGlobalGroupSceneId; - SceneId mSceneId = kUndefinedSceneId; + SceneGroupID mGroupId = kGlobalGroupSceneId; + SceneId mSceneId = kUndefinedSceneId; SceneStorageId() = default; SceneStorageId(EndpointId endpoint, SceneId id, SceneGroupID groupId = kGlobalGroupSceneId) : - mSceneEndpointId(endpoint), mSceneGroupId(groupId), mSceneId(id) + mEndpointId(endpoint), mGroupId(groupId), mSceneId(id) {} SceneStorageId(const SceneStorageId & storageId) : - mSceneEndpointId(storageId.mSceneEndpointId), mSceneGroupId(storageId.mSceneGroupId), mSceneId(storageId.mSceneId) + mEndpointId(storageId.mEndpointId), mGroupId(storageId.mGroupId), mSceneId(storageId.mSceneId) {} CHIP_ERROR Serialize(TLV::TLVWriter & writer) const { TLV::TLVType container; - ReturnErrorOnFailure(writer.StartContainer(TLV::ContextTag(1), TLV::kTLVType_Structure, container)); + ReturnErrorOnFailure( + writer.StartContainer(TLV::ContextTag(kTagSceneStorageIDContainer), TLV::kTLVType_Structure, container)); - ReturnErrorOnFailure(writer.Put(TagFirstSceneEndpointID(), static_cast(this->mSceneEndpointId))); - ReturnErrorOnFailure(writer.Put(TagFirstSceneGroupID(), static_cast(this->mSceneGroupId))); - ReturnErrorOnFailure(writer.Put(TagFirstSceneID(), static_cast(this->mSceneId))); + ReturnErrorOnFailure(writer.Put(TLV::ContextTag(kTagSceneEndpointID), static_cast(this->mEndpointId))); + ReturnErrorOnFailure(writer.Put(TLV::ContextTag(kTagSceneGroupID), static_cast(this->mGroupId))); + ReturnErrorOnFailure(writer.Put(TLV::ContextTag(kTagSceneID), static_cast(this->mSceneId))); return writer.EndContainer(container); } CHIP_ERROR Deserialize(TLV::TLVReader & reader) { TLV::TLVType container; - ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::ContextTag(1))); + ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::ContextTag(kTagSceneStorageIDContainer))); ReturnErrorOnFailure(reader.EnterContainer(container)); - ReturnErrorOnFailure(reader.Next(TagFirstSceneEndpointID())); - ReturnErrorOnFailure(reader.Get(this->mSceneEndpointId)); - ReturnErrorOnFailure(reader.Next(TagFirstSceneGroupID())); - ReturnErrorOnFailure(reader.Get(this->mSceneGroupId)); - ReturnErrorOnFailure(reader.Next(TagFirstSceneID())); + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(kTagSceneEndpointID))); + ReturnErrorOnFailure(reader.Get(this->mEndpointId)); + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(kTagSceneGroupID))); + ReturnErrorOnFailure(reader.Get(this->mGroupId)); + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(kTagSceneID))); ReturnErrorOnFailure(reader.Get(this->mSceneId)); return reader.ExitContainer(container); @@ -85,30 +141,25 @@ class SceneTable void Clear() { - mSceneEndpointId = kInvalidEndpointId; - mSceneGroupId = kGlobalGroupSceneId; - mSceneId = kUndefinedSceneId; + mEndpointId = kInvalidEndpointId; + mGroupId = kGlobalGroupSceneId; + mSceneId = kUndefinedSceneId; } bool operator==(const SceneStorageId & other) { - return (this->mSceneEndpointId == other.mSceneEndpointId && this->mSceneGroupId == other.mSceneGroupId && - this->mSceneId == other.mSceneId); + return (this->mEndpointId == other.mEndpointId && this->mGroupId == other.mGroupId && this->mSceneId == other.mSceneId); } void operator=(const SceneStorageId & other) { - this->mSceneEndpointId = other.mSceneEndpointId; - this->mSceneGroupId = other.mSceneGroupId; - this->mSceneId = other.mSceneId; + this->mEndpointId = other.mEndpointId; + this->mGroupId = other.mGroupId; + this->mSceneId = other.mSceneId; } }; /// @brief struct used to store data held in a scene struct SceneData { - static constexpr TLV::Tag TagSceneName() { return TLV::ContextTag(1); } - static constexpr TLV::Tag TagSceneTransitionTime() { return TLV::ContextTag(2); } - static constexpr TLV::Tag TagSceneTransitionTime100() { return TLV::ContextTag(3); } - char mName[kSceneNameMax] = { 0 }; size_t mNameLength = 0; SceneTransitionTime mSceneTransitionTime = 0; @@ -140,16 +191,19 @@ class SceneTable CHIP_ERROR Serialize(TLV::TLVWriter & writer) const { TLV::TLVType container; - ReturnErrorOnFailure(writer.StartContainer(TLV::ContextTag(1), TLV::kTLVType_Structure, container)); + ReturnErrorOnFailure( + writer.StartContainer(TLV::ContextTag(kTagSceneDataContainer), TLV::kTLVType_Structure, container)); // A 0 size means the name wasn't used so it won't get stored if (!this->mNameSpan.empty()) { - ReturnErrorOnFailure(writer.PutString(TagSceneName(), this->mNameSpan)); + ReturnErrorOnFailure(writer.PutString(TLV::ContextTag(kTagSceneName), this->mNameSpan)); } - ReturnErrorOnFailure(writer.Put(TagSceneTransitionTime(), static_cast(this->mSceneTransitionTime))); - ReturnErrorOnFailure(writer.Put(TagSceneTransitionTime100(), static_cast(this->mTransitionTime100ms))); + ReturnErrorOnFailure( + writer.Put(TLV::ContextTag(kTagSceneDTransitionTime), static_cast(this->mSceneTransitionTime))); + ReturnErrorOnFailure( + writer.Put(TLV::ContextTag(kTagSceneDTransitionTime100), static_cast(this->mTransitionTime100ms))); ReturnErrorOnFailure(this->mExtensionFieldsSets.Serialize(writer)); return writer.EndContainer(container); @@ -157,23 +211,24 @@ class SceneTable CHIP_ERROR Deserialize(TLV::TLVReader & reader) { TLV::TLVType container; - ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::ContextTag(1))); + ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::ContextTag(kTagSceneDataContainer))); ReturnErrorOnFailure(reader.EnterContainer(container)); ReturnErrorOnFailure(reader.Next()); TLV::Tag currTag = reader.GetTag(); - VerifyOrReturnError(TagSceneName() == currTag || TagSceneTransitionTime() == currTag, CHIP_ERROR_WRONG_TLV_TYPE); + VerifyOrReturnError(TLV::ContextTag(kTagSceneName) == currTag || TLV::ContextTag(kTagSceneDTransitionTime) == currTag, + CHIP_ERROR_WRONG_TLV_TYPE); // If there was no error, a name is expected from the storage, if there was an unexpectec TLV element, - if (currTag == TagSceneName()) + if (currTag == TLV::ContextTag(kTagSceneName)) { ReturnErrorOnFailure(reader.Get(this->mNameSpan)); this->SetName(this->mNameSpan); - ReturnErrorOnFailure(reader.Next(TagSceneTransitionTime())); + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(kTagSceneDTransitionTime))); } ReturnErrorOnFailure(reader.Get(this->mSceneTransitionTime)); - ReturnErrorOnFailure(reader.Next(TagSceneTransitionTime100())); + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(kTagSceneDTransitionTime100))); ReturnErrorOnFailure(reader.Get(this->mTransitionTime100ms)); ReturnErrorOnFailure(this->mExtensionFieldsSets.Deserialize(reader)); @@ -251,7 +306,7 @@ class SceneTable virtual ~SceneTable() = default; // Not copyable - SceneTable(const SceneTable &) = delete; + SceneTable(const SceneTable &) = delete; SceneTable & operator=(const SceneTable &) = delete; virtual CHIP_ERROR Init(PersistentStorageDelegate * storage) = 0; @@ -271,6 +326,10 @@ class SceneTable // Fabrics virtual CHIP_ERROR RemoveFabric(FabricIndex fabric_index) = 0; + // Handlers + SceneHandler * mHandlers[CHIP_CONFIG_SCENES_MAX_CLUSTERS_PER_SCENES] = { nullptr }; + uint8_t handlerNum = 0; + protected: const uint8_t mMaxScenePerFabric = kMaxScenePerFabric; }; diff --git a/src/app/clusters/scenes/SceneTableImpl.cpp b/src/app/clusters/scenes/SceneTableImpl.cpp index b0ce8de1fb8282..cfb3f0306ac82e 100644 --- a/src/app/clusters/scenes/SceneTableImpl.cpp +++ b/src/app/clusters/scenes/SceneTableImpl.cpp @@ -22,6 +22,12 @@ namespace chip { namespace scenes { +enum SceneImplTLVTag +{ + kTagSceneCount = 1, + kTagNext, +}; + using SceneTableEntry = DefaultSceneTableImpl::SceneTableEntry; using SceneStorageId = DefaultSceneTableImpl::SceneStorageId; using SceneData = DefaultSceneTableImpl::SceneData; @@ -35,7 +41,7 @@ struct FabricHavingSceneList : public CommonPersistentData::FabricList } }; -static constexpr size_t kPersistentBufferMax = 128; +static constexpr size_t kPersistentBufferMax = 256; struct SceneTableData : public SceneTableEntry, PersistentData { @@ -93,10 +99,6 @@ struct SceneTableData : public SceneTableEntry, PersistentData { - static constexpr TLV::Tag TagSceneCount() { return TLV::ContextTag(1); } - static constexpr TLV::Tag TagSceneMap() { return TLV::ContextTag(2); } - static constexpr TLV::Tag TagNext() { return TLV::ContextTag(3); } - FabricIndex fabric_index = kUndefinedFabricIndex; uint8_t scene_count = 0; SceneStorageId scene_map[kMaxScenePerFabric]; @@ -127,12 +129,12 @@ struct FabricSceneData : public PersistentData TLV::TLVType container; ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, container)); - ReturnErrorOnFailure(writer.Put(TagSceneCount(), static_cast(scene_count))); + ReturnErrorOnFailure(writer.Put(TLV::ContextTag(kTagSceneCount), static_cast(scene_count))); for (uint8_t i = 0; i < kMaxScenePerFabric; i++) { ReturnErrorOnFailure(scene_map[i].Serialize(writer)); } - ReturnErrorOnFailure(writer.Put(TagNext(), static_cast(next))); + ReturnErrorOnFailure(writer.Put(TLV::ContextTag(kTagNext), static_cast(next))); return writer.EndContainer(container); } @@ -145,13 +147,13 @@ struct FabricSceneData : public PersistentData TLV::TLVType container; ReturnErrorOnFailure(reader.EnterContainer(container)); - ReturnErrorOnFailure(reader.Next(TagSceneCount())); + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(kTagSceneCount))); ReturnErrorOnFailure(reader.Get(scene_count)); for (uint8_t i = 0; i < kMaxScenePerFabric; i++) { ReturnErrorOnFailure(scene_map[i].Deserialize(reader)); } - ReturnErrorOnFailure(reader.Next(TagNext())); + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(kTagNext))); ReturnErrorOnFailure(reader.Get(next)); return reader.ExitContainer(container); @@ -254,7 +256,7 @@ struct FabricSceneData : public PersistentData idx = index; return CHIP_NO_ERROR; // return scene at current index if scene found } - if (scene_map[index].mSceneEndpointId == kInvalidEndpointId && firstFreeIdx == kUndefinedSceneIndex) + if (scene_map[index].mEndpointId == kInvalidEndpointId && firstFreeIdx == kUndefinedSceneIndex) { firstFreeIdx = index; } @@ -280,6 +282,7 @@ struct FabricSceneData : public PersistentData ReturnErrorOnFailure(Unregister(storage)); return PersistentData::Delete(storage); } + CHIP_ERROR SaveScene(PersistentStorageDelegate * storage, const SceneTableEntry & entry) { CHIP_ERROR err; @@ -397,38 +400,37 @@ CHIP_ERROR DefaultSceneTableImpl::RemoveSceneTableEntryAtPosition(FabricIndex fa return CHIP_NO_ERROR; } -/// @brief Registers handle to get extension fields set for a specific cluster. If the handler is already present in the handler -/// array, it will be overwritten -/// @param ID ID of the cluster used to fill the extension fields set -/// @param get_function pointer to function to call to get the extension fiels set from the cluster -/// @param set_function pointer to function to call send an extension field to the cluster -/// @return CHIP_ERROR_NO_MEMORY if couldn't insert the handler, otherwise CHIP_NO_ERROR -CHIP_ERROR DefaultSceneTableImpl::RegisterHandler(ClusterId ID, clusterFieldsHandle get_function, clusterFieldsHandle set_function) +/// @brief Register a handler in the handler list +/// @param handler Cluster specific handler for extension field sets interaction +/// @return CHIP_NO_ERROR if handler was registered, CHIP_ERROR_NO_MEMORY if the handler list is full +CHIP_ERROR DefaultSceneTableImpl::RegisterHandler(SceneHandler * handler) { - CHIP_ERROR err = CHIP_ERROR_NO_MEMORY; - uint8_t idPosition = 0xff, fisrtEmptyPosition = 0xff; - for (uint8_t i = 0; i < CHIP_CONFIG_SCENES_MAX_CLUSTERS_PER_SCENES; i++) + CHIP_ERROR err = CHIP_ERROR_NO_MEMORY; + uint8_t idPosition = kInvalidPosition; + uint8_t fisrtEmptyPosition = kInvalidPosition; + + for (uint8_t i = 0; i < kMaxSceneHandlers; i++) { - if (this->handlers[i].GetID() == ID) + if (this->mHandlers[i] == handler) { idPosition = i; break; } - if (!this->handlers[i].IsInitialized() && fisrtEmptyPosition == 0xff) + if (this->mHandlers[i] == nullptr && fisrtEmptyPosition == kInvalidPosition) { fisrtEmptyPosition = i; } } // if found, insert at found position, otherwise at first free possition, otherwise return error - if (idPosition < CHIP_CONFIG_SCENES_MAX_CLUSTERS_PER_SCENES) + if (idPosition < kMaxSceneHandlers) { - this->handlers[idPosition].InitSceneHandler(ID, get_function, set_function); - err = CHIP_NO_ERROR; + this->mHandlers[idPosition] = handler; + err = CHIP_NO_ERROR; } - else if (fisrtEmptyPosition < CHIP_CONFIG_SCENES_MAX_CLUSTERS_PER_SCENES) + else if (fisrtEmptyPosition < kMaxSceneHandlers) { - this->handlers[fisrtEmptyPosition].InitSceneHandler(ID, get_function, set_function); + this->mHandlers[fisrtEmptyPosition] = handler; this->handlerNum++; err = CHIP_NO_ERROR; } @@ -438,36 +440,37 @@ CHIP_ERROR DefaultSceneTableImpl::RegisterHandler(ClusterId ID, clusterFieldsHan CHIP_ERROR DefaultSceneTableImpl::UnregisterHandler(uint8_t position) { - if (!HandlerListEmpty()) - { - if (position < CHIP_CONFIG_SCENES_MAX_CLUSTERS_PER_SCENES) - { - if (this->handlers[position].IsInitialized()) - { - this->handlers[position].ClearSceneHandler(); - this->handlerNum--; - } - } - else - { - return CHIP_ERROR_ACCESS_DENIED; - } - } + VerifyOrReturnError(position < kMaxSceneHandlers, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnValue(!this->HandlerListEmpty() && !(this->mHandlers[position] == nullptr), CHIP_NO_ERROR); + + uint8_t next = position + 1; + uint8_t moveNum = kMaxSceneHandlers - next; + + // TODO: Implement general array management methods + // Compress array after removal + memmove(&this->mHandlers[position], &this->mHandlers[next], sizeof(SceneHandler *) * moveNum); + + this->handlerNum--; + // Clear last occupied position + this->mHandlers[handlerNum] = nullptr; return CHIP_NO_ERROR; } -CHIP_ERROR DefaultSceneTableImpl::EFSValuesFromCluster(ExtensionFieldsSetsImpl & fieldSets) +CHIP_ERROR DefaultSceneTableImpl::SceneSaveEFS(SceneTableEntry & scene, clusterId cluster) { ExtensionFieldsSet EFS; + MutableByteSpan EFSSpan = MutableByteSpan(EFS.mBytesBuffer, kMaxFieldsPerCluster); if (!this->HandlerListEmpty()) { - for (uint8_t i = 0; i < CHIP_CONFIG_SCENES_MAX_CLUSTERS_PER_SCENES; i++) + for (uint8_t i = 0; i < this->handlerNum; i++) { - if (this->handlers[i].IsInitialized()) + if (this->mHandlers[i] != nullptr) { - ReturnErrorOnFailure(this->handlers[i].GetClusterEFS(EFS)); - ReturnErrorOnFailure(fieldSets.InsertFieldSet(EFS)); + EFS.mID = cluster; + ReturnErrorOnFailure(this->mHandlers[i]->SerializeSave(scene.mStorageId.mEndpointId, cluster, EFSSpan)); + EFS.mUsedBytes = (uint8_t) EFSSpan.size(); + ReturnErrorOnFailure(scene.mStorageData.mExtensionFieldsSets.InsertFieldSet(EFS)); } } } @@ -475,23 +478,33 @@ CHIP_ERROR DefaultSceneTableImpl::EFSValuesFromCluster(ExtensionFieldsSetsImpl & return CHIP_NO_ERROR; } -CHIP_ERROR DefaultSceneTableImpl::EFSValuesToCluster(ExtensionFieldsSetsImpl & fieldSets) +CHIP_ERROR DefaultSceneTableImpl::SceneApplyEFS(FabricIndex fabric_index, const SceneStorageId & scene_id) { + FabricSceneData fabric(fabric_index); + SceneTableData scene(fabric_index); ExtensionFieldsSet EFS; + TransitionTimeMs time; + clusterId cluster; + + ReturnErrorOnFailure(fabric.Load(mStorage)); + VerifyOrReturnError(fabric.Find(scene_id, scene.index) == CHIP_NO_ERROR, CHIP_ERROR_NOT_FOUND); + ReturnErrorOnFailure(scene.Load(mStorage)); + if (!this->HandlerListEmpty()) { - for (uint8_t i = 0; i < CHIP_CONFIG_SCENES_MAX_CLUSTERS_PER_SCENES; i++) + for (uint8_t i = 0; i < scene.mStorageData.mExtensionFieldsSets.GetFieldNum(); i++) { - fieldSets.GetFieldSetAtPosition(EFS, i); + scene.mStorageData.mExtensionFieldsSets.GetFieldSetAtPosition(EFS, i); + cluster = EFS.mID; + time = scene.mStorageData.mSceneTransitionTime * 1000 + + (scene.mStorageData.mTransitionTime100ms ? scene.mStorageData.mTransitionTime100ms * 10 : 0); + ByteSpan EFSSpan = MutableByteSpan(EFS.mBytesBuffer, EFS.mUsedBytes); if (!EFS.IsEmpty()) { - for (uint8_t j = 0; j < CHIP_CONFIG_SCENES_MAX_CLUSTERS_PER_SCENES; j++) + for (uint8_t j = 0; j < this->handlerNum; j++) { - if (EFS.mID == this->handlers[j].GetID()) - { - ReturnErrorOnFailure(this->handlers[j].SetClusterEFS(EFS)); - } + ReturnErrorOnFailure(this->mHandlers[j]->ApplyScene(scene.mStorageId.mEndpointId, cluster, EFSSpan, time)); } } } @@ -548,7 +561,7 @@ bool DefaultSceneTableImpl::SceneEntryIteratorImpl::Next(SceneTableEntry & outpu // looks for next available scene while (mSceneIndex < kMaxScenePerFabric) { - if (fabric.scene_map[mSceneIndex].mSceneEndpointId != kInvalidEndpointId) + if (fabric.scene_map[mSceneIndex].mEndpointId != kInvalidEndpointId) { scene.index = mSceneIndex; VerifyOrReturnError(scene.Load(mProvider.mStorage) == CHIP_NO_ERROR, false); diff --git a/src/app/clusters/scenes/SceneTableImpl.h b/src/app/clusters/scenes/SceneTableImpl.h index c3fb2e043b948a..aac47870632db4 100644 --- a/src/app/clusters/scenes/SceneTableImpl.h +++ b/src/app/clusters/scenes/SceneTableImpl.h @@ -27,86 +27,135 @@ namespace scenes { using clusterId = chip::ClusterId; -typedef CHIP_ERROR (*clusterFieldsHandle)(ExtensionFieldsSet & fields); - -/// @brief Class to allow extension field sets to be handled by the scene table without any knowledge of the cluster or its -/// implementation -class SceneHandler +/// @brief Default implementation of handler, handle EFS from add scene and view scene commands for any cluster +/// The implementation of SerializeSave and ApplyScene were omitted and must be implemented in a way that +/// is compatible with the SerializeAdd output in order to function with the Default Scene Handler. +/// It is worth noting that this implementation is very memory consuming. In the current worst case, +/// (Color control cluster), the Extension Field Set's value pair list TLV occupies 99 bytes of memory +class DefaultSceneHandlerImpl : public scenes::SceneHandler { public: - SceneHandler(ClusterId Id = kInvalidClusterId, clusterFieldsHandle getEFSHandle = nullptr, - clusterFieldsHandle setEFSHandle = nullptr) + static constexpr uint8_t kMaxValueSize = 4; + static constexpr uint8_t kMaxAvPair = 15; + + DefaultSceneHandlerImpl() = default; + ~DefaultSceneHandlerImpl() override{}; + + /// @brief Function to serialize data from an add scene command, assume the incoming extensionFieldSet is initialized + /// @param endpoint Target Endpoint + /// @param cluster Cluster in the Extension field set, filled by the function + /// @param serialyzedBytes Mutable Byte span to hold EFS data from command + /// @param extensionFieldSet Extension field set from commmand, pre initialized + /// @return CHIP_NO_ERROR if success, specific CHIP_ERROR otherwise + virtual CHIP_ERROR SerializeAdd(EndpointId endpoint, ClusterId & cluster, MutableByteSpan & serialyzedBytes, + app::Clusters::Scenes::Structs::ExtensionFieldSet::DecodableType & extensionFieldSet) override { - if (getEFSHandle != nullptr && setEFSHandle != nullptr && Id != kInvalidClusterId) - { - getEFS = getEFSHandle; - setEFS = setEFSHandle; - cID = Id; - initialized = true; - } - }; - ~SceneHandler(){}; + app::DataModel::List attributeValueList; + app::Clusters::Scenes::Structs::AttributeValuePair::DecodableType aVPair; + TLV::TLVWriter writer; + TLV::TLVType outer; - void InitSceneHandler(ClusterId Id, clusterFieldsHandle getEFSHandle, clusterFieldsHandle setEFSHandle) - { - if (getEFSHandle != nullptr && setEFSHandle != nullptr && Id != kInvalidClusterId) - { - getEFS = getEFSHandle; - setEFS = setEFSHandle; - cID = Id; - initialized = true; - } - } + uint8_t pairCount = 0; + uint8_t valueBytes = 0; - void ClearSceneHandler() - { - getEFS = nullptr; - setEFS = nullptr; - cID = kInvalidClusterId; - initialized = false; - } + VerifyOrReturnError(SupportsCluster(endpoint, extensionFieldSet.clusterID), CHIP_ERROR_INVALID_ARGUMENT); - CHIP_ERROR GetClusterEFS(ExtensionFieldsSet & clusterFields) - { - if (this->IsInitialized()) + cluster = extensionFieldSet.clusterID; + + auto pair_iterator = extensionFieldSet.attributeValueList.begin(); + while (pair_iterator.Next() && pairCount < kMaxAvPair) { - ReturnErrorOnFailure(getEFS(clusterFields)); + aVPair = pair_iterator.GetValue(); + mAVPairs[pairCount].attributeID = aVPair.attributeID; + auto value_iterator = aVPair.attributeValue.begin(); + + valueBytes = 0; + while (value_iterator.Next() && valueBytes < kMaxValueSize) + { + mValueBuffer[pairCount][valueBytes] = value_iterator.GetValue(); + valueBytes++; + } + // Check we could go through all bytes of the value + VerifyOrReturnError(value_iterator.Next() == false, CHIP_ERROR_BUFFER_TOO_SMALL); + mAVPairs[pairCount].attributeValue = mValueBuffer[pairCount]; + mAVPairs[pairCount].attributeValue.reduce_size(valueBytes); + pairCount++; } + // Check we could go through all pairs in incomming command + VerifyOrReturnError(pair_iterator.Next() == false, CHIP_ERROR_BUFFER_TOO_SMALL); + + attributeValueList = mAVPairs; + attributeValueList.reduce_size(pairCount); + + writer.Init(serialyzedBytes); + ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outer)); + ReturnErrorOnFailure(app::DataModel::Encode( + writer, TLV::ContextTag(to_underlying(app::Clusters::Scenes::Structs::ExtensionFieldSet::Fields::kAttributeValueList)), + attributeValueList)); + ReturnErrorOnFailure(writer.EndContainer(outer)); + return CHIP_NO_ERROR; } - CHIP_ERROR SetClusterEFS(ExtensionFieldsSet & clusterFields) + + /// @brief Simulates taking data from nvm and loading it in a command object if the cluster is supported by the endpoint + /// @param endpoint target endpoint + /// @param cluster target cluster + /// @param serialyzedBytes data to deserialize into EFS + /// @return CHIP_NO_ERROR if Extension Field Set was successfully populated, specific CHIP_ERROR otherwise + virtual CHIP_ERROR Deserialize(EndpointId endpoint, ClusterId cluster, ByteSpan & serialyzedBytes, + app::Clusters::Scenes::Structs::ExtensionFieldSet::Type & extensionFieldSet) override { - if (this->IsInitialized()) - { - ReturnErrorOnFailure(setEFS(clusterFields)); - } + app::DataModel::DecodableList attributeValueList; + app::Clusters::Scenes::Structs::AttributeValuePair::DecodableType decodePair; - return CHIP_NO_ERROR; - } + TLV::TLVReader reader; + TLV::TLVType outer; + uint8_t pairCount = 0; + uint8_t valueBytes = 0; - bool IsInitialized() const { return this->initialized; } + VerifyOrReturnError(SupportsCluster(endpoint, cluster), CHIP_ERROR_INVALID_ARGUMENT); - ClusterId GetID() { return cID; } + extensionFieldSet.clusterID = cluster; + reader.Init(serialyzedBytes); + ReturnErrorOnFailure(reader.Next()); + ReturnErrorOnFailure(reader.EnterContainer(outer)); + ReturnErrorOnFailure(reader.Next()); + attributeValueList.Decode(reader); - bool operator==(const SceneHandler & other) - { - return (this->getEFS == other.getEFS && this->setEFS == other.setEFS && this->cID == other.cID && - initialized == other.initialized); - } - void operator=(const SceneHandler & other) - { - this->getEFS = other.getEFS; - this->setEFS = other.setEFS; - this->cID = other.cID; - this->initialized = true; + auto pair_iterator = attributeValueList.begin(); + while (pair_iterator.Next() && pairCount < kMaxAvPair) + { + decodePair = pair_iterator.GetValue(); + mAVPairs[pairCount].attributeID = decodePair.attributeID; + auto value_iterator = decodePair.attributeValue.begin(); + valueBytes = 0; + + while (value_iterator.Next() && valueBytes < kMaxValueSize) + { + mValueBuffer[pairCount][valueBytes] = value_iterator.GetValue(); + valueBytes++; + } + // Check we could go through all bytes of the value + VerifyOrReturnError(value_iterator.Next() == false, CHIP_ERROR_BUFFER_TOO_SMALL); + mAVPairs[pairCount].attributeValue = mValueBuffer[pairCount]; + mAVPairs[pairCount].attributeValue.reduce_size(valueBytes); + pairCount++; + }; + + // Check we could go through all pairs stored in memory + VerifyOrReturnError(pair_iterator.Next() == false, CHIP_ERROR_BUFFER_TOO_SMALL); + ReturnErrorOnFailure(reader.ExitContainer(outer)); + + extensionFieldSet.attributeValueList = mAVPairs; + extensionFieldSet.attributeValueList.reduce_size(pairCount); + + return CHIP_NO_ERROR; } -protected: - clusterFieldsHandle getEFS = nullptr; - clusterFieldsHandle setEFS = nullptr; - ClusterId cID = kInvalidClusterId; - bool initialized = false; +private: + app::Clusters::Scenes::Structs::AttributeValuePair::Type mAVPairs[kMaxAvPair]; + uint8_t mValueBuffer[kMaxAvPair][kMaxValueSize]; }; /** @@ -129,18 +178,17 @@ class DefaultSceneTableImpl : public SceneTable // Scene access by Id CHIP_ERROR SetSceneTableEntry(FabricIndex fabric_index, const SceneTableEntry & entry) override; - CHIP_ERROR GetSceneTableEntry(FabricIndex fabric_index, DefaultSceneTableImpl::SceneStorageId scene_id, - SceneTableEntry & entry) override; - CHIP_ERROR RemoveSceneTableEntry(FabricIndex fabric_index, DefaultSceneTableImpl::SceneStorageId scene_id) override; + CHIP_ERROR GetSceneTableEntry(FabricIndex fabric_index, SceneStorageId scene_id, SceneTableEntry & entry) override; + CHIP_ERROR RemoveSceneTableEntry(FabricIndex fabric_index, SceneStorageId scene_id) override; CHIP_ERROR RemoveSceneTableEntryAtPosition(FabricIndex fabric_index, SceneIndex scened_idx) override; // SceneHandlers - CHIP_ERROR RegisterHandler(ClusterId ID, clusterFieldsHandle get_function, clusterFieldsHandle set_function); + CHIP_ERROR RegisterHandler(SceneHandler * handler); CHIP_ERROR UnregisterHandler(uint8_t position); // Extension field sets operation - CHIP_ERROR EFSValuesFromCluster(ExtensionFieldsSetsImpl & fieldSets); - CHIP_ERROR EFSValuesToCluster(ExtensionFieldsSetsImpl & fieldSets); + CHIP_ERROR SceneSaveEFS(SceneTableEntry & scene, clusterId cluster); + CHIP_ERROR SceneApplyEFS(FabricIndex fabric_index, const SceneStorageId & scene_id); // Fabrics CHIP_ERROR RemoveFabric(FabricIndex fabric_index) override; @@ -172,8 +220,6 @@ class DefaultSceneTableImpl : public SceneTable chip::PersistentStorageDelegate * mStorage = nullptr; ObjectPool mSceneEntryIterators; - SceneHandler handlers[CHIP_CONFIG_SCENES_MAX_CLUSTERS_PER_SCENES]; - uint8_t handlerNum = 0; }; // class DefaultSceneTableImpl /** diff --git a/src/app/tests/TestSceneTable.cpp b/src/app/tests/TestSceneTable.cpp index bf7d2b64bda9d1..f09059cca056da 100644 --- a/src/app/tests/TestSceneTable.cpp +++ b/src/app/tests/TestSceneTable.cpp @@ -17,41 +17,59 @@ */ #include +#include +#include #include #include #include -using FabricIndex = chip::FabricIndex; -using SceneTableEntry = chip::scenes::DefaultSceneTableImpl::SceneTableEntry; -using SceneTableImpl = chip::scenes::DefaultSceneTableImpl; -using SceneStorageId = chip::scenes::DefaultSceneTableImpl::SceneStorageId; -using SceneData = chip::scenes::DefaultSceneTableImpl::SceneData; -using CharSpan = chip::CharSpan; -using ExtensionFieldsSet = chip::scenes::ExtensionFieldsSet; +using namespace chip; +using SceneTableEntry = scenes::DefaultSceneTableImpl::SceneTableEntry; +using SceneTableImpl = scenes::DefaultSceneTableImpl; +using SceneStorageId = scenes::DefaultSceneTableImpl::SceneStorageId; +using SceneData = scenes::DefaultSceneTableImpl::SceneData; +using ExtensionFieldsSet = scenes::ExtensionFieldsSet; +using TransitionTimeMs = scenes::TransitionTimeMs; #define ON_OFF_CID 0x0006 #define LV_CTR_CID 0x0008 -#define CO_CTR_CID 0x0300 +#define CC_CTR_CID 0x0300 +#define TEST_ENDPOINT1 0x0001 +#define TEST_ENDPOINT2 0x0099 + +// ON OFF ATTRIBUTE IDs +#define ON_OFF_ID 0x0000 + +// LEVEL CONTROL ATTRIBUTE IDs +#define CURRENT_LVL_ID 0x0000 +#define CURRENT_FRQ_ID 0x0004 + +// COLOR CONTROL ATTRIBUTE IDs +#define CURRENT_SAT_ID 0x0001 +#define CURRENT_X_ID 0x0003 +#define CURRENT_Y_ID 0x0004 +#define COLOR_TEMP_MIR_ID 00007 +#define EN_CURRENT_HUE_ID 0x4000 +#define C_LOOP_ACTIVE_ID 0x4002 +#define C_LOOP_DIR_ID 0x4003 +#define C_LOOP_TIME_ID 0x4004 namespace { -static chip::TestPersistentStorageDelegate testStorage; -static SceneTableImpl sSceneTable; - // Test fabrics, adding more requires to modify the "ResetSceneTable" function constexpr chip::FabricIndex kFabric1 = 1; constexpr chip::FabricIndex kFabric2 = 7; // Scene storage ID -static const SceneStorageId sceneId1(1, 0xAA, 0x101); -static const SceneStorageId sceneId2(1, 0xBB, 0x00); -static const SceneStorageId sceneId3(1, 0xCC, 0x102); -static const SceneStorageId sceneId4(1, 0xBE, 0x00); -static const SceneStorageId sceneId5(1, 0x45, 0x103); -static const SceneStorageId sceneId6(1, 0x65, 0x00); -static const SceneStorageId sceneId7(1, 0x77, 0x101); -static const SceneStorageId sceneId8(1, 0xEE, 0x101); -static const SceneStorageId sceneId9(1, 0xAB, 0x101); +static const SceneStorageId sceneId1(TEST_ENDPOINT1, 0xAA, 0x101); +static const SceneStorageId sceneId2(TEST_ENDPOINT1, 0xBB, 0x00); +static const SceneStorageId sceneId3(TEST_ENDPOINT2, 0xCC, 0x102); +static const SceneStorageId sceneId4(TEST_ENDPOINT2, 0xBE, 0x00); +static const SceneStorageId sceneId5(TEST_ENDPOINT1, 0x45, 0x103); +static const SceneStorageId sceneId6(TEST_ENDPOINT1, 0x65, 0x00); +static const SceneStorageId sceneId7(TEST_ENDPOINT1, 0x77, 0x101); +static const SceneStorageId sceneId8(TEST_ENDPOINT2, 0xEE, 0x101); +static const SceneStorageId sceneId9(TEST_ENDPOINT2, 0xAB, 0x101); // Scene data static const SceneData sceneData1(CharSpan("Scene #1", sizeof("Scene #1"))); @@ -81,77 +99,163 @@ SceneTableEntry scene10(sceneId1, sceneData10); SceneTableEntry scene11(sceneId5, sceneData11); SceneTableEntry scene12(sceneId8, sceneData12); -// EFS -static const ExtensionFieldsSet onOffEFS1 = ExtensionFieldsSet(ON_OFF_CID, (uint8_t *) "1", 1); -static const ExtensionFieldsSet onOffEFS2 = ExtensionFieldsSet(ON_OFF_CID, (uint8_t *) "0", 1); -static const ExtensionFieldsSet lvCtrEFS1 = ExtensionFieldsSet(LV_CTR_CID, (uint8_t *) "511", 3); -static const ExtensionFieldsSet lvCtrEFS2 = ExtensionFieldsSet(LV_CTR_CID, (uint8_t *) "222", 3); -static const ExtensionFieldsSet coCtrEFS1 = ExtensionFieldsSet(CO_CTR_CID, (uint8_t *) "123456789abcde", 14); -static const ExtensionFieldsSet coCtrEFS2 = ExtensionFieldsSet(CO_CTR_CID, (uint8_t *) "abcdefghi12345", 14); +// Clusters EFS data +static app::Clusters::Scenes::Structs::ExtensionFieldSet::Type OOextensionFieldSet; +static app::Clusters::Scenes::Structs::ExtensionFieldSet::Type LCextensionFieldSet; +static app::Clusters::Scenes::Structs::ExtensionFieldSet::Type CCextensionFieldSet; -// Simulation of clusters callbacks (sate #1) -CHIP_ERROR oo_from_cluster_cb1(ExtensionFieldsSet & fields) -{ - ReturnErrorOnFailure(fields = onOffEFS1); - return CHIP_NO_ERROR; -} -CHIP_ERROR oo_to_cluster_cb1(ExtensionFieldsSet & fields) -{ - VerifyOrReturnError(fields == onOffEFS1, CHIP_ERROR_INVALID_ARGUMENT); - return CHIP_NO_ERROR; -} -CHIP_ERROR lc_from_cluster_cb1(ExtensionFieldsSet & fields) -{ - ReturnErrorOnFailure(fields = lvCtrEFS1); - return CHIP_NO_ERROR; -} -CHIP_ERROR lc_to_cluster_cb1(ExtensionFieldsSet & fields) -{ - VerifyOrReturnError(fields == lvCtrEFS1, CHIP_ERROR_INVALID_ARGUMENT); - return CHIP_NO_ERROR; -} -CHIP_ERROR cc_from_cluster_cb1(ExtensionFieldsSet & fields) -{ - ReturnErrorOnFailure(fields = coCtrEFS1); - return CHIP_NO_ERROR; -} -CHIP_ERROR cc_to_cluster_cb1(ExtensionFieldsSet & fields) -{ - VerifyOrReturnError(fields == coCtrEFS1, CHIP_ERROR_INVALID_ARGUMENT); - return CHIP_NO_ERROR; -} +static app::Clusters::Scenes::Structs::AttributeValuePair::Type OOPairs[1]; +static app::Clusters::Scenes::Structs::AttributeValuePair::Type LCPairs[2]; +static app::Clusters::Scenes::Structs::AttributeValuePair::Type CCPairs[8]; -// Simulation of clusters callbacks (sate #2) -CHIP_ERROR oo_from_cluster_cb2(ExtensionFieldsSet & fields) -{ - ReturnErrorOnFailure(fields = onOffEFS1); - return CHIP_NO_ERROR; -} -CHIP_ERROR oo_to_cluster_cb2(ExtensionFieldsSet & fields) -{ - VerifyOrReturnError(fields == onOffEFS1, CHIP_ERROR_INVALID_ARGUMENT); - return CHIP_NO_ERROR; -} -CHIP_ERROR lc_from_cluster_cb2(ExtensionFieldsSet & fields) -{ - ReturnErrorOnFailure(fields = lvCtrEFS2); - return CHIP_NO_ERROR; -} -CHIP_ERROR lc_to_cluster_cb2(ExtensionFieldsSet & fields) -{ - VerifyOrReturnError(fields == lvCtrEFS2, CHIP_ERROR_INVALID_ARGUMENT); - return CHIP_NO_ERROR; -} -CHIP_ERROR cc_from_cluster_cb2(ExtensionFieldsSet & fields) -{ - ReturnErrorOnFailure(fields = coCtrEFS2); - return CHIP_NO_ERROR; -} -CHIP_ERROR cc_to_cluster_cb2(ExtensionFieldsSet & fields) +static uint8_t OO_buffer[scenes::kMaxFieldsPerCluster] = { 0 }; +static uint8_t LC_buffer[scenes::kMaxFieldsPerCluster] = { 0 }; +static uint8_t CC_buffer[scenes::kMaxFieldsPerCluster] = { 0 }; + +/// @brief Simulates a Handler where Endpoint 1 supports onoff and level control and Endpoint 2 supports onoff and color control +class TestSceneHandler : public scenes::DefaultSceneHandlerImpl { - VerifyOrReturnError(fields == coCtrEFS2, CHIP_ERROR_INVALID_ARGUMENT); - return CHIP_NO_ERROR; -} +public: + static constexpr uint8_t kMaxValueSize = 4; + static constexpr uint8_t kMaxAvPair = 15; + + TestSceneHandler() = default; + ~TestSceneHandler() override {} + + // Default function only checks if endpoint and clusters are valid + bool SupportsCluster(EndpointId endpoint, ClusterId cluster) override + { + bool ret = false; + if (endpoint == TEST_ENDPOINT1) + { + if (cluster == ON_OFF_CID || cluster == LV_CTR_CID) + { + ret = true; + } + } + + if (endpoint == TEST_ENDPOINT2) + { + if (cluster == ON_OFF_CID || cluster == CC_CTR_CID) + { + ret = true; + } + } + + return ret; + } + + /// @brief Simulates save from cluster, data is already in an EFS struct but this isn't mandatory + /// @param endpoint target endpoint + /// @param cluster target cluster + /// @param serialyzedBytes data to serialize into EFS + /// @return success if successfully serialized the data, CHIP_ERROR_INVALID_ARGUMENT if endpoint or cluster not supported + CHIP_ERROR SerializeSave(EndpointId endpoint, ClusterId cluster, MutableByteSpan & serialyzedBytes) override + { + CHIP_ERROR err = CHIP_ERROR_INVALID_ARGUMENT; + + if (endpoint == TEST_ENDPOINT1) + { + switch (cluster) + { + case ON_OFF_CID: + err = CHIP_NO_ERROR; + memcpy(serialyzedBytes.data(), OO_buffer, scenes::kMaxFieldsPerCluster); + serialyzedBytes.reduce_size(15); // Used memory for OnOff TLV + break; + case LV_CTR_CID: + err = CHIP_NO_ERROR; + memcpy(serialyzedBytes.data(), LC_buffer, scenes::kMaxFieldsPerCluster); + serialyzedBytes.reduce_size(27); // Used memory for Level Control TLV + break; + default: + break; + } + } + if (endpoint == TEST_ENDPOINT2) + { + switch (cluster) + { + case ON_OFF_CID: + err = CHIP_NO_ERROR; + memcpy(serialyzedBytes.data(), OO_buffer, scenes::kMaxFieldsPerCluster); + serialyzedBytes.reduce_size(15); // Used memory for Color Control TLV + break; + case CC_CTR_CID: + err = CHIP_NO_ERROR; + memcpy(serialyzedBytes.data(), CC_buffer, scenes::kMaxFieldsPerCluster); + serialyzedBytes.reduce_size(99); // Used memory for Color Control TLV + break; + default: + break; + } + } + return err; + } + + /// @brief Simulates EFS being applied to a scene, here just validates that the data is as expected, no action taken by the + /// "cluster" + /// @param endpoint target endpoint + /// @param cluster target cluster + /// @param serialyzedBytes Data from nvm + /// @param timeMs transition time in ms + /// @return CHIP_NO_ERROR if value as expected, CHIP_ERROR_INVALID_ARGUMENT otherwise + CHIP_ERROR + ApplyScene(EndpointId endpoint, ClusterId cluster, ByteSpan & serialyzedBytes, TransitionTimeMs timeMs) override + { + CHIP_ERROR err = CHIP_ERROR_INVALID_ARGUMENT; + + // Takes values from cluster in Endpoint 1 + if (endpoint == TEST_ENDPOINT1) + { + switch (cluster) + { + case ON_OFF_CID: + if (!memcmp(serialyzedBytes.data(), OO_buffer, serialyzedBytes.size())) + { + err = CHIP_NO_ERROR; + } + break; + case LV_CTR_CID: + if (!memcmp(serialyzedBytes.data(), LC_buffer, serialyzedBytes.size())) + { + err = CHIP_NO_ERROR; + } + break; + default: + break; + } + } + + // Takes values from cluster in Endpoint 2 + if (endpoint == TEST_ENDPOINT2) + { + switch (cluster) + { + case ON_OFF_CID: + if (!memcmp(serialyzedBytes.data(), OO_buffer, serialyzedBytes.size())) + { + err = CHIP_NO_ERROR; + } + break; + case CC_CTR_CID: + if (!memcmp(serialyzedBytes.data(), CC_buffer, serialyzedBytes.size())) + { + err = CHIP_NO_ERROR; + } + break; + default: + break; + } + } + + return CHIP_NO_ERROR; + } +}; + +static chip::TestPersistentStorageDelegate testStorage; +static SceneTableImpl sSceneTable; +static TestSceneHandler sHandler; void ResetSceneTable(SceneTableImpl * sceneTable) { @@ -159,6 +263,201 @@ void ResetSceneTable(SceneTableImpl * sceneTable) sceneTable->RemoveFabric(kFabric2); } +void TestHandlerFunctions(nlTestSuite * aSuite, void * aContext) +{ + SceneTableImpl * sceneTable = chip::scenes::GetSceneTable(); + ClusterId tempCluster = 0; + + app::Clusters::Scenes::Structs::ExtensionFieldSet::Type extensionFieldSetOut; + app::Clusters::Scenes::Structs::ExtensionFieldSet::DecodableType extensionFieldSetIn; + app::Clusters::Scenes::Structs::AttributeValuePair::DecodableType aVPair; + + TLV::TLVReader reader; + TLV::TLVWriter writer; + TLV::TLVType outer; + TLV::TLVType outerRead; + + static const uint8_t OO_av_payload[1] = { 0x01 }; + static const uint8_t LC_av_payload[2][2] = { { 0x40, 0x00 }, { 0x01, 0xF0 } }; + static const uint8_t CC_av_payload[8][2] = { { 0x00, 0x00 }, { 0x00, 0x00 }, { 0x00, 0x00 }, { 0x00, 0x00 }, + { 0x00, 0x00 }, { 0x00, 0x00 }, { 0x00, 0x00 }, { 0x00, 0x00 } }; + + OOPairs[0].attributeID.SetValue(ON_OFF_ID); + OOPairs[0].attributeValue = OO_av_payload; + + LCPairs[0].attributeID.SetValue(CURRENT_LVL_ID); + LCPairs[0].attributeValue = LC_av_payload[0]; + LCPairs[0].attributeValue.reduce_size(1); + LCPairs[1].attributeID.SetValue(CURRENT_FRQ_ID); + LCPairs[1].attributeValue = LC_av_payload[1]; + + CCPairs[0].attributeID.SetValue(CURRENT_SAT_ID); + CCPairs[0].attributeValue = CC_av_payload[0]; + CCPairs[0].attributeValue.reduce_size(1); + CCPairs[1].attributeID.SetValue(CURRENT_X_ID); + CCPairs[1].attributeValue = CC_av_payload[1]; + CCPairs[2].attributeID.SetValue(CURRENT_Y_ID); + CCPairs[2].attributeValue = CC_av_payload[2]; + CCPairs[3].attributeID.SetValue(COLOR_TEMP_MIR_ID); + CCPairs[3].attributeValue = CC_av_payload[3]; + CCPairs[4].attributeID.SetValue(EN_CURRENT_HUE_ID); + CCPairs[4].attributeValue = CC_av_payload[4]; + CCPairs[5].attributeID.SetValue(C_LOOP_ACTIVE_ID); + CCPairs[5].attributeValue = CC_av_payload[5]; + CCPairs[5].attributeValue.reduce_size(1); + CCPairs[6].attributeID.SetValue(C_LOOP_DIR_ID); + CCPairs[6].attributeValue = CC_av_payload[6]; + CCPairs[6].attributeValue.reduce_size(1); + CCPairs[7].attributeID.SetValue(C_LOOP_TIME_ID); + CCPairs[7].attributeValue = CC_av_payload[7]; + + // Initialize Extension Field sets as if they were received by add commands + OOextensionFieldSet.clusterID = ON_OFF_CID; + OOextensionFieldSet.attributeValueList = OOPairs; + LCextensionFieldSet.clusterID = LV_CTR_CID; + LCextensionFieldSet.attributeValueList = LCPairs; + CCextensionFieldSet.clusterID = CC_CTR_CID; + CCextensionFieldSet.attributeValueList = CCPairs; + + ByteSpan OO_list(OO_buffer); + ByteSpan LC_list(LC_buffer); + ByteSpan CC_list(CC_buffer); + + uint8_t buffer[scenes::kMaxFieldsPerCluster] = { 0 }; + MutableByteSpan buff_span(buffer); + + // Serialize Extension Field sets as if they were recovered from memory + writer.Init(OO_buffer); + writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outer); + NL_TEST_ASSERT(aSuite, + CHIP_NO_ERROR == + app::DataModel::Encode(writer, + TLV::ContextTag(to_underlying( + app::Clusters::Scenes::Structs::ExtensionFieldSet::Fields::kAttributeValueList)), + OOextensionFieldSet.attributeValueList)); + writer.EndContainer(outer); + + writer.Init(LC_buffer); + writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outer); + NL_TEST_ASSERT(aSuite, + CHIP_NO_ERROR == + app::DataModel::Encode(writer, + TLV::ContextTag(to_underlying( + app::Clusters::Scenes::Structs::ExtensionFieldSet::Fields::kAttributeValueList)), + LCextensionFieldSet.attributeValueList)); + writer.EndContainer(outer); + + writer.Init(CC_buffer); + writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outer); + NL_TEST_ASSERT(aSuite, + CHIP_NO_ERROR == + app::DataModel::Encode(writer, + TLV::ContextTag(to_underlying( + app::Clusters::Scenes::Structs::ExtensionFieldSet::Fields::kAttributeValueList)), + CCextensionFieldSet.attributeValueList)); + writer.EndContainer(outer); + + // Test Registering SceneHandler + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->RegisterHandler(&sHandler)); + NL_TEST_ASSERT(aSuite, sceneTable->GetHandlerNum() == 1); + + // Setup the On Off Extension field set in the expected state from a command + reader.Init(OO_list); + extensionFieldSetIn.clusterID = ON_OFF_CID; + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == reader.Next()); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == reader.EnterContainer(outerRead)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == reader.Next()); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == extensionFieldSetIn.attributeValueList.Decode(reader)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == reader.ExitContainer(outerRead)); + + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sHandler.SerializeAdd(TEST_ENDPOINT1, tempCluster, buff_span, extensionFieldSetIn)); + + // Verify the handler extracted buffer matches the initial field sets + NL_TEST_ASSERT(aSuite, 0 == memcmp(OO_list.data(), buff_span.data(), buff_span.size())); + NL_TEST_ASSERT(aSuite, tempCluster == ON_OFF_CID); + memset(buffer, 0, buff_span.size()); + + // Setup the Level Control Extension field set in the expected state from a command + reader.Init(LC_list); + extensionFieldSetIn.clusterID = LV_CTR_CID; + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == reader.Next()); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == reader.EnterContainer(outerRead)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == reader.Next()); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == extensionFieldSetIn.attributeValueList.Decode(reader)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == reader.ExitContainer(outerRead)); + + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sHandler.SerializeAdd(TEST_ENDPOINT1, tempCluster, buff_span, extensionFieldSetIn)); + + // Verify the handler extracted buffer matches the initial field sets + NL_TEST_ASSERT(aSuite, 0 == memcmp(LC_list.data(), buff_span.data(), buff_span.size())); + NL_TEST_ASSERT(aSuite, tempCluster == LV_CTR_CID); + memset(buffer, 0, buff_span.size()); + + // Setup the Color control Extension field set in the expected state from a command + reader.Init(CC_list); + extensionFieldSetIn.clusterID = CC_CTR_CID; + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == reader.Next()); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == reader.EnterContainer(outerRead)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == reader.Next()); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == extensionFieldSetIn.attributeValueList.Decode(reader)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == reader.ExitContainer(outerRead)); + + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sHandler.SerializeAdd(TEST_ENDPOINT2, tempCluster, buff_span, extensionFieldSetIn)); + + // Verify the handler extracted buffer matches the initial field sets + NL_TEST_ASSERT(aSuite, 0 == memcmp(CC_list.data(), buff_span.data(), buff_span.size())); + NL_TEST_ASSERT(aSuite, tempCluster == CC_CTR_CID); + memset(buffer, 0, buff_span.size()); + + // Verify Deserializing is properly filling out output extension field set for on off + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sHandler.Deserialize(TEST_ENDPOINT1, ON_OFF_CID, OO_list, extensionFieldSetOut)); + + // Verify Encoding the Extension field set returns the same data as + writer.Init(buff_span); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outer)); + NL_TEST_ASSERT(aSuite, + CHIP_NO_ERROR == + app::DataModel::Encode(writer, + TLV::ContextTag(to_underlying( + app::Clusters::Scenes::Structs::ExtensionFieldSet::Fields::kAttributeValueList)), + extensionFieldSetOut.attributeValueList)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == writer.EndContainer(outer)); + NL_TEST_ASSERT(aSuite, 0 == memcmp(OO_list.data(), buff_span.data(), buff_span.size())); + memset(buffer, 0, buff_span.size()); + + // Verify Deserializing is properly filling out output extension field set for level control + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sHandler.Deserialize(TEST_ENDPOINT1, LV_CTR_CID, LC_list, extensionFieldSetOut)); + + // Verify Encoding the Extension field set returns the same data as + writer.Init(buff_span); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outer)); + NL_TEST_ASSERT(aSuite, + CHIP_NO_ERROR == + app::DataModel::Encode(writer, + TLV::ContextTag(to_underlying( + app::Clusters::Scenes::Structs::ExtensionFieldSet::Fields::kAttributeValueList)), + extensionFieldSetOut.attributeValueList)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == writer.EndContainer(outer)); + NL_TEST_ASSERT(aSuite, 0 == memcmp(LC_list.data(), buff_span.data(), buff_span.size())); + memset(buffer, 0, buff_span.size()); + + // Verify Deserializing is properly filling out output extension field set for color control + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sHandler.Deserialize(TEST_ENDPOINT2, CC_CTR_CID, CC_list, extensionFieldSetOut)); + + // Verify Encoding the Extension field set returns the same data as + writer.Init(buff_span); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outer)); + NL_TEST_ASSERT(aSuite, + CHIP_NO_ERROR == + app::DataModel::Encode(writer, + TLV::ContextTag(to_underlying( + app::Clusters::Scenes::Structs::ExtensionFieldSet::Fields::kAttributeValueList)), + extensionFieldSetOut.attributeValueList)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == writer.EndContainer(outer)); + NL_TEST_ASSERT(aSuite, 0 == memcmp(CC_list.data(), buff_span.data(), buff_span.size())); + memset(buffer, 0, buff_span.size()); +}; + void TestStoreScenes(nlTestSuite * aSuite, void * aContext) { SceneTableImpl * sceneTable = chip::scenes::GetSceneTable(); @@ -167,16 +466,25 @@ void TestStoreScenes(nlTestSuite * aSuite, void * aContext) // Reset test ResetSceneTable(sceneTable); - // Test SceneHandlers - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->RegisterHandler(onOffEFS1.mID, &oo_from_cluster_cb1, &oo_to_cluster_cb1)); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->RegisterHandler(lvCtrEFS1.mID, &lc_from_cluster_cb1, &lc_to_cluster_cb1)); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->RegisterHandler(coCtrEFS2.mID, &cc_from_cluster_cb1, &cc_to_cluster_cb1)); + // Populate scene1's EFS (Endpoint1) + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SceneSaveEFS(scene1, ON_OFF_CID)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SceneSaveEFS(scene1, LV_CTR_CID)); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_INVALID_ARGUMENT == sceneTable->SceneSaveEFS(scene1, CC_CTR_CID)); + + // Populate scene2's EFS (Endpoint1) + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SceneSaveEFS(scene2, ON_OFF_CID)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SceneSaveEFS(scene2, LV_CTR_CID)); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_INVALID_ARGUMENT == sceneTable->SceneSaveEFS(scene2, CC_CTR_CID)); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->EFSValuesFromCluster(scene1.mStorageData.mExtensionFieldsSets)); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->EFSValuesFromCluster(scene2.mStorageData.mExtensionFieldsSets)); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->EFSValuesFromCluster(scene3.mStorageData.mExtensionFieldsSets)); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->EFSValuesFromCluster(scene4.mStorageData.mExtensionFieldsSets)); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->EFSValuesFromCluster(scene5.mStorageData.mExtensionFieldsSets)); + // Populate scene3's EFS (Endpoint2) + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SceneSaveEFS(scene3, ON_OFF_CID)); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_INVALID_ARGUMENT == sceneTable->SceneSaveEFS(scene3, LV_CTR_CID)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SceneSaveEFS(scene3, CC_CTR_CID)); + + // Populate scene3's EFS (Endpoint2) + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SceneSaveEFS(scene4, ON_OFF_CID)); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_INVALID_ARGUMENT == sceneTable->SceneSaveEFS(scene4, LV_CTR_CID)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SceneSaveEFS(scene4, CC_CTR_CID)); SceneTableEntry scene; @@ -199,24 +507,22 @@ void TestStoreScenes(nlTestSuite * aSuite, void * aContext) // Get test NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric1, sceneId1, scene)); NL_TEST_ASSERT(aSuite, scene == scene1); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->EFSValuesToCluster(scene.mStorageData.mExtensionFieldsSets)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SceneApplyEFS(kFabric1, scene1.mStorageId)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric1, sceneId2, scene)); NL_TEST_ASSERT(aSuite, scene == scene2); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->EFSValuesToCluster(scene.mStorageData.mExtensionFieldsSets)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SceneApplyEFS(kFabric1, scene2.mStorageId)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric1, sceneId3, scene)); NL_TEST_ASSERT(aSuite, scene == scene3); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->EFSValuesToCluster(scene.mStorageData.mExtensionFieldsSets)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SceneApplyEFS(kFabric1, scene3.mStorageId)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric1, sceneId4, scene)); NL_TEST_ASSERT(aSuite, scene == scene4); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->EFSValuesToCluster(scene.mStorageData.mExtensionFieldsSets)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SceneApplyEFS(kFabric1, scene4.mStorageId)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric1, sceneId5, scene)); NL_TEST_ASSERT(aSuite, scene == scene5); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->EFSValuesToCluster(scene.mStorageData.mExtensionFieldsSets)); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric1, sceneId6, scene)); NL_TEST_ASSERT(aSuite, scene == scene6); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric1, sceneId7, scene)); @@ -230,18 +536,6 @@ void TestOverwriteScenes(nlTestSuite * aSuite, void * aContext) SceneTableImpl * sceneTable = chip::scenes::GetSceneTable(); NL_TEST_ASSERT(aSuite, sceneTable); - // Test SceneHandlers overwrite - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->RegisterHandler(onOffEFS2.mID, &oo_from_cluster_cb2, &oo_to_cluster_cb2)); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->RegisterHandler(lvCtrEFS2.mID, &lc_from_cluster_cb2, &lc_to_cluster_cb2)); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->RegisterHandler(coCtrEFS2.mID, &cc_from_cluster_cb2, &cc_to_cluster_cb2)); - - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->EFSValuesFromCluster(scene10.mStorageData.mExtensionFieldsSets)); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->EFSValuesFromCluster(scene11.mStorageData.mExtensionFieldsSets)); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->EFSValuesFromCluster(scene12.mStorageData.mExtensionFieldsSets)); - - // Verfies the overwrite hasn't changed the handlers number - NL_TEST_ASSERT(aSuite, sceneTable->GetHandlerNum() == 3); - SceneTableEntry scene; // Overwriting the first entry NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene10)); @@ -253,15 +547,12 @@ void TestOverwriteScenes(nlTestSuite * aSuite, void * aContext) // Scene 10 has the same sceneId as scene 1, Get->sceneId1 should thus return scene 10, etc. NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric1, sceneId1, scene)); NL_TEST_ASSERT(aSuite, scene == scene10); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->EFSValuesToCluster(scene.mStorageData.mExtensionFieldsSets)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric1, sceneId5, scene)); NL_TEST_ASSERT(aSuite, scene == scene11); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->EFSValuesToCluster(scene.mStorageData.mExtensionFieldsSets)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric1, sceneId8, scene)); NL_TEST_ASSERT(aSuite, scene == scene12); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->EFSValuesToCluster(scene.mStorageData.mExtensionFieldsSets)); } void TestIterateScenes(nlTestSuite * aSuite, void * aContext) @@ -479,11 +770,13 @@ int TestTeardown(void * inContext) } int TestSceneTable() { - static nlTest sTests[] = { - NL_TEST_DEF("TestStoreScenes", TestStoreScenes), NL_TEST_DEF("TestOverwriteScenes", TestOverwriteScenes), - NL_TEST_DEF("TestIterateScenes", TestIterateScenes), NL_TEST_DEF("TestRemoveScenes", TestRemoveScenes), - NL_TEST_DEF("TestFabricScenes", TestFabricScenes), NL_TEST_SENTINEL() - }; + static nlTest sTests[] = { NL_TEST_DEF("TestStoreScenes", TestHandlerFunctions), + NL_TEST_DEF("TestStoreScenes", TestStoreScenes), + NL_TEST_DEF("TestOverwriteScenes", TestOverwriteScenes), + NL_TEST_DEF("TestIterateScenes", TestIterateScenes), + NL_TEST_DEF("TestRemoveScenes", TestRemoveScenes), + NL_TEST_DEF("TestFabricScenes", TestFabricScenes), + NL_TEST_SENTINEL() }; nlTestSuite theSuite = { "SceneTable", diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index 6bbb1208c49b47..35e01fa5fda125 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -1424,7 +1424,7 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; * @brief The maximum size of a single extension field set for a single cluster */ #ifndef CHIP_CONFIG_SCENES_MAX_EXTENSION_FIELDSET_SIZE_PER_CLUSTER -#define CHIP_CONFIG_SCENES_MAX_EXTENSION_FIELDSET_SIZE_PER_CLUSTER 15 +#define CHIP_CONFIG_SCENES_MAX_EXTENSION_FIELDSET_SIZE_PER_CLUSTER 100 #endif /** @@ -1440,4 +1440,4 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; /** * @} - */ + */ \ No newline at end of file diff --git a/src/lib/support/TestSceneTable.h b/src/lib/support/TestSceneTable.h index 420d675e9a9044..a8706eaaf0638b 100644 --- a/src/lib/support/TestSceneTable.h +++ b/src/lib/support/TestSceneTable.h @@ -30,48 +30,379 @@ using SceneTableImpl = chip::scenes::DefaultSceneTableImpl; using SceneStorageId = chip::scenes::DefaultSceneTableImpl::SceneStorageId; using SceneData = chip::scenes::DefaultSceneTableImpl::SceneData; using ExtensionFieldsSet = chip::scenes::ExtensionFieldsSet; +using TransitionTimeMs = chip::scenes::TransitionTimeMs; + +#define ON_OFF_CID 0x0006 +#define LV_CTR_CID 0x0008 +#define CC_CTR_CID 0x0300 +#define TEST_ENDPOINT1 0x0001 +#define TEST_ENDPOINT2 0x0099 + +// ON OFF ATTRIBUTE IDs +#define ON_OFF_ID 0x0000 + +// LEVEL CONTROL ATTRIBUTE IDs +#define CURRENT_LVL_ID 0x0000 +#define CURRENT_FRQ_ID 0x0004 + +// COLOR CONTROL ATTRIBUTE IDs +#define CURRENT_SAT_ID 0x0001 +#define CURRENT_X_ID 0x0003 +#define CURRENT_Y_ID 0x0004 +#define COLOR_TEMP_MIR_ID 00007 +#define EN_CURRENT_HUE_ID 0x4000 +#define C_LOOP_ACTIVE_ID 0x4002 +#define C_LOOP_DIR_ID 0x4003 +#define C_LOOP_TIME_ID 0x4004 + +// Clusters EFS data +static app::Clusters::Scenes::Structs::ExtensionFieldSet::Type OOextensionFieldSet; +static app::Clusters::Scenes::Structs::ExtensionFieldSet::Type LCextensionFieldSet; +static app::Clusters::Scenes::Structs::ExtensionFieldSet::Type CCextensionFieldSet; + +static app::Clusters::Scenes::Structs::AttributeValuePair::Type OOPairs[1]; +static app::Clusters::Scenes::Structs::AttributeValuePair::Type LCPairs[2]; +static app::Clusters::Scenes::Structs::AttributeValuePair::Type CCPairs[8]; + +static uint8_t OO_buffer[scenes::kMaxFieldsPerCluster] = { 0 }; +static uint8_t LC_buffer[scenes::kMaxFieldsPerCluster] = { 0 }; +static uint8_t CC_buffer[scenes::kMaxFieldsPerCluster] = { 0 }; + +/// @brief Simulates a Handler where Endpoint 1 supports onoff and level control and Endpoint 2 supports onoff and color control +class TestSceneHandler : public scenes::DefaultSceneHandlerImpl +{ +public: + static constexpr uint8_t kMaxValueSize = 4; + static constexpr uint8_t kMaxAvPair = 15; -static const ExtensionFieldsSet onOffEFS1 = ExtensionFieldsSet(0x0006, (uint8_t *) "1", 1); -static const ExtensionFieldsSet levelControlEFS1 = ExtensionFieldsSet(0x0008, (uint8_t *) "511", 3); + TestSceneHandler() = default; + ~TestSceneHandler() override {} -static CHIP_ERROR test_on_off_from_cluster_callback(ExtensionFieldsSet & fields) -{ - ReturnErrorOnFailure(fields = onOffEFS1); - return CHIP_NO_ERROR; -} -static CHIP_ERROR test_on_off_to_cluster_callback(ExtensionFieldsSet & fields) -{ - VerifyOrReturnError(fields == onOffEFS1, CHIP_ERROR_WRITE_FAILED); - return CHIP_NO_ERROR; -} -static CHIP_ERROR test_level_control_from_cluster_callback(ExtensionFieldsSet & fields) -{ - ReturnErrorOnFailure(fields = levelControlEFS1); - return CHIP_NO_ERROR; -} -static CHIP_ERROR test_level_control_to_cluster_callback(ExtensionFieldsSet & fields) + // Default function only checks if endpoint and clusters are valid + bool SupportsCluster(EndpointId endpoint, ClusterId cluster) override + { + bool ret = false; + if (endpoint == TEST_ENDPOINT1) + { + if (cluster == ON_OFF_CID || cluster == LV_CTR_CID) + { + ret = true; + } + } + + if (endpoint == TEST_ENDPOINT2) + { + if (cluster == ON_OFF_CID || cluster == CC_CTR_CID) + { + ret = true; + } + } + + return ret; + } + + /// @brief Simulates save from cluster, data is already in an EFS struct but this isn't mandatory + /// @param endpoint target endpoint + /// @param cluster target cluster + /// @param serialyzedBytes data to serialize into EFS + /// @return success if successfully serialized the data, CHIP_ERROR_INVALID_ARGUMENT if endpoint or cluster not supported + CHIP_ERROR SerializeSave(EndpointId endpoint, ClusterId cluster, MutableByteSpan & serialyzedBytes) override + { + CHIP_ERROR err = CHIP_ERROR_INVALID_ARGUMENT; + + if (endpoint == TEST_ENDPOINT1) + { + switch (cluster) + { + case ON_OFF_CID: + err = CHIP_NO_ERROR; + memcpy(serialyzedBytes.data(), OO_buffer, scenes::kMaxFieldsPerCluster); + serialyzedBytes.reduce_size(15); // Used memory for OnOff TLV + break; + case LV_CTR_CID: + err = CHIP_NO_ERROR; + memcpy(serialyzedBytes.data(), LC_buffer, scenes::kMaxFieldsPerCluster); + serialyzedBytes.reduce_size(27); // Used memory for Level Control TLV + break; + default: + break; + } + } + if (endpoint == TEST_ENDPOINT2) + { + switch (cluster) + { + case ON_OFF_CID: + err = CHIP_NO_ERROR; + memcpy(serialyzedBytes.data(), OO_buffer, scenes::kMaxFieldsPerCluster); + serialyzedBytes.reduce_size(15); // Used memory for Color Control TLV + break; + case CC_CTR_CID: + err = CHIP_NO_ERROR; + memcpy(serialyzedBytes.data(), CC_buffer, scenes::kMaxFieldsPerCluster); + serialyzedBytes.reduce_size(99); // Used memory for Color Control TLV + break; + default: + break; + } + } + return err; + } + + /// @brief Simulates EFS being applied to a scene, here just validates that the data is as expected, no action taken by the + /// "cluster" + /// @param endpoint target endpoint + /// @param cluster target cluster + /// @param serialyzedBytes Data from nvm + /// @param timeMs transition time in ms + /// @return CHIP_NO_ERROR if value as expected, CHIP_ERROR_INVALID_ARGUMENT otherwise + CHIP_ERROR + ApplyScene(EndpointId endpoint, ClusterId cluster, ByteSpan & serialyzedBytes, TransitionTimeMs timeMs) override + { + CHIP_ERROR err = CHIP_ERROR_INVALID_ARGUMENT; + + // Takes values from cluster in Endpoint 1 + if (endpoint == TEST_ENDPOINT1) + { + switch (cluster) + { + case ON_OFF_CID: + if (!memcmp(serialyzedBytes.data(), OO_buffer, serialyzedBytes.size())) + { + err = CHIP_NO_ERROR; + } + break; + case LV_CTR_CID: + if (!memcmp(serialyzedBytes.data(), LC_buffer, serialyzedBytes.size())) + { + err = CHIP_NO_ERROR; + } + break; + default: + break; + } + } + + // Takes values from cluster in Endpoint 2 + if (endpoint == TEST_ENDPOINT2) + { + switch (cluster) + { + case ON_OFF_CID: + if (!memcmp(serialyzedBytes.data(), OO_buffer, serialyzedBytes.size())) + { + err = CHIP_NO_ERROR; + } + break; + case CC_CTR_CID: + if (!memcmp(serialyzedBytes.data(), CC_buffer, serialyzedBytes.size())) + { + err = CHIP_NO_ERROR; + } + break; + default: + break; + } + } + + return CHIP_NO_ERROR; + } +}; + +static TestSceneHandler sHandler; + +CHIP_ERROR scene_handler_test(SceneTableImpl * provider) { - VerifyOrReturnError(fields == levelControlEFS1, CHIP_ERROR_WRITE_FAILED); + ClusterId tempCluster = 0; + + app::Clusters::Scenes::Structs::ExtensionFieldSet::Type extensionFieldSetOut; + app::Clusters::Scenes::Structs::ExtensionFieldSet::DecodableType extensionFieldSetIn; + app::Clusters::Scenes::Structs::AttributeValuePair::DecodableType aVPair; + + TLV::TLVReader reader; + TLV::TLVWriter writer; + TLV::TLVType outer; + TLV::TLVType outerRead; + + static const uint8_t OO_av_payload[1] = { 0x01 }; + static const uint8_t LC_av_payload[2][2] = { { 0x40, 0x00 }, { 0x01, 0xF0 } }; + static const uint8_t CC_av_payload[8][2] = { { 0x00, 0x00 }, { 0x00, 0x00 }, { 0x00, 0x00 }, { 0x00, 0x00 }, + { 0x00, 0x00 }, { 0x00, 0x00 }, { 0x00, 0x00 }, { 0x00, 0x00 } }; + + OOPairs[0].attributeID.SetValue(ON_OFF_ID); + OOPairs[0].attributeValue = OO_av_payload; + + LCPairs[0].attributeID.SetValue(CURRENT_LVL_ID); + LCPairs[0].attributeValue = LC_av_payload[0]; + LCPairs[0].attributeValue.reduce_size(1); + LCPairs[1].attributeID.SetValue(CURRENT_FRQ_ID); + LCPairs[1].attributeValue = LC_av_payload[1]; + + CCPairs[0].attributeID.SetValue(CURRENT_SAT_ID); + CCPairs[0].attributeValue = CC_av_payload[0]; + CCPairs[0].attributeValue.reduce_size(1); + CCPairs[1].attributeID.SetValue(CURRENT_X_ID); + CCPairs[1].attributeValue = CC_av_payload[1]; + CCPairs[2].attributeID.SetValue(CURRENT_Y_ID); + CCPairs[2].attributeValue = CC_av_payload[2]; + CCPairs[3].attributeID.SetValue(COLOR_TEMP_MIR_ID); + CCPairs[3].attributeValue = CC_av_payload[3]; + CCPairs[4].attributeID.SetValue(EN_CURRENT_HUE_ID); + CCPairs[4].attributeValue = CC_av_payload[4]; + CCPairs[5].attributeID.SetValue(C_LOOP_ACTIVE_ID); + CCPairs[5].attributeValue = CC_av_payload[5]; + CCPairs[5].attributeValue.reduce_size(1); + CCPairs[6].attributeID.SetValue(C_LOOP_DIR_ID); + CCPairs[6].attributeValue = CC_av_payload[6]; + CCPairs[6].attributeValue.reduce_size(1); + CCPairs[7].attributeID.SetValue(C_LOOP_TIME_ID); + CCPairs[7].attributeValue = CC_av_payload[7]; + + // Initialize Extension Field sets as if they were received by add commands + OOextensionFieldSet.clusterID = ON_OFF_CID; + OOextensionFieldSet.attributeValueList = OOPairs; + LCextensionFieldSet.clusterID = LV_CTR_CID; + LCextensionFieldSet.attributeValueList = LCPairs; + CCextensionFieldSet.clusterID = CC_CTR_CID; + CCextensionFieldSet.attributeValueList = CCPairs; + + ByteSpan OO_list(OO_buffer); + ByteSpan LC_list(LC_buffer); + ByteSpan CC_list(CC_buffer); + + uint8_t buffer[scenes::kMaxFieldsPerCluster] = { 0 }; + MutableByteSpan buff_span(buffer); + + // Serialize Extension Field sets as if they were recovered from memory + writer.Init(OO_buffer); + writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outer); + ReturnErrorOnFailure(app::DataModel::Encode( + writer, TLV::ContextTag(to_underlying(app::Clusters::Scenes::Structs::ExtensionFieldSet::Fields::kAttributeValueList)), + OOextensionFieldSet.attributeValueList)); + writer.EndContainer(outer); + + writer.Init(LC_buffer); + writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outer); + ReturnErrorOnFailure(app::DataModel::Encode( + writer, TLV::ContextTag(to_underlying(app::Clusters::Scenes::Structs::ExtensionFieldSet::Fields::kAttributeValueList)), + LCextensionFieldSet.attributeValueList)); + writer.EndContainer(outer); + + writer.Init(CC_buffer); + writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outer); + ReturnErrorOnFailure(app::DataModel::Encode( + writer, TLV::ContextTag(to_underlying(app::Clusters::Scenes::Structs::ExtensionFieldSet::Fields::kAttributeValueList)), + CCextensionFieldSet.attributeValueList)); + writer.EndContainer(outer); + + ReturnErrorOnFailure(provider->RegisterHandler(&sHandler)); + + // Setup the On Off Extension field set in the expected state from a command + reader.Init(OO_list); + extensionFieldSetIn.clusterID = ON_OFF_CID; + ReturnErrorOnFailure(reader.Next()); + ReturnErrorOnFailure(reader.EnterContainer(outerRead)); + ReturnErrorOnFailure(reader.Next()); + ReturnErrorOnFailure(extensionFieldSetIn.attributeValueList.Decode(reader)); + ReturnErrorOnFailure(reader.ExitContainer(outerRead)); + + ReturnErrorOnFailure(sHandler.SerializeAdd(TEST_ENDPOINT1, tempCluster, buff_span, extensionFieldSetIn)); + + // Verify the handler extracted buffer matches the initial field sets + VerifyOrReturnError(0 == memcmp(OO_list.data(), buff_span.data(), buff_span.size()), CHIP_ERROR_WRITE_FAILED); + VerifyOrReturnError(tempCluster == ON_OFF_CID, CHIP_ERROR_WRITE_FAILED); + memset(buffer, 0, buff_span.size()); + + // Setup the Level Control Extension field set in the expected state from a command + reader.Init(LC_list); + extensionFieldSetIn.clusterID = LV_CTR_CID; + ReturnErrorOnFailure(reader.Next()); + ReturnErrorOnFailure(reader.EnterContainer(outerRead)); + ReturnErrorOnFailure(reader.Next()); + ReturnErrorOnFailure(extensionFieldSetIn.attributeValueList.Decode(reader)); + ReturnErrorOnFailure(reader.ExitContainer(outerRead)); + + ReturnErrorOnFailure(sHandler.SerializeAdd(TEST_ENDPOINT1, tempCluster, buff_span, extensionFieldSetIn)); + + // Verify the handler extracted buffer matches the initial field sets + VerifyOrReturnError(0 == memcmp(LC_list.data(), buff_span.data(), buff_span.size()), CHIP_ERROR_WRITE_FAILED); + VerifyOrReturnError(tempCluster == LV_CTR_CID, CHIP_ERROR_WRITE_FAILED); + memset(buffer, 0, buff_span.size()); + + // Setup the Color control Extension field set in the expected state from a command + reader.Init(CC_list); + extensionFieldSetIn.clusterID = CC_CTR_CID; + ReturnErrorOnFailure(reader.Next()); + ReturnErrorOnFailure(reader.EnterContainer(outerRead)); + ReturnErrorOnFailure(reader.Next()); + ReturnErrorOnFailure(extensionFieldSetIn.attributeValueList.Decode(reader)); + ReturnErrorOnFailure(reader.ExitContainer(outerRead)); + + ReturnErrorOnFailure(sHandler.SerializeAdd(TEST_ENDPOINT2, tempCluster, buff_span, extensionFieldSetIn)); + + // Verify the handler extracted buffer matches the initial field sets + VerifyOrReturnError(0 == memcmp(CC_list.data(), buff_span.data(), buff_span.size()), CHIP_ERROR_WRITE_FAILED); + VerifyOrReturnError(tempCluster == CC_CTR_CID, CHIP_ERROR_WRITE_FAILED); + memset(buffer, 0, buff_span.size()); + + // Verify Deserializing is properly filling out output extension field set for on off + ReturnErrorOnFailure(sHandler.Deserialize(TEST_ENDPOINT1, ON_OFF_CID, OO_list, extensionFieldSetOut)); + + // Verify Encoding the Extension field set returns the same data as + writer.Init(buff_span); + ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outer)); + ReturnErrorOnFailure(app::DataModel::Encode( + writer, TLV::ContextTag(to_underlying(app::Clusters::Scenes::Structs::ExtensionFieldSet::Fields::kAttributeValueList)), + extensionFieldSetOut.attributeValueList)); + ReturnErrorOnFailure(writer.EndContainer(outer)); + VerifyOrReturnError(0 == memcmp(OO_list.data(), buff_span.data(), buff_span.size()), CHIP_ERROR_READ_FAILED); + memset(buffer, 0, buff_span.size()); + + // Verify Deserializing is properly filling out output extension field set for level control + ReturnErrorOnFailure(sHandler.Deserialize(TEST_ENDPOINT1, LV_CTR_CID, LC_list, extensionFieldSetOut)); + + // Verify Encoding the Extension field set returns the same data as + writer.Init(buff_span); + ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outer)); + ReturnErrorOnFailure(app::DataModel::Encode( + writer, TLV::ContextTag(to_underlying(app::Clusters::Scenes::Structs::ExtensionFieldSet::Fields::kAttributeValueList)), + extensionFieldSetOut.attributeValueList)); + ReturnErrorOnFailure(writer.EndContainer(outer)); + VerifyOrReturnError(0 == memcmp(LC_list.data(), buff_span.data(), buff_span.size()), CHIP_ERROR_READ_FAILED); + memset(buffer, 0, buff_span.size()); + + // Verify Deserializing is properly filling out output extension field set for color control + ReturnErrorOnFailure(sHandler.Deserialize(TEST_ENDPOINT2, CC_CTR_CID, CC_list, extensionFieldSetOut)); + + // Verify Encoding the Extension field set returns the same data as + writer.Init(buff_span); + ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outer)); + ReturnErrorOnFailure(app::DataModel::Encode( + writer, TLV::ContextTag(to_underlying(app::Clusters::Scenes::Structs::ExtensionFieldSet::Fields::kAttributeValueList)), + extensionFieldSetOut.attributeValueList)); + ReturnErrorOnFailure(writer.EndContainer(outer)); + VerifyOrReturnError(0 == memcmp(CC_list.data(), buff_span.data(), buff_span.size()), CHIP_ERROR_READ_FAILED); + memset(buffer, 0, buff_span.size()); + return CHIP_NO_ERROR; -} +}; CHIP_ERROR scene_store_test(SceneTableImpl * provider, FabricIndex fabric_index, SceneTableEntry & entry) { - CHIP_ERROR err = CHIP_NO_ERROR; SceneTableEntry temp; LogErrorOnFailure(provider->SetSceneTableEntry(fabric_index, entry)); LogErrorOnFailure(provider->GetSceneTableEntry(fabric_index, entry.mStorageId, temp)); VerifyOrReturnError(temp.mStorageId == entry.mStorageId, CHIP_ERROR_WRITE_FAILED); VerifyOrReturnError(temp.mStorageData == entry.mStorageData, CHIP_ERROR_WRITE_FAILED); + LogErrorOnFailure(provider->SceneApplyEFS(fabric_index, temp.mStorageId)); - return err; + return CHIP_NO_ERROR; } CHIP_ERROR scene_iterator_test(SceneTableImpl * provider, FabricIndex fabric_index, const SceneTableEntry & entry1, const SceneTableEntry & entry2, const SceneTableEntry & entry3) { - CHIP_ERROR err = CHIP_NO_ERROR; SceneTableEntry temp; auto * iterator = provider->IterateSceneEntry(fabric_index); @@ -94,13 +425,12 @@ CHIP_ERROR scene_iterator_test(SceneTableImpl * provider, FabricIndex fabric_ind iterator->Release(); } - return err; + return CHIP_NO_ERROR; } CHIP_ERROR scene_remove_test(SceneTableImpl * provider, FabricIndex fabric_index, SceneTableEntry & entry1, SceneTableEntry & entry2, SceneTableEntry & entry3) { - CHIP_ERROR err = CHIP_NO_ERROR; SceneTableEntry temp; LogErrorOnFailure(provider->RemoveSceneTableEntry(fabric_index, entry2.mStorageId)); @@ -125,16 +455,16 @@ CHIP_ERROR scene_remove_test(SceneTableImpl * provider, FabricIndex fabric_index VerifyOrReturnError(iterator->Next(temp) == false, CHIP_ERROR_INVALID_ACCESS_TOKEN); iterator->Release(); - return err; + return CHIP_NO_ERROR; } CHIP_ERROR TestSceneData(SceneTableImpl * provider, FabricIndex fabric_index) { CHIP_ERROR err = CHIP_NO_ERROR; // Scene storage ID - static const SceneStorageId sceneId1(1, 0xAA, 0x101); - static const SceneStorageId sceneId2(1, 0xBB, 0x00); - static const SceneStorageId sceneId3(1, 0xCC, 0x102); + static const SceneStorageId sceneId1(TEST_ENDPOINT1, 0xAA, 0x101); + static const SceneStorageId sceneId2(TEST_ENDPOINT1, 0xBB, 0x00); + static const SceneStorageId sceneId3(TEST_ENDPOINT2, 0xCC, 0x102); // Scene data static const SceneData sceneData1(CharSpan("Scene #1", sizeof("Scene #1"))); @@ -146,21 +476,21 @@ CHIP_ERROR TestSceneData(SceneTableImpl * provider, FabricIndex fabric_index) SceneTableEntry scene2(sceneId2, sceneData2); SceneTableEntry scene3(sceneId3, sceneData3); - err = provider->registerHandler(onOffEFS1.mID, &test_on_off_from_cluster_callback, &test_on_off_to_cluster_callback); + LogErrorOnFailure(scene_handler_test(provider)); + + err = provider->SceneSaveEFS(scene1, ON_OFF_CID); LogErrorOnFailure(err); - err = provider->registerHandler(levelControlEFS1.mID, &test_level_control_from_cluster_callback, - &test_level_control_to_cluster_callback); + err = provider->SceneSaveEFS(scene1, LV_CTR_CID); LogErrorOnFailure(err); - err = provider->EFSValuesFromCluster(scene1.mStorageData.mExtensionFieldsSets); + + err = provider->SceneSaveEFS(scene3, ON_OFF_CID); + LogErrorOnFailure(err); + err = provider->SceneSaveEFS(scene3, CC_CTR_CID); LogErrorOnFailure(err); // Tests err = scene_store_test(provider, fabric_index, scene1); LogErrorOnFailure(err); - - err = provider->EFSValuesToCluster(scene1.mStorageData.mExtensionFieldsSets); - LogErrorOnFailure(err); - err = scene_store_test(provider, fabric_index, scene2); LogErrorOnFailure(err); err = scene_store_test(provider, fabric_index, scene3);