Coverage Report

Created: 2026-09-24 06:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/connectedhomeip/src/app/clusters/mode-select-server/ModeSelectCluster.cpp
Line
Count
Source
1
/*
2
 *    Copyright (c) 2025 Project CHIP Authors
3
 *    All rights reserved.
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
18
#include <app/clusters/mode-select-server/ModeSelectCluster.h>
19
20
#include <app/persistence/AttributePersistence.h>
21
#include <app/server-cluster/AttributeListBuilder.h>
22
#include <clusters/ModeSelect/Commands.h>
23
#include <clusters/ModeSelect/Metadata.h>
24
#include <lib/support/CodeUtils.h>
25
#include <lib/support/logging/CHIPLogging.h>
26
27
using namespace chip;
28
using namespace chip::app;
29
using namespace chip::app::Clusters;
30
using namespace chip::app::Clusters::ModeSelect;
31
using namespace chip::app::Clusters::ModeSelect::Attributes;
32
using chip::Protocols::InteractionModel::Status;
33
using BootReasonType = GeneralDiagnostics::BootReasonEnum;
34
35
namespace {
36
37
class ModeSelectSceneValidator : public scenes::AttributeValuePairValidator
38
{
39
public:
40
    CHIP_ERROR Validate(const app::ConcreteClusterPath & clusterPath,
41
                        AttributeValuePairValidator::AttributeValuePairType & value) override
42
0
    {
43
0
        VerifyOrReturnError(clusterPath.mClusterId == ModeSelect::Id, CHIP_ERROR_INVALID_ARGUMENT);
44
0
        VerifyOrReturnError(value.attributeID == CurrentMode::Id, CHIP_ERROR_INVALID_ARGUMENT);
45
0
        return CHIP_NO_ERROR;
46
0
    }
47
};
48
49
ModeSelectSceneValidator & GlobalValidator()
50
0
{
51
0
    static ModeSelectSceneValidator sValidator;
52
0
    return sValidator;
53
0
}
54
55
} // namespace
56
57
ModeSelectCluster::ModeSelectCluster(EndpointId endpointId, Delegate & delegate, const Config & config) :
58
0
    DefaultServerCluster({ endpointId, ModeSelect::Id }), DefaultSceneHandlerImpl(GlobalValidator()), mDelegate(delegate),
59
0
    mFeatureMap(config.featureMap), mOptionalAttributeSet(config.optionalAttributeSet), mDescription(config.description),
60
0
    mStandardNamespace(config.standardNamespace), mOnOffValueForStartUp(config.onOffValueForStartUp),
61
0
    mDiagnosticDataProvider(config.diagnosticDataProvider)
62
0
{}
63
64
CHIP_ERROR ModeSelectCluster::Startup(ServerClusterContext & context)
65
0
{
66
0
    ReturnErrorOnFailure(DefaultServerCluster::Startup(context));
67
0
    LoadPersistentAttributes(context.attributeStorage);
68
0
    ApplyStartupModeLogic();
69
0
    return CHIP_NO_ERROR;
70
0
}
71
72
void ModeSelectCluster::ApplyStartupModeLogic()
73
0
{
74
0
    if (mContext == nullptr)
75
0
    {
76
0
        return;
77
0
    }
78
79
0
    auto modes = mDelegate.GetSupportedModes();
80
0
    VerifyOrReturn(!modes.empty());
81
82
0
    if (!IsSupportedMode(mCurrentMode))
83
0
    {
84
        // First boot or stale persisted value: initialize to first available mode.
85
0
        SetAttributeValue(mCurrentMode, modes[0].mode, CurrentMode::Id);
86
0
        AttributePersistence persistence{ mContext->attributeStorage };
87
0
        LogErrorOnFailure(persistence.StoreNativeEndianValue(
88
0
            ConcreteAttributePath(mPath.mEndpointId, mPath.mClusterId, CurrentMode::Id), mCurrentMode));
89
0
    }
90
91
0
    if (!mStartUpMode.IsNull())
92
0
    {
93
0
        BootReasonType bootReason = BootReasonType::kUnspecified;
94
0
        CHIP_ERROR err            = mDiagnosticDataProvider.GetBootReason(bootReason);
95
0
        if (err != CHIP_NO_ERROR)
96
0
        {
97
0
            ChipLogError(Zcl, "ModeSelect: Unable to retrieve boot reason: %" CHIP_ERROR_FORMAT ". Assuming non-OTA reboot",
98
0
                         err.Format());
99
0
            bootReason = BootReasonType::kUnspecified;
100
0
        }
101
102
0
        if (bootReason == BootReasonType::kSoftwareUpdateCompleted)
103
0
        {
104
0
            ChipLogDetail(Zcl, "ModeSelect: StartUpMode ignored for OTA reboot.");
105
0
        }
106
0
        else if (mStartUpMode.Value() != mCurrentMode)
107
0
        {
108
0
            ChipLogProgress(Zcl, "ModeSelect: Applying StartUpMode %u to CurrentMode.", mStartUpMode.Value());
109
0
            Status status = UpdateCurrentMode(mStartUpMode.Value());
110
0
            if (status != Status::Success)
111
0
            {
112
0
                ChipLogError(Zcl, "ModeSelect: StartUpMode %u not supported, ignoring.", mStartUpMode.Value());
113
0
            }
114
0
        }
115
0
    }
116
117
0
    if (mOnOffValueForStartUp && !mOnMode.IsNull() && mOnMode.Value() != mCurrentMode)
118
0
    {
119
0
        ChipLogProgress(Zcl, "ModeSelect: Applying OnMode %u to CurrentMode.", mOnMode.Value());
120
0
        Status status = UpdateCurrentMode(mOnMode.Value());
121
0
        if (status != Status::Success)
122
0
        {
123
0
            ChipLogError(Zcl, "ModeSelect: OnMode %u not supported, ignoring.", mOnMode.Value());
124
0
        }
125
0
    }
126
0
}
127
128
void ModeSelectCluster::LoadPersistentAttributes(AttributePersistenceProvider & provider)
129
0
{
130
0
    AttributePersistence persistence{ provider };
131
0
    persistence.LoadNativeEndianValue({ mPath.mEndpointId, mPath.mClusterId, CurrentMode::Id }, mCurrentMode, mCurrentMode);
132
133
0
    if (mOptionalAttributeSet.IsSet(StartUpMode::Id))
134
0
    {
135
0
        persistence.LoadNativeEndianValue({ mPath.mEndpointId, mPath.mClusterId, StartUpMode::Id }, mStartUpMode, mStartUpMode);
136
0
    }
137
138
0
    if (mFeatureMap.Has(Feature::kOnOff))
139
0
    {
140
0
        persistence.LoadNativeEndianValue({ mPath.mEndpointId, mPath.mClusterId, OnMode::Id }, mOnMode, mOnMode);
141
0
    }
142
0
}
143
144
bool ModeSelectCluster::IsSupportedMode(uint8_t mode) const
145
0
{
146
0
    for (const auto & option : mDelegate.GetSupportedModes())
147
0
    {
148
0
        if (option.mode == mode)
149
0
        {
150
0
            return true;
151
0
        }
152
0
    }
153
0
    return false;
154
0
}
155
156
Status ModeSelectCluster::UpdateCurrentMode(uint8_t newMode)
157
0
{
158
0
    VerifyOrReturnValue(mContext != nullptr, Status::InvalidInState);
159
0
    VerifyOrReturnValue(IsSupportedMode(newMode), Status::ConstraintError);
160
0
    if (!SetAttributeValue(mCurrentMode, newMode, CurrentMode::Id))
161
0
    {
162
0
        return Status::Success;
163
0
    }
164
165
0
    AttributePersistence persistence{ mContext->attributeStorage };
166
0
    LogErrorOnFailure(persistence.StoreNativeEndianValue(
167
0
        ConcreteAttributePath(mPath.mEndpointId, mPath.mClusterId, CurrentMode::Id), mCurrentMode));
168
0
    mDelegate.OnModeChanged(mCurrentMode);
169
0
    return Status::Success;
170
0
}
171
172
Status ModeSelectCluster::UpdateStartUpMode(DataModel::Nullable<uint8_t> newStartUpMode)
173
0
{
174
0
    VerifyOrReturnValue(mContext != nullptr, Status::InvalidInState);
175
0
    VerifyOrReturnValue(newStartUpMode.IsNull() || IsSupportedMode(newStartUpMode.Value()), Status::ConstraintError);
176
0
    VerifyOrReturnValue(SetAttributeValue(mStartUpMode, newStartUpMode, StartUpMode::Id), Status::Success);
177
178
0
    AttributePersistence persistence{ mContext->attributeStorage };
179
0
    LogErrorOnFailure(persistence.StoreNativeEndianValue(
180
0
        ConcreteAttributePath(mPath.mEndpointId, mPath.mClusterId, StartUpMode::Id), mStartUpMode));
181
0
    return Status::Success;
182
0
}
183
184
Status ModeSelectCluster::UpdateOnMode(DataModel::Nullable<uint8_t> newOnMode)
185
0
{
186
0
    VerifyOrReturnValue(mContext != nullptr, Status::InvalidInState);
187
0
    VerifyOrReturnValue(newOnMode.IsNull() || IsSupportedMode(newOnMode.Value()), Status::ConstraintError);
188
0
    VerifyOrReturnValue(SetAttributeValue(mOnMode, newOnMode, OnMode::Id), Status::Success);
189
190
0
    AttributePersistence persistence{ mContext->attributeStorage };
191
0
    LogErrorOnFailure(
192
0
        persistence.StoreNativeEndianValue(ConcreteAttributePath(mPath.mEndpointId, mPath.mClusterId, OnMode::Id), mOnMode));
193
0
    return Status::Success;
194
0
}
195
196
DataModel::ActionReturnStatus ModeSelectCluster::ReadAttribute(const DataModel::ReadAttributeRequest & request,
197
                                                               AttributeValueEncoder & encoder)
198
0
{
199
0
    switch (request.path.mAttributeId)
200
0
    {
201
0
    case ClusterRevision::Id:
202
0
        return encoder.Encode(kRevision);
203
0
    case FeatureMap::Id:
204
0
        return encoder.Encode(mFeatureMap);
205
0
    case Description::Id:
206
0
        return encoder.Encode(mDescription);
207
0
    case StandardNamespace::Id:
208
0
        return encoder.Encode(mStandardNamespace);
209
0
    case SupportedModes::Id:
210
0
        return encoder.EncodeList([this](const auto & encod) -> CHIP_ERROR {
211
0
            for (const auto & mode : mDelegate.GetSupportedModes())
212
0
            {
213
0
                ReturnErrorOnFailure(encod.Encode(mode));
214
0
            }
215
0
            return CHIP_NO_ERROR;
216
0
        });
217
0
    case CurrentMode::Id:
218
0
        return encoder.Encode(mCurrentMode);
219
0
    case StartUpMode::Id:
220
0
        return encoder.Encode(mStartUpMode);
221
0
    case OnMode::Id:
222
0
        return encoder.Encode(mOnMode);
223
0
    default:
224
0
        return Status::UnsupportedAttribute;
225
0
    }
226
0
}
227
228
DataModel::ActionReturnStatus ModeSelectCluster::WriteAttribute(const DataModel::WriteAttributeRequest & request,
229
                                                                AttributeValueDecoder & decoder)
230
0
{
231
0
    switch (request.path.mAttributeId)
232
0
    {
233
0
    case StartUpMode::Id: {
234
0
        DataModel::Nullable<uint8_t> newValue;
235
0
        ReturnErrorOnFailure(decoder.Decode(newValue));
236
0
        return UpdateStartUpMode(newValue);
237
0
    }
238
0
    case OnMode::Id: {
239
0
        DataModel::Nullable<uint8_t> newValue;
240
0
        ReturnErrorOnFailure(decoder.Decode(newValue));
241
0
        return UpdateOnMode(newValue);
242
0
    }
243
0
    default:
244
0
        return Status::UnsupportedAttribute;
245
0
    }
246
0
}
247
248
std::optional<DataModel::ActionReturnStatus> ModeSelectCluster::InvokeCommand(const DataModel::InvokeRequest & request,
249
                                                                              TLV::TLVReader & input_arguments,
250
                                                                              CommandHandler * handler)
251
0
{
252
0
    switch (request.path.mCommandId)
253
0
    {
254
0
    case Commands::ChangeToMode::Id: {
255
0
        Commands::ChangeToMode::DecodableType data;
256
0
        ReturnErrorOnFailure(data.Decode(input_arguments));
257
0
        if (!IsSupportedMode(data.newMode))
258
0
        {
259
0
            ChipLogProgress(Zcl, "ModeSelect: Mode %u is not supported", data.newMode);
260
0
            return Status::InvalidCommand;
261
0
        }
262
0
        return UpdateCurrentMode(data.newMode);
263
0
    }
264
0
    default:
265
0
        return Status::UnsupportedCommand;
266
0
    }
267
0
}
268
269
CHIP_ERROR ModeSelectCluster::Attributes(const ConcreteClusterPath & path,
270
                                         ReadOnlyBufferBuilder<DataModel::AttributeEntry> & builder)
271
0
{
272
0
    AttributeListBuilder listBuilder(builder);
273
274
0
    static constexpr DataModel::AttributeEntry kMandatory[] = {
275
0
        Description::kMetadataEntry,
276
0
        StandardNamespace::kMetadataEntry,
277
0
        SupportedModes::kMetadataEntry,
278
0
        CurrentMode::kMetadataEntry,
279
0
    };
280
281
0
    const AttributeListBuilder::OptionalAttributeEntry optionalAttributes[] = {
282
0
        { mOptionalAttributeSet.IsSet(StartUpMode::Id), StartUpMode::kMetadataEntry },
283
0
        { mFeatureMap.Has(Feature::kOnOff), OnMode::kMetadataEntry },
284
0
    };
285
286
0
    return listBuilder.Append(Span(kMandatory), Span(optionalAttributes));
287
0
}
288
289
CHIP_ERROR ModeSelectCluster::AcceptedCommands(const ConcreteClusterPath & path,
290
                                               ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & builder)
291
0
{
292
0
    return builder.AppendElements({ Commands::ChangeToMode::kMetadataEntry });
293
0
}
294
295
bool ModeSelectCluster::SupportsCluster(EndpointId endpoint, ClusterId cluster)
296
0
{
297
0
    return (cluster == ModeSelect::Id) && (endpoint == mPath.mEndpointId);
298
0
}
299
300
CHIP_ERROR ModeSelectCluster::SerializeSave(EndpointId endpoint, ClusterId cluster, MutableByteSpan & serializedBytes)
301
0
{
302
0
    using AttributeValuePair = ScenesManagement::Structs::AttributeValuePairStruct::Type;
303
304
0
    VerifyOrReturnError(SupportsCluster(endpoint, cluster), CHIP_ERROR_INVALID_ARGUMENT);
305
306
0
    AttributeValuePair pairs[1];
307
0
    pairs[0].attributeID = CurrentMode::Id;
308
0
    pairs[0].valueUnsigned8.SetValue(mCurrentMode);
309
310
0
    app::DataModel::List<AttributeValuePair> attributeValueList(pairs);
311
0
    return EncodeAttributeValueList(attributeValueList, serializedBytes);
312
0
}
313
314
CHIP_ERROR ModeSelectCluster::ApplyScene(EndpointId endpoint, ClusterId cluster, const ByteSpan & serializedBytes,
315
                                         scenes::TransitionTimeMs timeMs)
316
0
{
317
0
    VerifyOrReturnError(SupportsCluster(endpoint, cluster), CHIP_ERROR_INVALID_ARGUMENT);
318
319
0
    app::DataModel::DecodableList<ScenesManagement::Structs::AttributeValuePairStruct::DecodableType> attributeValueList;
320
0
    ReturnErrorOnFailure(DecodeAttributeValueList(serializedBytes, attributeValueList));
321
322
0
    auto pair_iterator = attributeValueList.begin();
323
0
    while (pair_iterator.Next())
324
0
    {
325
0
        auto & decodePair = pair_iterator.GetValue();
326
0
        VerifyOrReturnError(decodePair.attributeID == CurrentMode::Id, CHIP_ERROR_INVALID_ARGUMENT);
327
0
        VerifyOrReturnError(decodePair.valueUnsigned8.HasValue(), CHIP_ERROR_INVALID_ARGUMENT);
328
329
0
        Status status = UpdateCurrentMode(decodePair.valueUnsigned8.Value());
330
0
        VerifyOrReturnError(status == Status::Success, StatusIB(status).ToChipError());
331
0
    }
332
0
    return pair_iterator.GetStatus();
333
0
}