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/dishwasher-alarm-server/CodegenIntegration.cpp
Line
Count
Source
1
/*
2
 *
3
 *    Copyright (c) 2026 Project CHIP Authors
4
 *    All rights reserved.
5
 *
6
 *    Licensed under the Apache License, Version 2.0 (the "License");
7
 *    you may not use this file except in compliance with the License.
8
 *    You may obtain a copy of the License at
9
 *
10
 *        http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 *    Unless required by applicable law or agreed to in writing, software
13
 *    distributed under the License is distributed on an "AS IS" BASIS,
14
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 *    See the License for the specific language governing permissions and
16
 *    limitations under the License.
17
 */
18
19
#include <app-common/zap-generated/attributes/Accessors.h>
20
#include <app/clusters/alarm-base-server/alarm-base-cluster-objects.h>
21
#include <app/clusters/dishwasher-alarm-server/CodegenIntegration.h>
22
#include <app/clusters/dishwasher-alarm-server/DishwasherAlarmCluster.h>
23
#include <app/static-cluster-config/DishwasherAlarm.h>
24
#include <app/util/attribute-storage.h>
25
#include <app/util/endpoint-config-api.h>
26
#include <app/util/generic-callbacks.h>
27
#include <data-model-providers/codegen/ClusterIntegration.h>
28
#include <data-model-providers/codegen/CodegenDataModelProvider.h>
29
#include <lib/support/BitFlags.h>
30
#include <lib/support/CodeUtils.h>
31
32
using namespace chip;
33
using namespace chip::app;
34
using namespace chip::app::Clusters;
35
using namespace chip::app::Clusters::AlarmBase;
36
using namespace chip::app::Clusters::DishwasherAlarm;
37
using namespace chip::app::Clusters::DishwasherAlarm::Attributes;
38
using chip::Protocols::InteractionModel::Status;
39
40
namespace {
41
42
constexpr size_t kDishwasherAlarmFixedClusterCount = DishwasherAlarm::StaticApplicationConfig::kFixedClusterConfig.size();
43
constexpr size_t kDishwasherAlarmMaxClusterCount   = kDishwasherAlarmFixedClusterCount + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT;
44
45
static_assert(kDishwasherAlarmFixedClusterCount == MATTER_DM_DISHWASHER_ALARM_CLUSTER_SERVER_ENDPOINT_COUNT,
46
              "DishwasherAlarm static cluster config must match ZAP server endpoint count");
47
static_assert(kDishwasherAlarmMaxClusterCount <= kEmberInvalidEndpointIndex, "DishwasherAlarm cluster table size error");
48
49
BitMask<DishwasherAlarm::AlarmMap> ToAlarmMap(AlarmBase::AlarmMap map)
50
0
{
51
0
    return BitMask<DishwasherAlarm::AlarmMap>(map.Raw());
52
0
}
53
54
AlarmBase::AlarmMap FromAlarmMap(const BitMask<DishwasherAlarm::AlarmMap> map)
55
0
{
56
0
    return AlarmBase::AlarmMap(map.Raw());
57
0
}
58
59
bool EndpointHasModifyEnabledAlarmsCommand(EndpointId endpointId)
60
0
{
61
0
    constexpr CommandId kCommandId = DishwasherAlarm::Commands::ModifyEnabledAlarms::Id;
62
0
    const EmberAfCluster * cluster = emberAfFindServerCluster(endpointId, DishwasherAlarm::Id);
63
0
    VerifyOrReturnValue(cluster != nullptr, false);
64
0
    VerifyOrReturnValue(cluster->acceptedCommandList != nullptr, false);
65
66
0
    for (const CommandId * cmd = cluster->acceptedCommandList; *cmd != kInvalidCommandId; cmd++)
67
0
    {
68
0
        if (*cmd == kCommandId)
69
0
        {
70
0
            return true;
71
0
        }
72
0
    }
73
0
    return false;
74
0
}
75
76
// Proxy delegate used only by the codegen integration layer.
77
//
78
// AlarmBaseCluster is constructed with a mandatory AlarmBase::Delegate& (no nullptr). Apps may register
79
// a DishwasherAlarm::Delegate later (or never) via SetDefaultDelegate, so the cluster cannot assume an
80
// application delegate exists at construction time.
81
//
82
// This wrapper is the object passed into AlarmBaseCluster::Config: it lives for the endpoint slot and
83
// holds an optional pointer to the application delegate. When registered, command callbacks are forwarded
84
// to the app. When mWrapped is null, ModifyEnabledAlarms and ResetAlarms return true so the cluster still
85
// updates Matter attributes. That matches master Ember behavior (GetDelegate() == nullptr skipped the
86
// callback and allowed the attribute update) and lets example/simulator apps work without product logic.
87
class DishwasherAlarmIntegrationDelegateWrapper final : public AlarmBase::Delegate
88
{
89
public:
90
0
    void SetWrapped(DishwasherAlarm::Delegate * wrapped) { mWrapped = wrapped; }
91
92
0
    DishwasherAlarm::Delegate * GetWrapped() const { return mWrapped; }
93
94
    bool ModifyEnabledAlarms(AlarmBase::AlarmMap mask) override
95
0
    {
96
0
        if (mWrapped == nullptr)
97
0
        {
98
0
            return true;
99
0
        }
100
0
        return mWrapped->ModifyEnabledAlarmsCallback(BitMask<DishwasherAlarm::AlarmBitmap>(mask.Raw()));
101
0
    }
102
103
    bool ResetAlarms(AlarmBase::AlarmMap alarms) override
104
0
    {
105
0
        if (mWrapped == nullptr)
106
0
        {
107
0
            return true;
108
0
        }
109
0
        return mWrapped->ResetAlarmsCallback(BitMask<DishwasherAlarm::AlarmBitmap>(alarms.Raw()));
110
0
    }
111
112
private:
113
    DishwasherAlarm::Delegate * mWrapped = nullptr;
114
};
115
116
struct DishwasherAlarmClusterSlot
117
{
118
    LazyRegisteredServerCluster<DishwasherAlarmCluster> cluster;
119
    DishwasherAlarmIntegrationDelegateWrapper integrationDelegateWrapper;
120
};
121
122
DishwasherAlarmClusterSlot gDishwasherAlarmClusters[kDishwasherAlarmMaxClusterCount];
123
124
class DishwasherAlarmIntegrationDelegate : public CodegenClusterIntegration::Delegate
125
{
126
public:
127
    ServerClusterRegistration & CreateRegistration(EndpointId endpointId, unsigned clusterInstanceIndex, uint32_t,
128
                                                   uint32_t featureMap) override
129
0
    {
130
0
        BitFlags<DishwasherAlarm::Feature> features(featureMap);
131
132
0
        AlarmBase::AlarmMap supported{};
133
0
        BitMask<DishwasherAlarm::AlarmBitmap> supportedDefault{};
134
0
        if (Supported::GetDefault(endpointId, supportedDefault) == Status::Success)
135
0
        {
136
0
            supported = AlarmBase::AlarmMap(supportedDefault.Raw());
137
0
        }
138
139
0
        AlarmBase::AlarmMap latch{};
140
0
        BitMask<DishwasherAlarm::AlarmBitmap> latchDefault{};
141
0
        if (Latch::GetDefault(endpointId, latchDefault) == Status::Success)
142
0
        {
143
0
            latch = AlarmBase::AlarmMap(latchDefault.Raw());
144
0
        }
145
146
0
        DishwasherAlarmCluster::Config config{
147
0
            .delegate                    = gDishwasherAlarmClusters[clusterInstanceIndex].integrationDelegateWrapper,
148
0
            .feature                     = features,
149
0
            .supported                   = supported,
150
0
            .latch                       = latch,
151
0
            .supportsModifyEnabledAlarms = EndpointHasModifyEnabledAlarmsCommand(endpointId),
152
0
        };
153
154
0
        gDishwasherAlarmClusters[clusterInstanceIndex].cluster.Create(endpointId, config);
155
156
0
        DishwasherAlarmCluster & cluster = gDishwasherAlarmClusters[clusterInstanceIndex].cluster.Cluster();
157
158
0
        BitMask<DishwasherAlarm::AlarmBitmap> maskDefault{};
159
0
        if (Mask::GetDefault(endpointId, maskDefault) == Status::Success)
160
0
        {
161
0
            cluster.SetMask(AlarmBase::AlarmMap(maskDefault.Raw()));
162
0
        }
163
164
0
        BitMask<DishwasherAlarm::AlarmBitmap> stateDefault{};
165
0
        if (State::GetDefault(endpointId, stateDefault) == Status::Success)
166
0
        {
167
0
            cluster.SetState(AlarmBase::AlarmMap(stateDefault.Raw()));
168
0
        }
169
170
0
        return gDishwasherAlarmClusters[clusterInstanceIndex].cluster.Registration();
171
0
    }
172
173
    ServerClusterInterface * FindRegistration(unsigned clusterInstanceIndex) override
174
0
    {
175
0
        VerifyOrReturnValue(gDishwasherAlarmClusters[clusterInstanceIndex].cluster.IsConstructed(), nullptr);
176
0
        return &gDishwasherAlarmClusters[clusterInstanceIndex].cluster.Cluster();
177
0
    }
178
179
    void ReleaseRegistration(unsigned clusterInstanceIndex) override
180
0
    {
181
0
        gDishwasherAlarmClusters[clusterInstanceIndex].cluster.Destroy();
182
0
    }
183
};
184
185
} // namespace
186
187
void MatterDishwasherAlarmClusterInitCallback(EndpointId endpointId)
188
0
{
189
0
    DishwasherAlarmIntegrationDelegate integrationDelegate;
190
0
    CodegenClusterIntegration::RegisterServer(
191
0
        {
192
0
            .endpointId                = endpointId,
193
0
            .clusterId                 = DishwasherAlarm::Id,
194
0
            .fixedClusterInstanceCount = kDishwasherAlarmFixedClusterCount,
195
0
            .maxClusterInstanceCount   = kDishwasherAlarmMaxClusterCount,
196
0
            .fetchFeatureMap           = true,
197
0
            .fetchOptionalAttributes   = false,
198
0
        },
199
0
        integrationDelegate);
200
0
}
201
202
void MatterDishwasherAlarmClusterShutdownCallback(EndpointId endpointId, MatterClusterShutdownType shutdownType)
203
0
{
204
0
    DishwasherAlarmIntegrationDelegate integrationDelegate;
205
0
    CodegenClusterIntegration::UnregisterServer(
206
0
        {
207
0
            .endpointId                = endpointId,
208
0
            .clusterId                 = DishwasherAlarm::Id,
209
0
            .fixedClusterInstanceCount = kDishwasherAlarmFixedClusterCount,
210
0
            .maxClusterInstanceCount   = kDishwasherAlarmMaxClusterCount,
211
0
        },
212
0
        integrationDelegate, shutdownType);
213
0
}
214
215
__attribute__((weak)) void MatterDishwasherAlarmPluginServerInitCallback() {}
216
0
__attribute__((weak)) void MatterDishwasherAlarmPluginServerShutdownCallback() {}
217
218
namespace chip::app::Clusters::DishwasherAlarm {
219
220
DishwasherAlarmCluster * FindClusterOnEndpoint(EndpointId endpointId)
221
0
{
222
0
    DishwasherAlarmIntegrationDelegate integrationDelegate;
223
0
    return static_cast<DishwasherAlarmCluster *>(CodegenClusterIntegration::FindClusterOnEndpoint(
224
0
        {
225
0
            .endpointId                = endpointId,
226
0
            .clusterId                 = DishwasherAlarm::Id,
227
0
            .fixedClusterInstanceCount = kDishwasherAlarmFixedClusterCount,
228
0
            .maxClusterInstanceCount   = kDishwasherAlarmMaxClusterCount,
229
0
        },
230
0
        integrationDelegate));
231
0
}
232
233
Delegate * GetDelegate(EndpointId endpoint)
234
0
{
235
0
    uint16_t ep = emberAfGetClusterServerEndpointIndex(endpoint, DishwasherAlarm::Id,
236
0
                                                       MATTER_DM_DISHWASHER_ALARM_CLUSTER_SERVER_ENDPOINT_COUNT);
237
0
    return (ep >= kDishwasherAlarmMaxClusterCount ? nullptr : gDishwasherAlarmClusters[ep].integrationDelegateWrapper.GetWrapped());
238
0
}
239
240
void SetDefaultDelegate(EndpointId endpoint, Delegate * delegate)
241
0
{
242
0
    uint16_t ep = emberAfGetClusterServerEndpointIndex(endpoint, DishwasherAlarm::Id,
243
0
                                                       MATTER_DM_DISHWASHER_ALARM_CLUSTER_SERVER_ENDPOINT_COUNT);
244
0
    if (ep < kDishwasherAlarmMaxClusterCount)
245
0
    {
246
0
        gDishwasherAlarmClusters[ep].integrationDelegateWrapper.SetWrapped(delegate);
247
0
    }
248
0
}
249
250
DishwasherAlarmServer DishwasherAlarmServer::instance;
251
252
DishwasherAlarmServer & DishwasherAlarmServer::Instance()
253
0
{
254
0
    return instance;
255
0
}
256
257
Status DishwasherAlarmServer::GetMaskValue(EndpointId endpoint, BitMask<AlarmMap> * mask)
258
0
{
259
0
    DishwasherAlarmCluster * cluster = FindClusterOnEndpoint(endpoint);
260
0
    VerifyOrReturnError(cluster != nullptr, Status::UnsupportedEndpoint);
261
262
0
    if (mask != nullptr)
263
0
    {
264
0
        *mask = ToAlarmMap(cluster->GetMask());
265
0
    }
266
0
    return Status::Success;
267
0
}
268
269
Status DishwasherAlarmServer::GetLatchValue(EndpointId endpoint, BitMask<AlarmMap> * latch)
270
0
{
271
0
    DishwasherAlarmCluster * cluster = FindClusterOnEndpoint(endpoint);
272
0
    VerifyOrReturnError(cluster != nullptr, Status::UnsupportedEndpoint);
273
0
    VerifyOrReturnError(HasResetFeature(endpoint), Status::UnsupportedAttribute);
274
275
0
    if (latch != nullptr)
276
0
    {
277
0
        *latch = ToAlarmMap(cluster->GetLatch());
278
0
    }
279
0
    return Status::Success;
280
0
}
281
282
Status DishwasherAlarmServer::GetStateValue(EndpointId endpoint, BitMask<AlarmMap> * state)
283
0
{
284
0
    DishwasherAlarmCluster * cluster = FindClusterOnEndpoint(endpoint);
285
0
    VerifyOrReturnError(cluster != nullptr, Status::UnsupportedEndpoint);
286
287
0
    if (state != nullptr)
288
0
    {
289
0
        *state = ToAlarmMap(cluster->GetState());
290
0
    }
291
0
    return Status::Success;
292
0
}
293
294
Status DishwasherAlarmServer::GetSupportedValue(EndpointId endpoint, BitMask<AlarmMap> * supported)
295
0
{
296
0
    DishwasherAlarmCluster * cluster = FindClusterOnEndpoint(endpoint);
297
0
    VerifyOrReturnError(cluster != nullptr, Status::UnsupportedEndpoint);
298
299
0
    if (supported != nullptr)
300
0
    {
301
0
        *supported = ToAlarmMap(cluster->GetSupported());
302
0
    }
303
0
    return Status::Success;
304
0
}
305
306
Status DishwasherAlarmServer::SetMaskValue(EndpointId endpoint, const BitMask<AlarmMap> mask)
307
0
{
308
0
    DishwasherAlarmCluster * cluster = FindClusterOnEndpoint(endpoint);
309
0
    VerifyOrReturnError(cluster != nullptr, Status::UnsupportedEndpoint);
310
0
    return cluster->SetMask(FromAlarmMap(mask));
311
0
}
312
313
Status DishwasherAlarmServer::SetStateValue(EndpointId endpoint, const BitMask<AlarmMap> newState, bool ignoreLatchState)
314
0
{
315
0
    DishwasherAlarmCluster * cluster = FindClusterOnEndpoint(endpoint);
316
0
    VerifyOrReturnError(cluster != nullptr, Status::UnsupportedEndpoint);
317
0
    if (ignoreLatchState)
318
0
    {
319
0
        return cluster->SetStateIgnoringLatch(FromAlarmMap(newState));
320
0
    }
321
0
    return cluster->SetState(FromAlarmMap(newState));
322
0
}
323
324
Status DishwasherAlarmServer::ResetLatchedAlarms(EndpointId endpoint, const BitMask<AlarmMap> alarms)
325
0
{
326
0
    DishwasherAlarmCluster * cluster = FindClusterOnEndpoint(endpoint);
327
0
    VerifyOrReturnError(cluster != nullptr, Status::UnsupportedEndpoint);
328
0
    return cluster->ResetLatchedAlarms(FromAlarmMap(alarms));
329
0
}
330
331
bool DishwasherAlarmServer::HasResetFeature(EndpointId endpoint)
332
0
{
333
0
    DishwasherAlarmCluster * cluster = FindClusterOnEndpoint(endpoint);
334
0
    VerifyOrReturnError(cluster != nullptr, false);
335
0
    return cluster->GetFeatures().Has(Feature::kReset);
336
0
}
337
338
namespace Attributes {
339
340
namespace Mask {
341
342
Status Get(EndpointId endpoint, BitMask<AlarmMap> * value)
343
0
{
344
0
    return DishwasherAlarmServer::Instance().GetMaskValue(endpoint, value);
345
0
}
346
347
Status Set(EndpointId endpoint, BitMask<AlarmMap> value)
348
0
{
349
0
    return DishwasherAlarmServer::Instance().SetMaskValue(endpoint, value);
350
0
}
351
352
} // namespace Mask
353
354
namespace Latch {
355
356
Status Get(EndpointId endpoint, BitMask<AlarmMap> * value)
357
0
{
358
0
    return DishwasherAlarmServer::Instance().GetLatchValue(endpoint, value);
359
0
}
360
361
} // namespace Latch
362
363
namespace State {
364
365
Status Get(EndpointId endpoint, BitMask<AlarmMap> * value)
366
0
{
367
0
    return DishwasherAlarmServer::Instance().GetStateValue(endpoint, value);
368
0
}
369
370
Status Set(EndpointId endpoint, BitMask<AlarmMap> value)
371
0
{
372
0
    return DishwasherAlarmServer::Instance().SetStateValue(endpoint, value);
373
0
}
374
375
} // namespace State
376
377
namespace Supported {
378
379
Status Get(EndpointId endpoint, BitMask<AlarmMap> * value)
380
0
{
381
0
    return DishwasherAlarmServer::Instance().GetSupportedValue(endpoint, value);
382
0
}
383
384
} // namespace Supported
385
386
} // namespace Attributes
387
388
} // namespace chip::app::Clusters::DishwasherAlarm