| /* | 
 |  * | 
 |  *    Copyright (c) 2020 Project CHIP Authors | 
 |  *    All rights reserved. | 
 |  * | 
 |  *    Licensed under the Apache License, Version 2.0 (the "License"); | 
 |  *    you may not use this file except in compliance with the License. | 
 |  *    You may obtain a copy of the License at | 
 |  * | 
 |  *        http://www.apache.org/licenses/LICENSE-2.0 | 
 |  * | 
 |  *    Unless required by applicable law or agreed to in writing, software | 
 |  *    distributed under the License is distributed on an "AS IS" BASIS, | 
 |  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 |  *    See the License for the specific language governing permissions and | 
 |  *    limitations under the License. | 
 |  */ | 
 |  | 
 | /** | 
 |  *  @file | 
 |  *    This file contains definitions for a base Cluster class. This class will | 
 |  *    be derived by various ZCL clusters supported by CHIP. The objects of the | 
 |  *    ZCL cluster class will be used by Controller applications to interact with | 
 |  *    the CHIP device. | 
 |  */ | 
 |  | 
 | #pragma once | 
 |  | 
 | #include "app/ConcreteCommandPath.h" | 
 | #include <app/DeviceProxy.h> | 
 | #include <app/util/error-mapping.h> | 
 | #include <controller/InvokeInteraction.h> | 
 | #include <controller/ReadInteraction.h> | 
 | #include <controller/WriteInteraction.h> | 
 | #include <lib/core/Optional.h> | 
 | #include <messaging/ExchangeMgr.h> | 
 | #include <system/SystemClock.h> | 
 |  | 
 | namespace chip { | 
 | namespace Controller { | 
 |  | 
 | template <typename T> | 
 | using CommandResponseSuccessCallback = void(void * context, const T & responseObject); | 
 | using CommandResponseFailureCallback = void(void * context, CHIP_ERROR err); | 
 | using CommandResponseDoneCallback    = void(); | 
 | using WriteResponseSuccessCallback   = void (*)(void * context); | 
 | using WriteResponseFailureCallback   = void (*)(void * context, CHIP_ERROR err); | 
 | using WriteResponseDoneCallback      = void (*)(void * context); | 
 | template <typename T> | 
 | using ReadResponseSuccessCallback     = void (*)(void * context, T responseData); | 
 | using ReadResponseFailureCallback     = void (*)(void * context, CHIP_ERROR err); | 
 | using ReadDoneCallback                = void (*)(void * context); | 
 | using SubscriptionEstablishedCallback = void (*)(void * context, SubscriptionId subscriptionId); | 
 | using ResubscriptionAttemptCallback   = void (*)(void * context, CHIP_ERROR aError, uint32_t aNextResubscribeIntervalMsec); | 
 | using SubscriptionOnDoneCallback      = std::function<void(void)>; | 
 |  | 
 | class DLL_EXPORT ClusterBase | 
 | { | 
 | public: | 
 |     ClusterBase(Messaging::ExchangeManager & exchangeManager, const SessionHandle & session, EndpointId endpoint) : | 
 |         mExchangeManager(exchangeManager), mSession(session), mEndpoint(endpoint) | 
 |     {} | 
 |  | 
 |     virtual ~ClusterBase() {} | 
 |  | 
 |     // Temporary function to set command timeout before we move over to InvokeCommand | 
 |     // TODO: remove when we start using InvokeCommand everywhere | 
 |     void SetCommandTimeout(Optional<System::Clock::Timeout> timeout) { mTimeout = timeout; } | 
 |  | 
 |     /** | 
 |      * Returns the current command timeout set via SetCommandTimeout, or an | 
 |      * empty optional if no timeout has been set. | 
 |      */ | 
 |     Optional<System::Clock::Timeout> GetCommandTimeout() { return mTimeout; } | 
 |  | 
 |     /* | 
 |      * This function permits sending an invoke request using cluster objects that represent the request and response data payloads. | 
 |      * | 
 |      * Success and Failure callbacks must be passed in through which the decoded response is provided as well as notification of any | 
 |      * failure. | 
 |      */ | 
 |     template <typename RequestDataT> | 
 |     CHIP_ERROR InvokeCommand(const RequestDataT & requestData, void * context, | 
 |                              CommandResponseSuccessCallback<typename RequestDataT::ResponseType> successCb, | 
 |                              CommandResponseFailureCallback failureCb, const Optional<uint16_t> & timedInvokeTimeoutMs) | 
 |     { | 
 |         auto onSuccessCb = [context, successCb](const app::ConcreteCommandPath & aPath, const app::StatusIB & aStatus, | 
 |                                                 const typename RequestDataT::ResponseType & responseData) { | 
 |             successCb(context, responseData); | 
 |         }; | 
 |  | 
 |         auto onFailureCb = [context, failureCb](CHIP_ERROR aError) { failureCb(context, aError); }; | 
 |  | 
 |         return InvokeCommandRequest(&mExchangeManager, mSession.Get().Value(), mEndpoint, requestData, onSuccessCb, onFailureCb, | 
 |                                     timedInvokeTimeoutMs, mTimeout); | 
 |     } | 
 |  | 
 |     template <typename RequestDataT> | 
 |     CHIP_ERROR InvokeCommand(const RequestDataT & requestData, void * context, | 
 |                              CommandResponseSuccessCallback<typename RequestDataT::ResponseType> successCb, | 
 |                              CommandResponseFailureCallback failureCb, uint16_t timedInvokeTimeoutMs) | 
 |     { | 
 |         return InvokeCommand(requestData, context, successCb, failureCb, MakeOptional(timedInvokeTimeoutMs)); | 
 |     } | 
 |  | 
 |     template <typename RequestDataT, typename std::enable_if_t<!RequestDataT::MustUseTimedInvoke(), int> = 0> | 
 |     CHIP_ERROR InvokeCommand(const RequestDataT & requestData, void * context, | 
 |                              CommandResponseSuccessCallback<typename RequestDataT::ResponseType> successCb, | 
 |                              CommandResponseFailureCallback failureCb) | 
 |     { | 
 |         return InvokeCommand(requestData, context, successCb, failureCb, NullOptional); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Functions for writing attributes.  We have lots of different | 
 |      * AttributeInfo but a fairly small set of types that get written.  So we | 
 |      * want to keep the template on AttributeInfo very small, and put all the | 
 |      * work in the template with a small number of instantiations (one per | 
 |      * type). | 
 |      */ | 
 |     template <typename AttrType> | 
 |     CHIP_ERROR WriteAttribute(const AttrType & requestData, void * context, ClusterId clusterId, AttributeId attributeId, | 
 |                               WriteResponseSuccessCallback successCb, WriteResponseFailureCallback failureCb, | 
 |                               const Optional<uint16_t> & aTimedWriteTimeoutMs, WriteResponseDoneCallback doneCb = nullptr, | 
 |                               const Optional<DataVersion> & aDataVersion = NullOptional) | 
 |     { | 
 |         auto onSuccessCb = [context, successCb](const app::ConcreteAttributePath & aPath) { | 
 |             if (successCb != nullptr) | 
 |             { | 
 |                 successCb(context); | 
 |             } | 
 |         }; | 
 |  | 
 |         auto onFailureCb = [context, failureCb](const app::ConcreteAttributePath * aPath, CHIP_ERROR aError) { | 
 |             if (failureCb != nullptr) | 
 |             { | 
 |                 failureCb(context, aError); | 
 |             } | 
 |         }; | 
 |  | 
 |         auto onDoneCb = [context, doneCb](app::WriteClient * pWriteClient) { | 
 |             if (doneCb != nullptr) | 
 |             { | 
 |                 doneCb(context); | 
 |             } | 
 |         }; | 
 |  | 
 |         return chip::Controller::WriteAttribute<AttrType>(mSession.Get().Value(), mEndpoint, clusterId, attributeId, requestData, | 
 |                                                           onSuccessCb, onFailureCb, aTimedWriteTimeoutMs, onDoneCb, aDataVersion); | 
 |     } | 
 |  | 
 |     template <typename AttrType> | 
 |     CHIP_ERROR WriteAttribute(GroupId groupId, FabricIndex fabricIndex, const AttrType & requestData, void * context, | 
 |                               ClusterId clusterId, AttributeId attributeId, WriteResponseSuccessCallback successCb, | 
 |                               WriteResponseFailureCallback failureCb, const Optional<uint16_t> & aTimedWriteTimeoutMs, | 
 |                               WriteResponseDoneCallback doneCb = nullptr, const Optional<DataVersion> & aDataVersion = NullOptional) | 
 |     { | 
 |  | 
 |         auto onSuccessCb = [context, successCb](const app::ConcreteAttributePath & aPath) { | 
 |             if (successCb != nullptr) | 
 |             { | 
 |                 successCb(context); | 
 |             } | 
 |         }; | 
 |  | 
 |         auto onFailureCb = [context, failureCb](const app::ConcreteAttributePath * aPath, CHIP_ERROR aError) { | 
 |             if (failureCb != nullptr) | 
 |             { | 
 |                 failureCb(context, aError); | 
 |             } | 
 |         }; | 
 |  | 
 |         auto onDoneCb = [context, doneCb](app::WriteClient * pWriteClient) { | 
 |             if (doneCb != nullptr) | 
 |             { | 
 |                 doneCb(context); | 
 |             } | 
 |         }; | 
 |  | 
 |         Transport::OutgoingGroupSession groupSession(groupId, fabricIndex); | 
 |         return chip::Controller::WriteAttribute<AttrType>(SessionHandle(groupSession), 0 /*Unused for Group*/, clusterId, | 
 |                                                           attributeId, requestData, onSuccessCb, onFailureCb, aTimedWriteTimeoutMs, | 
 |                                                           onDoneCb, aDataVersion); | 
 |     } | 
 |  | 
 |     template <typename AttributeInfo> | 
 |     CHIP_ERROR WriteAttribute(GroupId groupId, FabricIndex fabricIndex, const typename AttributeInfo::Type & requestData, | 
 |                               void * context, WriteResponseSuccessCallback successCb, WriteResponseFailureCallback failureCb, | 
 |                               WriteResponseDoneCallback doneCb = nullptr, const Optional<DataVersion> & aDataVersion = NullOptional, | 
 |                               const Optional<uint16_t> & aTimedWriteTimeoutMs = NullOptional) | 
 |     { | 
 |         return WriteAttribute(groupId, fabricIndex, requestData, context, AttributeInfo::GetClusterId(), | 
 |                               AttributeInfo::GetAttributeId(), successCb, failureCb, aTimedWriteTimeoutMs, doneCb, aDataVersion); | 
 |     } | 
 |  | 
 |     template <typename AttributeInfo> | 
 |     CHIP_ERROR WriteAttribute(const typename AttributeInfo::Type & requestData, void * context, | 
 |                               WriteResponseSuccessCallback successCb, WriteResponseFailureCallback failureCb, | 
 |                               const Optional<uint16_t> & aTimedWriteTimeoutMs, WriteResponseDoneCallback doneCb = nullptr, | 
 |                               const Optional<DataVersion> & aDataVersion = NullOptional) | 
 |     { | 
 |         return WriteAttribute(requestData, context, AttributeInfo::GetClusterId(), AttributeInfo::GetAttributeId(), successCb, | 
 |                               failureCb, aTimedWriteTimeoutMs, doneCb, aDataVersion); | 
 |     } | 
 |  | 
 |     template <typename AttributeInfo> | 
 |     CHIP_ERROR WriteAttribute(const typename AttributeInfo::Type & requestData, void * context, | 
 |                               WriteResponseSuccessCallback successCb, WriteResponseFailureCallback failureCb, | 
 |                               uint16_t aTimedWriteTimeoutMs, WriteResponseDoneCallback doneCb = nullptr, | 
 |                               const Optional<DataVersion> & aDataVersion = NullOptional) | 
 |     { | 
 |         return WriteAttribute<AttributeInfo>(requestData, context, successCb, failureCb, MakeOptional(aTimedWriteTimeoutMs), doneCb, | 
 |                                              aDataVersion); | 
 |     } | 
 |  | 
 |     template <typename AttributeInfo, typename std::enable_if_t<!AttributeInfo::MustUseTimedWrite(), int> = 0> | 
 |     CHIP_ERROR WriteAttribute(const typename AttributeInfo::Type & requestData, void * context, | 
 |                               WriteResponseSuccessCallback successCb, WriteResponseFailureCallback failureCb, | 
 |                               WriteResponseDoneCallback doneCb = nullptr, const Optional<DataVersion> & aDataVersion = NullOptional) | 
 |     { | 
 |         return WriteAttribute<AttributeInfo>(requestData, context, successCb, failureCb, NullOptional, doneCb, aDataVersion); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Read an attribute and get a type-safe callback with the attribute value. | 
 |      */ | 
 |     template <typename AttributeInfo> | 
 |     CHIP_ERROR ReadAttribute(void * context, ReadResponseSuccessCallback<typename AttributeInfo::DecodableArgType> successCb, | 
 |                              ReadResponseFailureCallback failureCb, bool aIsFabricFiltered = true) | 
 |     { | 
 |         return ReadAttribute<typename AttributeInfo::DecodableType, typename AttributeInfo::DecodableArgType>( | 
 |             context, AttributeInfo::GetClusterId(), AttributeInfo::GetAttributeId(), successCb, failureCb, aIsFabricFiltered); | 
 |     } | 
 |  | 
 |     template <typename DecodableType, typename DecodableArgType> | 
 |     CHIP_ERROR ReadAttribute(void * context, ClusterId clusterId, AttributeId attributeId, | 
 |                              ReadResponseSuccessCallback<DecodableArgType> successCb, ReadResponseFailureCallback failureCb, | 
 |                              bool aIsFabricFiltered = true) | 
 |     { | 
 |         auto onSuccessCb = [context, successCb](const app::ConcreteAttributePath & aPath, const DecodableType & aData) { | 
 |             if (successCb != nullptr) | 
 |             { | 
 |                 successCb(context, aData); | 
 |             } | 
 |         }; | 
 |  | 
 |         auto onFailureCb = [context, failureCb](const app::ConcreteAttributePath * aPath, CHIP_ERROR aError) { | 
 |             if (failureCb != nullptr) | 
 |             { | 
 |                 failureCb(context, aError); | 
 |             } | 
 |         }; | 
 |  | 
 |         return Controller::ReadAttribute<DecodableType>(&mExchangeManager, mSession.Get().Value(), mEndpoint, clusterId, | 
 |                                                         attributeId, onSuccessCb, onFailureCb, aIsFabricFiltered); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Subscribe to attribute and get a type-safe callback with the attribute | 
 |      * value when it changes. | 
 |      */ | 
 |     template <typename AttributeInfo> | 
 |     CHIP_ERROR | 
 |     SubscribeAttribute(void * context, ReadResponseSuccessCallback<typename AttributeInfo::DecodableArgType> reportCb, | 
 |                        ReadResponseFailureCallback failureCb, uint16_t minIntervalFloorSeconds, uint16_t maxIntervalCeilingSeconds, | 
 |                        SubscriptionEstablishedCallback subscriptionEstablishedCb = nullptr, | 
 |                        ResubscriptionAttemptCallback resubscriptionAttemptCb = nullptr, bool aIsFabricFiltered = true, | 
 |                        bool aKeepPreviousSubscriptions = false, const Optional<DataVersion> & aDataVersion = NullOptional, | 
 |                        SubscriptionOnDoneCallback subscriptionDoneCb = nullptr) | 
 |     { | 
 |         return SubscribeAttribute<typename AttributeInfo::DecodableType, typename AttributeInfo::DecodableArgType>( | 
 |             context, AttributeInfo::GetClusterId(), AttributeInfo::GetAttributeId(), reportCb, failureCb, minIntervalFloorSeconds, | 
 |             maxIntervalCeilingSeconds, subscriptionEstablishedCb, resubscriptionAttemptCb, aIsFabricFiltered, | 
 |             aKeepPreviousSubscriptions, aDataVersion, subscriptionDoneCb); | 
 |     } | 
 |  | 
 |     template <typename DecodableType, typename DecodableArgType> | 
 |     CHIP_ERROR SubscribeAttribute(void * context, ClusterId clusterId, AttributeId attributeId, | 
 |                                   ReadResponseSuccessCallback<DecodableArgType> reportCb, ReadResponseFailureCallback failureCb, | 
 |                                   uint16_t minIntervalFloorSeconds, uint16_t maxIntervalCeilingSeconds, | 
 |                                   SubscriptionEstablishedCallback subscriptionEstablishedCb = nullptr, | 
 |                                   ResubscriptionAttemptCallback resubscriptionAttemptCb = nullptr, bool aIsFabricFiltered = true, | 
 |                                   bool aKeepPreviousSubscriptions               = false, | 
 |                                   const Optional<DataVersion> & aDataVersion    = NullOptional, | 
 |                                   SubscriptionOnDoneCallback subscriptionDoneCb = nullptr) | 
 |     { | 
 |         auto onReportCb = [context, reportCb](const app::ConcreteAttributePath & aPath, const DecodableType & aData) { | 
 |             if (reportCb != nullptr) | 
 |             { | 
 |                 reportCb(context, aData); | 
 |             } | 
 |         }; | 
 |  | 
 |         auto onFailureCb = [context, failureCb](const app::ConcreteAttributePath * aPath, CHIP_ERROR aError) { | 
 |             if (failureCb != nullptr) | 
 |             { | 
 |                 failureCb(context, aError); | 
 |             } | 
 |         }; | 
 |  | 
 |         auto onSubscriptionEstablishedCb = [context, subscriptionEstablishedCb](const app::ReadClient & readClient, | 
 |                                                                                 SubscriptionId subscriptionId) { | 
 |             if (subscriptionEstablishedCb != nullptr) | 
 |             { | 
 |                 subscriptionEstablishedCb(context, subscriptionId); | 
 |             } | 
 |         }; | 
 |  | 
 |         auto onResubscriptionAttemptCb = [context, resubscriptionAttemptCb](const app::ReadClient & readClient, CHIP_ERROR aError, | 
 |                                                                             uint32_t aNextResubscribeIntervalMsec) { | 
 |             if (resubscriptionAttemptCb != nullptr) | 
 |             { | 
 |                 resubscriptionAttemptCb(context, aError, aNextResubscribeIntervalMsec); | 
 |             } | 
 |         }; | 
 |  | 
 |         return Controller::SubscribeAttribute<DecodableType>( | 
 |             &mExchangeManager, mSession.Get().Value(), mEndpoint, clusterId, attributeId, onReportCb, onFailureCb, | 
 |             minIntervalFloorSeconds, maxIntervalCeilingSeconds, onSubscriptionEstablishedCb, onResubscriptionAttemptCb, | 
 |             aIsFabricFiltered, aKeepPreviousSubscriptions, aDataVersion, subscriptionDoneCb); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Read an event and get a type-safe callback with the event data. | 
 |      * | 
 |      * @param[in] successCb Used to deliver event data received through the Read interactions | 
 |      * @param[in] failureCb failureCb will be called when an error occurs *after* a successful call to ReadEvent. | 
 |      * @param[in] doneCb    OnDone will be called when ReadClient has finished all work for event retrieval, it is possible that | 
 |      * there is no event. | 
 |      */ | 
 |     template <typename DecodableType> | 
 |     CHIP_ERROR ReadEvent(void * context, ReadResponseSuccessCallback<DecodableType> successCb, | 
 |                          ReadResponseFailureCallback failureCb, ReadDoneCallback doneCb) | 
 |     { | 
 |         auto onSuccessCb = [context, successCb](const app::EventHeader & aEventHeader, const DecodableType & aData) { | 
 |             if (successCb != nullptr) | 
 |             { | 
 |                 successCb(context, aData); | 
 |             } | 
 |         }; | 
 |  | 
 |         auto onFailureCb = [context, failureCb](const app::EventHeader * aEventHeader, CHIP_ERROR aError) { | 
 |             if (failureCb != nullptr) | 
 |             { | 
 |                 failureCb(context, aError); | 
 |             } | 
 |         }; | 
 |  | 
 |         auto onDoneCb = [context, doneCb](app::ReadClient * apReadClient) { | 
 |             if (doneCb != nullptr) | 
 |             { | 
 |                 doneCb(context); | 
 |             } | 
 |         }; | 
 |         return Controller::ReadEvent<DecodableType>(&mExchangeManager, mSession.Get().Value(), mEndpoint, onSuccessCb, onFailureCb, | 
 |                                                     onDoneCb); | 
 |     } | 
 |  | 
 |     template <typename DecodableType> | 
 |     CHIP_ERROR SubscribeEvent(void * context, ReadResponseSuccessCallback<DecodableType> reportCb, | 
 |                               ReadResponseFailureCallback failureCb, uint16_t minIntervalFloorSeconds, | 
 |                               uint16_t maxIntervalCeilingSeconds, | 
 |                               SubscriptionEstablishedCallback subscriptionEstablishedCb = nullptr, | 
 |                               ResubscriptionAttemptCallback resubscriptionAttemptCb     = nullptr, | 
 |                               bool aKeepPreviousSubscriptions = false, bool aIsUrgentEvent = false) | 
 |     { | 
 |         auto onReportCb = [context, reportCb](const app::EventHeader & aEventHeader, const DecodableType & aData) { | 
 |             if (reportCb != nullptr) | 
 |             { | 
 |                 reportCb(context, aData); | 
 |             } | 
 |         }; | 
 |  | 
 |         auto onFailureCb = [context, failureCb](const app::EventHeader * aEventHeader, CHIP_ERROR aError) { | 
 |             if (failureCb != nullptr) | 
 |             { | 
 |                 failureCb(context, aError); | 
 |             } | 
 |         }; | 
 |  | 
 |         auto onSubscriptionEstablishedCb = [context, subscriptionEstablishedCb](const app::ReadClient & readClient, | 
 |                                                                                 SubscriptionId subscriptionId) { | 
 |             if (subscriptionEstablishedCb != nullptr) | 
 |             { | 
 |                 subscriptionEstablishedCb(context, subscriptionId); | 
 |             } | 
 |         }; | 
 |  | 
 |         auto onResubscriptionAttemptCb = [context, resubscriptionAttemptCb](const app::ReadClient & readClient, CHIP_ERROR aError, | 
 |                                                                             uint32_t aNextResubscribeIntervalMsec) { | 
 |             if (resubscriptionAttemptCb != nullptr) | 
 |             { | 
 |                 resubscriptionAttemptCb(context, aError, aNextResubscribeIntervalMsec); | 
 |             } | 
 |         }; | 
 |  | 
 |         return Controller::SubscribeEvent<DecodableType>(&mExchangeManager, mSession.Get().Value(), mEndpoint, onReportCb, | 
 |                                                          onFailureCb, minIntervalFloorSeconds, maxIntervalCeilingSeconds, | 
 |                                                          onSubscriptionEstablishedCb, onResubscriptionAttemptCb, | 
 |                                                          aKeepPreviousSubscriptions, aIsUrgentEvent); | 
 |     } | 
 |  | 
 | protected: | 
 |     Messaging::ExchangeManager & mExchangeManager; | 
 |  | 
 |     // Since cluster object is ephemeral, the session shall be valid during the entire lifespan, so we do not need to check the | 
 |     // session existence when using it. For java and objective-c binding, the cluster object is allocated in the heap, such that we | 
 |     // can't use SessionHandle here, in such case, the cluster object must be freed when the session is released. | 
 |     SessionHolder mSession; | 
 |  | 
 |     EndpointId mEndpoint; | 
 |     Optional<System::Clock::Timeout> mTimeout; | 
 | }; | 
 |  | 
 | } // namespace Controller | 
 | } // namespace chip |