Coverage Report

Created: 2026-11-12 07:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/connectedhomeip/src/app/clusters/scenes-server/SceneTable.h
Line
Count
Source
1
/*
2
 *
3
 *    Copyright (c) 2022 Project CHIP Authors
4
 *
5
 *    Licensed under the Apache License, Version 2.0 (the "License");
6
 *    you may not use this file except in compliance with the License.
7
 *    You may obtain a copy of the License at
8
 *
9
 *        http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 *    Unless required by applicable law or agreed to in writing, software
12
 *    distributed under the License is distributed on an "AS IS" BASIS,
13
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 *    See the License for the specific language governing permissions and
15
 *    limitations under the License.
16
 */
17
#pragma once
18
19
#include <app-common/zap-generated/cluster-objects.h>
20
#include <app/clusters/scenes-server/ExtensionFieldSets.h>
21
#include <app/data-model-provider/Provider.h>
22
#include <app/storage/TableEntry.h>
23
#include <lib/support/CHIPMemString.h>
24
#include <lib/support/IntrusiveList.h>
25
#include <lib/support/PersistentData.h>
26
#include <lib/support/Span.h>
27
28
namespace chip {
29
namespace scenes {
30
31
static constexpr uint16_t kMaxScenesPerEndpoint = CHIP_CONFIG_MAX_SCENES_TABLE_SIZE;
32
static_assert(kMaxScenesPerEndpoint >= 16, "Per spec, kMaxScenesPerEndpoint must be at least 16");
33
34
static constexpr uint16_t kMaxScenesPerFabric = (kMaxScenesPerEndpoint - 1) / 2;
35
36
// Storage index for scenes in nvm
37
using SceneIndex          = app::Storage::Data::EntryIndex;
38
using TransitionTimeMs    = uint32_t;
39
using SceneTransitionTime = uint32_t;
40
41
inline constexpr GroupId kGlobalGroupSceneId = 0x0000;
42
inline constexpr SceneId kUndefinedSceneId   = 0xff;
43
44
static constexpr size_t kSceneNameMaxLength      = CHIP_CONFIG_SCENES_CLUSTER_MAXIMUM_NAME_LENGTH;
45
static constexpr size_t kScenesMaxTransitionTime = 60'000'000u;
46
47
/// @brief SceneHandlers are meant as interface between various clusters and the Scene table.
48
/// When a scene command involving extension field sets is received, the Scene Table will go through
49
/// the list of handlers to either retrieve, populate or apply those extension field sets.
50
///
51
/// Generally, for each specific <endpoint, cluster> pair there should be one and only one handler
52
/// registered with the scene table that claims to handle that pair.
53
///
54
/// A SceneHandler can handle a single <endpoint, cluster> pair, or many such pairs.
55
///
56
/// @note If more than one handler claims to handle a specific <endpoint, cluster> pair, only one of
57
/// those handlers will get called when executing actions related to extension field sets on the scene
58
/// table.  It is not defined which handler will be selected.
59
60
class SceneHandler : public IntrusiveListNodeBase<>
61
{
62
public:
63
0
    SceneHandler()          = default;
64
0
    virtual ~SceneHandler() = default;
65
66
    /// @brief Returns whether or not a cluster for scenes is supported on an endpoint
67
    ///
68
    /// @param endpoint Target Endpoint ID
69
    /// @param cluster Target Cluster ID
70
    /// @return true if supported, false if not supported
71
    virtual bool SupportsCluster(EndpointId endpoint, ClusterId cluster) = 0;
72
73
    /// @brief Called when handling AddScene.  Allows the handler to filter through the clusters in the command to serialize only
74
    /// the supported ones.
75
    ///
76
    /// @param endpoint[in] Endpoint ID
77
    /// @param extensionFieldSet[in] ExtensionFieldSetStruct provided by the AddScene Command, pre initialized
78
    /// @param serialisedBytes[out] Buffer to fill from the ExtensionFieldSetStruct in command
79
    /// @return CHIP_NO_ERROR if successful, CHIP_ERROR value otherwise
80
    /// @note Only gets called after the scene-cluster has previously verified that the endpoint,cluster pair is supported by
81
    /// the handler. It is therefore the implementation's reponsibility to also implement the SupportsCluster method.
82
    virtual CHIP_ERROR
83
    SerializeAdd(EndpointId endpoint,
84
                 const app::Clusters::ScenesManagement::Structs::ExtensionFieldSetStruct::DecodableType & extensionFieldSet,
85
                 MutableByteSpan & serialisedBytes) = 0;
86
87
    /// @brief Called when handling StoreScene, and only if the handler supports the given endpoint and cluster.
88
    ///
89
    /// The implementation must write the actual scene data to store to serializedBytes as described below.
90
    ///
91
    /// @param endpoint[in] Target Endpoint
92
    /// @param cluster[in] Target Cluster
93
    /// @param serializedBytes[out] Output buffer, data needs to be writen in there and size adjusted to the size of the data
94
    /// written.
95
    ///
96
    /// @return CHIP_NO_ERROR if successful, CHIP_ERROR value otherwise
97
    virtual CHIP_ERROR SerializeSave(EndpointId endpoint, ClusterId cluster, MutableByteSpan & serializedBytes) = 0;
98
99
    /// @brief Deserialize an ExtensionFieldSetStruct into a cluster object (e.g. when handling ViewScene).
100
    ///
101
    /// @param endpoint[in] Endpoint ID
102
    /// @param cluster[in] Cluster ID
103
    /// @param serializedBytes[in] ExtensionFieldSetStruct stored in NVM
104
    ///
105
    /// @param extensionFieldSet[out] ExtensionFieldSetStruct in command format
106
    /// @return CHIP_NO_ERROR if successful, CHIP_ERROR value otherwise
107
    /// @note Only gets called for handlers for which SupportsCluster() is true for the given endpoint and cluster.
108
    virtual CHIP_ERROR Deserialize(EndpointId endpoint, ClusterId cluster, const ByteSpan & serializedBytes,
109
110
                                   app::Clusters::ScenesManagement::Structs::ExtensionFieldSetStruct::Type & extensionFieldSet) = 0;
111
112
    /// @brief Restore a stored scene for the given cluster instance, over timeMs milliseconds (e.g. when handling RecallScene)
113
    ///
114
    /// @param endpoint[in] Endpoint ID
115
    /// @param cluster[in] Cluster ID
116
    /// @param serializedBytes[in] ExtensionFieldSetStruct stored in NVM
117
    ///
118
    /// @param timeMs[in] Transition time in ms to apply the scene
119
    /// @return CHIP_NO_ERROR if successful, CHIP_ERROR value otherwise
120
    /// @note Only gets called for handlers for which SupportsCluster() is true for the given endpoint and cluster.
121
    virtual CHIP_ERROR ApplyScene(EndpointId endpoint, ClusterId cluster, const ByteSpan & serializedBytes,
122
                                  TransitionTimeMs timeMs) = 0;
123
};
124
125
template <class EFStype>
126
class SceneTable
127
{
128
public:
129
    /// @brief struct used to identify a scene in storage by 3 ids, endpoint, group and scene
130
    struct SceneStorageId
131
    {
132
        // Identifies group within the scope of the given fabric
133
        GroupId mGroupId = kGlobalGroupSceneId;
134
        SceneId mSceneId = kUndefinedSceneId;
135
136
0
        SceneStorageId() = default;
137
0
        SceneStorageId(SceneId id, GroupId groupId = kGlobalGroupSceneId) : mGroupId(groupId), mSceneId(id) {}
138
139
        void Clear()
140
0
        {
141
0
            mGroupId = kGlobalGroupSceneId;
142
0
            mSceneId = kUndefinedSceneId;
143
0
        }
144
145
0
        bool IsValid() { return (mSceneId != kUndefinedSceneId); }
146
147
0
        bool operator==(const SceneStorageId & other) const { return (mGroupId == other.mGroupId && mSceneId == other.mSceneId); }
148
    };
149
150
    /// @brief struct used to store data held in a scene
151
    /// Members:
152
    /// mName: char buffer holding the name of the scene, only serialized when mNameLength is greater than 0
153
    /// mNameLength: length of the name if a name was provided at scene creation
154
    /// mSceneTransitionTimeSeconds: Time in seconds it will take a cluster to change to the scene
155
    /// mExtensionFieldSets: class holding the different field sets of each cluster values to store with the scene
156
    /// mTransitionTime100ms: Transition time in tenths of a second, allows for more precise transition when combined with
157
    /// mSceneTransitionTimeSeconds in enhanced scene commands
158
    struct SceneData
159
    {
160
        char mName[kSceneNameMaxLength]            = { 0 };
161
        size_t mNameLength                         = 0;
162
        SceneTransitionTime mSceneTransitionTimeMs = 0;
163
        EFStype mExtensionFieldSets;
164
165
0
        SceneData(const CharSpan & sceneName = CharSpan(), SceneTransitionTime time = 0) : mSceneTransitionTimeMs(time)
166
0
        {
167
0
            SetName(sceneName);
168
0
        }
169
        SceneData(EFStype fields, const CharSpan & sceneName = CharSpan(), SceneTransitionTime time = 0) :
170
            mSceneTransitionTimeMs(time)
171
        {
172
            SetName(sceneName);
173
174
            mExtensionFieldSets = fields;
175
        }
176
0
        SceneData(const SceneData & other) : mSceneTransitionTimeMs(other.mSceneTransitionTimeMs)
177
0
        {
178
0
            SetName(CharSpan(other.mName, other.mNameLength));
179
180
0
            mExtensionFieldSets = other.mExtensionFieldSets;
181
0
        }
182
0
        ~SceneData(){};
183
184
        bool operator==(const SceneData & other) const
185
        {
186
            return ((CharSpan(mName, mNameLength).data_equal(CharSpan(other.mName, other.mNameLength))) &&
187
                    (mSceneTransitionTimeMs == other.mSceneTransitionTimeMs) && (mExtensionFieldSets == other.mExtensionFieldSets));
188
        }
189
190
        void SetName(const CharSpan & sceneName)
191
0
        {
192
0
            if (nullptr == sceneName.data())
193
0
            {
194
0
                mName[0]    = 0;
195
0
                mNameLength = 0;
196
0
            }
197
0
            else
198
0
            {
199
0
                size_t maxChars = std::min(sceneName.size(), kSceneNameMaxLength);
200
0
                memcpy(mName, sceneName.data(), maxChars);
201
0
                mNameLength = maxChars;
202
0
            }
203
0
        }
204
205
        void Clear()
206
0
        {
207
0
            SetName(CharSpan());
208
0
            mSceneTransitionTimeMs = 0;
209
0
            mExtensionFieldSets.Clear();
210
0
        }
211
212
        void operator=(const SceneData & other)
213
        {
214
            SetName(CharSpan(other.mName, other.mNameLength));
215
            mExtensionFieldSets    = other.mExtensionFieldSets;
216
            mSceneTransitionTimeMs = other.mSceneTransitionTimeMs;
217
        }
218
    };
219
220
    using SceneTableEntry = app::Storage::Data::TableEntry<SceneStorageId, SceneData>;
221
222
0
    SceneTable(){};
223
224
0
    virtual ~SceneTable(){};
225
226
    // Not copyable
227
    SceneTable(const SceneTable &) = delete;
228
229
    SceneTable & operator=(const SceneTable &) = delete;
230
231
    virtual CHIP_ERROR Init(PersistentStorageDelegate & storage, app::DataModel::Provider & dataModel) = 0;
232
    virtual void Finish()                                                                              = 0;
233
234
    // Table size
235
    virtual uint16_t GetTableSize() const = 0;
236
237
    // Global scene count
238
    virtual CHIP_ERROR GetEndpointSceneCount(uint8_t & scene_count)                         = 0;
239
    virtual CHIP_ERROR GetFabricSceneCount(FabricIndex fabric_index, uint8_t & scene_count) = 0;
240
241
    // Data
242
    virtual CHIP_ERROR GetRemainingCapacity(FabricIndex fabric_index, uint8_t & capacity)                                   = 0;
243
    virtual CHIP_ERROR SetSceneTableEntry(FabricIndex fabric_index, const SceneTableEntry & entry)                          = 0;
244
    virtual CHIP_ERROR GetSceneTableEntry(FabricIndex fabric_index, SceneStorageId scene_id, SceneTableEntry & entry)       = 0;
245
    virtual CHIP_ERROR RemoveSceneTableEntry(FabricIndex fabric_index, SceneStorageId scene_id)                             = 0;
246
    virtual CHIP_ERROR RemoveSceneTableEntryAtPosition(EndpointId endpoint, FabricIndex fabric_index, SceneIndex scene_idx) = 0;
247
248
    // Groups
249
    virtual CHIP_ERROR GetAllSceneIdsInGroup(FabricIndex fabric_index, GroupId group_id, Span<SceneId> & scene_list) = 0;
250
    virtual CHIP_ERROR DeleteAllScenesInGroup(FabricIndex fabric_index, GroupId group_id)                            = 0;
251
252
    // SceneHandlers
253
    virtual void RegisterHandler(SceneHandler * handler)   = 0;
254
    virtual void UnregisterHandler(SceneHandler * handler) = 0;
255
    virtual void UnregisterAllHandlers()                   = 0;
256
257
    // Extension field sets operation
258
    virtual CHIP_ERROR SceneSaveEFS(SceneTableEntry & scene)        = 0;
259
    virtual CHIP_ERROR SceneApplyEFS(const SceneTableEntry & scene) = 0;
260
261
    // Fabrics
262
263
    /**
264
     * @brief Removes all scenes associated with a fabric index and the stored FabricSceneData that maps them
265
     * @param fabric_index Fabric index to remove
266
     * @return CHIP_ERROR, CHIP_NO_ERROR if successful or if the Fabric was not found, specific CHIP_ERROR otherwise
267
     * @note This function is meant to be used after a fabric is removed from the device, the implementation MUST ensure that it
268
     * won't interact with the actual fabric table as it will be removed beforehand.
269
     */
270
    virtual CHIP_ERROR RemoveFabric(FabricIndex fabric_index) = 0;
271
    virtual CHIP_ERROR RemoveEndpoint()                       = 0;
272
273
    // Handlers
274
0
    virtual bool HandlerListEmpty() { return mHandlerList.Empty(); }
275
276
    IntrusiveList<SceneHandler> mHandlerList;
277
};
278
279
} // namespace scenes
280
} // namespace chip