blob: 1b634211ead0639ec5ef8815787645427dee6f71 [file] [log] [blame]
/*
*
* Copyright (c) 2022 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <app-common/zap-generated/cluster-objects.h>
#include <app/clusters/scenes-server/ExtensionFieldSets.h>
#include <lib/support/CHIPMemString.h>
#include <lib/support/CommonIterator.h>
#include <lib/support/IntrusiveList.h>
#include <lib/support/PersistentData.h>
#include <lib/support/Span.h>
namespace chip {
namespace scenes {
// Storage index for scenes in nvm
typedef uint16_t SceneIndex;
typedef uint32_t TransitionTimeMs;
typedef uint32_t SceneTransitionTime;
inline constexpr GroupId kGlobalGroupSceneId = 0x0000;
inline constexpr SceneIndex kUndefinedSceneIndex = 0xffff;
inline constexpr SceneId kUndefinedSceneId = 0xff;
static constexpr size_t kIteratorsMax = CHIP_CONFIG_MAX_SCENES_CONCURRENT_ITERATORS;
static constexpr size_t kSceneNameMaxLength = CHIP_CONFIG_SCENES_CLUSTER_MAXIMUM_NAME_LENGTH;
static constexpr size_t kScenesMaxTransitionTime = 60'000'000u;
/// @brief SceneHandlers are meant as interface between various clusters and the Scene table.
/// When a scene command involving extension field sets is received, the Scene Table will go through
/// the list of handlers to either retrieve, populate or apply those extension field sets.
///
/// Generally, for each specific <endpoint, cluster> pair there should be one and only one handler
/// registered with the scene table that claims to handle that pair.
///
/// A SceneHandler can handle a single <endpoint, cluster> pair, or many such pairs.
///
/// @note If more than one handler claims to handle a specific <endpoint, cluster> pair, only one of
/// those handlers will get called when executing actions related to extension field sets on the scene
/// table. It is not defined which handler will be selected.
class SceneHandler : public IntrusiveListNodeBase<>
{
public:
SceneHandler(){};
virtual ~SceneHandler() = default;
/// @brief Copies the list of supported clusters for an endpoint in a Span and resizes the span to fit the actual number of
/// supported clusters
/// @param endpoint target endpoint
/// @param clusterBuffer Buffer to hold the supported cluster IDs, cannot hold more than
/// CHIP_CONFIG_SCENES_MAX_CLUSTERS_PER_SCENE. The function shall use the reduce_size() method in the event it is supporting
/// less than CHIP_CONFIG_SCENES_MAX_CLUSTERS_PER_SCENE clusters.
virtual void GetSupportedClusters(EndpointId endpoint, Span<ClusterId> & clusterBuffer) = 0;
/// @brief Returns whether or not a cluster for scenes is supported on an endpoint
///
/// @param endpoint Target Endpoint ID
/// @param cluster Target Cluster ID
/// @return true if supported, false if not supported
virtual bool SupportsCluster(EndpointId endpoint, ClusterId cluster) = 0;
/// @brief Called when handling AddScene. Allows the handler to filter through the clusters in the command to serialize only
/// the supported ones.
///
/// @param endpoint[in] Endpoint ID
/// @param extensionFieldSet[in] ExtensionFieldSets provided by the AddScene Command, pre initialized
/// @param serialisedBytes[out] Buffer to fill from the ExtensionFieldSet in command
/// @return CHIP_NO_ERROR if successful, CHIP_ERROR value otherwise
/// @note Only gets called after the scene-cluster has previously verified that the endpoint,cluster pair is supported by
/// the handler. It is therefore the implementation's reponsibility to also implement the SupportsCluster method.
virtual CHIP_ERROR
SerializeAdd(EndpointId endpoint,
const app::Clusters::ScenesManagement::Structs::ExtensionFieldSet::DecodableType & extensionFieldSet,
MutableByteSpan & serialisedBytes) = 0;
/// @brief Called when handling StoreScene, and only if the handler supports the given endpoint and cluster.
///
/// The implementation must write the actual scene data to store to serializedBytes as described below.
///
/// @param endpoint[in] Target Endpoint
/// @param cluster[in] Target Cluster
/// @param serializedBytes[out] Output buffer, data needs to be writen in there and size adjusted to the size of the data
/// written.
///
/// @return CHIP_NO_ERROR if successful, CHIP_ERROR value otherwise
virtual CHIP_ERROR SerializeSave(EndpointId endpoint, ClusterId cluster, MutableByteSpan & serializedBytes) = 0;
/// @brief Deserialize an ExtensionFieldSet into a cluster object (e.g. when handling ViewScene).
///
/// @param endpoint[in] Endpoint ID
/// @param cluster[in] Cluster ID
/// @param serializedBytes[in] ExtensionFieldSet stored in NVM
///
/// @param extensionFieldSet[out] ExtensionFieldSet in command format
/// @return CHIP_NO_ERROR if successful, CHIP_ERROR value otherwise
/// @note Only gets called for handlers for which SupportsCluster() is true for the given endpoint and cluster.
virtual CHIP_ERROR Deserialize(EndpointId endpoint, ClusterId cluster, const ByteSpan & serializedBytes,
app::Clusters::ScenesManagement::Structs::ExtensionFieldSet::Type & extensionFieldSet) = 0;
/// @brief Restore a stored scene for the given cluster instance, over timeMs milliseconds (e.g. when handling RecallScene)
///
/// @param endpoint[in] Endpoint ID
/// @param cluster[in] Cluster ID
/// @param serializedBytes[in] ExtensionFieldSet stored in NVM
///
/// @param timeMs[in] Transition time in ms to apply the scene
/// @return CHIP_NO_ERROR if successful, CHIP_ERROR value otherwise
/// @note Only gets called for handlers for which SupportsCluster() is true for the given endpoint and cluster.
virtual CHIP_ERROR ApplyScene(EndpointId endpoint, ClusterId cluster, const ByteSpan & serializedBytes,
TransitionTimeMs timeMs) = 0;
};
template <class EFStype>
class SceneTable
{
public:
/// @brief struct used to identify a scene in storage by 3 ids, endpoint, group and scene
struct SceneStorageId
{
// Identifies group within the scope of the given fabric
GroupId mGroupId = kGlobalGroupSceneId;
SceneId mSceneId = kUndefinedSceneId;
SceneStorageId() = default;
SceneStorageId(SceneId id, GroupId groupId = kGlobalGroupSceneId) : mGroupId(groupId), mSceneId(id) {}
void Clear()
{
mGroupId = kGlobalGroupSceneId;
mSceneId = kUndefinedSceneId;
}
bool IsValid() { return (mSceneId != kUndefinedSceneId); }
bool operator==(const SceneStorageId & other) { return (mGroupId == other.mGroupId && mSceneId == other.mSceneId); }
};
/// @brief struct used to store data held in a scene
/// Members:
/// mName: char buffer holding the name of the scene, only serialized when mNameLenght is greater than 0
/// mNameLength: lentgh of the name if a name was provided at scene creation
/// mSceneTransitionTimeSeconds: Time in seconds it will take a cluster to change to the scene
/// mExtensionFieldSets: class holding the different field sets of each cluster values to store with the scene
/// mTransitionTime100ms: Transition time in tenths of a second, allows for more precise transition when combiened with
/// mSceneTransitionTimeSeconds in enhanced scene commands
struct SceneData
{
char mName[kSceneNameMaxLength] = { 0 };
size_t mNameLength = 0;
SceneTransitionTime mSceneTransitionTimeMs = 0;
EFStype mExtensionFieldSets;
SceneData(const CharSpan & sceneName = CharSpan(), SceneTransitionTime time = 0) : mSceneTransitionTimeMs(time)
{
SetName(sceneName);
}
SceneData(EFStype fields, const CharSpan & sceneName = CharSpan(), SceneTransitionTime time = 0) :
mSceneTransitionTimeMs(time)
{
SetName(sceneName);
mExtensionFieldSets = fields;
}
SceneData(const SceneData & other) : mSceneTransitionTimeMs(other.mSceneTransitionTimeMs)
{
SetName(CharSpan(other.mName, other.mNameLength));
mExtensionFieldSets = other.mExtensionFieldSets;
}
~SceneData(){};
bool operator==(const SceneData & other) const
{
return ((CharSpan(mName, mNameLength).data_equal(CharSpan(other.mName, other.mNameLength))) &&
(mSceneTransitionTimeMs == other.mSceneTransitionTimeMs) && (mExtensionFieldSets == other.mExtensionFieldSets));
}
void SetName(const CharSpan & sceneName)
{
if (nullptr == sceneName.data())
{
mName[0] = 0;
mNameLength = 0;
}
else
{
size_t maxChars = std::min(sceneName.size(), kSceneNameMaxLength);
memcpy(mName, sceneName.data(), maxChars);
mNameLength = maxChars;
}
}
void Clear()
{
SetName(CharSpan());
mSceneTransitionTimeMs = 0;
mExtensionFieldSets.Clear();
}
void operator=(const SceneData & other)
{
SetName(CharSpan(other.mName, other.mNameLength));
mExtensionFieldSets = other.mExtensionFieldSets;
mSceneTransitionTimeMs = other.mSceneTransitionTimeMs;
}
};
/// @brief Struct combining both ID and data of a table entry
struct SceneTableEntry
{
// ID
SceneStorageId mStorageId;
// DATA
SceneData mStorageData;
SceneTableEntry() = default;
SceneTableEntry(SceneStorageId id) : mStorageId(id) {}
SceneTableEntry(const SceneStorageId id, const SceneData data) : mStorageId(id), mStorageData(data) {}
bool operator==(const SceneTableEntry & other)
{
return (mStorageId == other.mStorageId && mStorageData == other.mStorageData);
}
void operator=(const SceneTableEntry & other)
{
mStorageId = other.mStorageId;
mStorageData = other.mStorageData;
}
};
SceneTable(){};
virtual ~SceneTable(){};
// Not copyable
SceneTable(const SceneTable &) = delete;
SceneTable & operator=(const SceneTable &) = delete;
virtual CHIP_ERROR Init(PersistentStorageDelegate * storage) = 0;
virtual void Finish() = 0;
// Global scene count
virtual CHIP_ERROR GetEndpointSceneCount(uint8_t & scene_count) = 0;
virtual CHIP_ERROR GetFabricSceneCount(FabricIndex fabric_index, uint8_t & scene_count) = 0;
// Data
virtual CHIP_ERROR GetRemainingCapacity(FabricIndex fabric_index, uint8_t & capacity) = 0;
virtual CHIP_ERROR SetSceneTableEntry(FabricIndex fabric_index, const SceneTableEntry & entry) = 0;
virtual CHIP_ERROR GetSceneTableEntry(FabricIndex fabric_index, SceneStorageId scene_id, SceneTableEntry & entry) = 0;
virtual CHIP_ERROR RemoveSceneTableEntry(FabricIndex fabric_index, SceneStorageId scene_id) = 0;
virtual CHIP_ERROR RemoveSceneTableEntryAtPosition(EndpointId endpoint, FabricIndex fabric_index, SceneIndex scene_idx) = 0;
// Groups
virtual CHIP_ERROR GetAllSceneIdsInGroup(FabricIndex fabric_index, GroupId group_id, Span<SceneId> & scene_list) = 0;
virtual CHIP_ERROR DeleteAllScenesInGroup(FabricIndex fabric_index, GroupId group_id) = 0;
// SceneHandlers
virtual void RegisterHandler(SceneHandler * handler) = 0;
virtual void UnregisterHandler(SceneHandler * handler) = 0;
virtual void UnregisterAllHandlers() = 0;
// Extension field sets operation
virtual CHIP_ERROR SceneSaveEFS(SceneTableEntry & scene) = 0;
virtual CHIP_ERROR SceneApplyEFS(const SceneTableEntry & scene) = 0;
// Fabrics
/**
* @brief Removes all scenes associated with a fabric index and the stored FabricSceneData that maps them
* @param fabric_index Fabric index to remove
* @return CHIP_ERROR, CHIP_NO_ERROR if successful or if the Fabric was not found, specific CHIP_ERROR otherwise
* @note This function is meant to be used after a fabric is removed from the device, the implementation MUST ensure that it
* won't interact with the actual fabric table as it will be removed beforehand.
*/
virtual CHIP_ERROR RemoveFabric(FabricIndex fabric_index) = 0;
virtual CHIP_ERROR RemoveEndpoint() = 0;
// Iterators
using SceneEntryIterator = CommonIterator<SceneTableEntry>;
virtual SceneEntryIterator * IterateSceneEntries(FabricIndex fabric_index) = 0;
// Handlers
virtual bool HandlerListEmpty() { return mHandlerList.Empty(); }
IntrusiveList<SceneHandler> mHandlerList;
};
} // namespace scenes
} // namespace chip