| /* |
| * |
| * 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/AppBuildConfig.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); |
| } |
| |
| #if CHIP_CONFIG_ENABLE_READ_CLIENT |
| /** |
| * 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); |
| } |
| #endif // CHIP_CONFIG_ENABLE_READ_CLIENT |
| |
| 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 |