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