blob: 4324092871424b678ea6845592c916daa9bf4503 [file] [log] [blame]
/*
*
* 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);
}
#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