Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2020 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 : /**
20 : * @file
21 : * This file contains definitions for a base Cluster class. This class will
22 : * be derived by various ZCL clusters supported by CHIP. The objects of the
23 : * ZCL cluster class will be used by Controller applications to interact with
24 : * the CHIP device.
25 : */
26 :
27 : #pragma once
28 :
29 : #include <app/AppConfig.h>
30 : #include <app/ConcreteCommandPath.h>
31 : #include <controller/InvokeInteraction.h>
32 : #include <controller/ReadInteraction.h>
33 : #include <controller/WriteInteraction.h>
34 : #include <lib/core/Optional.h>
35 : #include <messaging/ExchangeMgr.h>
36 : #include <system/SystemClock.h>
37 :
38 : namespace chip {
39 : namespace Controller {
40 :
41 : template <typename T>
42 : using CommandResponseSuccessCallback = void(void * context, const T & responseObject);
43 : using CommandResponseFailureCallback = void(void * context, CHIP_ERROR err);
44 : using CommandResponseDoneCallback = void();
45 : using WriteResponseSuccessCallback = void (*)(void * context);
46 : using WriteResponseFailureCallback = void (*)(void * context, CHIP_ERROR err);
47 : using WriteResponseDoneCallback = void (*)(void * context);
48 : template <typename T>
49 : using ReadResponseSuccessCallback = void (*)(void * context, T responseData);
50 : using ReadResponseFailureCallback = void (*)(void * context, CHIP_ERROR err);
51 : using ReadDoneCallback = void (*)(void * context);
52 : using SubscriptionEstablishedCallback = void (*)(void * context, SubscriptionId subscriptionId);
53 : using ResubscriptionAttemptCallback = void (*)(void * context, CHIP_ERROR aError, uint32_t aNextResubscribeIntervalMsec);
54 : using SubscriptionOnDoneCallback = std::function<void(void)>;
55 :
56 : class DLL_EXPORT ClusterBase
57 : {
58 : public:
59 0 : ClusterBase(Messaging::ExchangeManager & exchangeManager, const SessionHandle & session, EndpointId endpoint) :
60 0 : mExchangeManager(exchangeManager), mSession(session), mEndpoint(endpoint)
61 0 : {}
62 :
63 0 : virtual ~ClusterBase() {}
64 :
65 : // Temporary function to set command timeout before we move over to InvokeCommand
66 : // TODO: remove when we start using InvokeCommand everywhere
67 : void SetCommandTimeout(Optional<System::Clock::Timeout> timeout) { mTimeout = timeout; }
68 :
69 : /**
70 : * Returns the current command timeout set via SetCommandTimeout, or an
71 : * empty optional if no timeout has been set.
72 : */
73 : Optional<System::Clock::Timeout> GetCommandTimeout() { return mTimeout; }
74 :
75 : /*
76 : * This function permits sending an invoke request using cluster objects that represent the request and response data payloads.
77 : *
78 : * Success and Failure callbacks must be passed in through which the decoded response is provided as well as notification of any
79 : * failure.
80 : */
81 : template <typename RequestDataT>
82 0 : CHIP_ERROR InvokeCommand(const RequestDataT & requestData, void * context,
83 : CommandResponseSuccessCallback<typename RequestDataT::ResponseType> successCb,
84 : CommandResponseFailureCallback failureCb, const Optional<uint16_t> & timedInvokeTimeoutMs)
85 : {
86 0 : auto onSuccessCb = [context, successCb](const app::ConcreteCommandPath & aPath, const app::StatusIB & aStatus,
87 : const typename RequestDataT::ResponseType & responseData) {
88 0 : successCb(context, responseData);
89 : };
90 :
91 0 : auto onFailureCb = [context, failureCb](CHIP_ERROR aError) { failureCb(context, aError); };
92 :
93 0 : return InvokeCommandRequest(&mExchangeManager, mSession.Get().Value(), mEndpoint, requestData, onSuccessCb, onFailureCb,
94 0 : timedInvokeTimeoutMs, mTimeout);
95 : }
96 :
97 : template <typename RequestDataT>
98 : CHIP_ERROR InvokeCommand(const RequestDataT & requestData, void * context,
99 : CommandResponseSuccessCallback<typename RequestDataT::ResponseType> successCb,
100 : CommandResponseFailureCallback failureCb, uint16_t timedInvokeTimeoutMs)
101 : {
102 : return InvokeCommand(requestData, context, successCb, failureCb, MakeOptional(timedInvokeTimeoutMs));
103 : }
104 :
105 : template <typename RequestDataT, typename std::enable_if_t<!RequestDataT::MustUseTimedInvoke(), int> = 0>
106 0 : CHIP_ERROR InvokeCommand(const RequestDataT & requestData, void * context,
107 : CommandResponseSuccessCallback<typename RequestDataT::ResponseType> successCb,
108 : CommandResponseFailureCallback failureCb)
109 : {
110 0 : return InvokeCommand(requestData, context, successCb, failureCb, NullOptional);
111 : }
112 :
113 : /**
114 : * Functions for writing attributes. We have lots of different
115 : * AttributeInfo but a fairly small set of types that get written. So we
116 : * want to keep the template on AttributeInfo very small, and put all the
117 : * work in the template with a small number of instantiations (one per
118 : * type).
119 : */
120 : template <typename AttrType>
121 : CHIP_ERROR WriteAttribute(const AttrType & requestData, void * context, ClusterId clusterId, AttributeId attributeId,
122 : WriteResponseSuccessCallback successCb, WriteResponseFailureCallback failureCb,
123 : const Optional<uint16_t> & aTimedWriteTimeoutMs, WriteResponseDoneCallback doneCb = nullptr,
124 : const Optional<DataVersion> & aDataVersion = NullOptional)
125 : {
126 : auto onSuccessCb = [context, successCb](const app::ConcreteAttributePath & aPath) {
127 : if (successCb != nullptr)
128 : {
129 : successCb(context);
130 : }
131 : };
132 :
133 : auto onFailureCb = [context, failureCb](const app::ConcreteAttributePath * aPath, CHIP_ERROR aError) {
134 : if (failureCb != nullptr)
135 : {
136 : failureCb(context, aError);
137 : }
138 : };
139 :
140 : auto onDoneCb = [context, doneCb](app::WriteClient * pWriteClient) {
141 : if (doneCb != nullptr)
142 : {
143 : doneCb(context);
144 : }
145 : };
146 :
147 : return chip::Controller::WriteAttribute<AttrType>(mSession.Get().Value(), mEndpoint, clusterId, attributeId, requestData,
148 : onSuccessCb, onFailureCb, aTimedWriteTimeoutMs, onDoneCb, aDataVersion);
149 : }
150 :
151 : template <typename AttrType>
152 : CHIP_ERROR WriteAttribute(GroupId groupId, FabricIndex fabricIndex, const AttrType & requestData, void * context,
153 : ClusterId clusterId, AttributeId attributeId, WriteResponseSuccessCallback successCb,
154 : WriteResponseFailureCallback failureCb, const Optional<uint16_t> & aTimedWriteTimeoutMs,
155 : WriteResponseDoneCallback doneCb = nullptr, const Optional<DataVersion> & aDataVersion = NullOptional)
156 : {
157 :
158 : auto onSuccessCb = [context, successCb](const app::ConcreteAttributePath & aPath) {
159 : if (successCb != nullptr)
160 : {
161 : successCb(context);
162 : }
163 : };
164 :
165 : auto onFailureCb = [context, failureCb](const app::ConcreteAttributePath * aPath, CHIP_ERROR aError) {
166 : if (failureCb != nullptr)
167 : {
168 : failureCb(context, aError);
169 : }
170 : };
171 :
172 : auto onDoneCb = [context, doneCb](app::WriteClient * pWriteClient) {
173 : if (doneCb != nullptr)
174 : {
175 : doneCb(context);
176 : }
177 : };
178 :
179 : Transport::OutgoingGroupSession groupSession(groupId, fabricIndex);
180 : return chip::Controller::WriteAttribute<AttrType>(SessionHandle(groupSession), 0 /*Unused for Group*/, clusterId,
181 : attributeId, requestData, onSuccessCb, onFailureCb, aTimedWriteTimeoutMs,
182 : onDoneCb, aDataVersion);
183 : }
184 :
185 : template <typename AttributeInfo>
186 : CHIP_ERROR WriteAttribute(GroupId groupId, FabricIndex fabricIndex, const typename AttributeInfo::Type & requestData,
187 : void * context, WriteResponseSuccessCallback successCb, WriteResponseFailureCallback failureCb,
188 : WriteResponseDoneCallback doneCb = nullptr, const Optional<DataVersion> & aDataVersion = NullOptional,
189 : const Optional<uint16_t> & aTimedWriteTimeoutMs = NullOptional)
190 : {
191 : return WriteAttribute(groupId, fabricIndex, requestData, context, AttributeInfo::GetClusterId(),
192 : AttributeInfo::GetAttributeId(), successCb, failureCb, aTimedWriteTimeoutMs, doneCb, aDataVersion);
193 : }
194 :
195 : template <typename AttributeInfo>
196 : CHIP_ERROR WriteAttribute(const typename AttributeInfo::Type & requestData, void * context,
197 : WriteResponseSuccessCallback successCb, WriteResponseFailureCallback failureCb,
198 : const Optional<uint16_t> & aTimedWriteTimeoutMs, WriteResponseDoneCallback doneCb = nullptr,
199 : const Optional<DataVersion> & aDataVersion = NullOptional)
200 : {
201 : return WriteAttribute(requestData, context, AttributeInfo::GetClusterId(), AttributeInfo::GetAttributeId(), successCb,
202 : failureCb, aTimedWriteTimeoutMs, doneCb, aDataVersion);
203 : }
204 :
205 : template <typename AttributeInfo>
206 : CHIP_ERROR WriteAttribute(const typename AttributeInfo::Type & requestData, void * context,
207 : WriteResponseSuccessCallback successCb, WriteResponseFailureCallback failureCb,
208 : uint16_t aTimedWriteTimeoutMs, WriteResponseDoneCallback doneCb = nullptr,
209 : const Optional<DataVersion> & aDataVersion = NullOptional)
210 : {
211 : return WriteAttribute<AttributeInfo>(requestData, context, successCb, failureCb, MakeOptional(aTimedWriteTimeoutMs), doneCb,
212 : aDataVersion);
213 : }
214 :
215 : template <typename AttributeInfo, typename std::enable_if_t<!AttributeInfo::MustUseTimedWrite(), int> = 0>
216 : CHIP_ERROR WriteAttribute(const typename AttributeInfo::Type & requestData, void * context,
217 : WriteResponseSuccessCallback successCb, WriteResponseFailureCallback failureCb,
218 : WriteResponseDoneCallback doneCb = nullptr, const Optional<DataVersion> & aDataVersion = NullOptional)
219 : {
220 : return WriteAttribute<AttributeInfo>(requestData, context, successCb, failureCb, NullOptional, doneCb, aDataVersion);
221 : }
222 :
223 : #if CHIP_CONFIG_ENABLE_READ_CLIENT
224 : /**
225 : * Read an attribute and get a type-safe callback with the attribute value.
226 : */
227 : template <typename AttributeInfo>
228 0 : CHIP_ERROR ReadAttribute(void * context, ReadResponseSuccessCallback<typename AttributeInfo::DecodableArgType> successCb,
229 : ReadResponseFailureCallback failureCb, bool aIsFabricFiltered = true)
230 : {
231 0 : return ReadAttribute<typename AttributeInfo::DecodableType, typename AttributeInfo::DecodableArgType>(
232 0 : context, AttributeInfo::GetClusterId(), AttributeInfo::GetAttributeId(), successCb, failureCb, aIsFabricFiltered);
233 : }
234 :
235 : template <typename DecodableType, typename DecodableArgType>
236 0 : CHIP_ERROR ReadAttribute(void * context, ClusterId clusterId, AttributeId attributeId,
237 : ReadResponseSuccessCallback<DecodableArgType> successCb, ReadResponseFailureCallback failureCb,
238 : bool aIsFabricFiltered = true)
239 : {
240 0 : auto onSuccessCb = [context, successCb](const app::ConcreteAttributePath & aPath, const DecodableType & aData) {
241 0 : if (successCb != nullptr)
242 : {
243 0 : successCb(context, aData);
244 : }
245 : };
246 :
247 0 : auto onFailureCb = [context, failureCb](const app::ConcreteAttributePath * aPath, CHIP_ERROR aError) {
248 0 : if (failureCb != nullptr)
249 : {
250 0 : failureCb(context, aError);
251 : }
252 : };
253 :
254 0 : return Controller::ReadAttribute<DecodableType>(&mExchangeManager, mSession.Get().Value(), mEndpoint, clusterId,
255 0 : attributeId, onSuccessCb, onFailureCb, aIsFabricFiltered);
256 : }
257 :
258 : /**
259 : * Subscribe to attribute and get a type-safe callback with the attribute
260 : * value when it changes.
261 : */
262 : template <typename AttributeInfo>
263 : CHIP_ERROR
264 : SubscribeAttribute(void * context, ReadResponseSuccessCallback<typename AttributeInfo::DecodableArgType> reportCb,
265 : ReadResponseFailureCallback failureCb, uint16_t minIntervalFloorSeconds, uint16_t maxIntervalCeilingSeconds,
266 : SubscriptionEstablishedCallback subscriptionEstablishedCb = nullptr,
267 : ResubscriptionAttemptCallback resubscriptionAttemptCb = nullptr, bool aIsFabricFiltered = true,
268 : bool aKeepPreviousSubscriptions = false, const Optional<DataVersion> & aDataVersion = NullOptional,
269 : SubscriptionOnDoneCallback subscriptionDoneCb = nullptr)
270 : {
271 : return SubscribeAttribute<typename AttributeInfo::DecodableType, typename AttributeInfo::DecodableArgType>(
272 : context, AttributeInfo::GetClusterId(), AttributeInfo::GetAttributeId(), reportCb, failureCb, minIntervalFloorSeconds,
273 : maxIntervalCeilingSeconds, subscriptionEstablishedCb, resubscriptionAttemptCb, aIsFabricFiltered,
274 : aKeepPreviousSubscriptions, aDataVersion, subscriptionDoneCb);
275 : }
276 :
277 : template <typename DecodableType, typename DecodableArgType>
278 : CHIP_ERROR SubscribeAttribute(void * context, ClusterId clusterId, AttributeId attributeId,
279 : ReadResponseSuccessCallback<DecodableArgType> reportCb, ReadResponseFailureCallback failureCb,
280 : uint16_t minIntervalFloorSeconds, uint16_t maxIntervalCeilingSeconds,
281 : SubscriptionEstablishedCallback subscriptionEstablishedCb = nullptr,
282 : ResubscriptionAttemptCallback resubscriptionAttemptCb = nullptr, bool aIsFabricFiltered = true,
283 : bool aKeepPreviousSubscriptions = false,
284 : const Optional<DataVersion> & aDataVersion = NullOptional,
285 : SubscriptionOnDoneCallback subscriptionDoneCb = nullptr)
286 : {
287 : auto onReportCb = [context, reportCb](const app::ConcreteAttributePath & aPath, const DecodableType & aData) {
288 : if (reportCb != nullptr)
289 : {
290 : reportCb(context, aData);
291 : }
292 : };
293 :
294 : auto onFailureCb = [context, failureCb](const app::ConcreteAttributePath * aPath, CHIP_ERROR aError) {
295 : if (failureCb != nullptr)
296 : {
297 : failureCb(context, aError);
298 : }
299 : };
300 :
301 : auto onSubscriptionEstablishedCb = [context, subscriptionEstablishedCb](const app::ReadClient & readClient,
302 : SubscriptionId subscriptionId) {
303 : if (subscriptionEstablishedCb != nullptr)
304 : {
305 : subscriptionEstablishedCb(context, subscriptionId);
306 : }
307 : };
308 :
309 : auto onResubscriptionAttemptCb = [context, resubscriptionAttemptCb](const app::ReadClient & readClient, CHIP_ERROR aError,
310 : uint32_t aNextResubscribeIntervalMsec) {
311 : if (resubscriptionAttemptCb != nullptr)
312 : {
313 : resubscriptionAttemptCb(context, aError, aNextResubscribeIntervalMsec);
314 : }
315 : };
316 :
317 : return Controller::SubscribeAttribute<DecodableType>(
318 : &mExchangeManager, mSession.Get().Value(), mEndpoint, clusterId, attributeId, onReportCb, onFailureCb,
319 : minIntervalFloorSeconds, maxIntervalCeilingSeconds, onSubscriptionEstablishedCb, onResubscriptionAttemptCb,
320 : aIsFabricFiltered, aKeepPreviousSubscriptions, aDataVersion, subscriptionDoneCb);
321 : }
322 :
323 : /**
324 : * Read an event and get a type-safe callback with the event data.
325 : *
326 : * @param[in] successCb Used to deliver event data received through the Read interactions
327 : * @param[in] failureCb failureCb will be called when an error occurs *after* a successful call to ReadEvent.
328 : * @param[in] doneCb OnDone will be called when ReadClient has finished all work for event retrieval, it is possible that
329 : * there is no event.
330 : */
331 : template <typename DecodableType>
332 : CHIP_ERROR ReadEvent(void * context, ReadResponseSuccessCallback<DecodableType> successCb,
333 : ReadResponseFailureCallback failureCb, ReadDoneCallback doneCb)
334 : {
335 : auto onSuccessCb = [context, successCb](const app::EventHeader & aEventHeader, const DecodableType & aData) {
336 : if (successCb != nullptr)
337 : {
338 : successCb(context, aData);
339 : }
340 : };
341 :
342 : auto onFailureCb = [context, failureCb](const app::EventHeader * aEventHeader, CHIP_ERROR aError) {
343 : if (failureCb != nullptr)
344 : {
345 : failureCb(context, aError);
346 : }
347 : };
348 :
349 : auto onDoneCb = [context, doneCb](app::ReadClient * apReadClient) {
350 : if (doneCb != nullptr)
351 : {
352 : doneCb(context);
353 : }
354 : };
355 : return Controller::ReadEvent<DecodableType>(&mExchangeManager, mSession.Get().Value(), mEndpoint, onSuccessCb, onFailureCb,
356 : onDoneCb);
357 : }
358 :
359 : template <typename DecodableType>
360 : CHIP_ERROR SubscribeEvent(void * context, ReadResponseSuccessCallback<DecodableType> reportCb,
361 : ReadResponseFailureCallback failureCb, uint16_t minIntervalFloorSeconds,
362 : uint16_t maxIntervalCeilingSeconds,
363 : SubscriptionEstablishedCallback subscriptionEstablishedCb = nullptr,
364 : ResubscriptionAttemptCallback resubscriptionAttemptCb = nullptr,
365 : bool aKeepPreviousSubscriptions = false, bool aIsUrgentEvent = false)
366 : {
367 : auto onReportCb = [context, reportCb](const app::EventHeader & aEventHeader, const DecodableType & aData) {
368 : if (reportCb != nullptr)
369 : {
370 : reportCb(context, aData);
371 : }
372 : };
373 :
374 : auto onFailureCb = [context, failureCb](const app::EventHeader * aEventHeader, CHIP_ERROR aError) {
375 : if (failureCb != nullptr)
376 : {
377 : failureCb(context, aError);
378 : }
379 : };
380 :
381 : auto onSubscriptionEstablishedCb = [context, subscriptionEstablishedCb](const app::ReadClient & readClient,
382 : SubscriptionId subscriptionId) {
383 : if (subscriptionEstablishedCb != nullptr)
384 : {
385 : subscriptionEstablishedCb(context, subscriptionId);
386 : }
387 : };
388 :
389 : auto onResubscriptionAttemptCb = [context, resubscriptionAttemptCb](const app::ReadClient & readClient, CHIP_ERROR aError,
390 : uint32_t aNextResubscribeIntervalMsec) {
391 : if (resubscriptionAttemptCb != nullptr)
392 : {
393 : resubscriptionAttemptCb(context, aError, aNextResubscribeIntervalMsec);
394 : }
395 : };
396 :
397 : return Controller::SubscribeEvent<DecodableType>(&mExchangeManager, mSession.Get().Value(), mEndpoint, onReportCb,
398 : onFailureCb, minIntervalFloorSeconds, maxIntervalCeilingSeconds,
399 : onSubscriptionEstablishedCb, onResubscriptionAttemptCb,
400 : aKeepPreviousSubscriptions, aIsUrgentEvent);
401 : }
402 : #endif // CHIP_CONFIG_ENABLE_READ_CLIENT
403 :
404 : protected:
405 : Messaging::ExchangeManager & mExchangeManager;
406 :
407 : // Since cluster object is ephemeral, the session shall be valid during the entire lifespan, so we do not need to check the
408 : // session existence when using it. For java and objective-c binding, the cluster object is allocated in the heap, such that we
409 : // can't use SessionHandle here, in such case, the cluster object must be freed when the session is released.
410 : SessionHolder mSession;
411 :
412 : EndpointId mEndpoint;
413 : Optional<System::Clock::Timeout> mTimeout;
414 : };
415 :
416 : } // namespace Controller
417 : } // namespace chip
|