blob: de95e45f6c100c04facdc0718169613e9a6ca9a4 [file] [log] [blame]
/*
*
* Copyright (c) 2021 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.
*/
#pragma once
#include <app/ChunkedWriteCallback.h>
#include <app/InteractionModelEngine.h>
#include <app/WriteClient.h>
#include <controller/CommandSenderAllocator.h>
#include <controller/TypedCommandCallback.h>
#include <functional>
#include <lib/core/Optional.h>
namespace chip {
namespace Controller {
namespace Internal {
// WriteCancelFn functions on WriteAttribute() are for internal use only.
typedef std::function<void()> WriteCancelFn;
} // namespace Internal
/*
* An adapter callback that permits applications to provide std::function callbacks for success, error and on done.
* This permits a slightly more flexible programming model that allows applications to pass in lambdas and bound member functions
* as they see fit instead.
*
*/
class WriteCallback final : public app::WriteClient::Callback
{
public:
using OnSuccessCallbackType = std::function<void(const app::ConcreteAttributePath &)>;
//
// Callback to deliver any error that occurs during the write. This includes
// errors global to the write as a whole (e.g timeout) as well as per-attribute
// errors.
//
// In the latter case, path will be non-null. Otherwise, it shall be null.
//
using OnErrorCallbackType = std::function<void(const app::ConcreteAttributePath * path, CHIP_ERROR err)>;
using OnDoneCallbackType = std::function<void(app::WriteClient *)>;
WriteCallback(OnSuccessCallbackType aOnSuccess, OnErrorCallbackType aOnError, OnDoneCallbackType aOnDone, bool aIsGroupWrite) :
mOnSuccess(aOnSuccess), mOnError(aOnError), mOnDone(aOnDone), mIsGroupWrite(aIsGroupWrite), mCallback(this)
{}
app::WriteClient::Callback * GetChunkedCallback() { return &mCallback; }
void OnResponse(const app::WriteClient * apWriteClient, const app::ConcreteDataAttributePath & aPath,
app::StatusIB status) override
{
if (mCalledCallback)
{
return;
}
mCalledCallback = true;
if (status.IsSuccess())
{
mOnSuccess(aPath);
}
else
{
mOnError(&aPath, status.ToChipError());
}
}
void OnError(const app::WriteClient * apWriteClient, CHIP_ERROR aError) override
{
if (mCalledCallback)
{
return;
}
mCalledCallback = true;
mOnError(nullptr, aError);
}
void OnDone(app::WriteClient * apWriteClient) override
{
if (!mIsGroupWrite && !mCalledCallback)
{
// This can happen if the server sends a response with an empty
// WriteResponses list. Since we are not sending wildcard write
// paths, that's not a valid response and we should treat it as an
// error. Use the error we would have gotten if we in fact expected
// a nonempty list.
OnError(apWriteClient, CHIP_END_OF_TLV);
}
if (mOnDone != nullptr)
{
mOnDone(apWriteClient);
}
chip::Platform::Delete(apWriteClient);
// Always needs to be the last call
chip::Platform::Delete(this);
}
private:
OnSuccessCallbackType mOnSuccess = nullptr;
OnErrorCallbackType mOnError = nullptr;
OnDoneCallbackType mOnDone = nullptr;
bool mCalledCallback = false;
bool mIsGroupWrite = false;
app::ChunkedWriteCallback mCallback;
};
/**
* 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 SessionHandle & sessionHandle, chip::EndpointId endpointId, ClusterId clusterId,
AttributeId attributeId, const AttrType & requestData, WriteCallback::OnSuccessCallbackType onSuccessCb,
WriteCallback::OnErrorCallbackType onErrorCb, const Optional<uint16_t> & aTimedWriteTimeoutMs,
WriteCallback::OnDoneCallbackType onDoneCb = nullptr,
const Optional<DataVersion> & aDataVersion = NullOptional,
Internal::WriteCancelFn * outCancelFn = nullptr)
{
auto callback = Platform::MakeUnique<WriteCallback>(onSuccessCb, onErrorCb, onDoneCb, sessionHandle->IsGroupSession());
VerifyOrReturnError(callback != nullptr, CHIP_ERROR_NO_MEMORY);
auto client = Platform::MakeUnique<app::WriteClient>(app::InteractionModelEngine::GetInstance()->GetExchangeManager(),
callback->GetChunkedCallback(), aTimedWriteTimeoutMs);
VerifyOrReturnError(client != nullptr, CHIP_ERROR_NO_MEMORY);
if (sessionHandle->IsGroupSession())
{
ReturnErrorOnFailure(client->EncodeAttribute(chip::app::AttributePathParams(clusterId, attributeId), requestData));
}
else
{
ReturnErrorOnFailure(
client->EncodeAttribute(chip::app::AttributePathParams(endpointId, clusterId, attributeId), requestData, aDataVersion));
}
ReturnErrorOnFailure(client->SendWriteRequest(sessionHandle));
// If requested by the caller, provide a way to cancel the write interaction.
if (outCancelFn != nullptr)
{
*outCancelFn = [rawCallback = callback.get(), rawClient = client.get()]() {
chip::Platform::Delete(rawClient);
chip::Platform::Delete(rawCallback);
};
}
// At this point the handle will ensure our callback's OnDone is always
// called.
client.release();
callback.release();
return CHIP_NO_ERROR;
}
template <typename AttributeInfo>
CHIP_ERROR WriteAttribute(const SessionHandle & sessionHandle, chip::EndpointId endpointId,
const typename AttributeInfo::Type & requestData, WriteCallback::OnSuccessCallbackType onSuccessCb,
WriteCallback::OnErrorCallbackType onErrorCb, const Optional<uint16_t> & aTimedWriteTimeoutMs,
WriteCallback::OnDoneCallbackType onDoneCb = nullptr,
const Optional<DataVersion> & aDataVersion = NullOptional)
{
return WriteAttribute(sessionHandle, endpointId, AttributeInfo::GetClusterId(), AttributeInfo::GetAttributeId(), requestData,
onSuccessCb, onErrorCb, aTimedWriteTimeoutMs, onDoneCb, aDataVersion);
}
template <typename AttributeInfo>
CHIP_ERROR WriteAttribute(const SessionHandle & sessionHandle, chip::EndpointId endpointId,
const typename AttributeInfo::Type & requestData, WriteCallback::OnSuccessCallbackType onSuccessCb,
WriteCallback::OnErrorCallbackType onErrorCb, uint16_t aTimedWriteTimeoutMs,
WriteCallback::OnDoneCallbackType onDoneCb = nullptr,
const Optional<DataVersion> & aDataVersion = NullOptional)
{
return WriteAttribute<AttributeInfo>(sessionHandle, endpointId, requestData, onSuccessCb, onErrorCb, onDoneCb,
MakeOptional(aTimedWriteTimeoutMs), onDoneCb, aDataVersion);
}
template <typename AttributeInfo, typename std::enable_if_t<!AttributeInfo::MustUseTimedWrite(), int> = 0>
CHIP_ERROR WriteAttribute(const SessionHandle & sessionHandle, chip::EndpointId endpointId,
const typename AttributeInfo::Type & requestData, WriteCallback::OnSuccessCallbackType onSuccessCb,
WriteCallback::OnErrorCallbackType onErrorCb, WriteCallback::OnDoneCallbackType onDoneCb = nullptr,
const Optional<DataVersion> & aDataVersion = NullOptional)
{
return WriteAttribute<AttributeInfo>(sessionHandle, endpointId, requestData, onSuccessCb, onErrorCb, NullOptional, onDoneCb,
aDataVersion);
}
} // namespace Controller
} // namespace chip