blob: c16318070a68ae0f986facdcc7e0797223cadf52 [file] [log] [blame]
/*
* Copyright (c) 2022 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/BufferedReadCallback.h>
#include <app/ChunkedWriteCallback.h>
#include <app/CommandSender.h>
#include <app/DeviceProxy.h>
#include <app/InteractionModelEngine.h>
#include <app/ReadClient.h>
#include <app/WriteClient.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/UnitTestUtils.h>
constexpr uint8_t kMaxAllowedPaths = 10;
class InteractionModelConfig
{
public:
struct AttributePathsConfig
{
size_t count = 0;
std::unique_ptr<chip::app::AttributePathParams[]> attributePathParams;
std::unique_ptr<chip::app::DataVersionFilter[]> dataVersionFilter;
};
static CHIP_ERROR GetAttributePaths(std::vector<chip::EndpointId> endpointIds, std::vector<chip::ClusterId> clusterIds,
std::vector<chip::AttributeId> attributeIds,
const chip::Optional<std::vector<chip::DataVersion>> & dataVersions,
AttributePathsConfig & pathsConfig);
};
class InteractionModelReports
{
public:
InteractionModelReports(chip::app::ReadClient::Callback * callback) : mBufferedReadAdapter(*callback) {}
protected:
CHIP_ERROR ReadAttribute(chip::DeviceProxy * device, std::vector<chip::EndpointId> endpointIds,
std::vector<chip::ClusterId> clusterIds, std::vector<chip::AttributeId> attributeIds,
const chip::Optional<bool> & fabricFiltered = chip::Optional<bool>(true),
const chip::Optional<std::vector<chip::DataVersion>> & dataVersions = chip::NullOptional)
{
return ReportAttribute(device, endpointIds, clusterIds, attributeIds, chip::app::ReadClient::InteractionType::Read, 0, 0,
fabricFiltered, dataVersions, chip::NullOptional, chip::NullOptional);
}
CHIP_ERROR SubscribeAttribute(chip::DeviceProxy * device, std::vector<chip::EndpointId> endpointIds,
std::vector<chip::ClusterId> clusterIds, std::vector<chip::AttributeId> attributeIds,
uint16_t minInterval, uint16_t maxInterval, const chip::Optional<bool> & fabricFiltered,
const chip::Optional<std::vector<chip::DataVersion>> & dataVersions,
const chip::Optional<bool> & keepSubscriptions, const chip::Optional<bool> & autoResubscribe)
{
return ReportAttribute(device, endpointIds, clusterIds, attributeIds, chip::app::ReadClient::InteractionType::Subscribe,
minInterval, maxInterval, fabricFiltered, dataVersions, keepSubscriptions, autoResubscribe);
}
CHIP_ERROR ReportAttribute(chip::DeviceProxy * device, std::vector<chip::EndpointId> endpointIds,
std::vector<chip::ClusterId> clusterIds, std::vector<chip::AttributeId> attributeIds,
chip::app::ReadClient::InteractionType interactionType, uint16_t minInterval, uint16_t maxInterval,
const chip::Optional<bool> & fabricFiltered,
const chip::Optional<std::vector<chip::DataVersion>> & dataVersions,
const chip::Optional<bool> & keepSubscriptions, const chip::Optional<bool> & autoResubscribe);
CHIP_ERROR ReadEvent(chip::DeviceProxy * device, std::vector<chip::EndpointId> endpointIds,
std::vector<chip::ClusterId> clusterIds, std::vector<chip::EventId> eventIds,
const chip::Optional<bool> & fabricFiltered = chip::Optional<bool>(true),
const chip::Optional<chip::EventNumber> & eventNumber = chip::NullOptional)
{
return ReportEvent(device, endpointIds, clusterIds, eventIds, chip::app::ReadClient::InteractionType::Read, 0, 0,
fabricFiltered, eventNumber, chip::NullOptional, chip::NullOptional, chip::NullOptional);
}
CHIP_ERROR SubscribeEvent(chip::DeviceProxy * device, std::vector<chip::EndpointId> endpointIds,
std::vector<chip::ClusterId> clusterIds, std::vector<chip::EventId> eventIds, uint16_t minInterval,
uint16_t maxInterval, const chip::Optional<bool> & fabricFiltered,
const chip::Optional<chip::EventNumber> & eventNumber, const chip::Optional<bool> & keepSubscriptions,
const chip::Optional<std::vector<bool>> & isUrgents, const chip::Optional<bool> & autoResubscribe)
{
return ReportEvent(device, endpointIds, clusterIds, eventIds, chip::app::ReadClient::InteractionType::Subscribe,
minInterval, maxInterval, fabricFiltered, eventNumber, keepSubscriptions, isUrgents, autoResubscribe);
}
CHIP_ERROR ReportEvent(chip::DeviceProxy * device, std::vector<chip::EndpointId> endpointIds,
std::vector<chip::ClusterId> clusterIds, std::vector<chip::EventId> eventIds,
chip::app::ReadClient::InteractionType interactionType, uint16_t minInterval, uint16_t maxInterval,
const chip::Optional<bool> & fabricFiltered, const chip::Optional<chip::EventNumber> & eventNumber,
const chip::Optional<bool> & keepSubscriptions, const chip::Optional<std::vector<bool>> & isUrgents,
const chip::Optional<bool> & autoResubscribe);
CHIP_ERROR ReadAll(chip::DeviceProxy * device, std::vector<chip::EndpointId> endpointIds,
std::vector<chip::ClusterId> clusterIds, std::vector<chip::AttributeId> attributeIds,
std::vector<chip::EventId> eventIds,
const chip::Optional<bool> & fabricFiltered = chip::Optional<bool>(true),
const chip::Optional<std::vector<chip::DataVersion>> & dataVersions = chip::NullOptional,
const chip::Optional<chip::EventNumber> & eventNumber = chip::NullOptional)
{
return ReportAll(device, endpointIds, clusterIds, attributeIds, eventIds, chip::app::ReadClient::InteractionType::Read, 0,
0, fabricFiltered, dataVersions, eventNumber);
}
CHIP_ERROR SubscribeAll(chip::DeviceProxy * device, std::vector<chip::EndpointId> endpointIds,
std::vector<chip::ClusterId> clusterIds, std::vector<chip::AttributeId> attributeIds,
std::vector<chip::EventId> eventIds, uint16_t minInterval = 0, uint16_t maxInterval = 0,
const chip::Optional<bool> & fabricFiltered = chip::Optional<bool>(true),
const chip::Optional<chip::EventNumber> & eventNumber = chip::NullOptional,
const chip::Optional<bool> & keepSubscriptions = chip::NullOptional)
{
return ReportAll(device, endpointIds, clusterIds, attributeIds, eventIds, chip::app::ReadClient::InteractionType::Subscribe,
minInterval, maxInterval, fabricFiltered, chip::NullOptional, eventNumber, keepSubscriptions);
}
CHIP_ERROR ReportAll(chip::DeviceProxy * device, std::vector<chip::EndpointId> endpointIds,
std::vector<chip::ClusterId> clusterIds, std::vector<chip::AttributeId> attributeIds,
std::vector<chip::EventId> eventIds, chip::app::ReadClient::InteractionType interactionType,
uint16_t minInterval = 0, uint16_t maxInterval = 0,
const chip::Optional<bool> & fabricFiltered = chip::Optional<bool>(true),
const chip::Optional<std::vector<chip::DataVersion>> & dataVersions = chip::NullOptional,
const chip::Optional<chip::EventNumber> & eventNumber = chip::NullOptional,
const chip::Optional<bool> & keepSubscriptions = chip::NullOptional);
void OnDeallocatePaths(chip::app::ReadPrepareParams && aReadPrepareParams);
void Shutdown() { mReadClients.clear(); }
void CleanupReadClient(chip::app::ReadClient * aReadClient);
std::vector<std::unique_ptr<chip::app::ReadClient>> mReadClients;
chip::app::BufferedReadCallback mBufferedReadAdapter;
};
class InteractionModelCommands
{
public:
InteractionModelCommands(chip::app::CommandSender::Callback * callback) : mCallback(callback) {}
protected:
template <class T>
CHIP_ERROR SendCommand(chip::DeviceProxy * device, chip::EndpointId endpointId, chip::ClusterId clusterId,
chip::CommandId commandId, const T & value,
const chip::Optional<uint16_t> & timedInteractionTimeoutMs = chip::NullOptional,
const chip::Optional<bool> & suppressResponse = chip::NullOptional,
const chip::Optional<uint16_t> & repeatCount = chip::NullOptional,
const chip::Optional<uint16_t> & repeatDelayInMs = chip::NullOptional)
{
uint16_t repeat = repeatCount.ValueOr(1);
while (repeat--)
{
chip::app::CommandPathParams commandPath = { endpointId, clusterId, commandId,
(chip::app::CommandPathFlags::kEndpointIdValid) };
auto commandSender = std::make_unique<chip::app::CommandSender>(mCallback, device->GetExchangeManager(),
timedInteractionTimeoutMs.HasValue());
VerifyOrReturnError(commandSender != nullptr, CHIP_ERROR_NO_MEMORY);
ReturnErrorOnFailure(commandSender->AddRequestDataNoTimedCheck(commandPath, value, timedInteractionTimeoutMs,
suppressResponse.ValueOr(false)));
ReturnErrorOnFailure(commandSender->SendCommandRequest(device->GetSecureSession().Value()));
mCommandSender.push_back(std::move(commandSender));
if (repeatDelayInMs.HasValue())
{
chip::test_utils::SleepMillis(repeatDelayInMs.Value());
}
}
return CHIP_NO_ERROR;
}
template <class T>
CHIP_ERROR SendGroupCommand(chip::GroupId groupId, chip::FabricIndex fabricIndex, chip::ClusterId clusterId,
chip::CommandId commandId, const T & value)
{
chip::app::CommandPathParams commandPath = { groupId, clusterId, commandId, (chip::app::CommandPathFlags::kGroupIdValid) };
chip::Messaging::ExchangeManager * exchangeManager = chip::app::InteractionModelEngine::GetInstance()->GetExchangeManager();
VerifyOrReturnError(exchangeManager != nullptr, CHIP_ERROR_INCORRECT_STATE);
chip::app::CommandSender commandSender(mCallback, exchangeManager);
ReturnErrorOnFailure(commandSender.AddRequestDataNoTimedCheck(commandPath, value, chip::NullOptional));
chip::Transport::OutgoingGroupSession session(groupId, fabricIndex);
return commandSender.SendGroupCommandRequest(chip::SessionHandle(session));
}
void Shutdown()
{
for (auto & commandSender : mCommandSender)
{
commandSender.reset();
}
mCommandSender.clear();
}
std::vector<std::unique_ptr<chip::app::CommandSender>> mCommandSender;
chip::app::CommandSender::Callback * mCallback;
};
class InteractionModelWriter
{
public:
InteractionModelWriter(chip::app::WriteClient::Callback * callback) : mChunkedWriteCallback(callback) {}
protected:
template <class T>
CHIP_ERROR WriteAttribute(chip::DeviceProxy * device, std::vector<chip::EndpointId> endpointIds,
std::vector<chip::ClusterId> clusterIds, std::vector<chip::AttributeId> attributeIds,
const std::vector<T> & values,
const chip::Optional<uint16_t> & timedInteractionTimeoutMs = chip::NullOptional,
const chip::Optional<bool> & suppressResponse = chip::NullOptional,
const chip::Optional<std::vector<chip::DataVersion>> & dataVersions = chip::NullOptional,
const chip::Optional<uint16_t> & repeatCount = chip::NullOptional,
const chip::Optional<uint16_t> & repeatDelayInMs = chip::NullOptional)
{
InteractionModelConfig::AttributePathsConfig pathsConfig;
ReturnErrorOnFailure(
InteractionModelConfig::GetAttributePaths(endpointIds, clusterIds, attributeIds, dataVersions, pathsConfig));
VerifyOrReturnError(pathsConfig.count == values.size() || values.size() == 1, CHIP_ERROR_INVALID_ARGUMENT);
uint16_t repeat = repeatCount.ValueOr(1);
while (repeat--)
{
mWriteClient = std::make_unique<chip::app::WriteClient>(device->GetExchangeManager(), &mChunkedWriteCallback,
timedInteractionTimeoutMs, suppressResponse.ValueOr(false));
VerifyOrReturnError(mWriteClient != nullptr, CHIP_ERROR_NO_MEMORY);
for (uint8_t i = 0; i < pathsConfig.count; i++)
{
auto & path = pathsConfig.attributePathParams[i];
auto & dataVersion = pathsConfig.dataVersionFilter[i].mDataVersion;
const T & value = i >= values.size() ? values.at(0) : values.at(i);
ReturnErrorOnFailure(EncodeAttribute<T>(path, dataVersion, value));
}
ReturnErrorOnFailure(mWriteClient->SendWriteRequest(device->GetSecureSession().Value()));
if (repeatDelayInMs.HasValue())
{
chip::test_utils::SleepMillis(repeatDelayInMs.Value());
}
}
return CHIP_NO_ERROR;
}
template <class T>
CHIP_ERROR WriteAttribute(chip::DeviceProxy * device, std::vector<chip::EndpointId> endpointIds,
std::vector<chip::ClusterId> clusterIds, std::vector<chip::AttributeId> attributeIds, const T & value,
const chip::Optional<uint16_t> & timedInteractionTimeoutMs = chip::NullOptional,
const chip::Optional<bool> & suppressResponse = chip::NullOptional,
const chip::Optional<std::vector<chip::DataVersion>> & dataVersions = chip::NullOptional,
const chip::Optional<uint16_t> & repeatCount = chip::NullOptional,
const chip::Optional<uint16_t> & repeatDelayInMs = chip::NullOptional)
{
std::vector<T> values = { value };
return WriteAttribute(device, endpointIds, clusterIds, attributeIds, values, timedInteractionTimeoutMs, suppressResponse,
dataVersions, repeatCount, repeatDelayInMs);
}
template <class T>
CHIP_ERROR WriteGroupAttribute(chip::GroupId groupId, chip::FabricIndex fabricIndex, chip::ClusterId clusterId,
chip::AttributeId attributeId, const std::vector<T> & value,
const chip::Optional<chip::DataVersion> & dataVersion = chip::NullOptional)
{
return CHIP_ERROR_NOT_IMPLEMENTED;
}
template <class T>
CHIP_ERROR WriteGroupAttribute(chip::GroupId groupId, chip::FabricIndex fabricIndex, chip::ClusterId clusterId,
chip::AttributeId attributeId, const T & value,
const chip::Optional<chip::DataVersion> & dataVersion = chip::NullOptional)
{
chip::app::AttributePathParams attributePathParams;
if (clusterId != chip::kInvalidClusterId)
{
attributePathParams.mClusterId = clusterId;
}
if (attributeId != chip::kInvalidAttributeId)
{
attributePathParams.mAttributeId = attributeId;
}
chip::Messaging::ExchangeManager * exchangeManager = chip::app::InteractionModelEngine::GetInstance()->GetExchangeManager();
VerifyOrReturnError(exchangeManager != nullptr, CHIP_ERROR_INCORRECT_STATE);
chip::app::WriteClient writeClient(exchangeManager, &mChunkedWriteCallback, chip::NullOptional);
ReturnErrorOnFailure(writeClient.EncodeAttribute(attributePathParams, value, dataVersion));
chip::Transport::OutgoingGroupSession session(groupId, fabricIndex);
return writeClient.SendWriteRequest(chip::SessionHandle(session));
}
void Shutdown() { mWriteClient.reset(); }
std::unique_ptr<chip::app::WriteClient> mWriteClient;
chip::app::ChunkedWriteCallback mChunkedWriteCallback;
private:
template <typename T>
CHIP_ERROR EncodeAttribute(const chip::app::AttributePathParams & path, const chip::Optional<chip::DataVersion> & dataVersion,
T value, typename std::enable_if<!std::is_pointer<T>::value>::type * = 0)
{
return mWriteClient->EncodeAttribute(path, value, dataVersion);
}
template <typename T>
CHIP_ERROR EncodeAttribute(const chip::app::AttributePathParams & path, const chip::Optional<chip::DataVersion> & dataVersion,
T value, typename std::enable_if<std::is_pointer<T>::value>::type * = 0)
{
return mWriteClient->EncodeAttribute(path, *value, dataVersion);
}
};
class InteractionModel : public InteractionModelReports,
public InteractionModelCommands,
public InteractionModelWriter,
public chip::app::ReadClient::Callback,
public chip::app::WriteClient::Callback,
public chip::app::CommandSender::Callback
{
public:
InteractionModel() : InteractionModelReports(this), InteractionModelCommands(this), InteractionModelWriter(this){};
virtual ~InteractionModel(){};
virtual void OnResponse(const chip::app::StatusIB & status, chip::TLV::TLVReader * data) = 0;
virtual CHIP_ERROR ContinueOnChipMainThread(CHIP_ERROR err) = 0;
virtual chip::DeviceProxy * GetDevice(const char * identity) = 0;
CHIP_ERROR ReadAttribute(const char * identity, chip::EndpointId endpointId, chip::ClusterId clusterId,
chip::AttributeId attributeId, bool fabricFiltered = true,
const chip::Optional<chip::DataVersion> & dataVersion = chip::NullOptional);
CHIP_ERROR SubscribeAttribute(const char * identity, chip::EndpointId endpointId, chip::ClusterId clusterId,
chip::AttributeId attributeId, uint16_t minInterval, uint16_t maxInterval, bool fabricFiltered,
const chip::Optional<chip::DataVersion> & dataVersion,
const chip::Optional<bool> & keepSubscriptions, const chip::Optional<bool> & autoResubscribe);
CHIP_ERROR ReadEvent(const char * identity, chip::EndpointId endpointId, chip::ClusterId clusterId, chip::EventId eventId,
bool fabricFiltered = true, const chip::Optional<chip::EventNumber> & eventNumber = chip::NullOptional);
CHIP_ERROR SubscribeEvent(const char * identity, chip::EndpointId endpointId, chip::ClusterId clusterId, chip::EventId eventId,
uint16_t minInterval, uint16_t maxInterval, bool fabricFiltered,
const chip::Optional<chip::EventNumber> & eventNumber, const chip::Optional<bool> & keepSubscriptions,
const chip::Optional<bool> & autoResubscribe);
CHIP_ERROR WaitForReport() { return CHIP_NO_ERROR; }
template <class T>
CHIP_ERROR WriteAttribute(const char * identity, chip::EndpointId endpointId, chip::ClusterId clusterId,
chip::AttributeId attributeId, const T & value,
const chip::Optional<uint16_t> & timedInteractionTimeoutMs = chip::NullOptional,
const chip::Optional<bool> & suppressResponse = chip::NullOptional,
const chip::Optional<chip::DataVersion> & dataVersion = chip::NullOptional)
{
chip::DeviceProxy * device = GetDevice(identity);
VerifyOrReturnError(device != nullptr, CHIP_ERROR_INCORRECT_STATE);
std::vector<chip::EndpointId> endpointIds = { endpointId };
std::vector<chip::ClusterId> clusterIds = { clusterId };
std::vector<chip::AttributeId> attributeIds = { attributeId };
chip::Optional<std::vector<chip::DataVersion>> optionalDataVersions;
if (dataVersion.HasValue())
{
std::vector<chip::DataVersion> dataVersions = { dataVersion.Value() };
optionalDataVersions.SetValue(dataVersions);
}
return InteractionModelWriter::WriteAttribute(device, endpointIds, clusterIds, attributeIds, value,
timedInteractionTimeoutMs, suppressResponse, optionalDataVersions);
}
template <class T>
CHIP_ERROR WriteGroupAttribute(const char * identity, chip::GroupId groupId, chip::ClusterId clusterId,
chip::AttributeId attributeId, const T & value,
const chip::Optional<chip::DataVersion> & dataVersion = chip::NullOptional)
{
chip::DeviceProxy * device = GetDevice(identity);
VerifyOrReturnError(device != nullptr, CHIP_ERROR_INCORRECT_STATE);
chip::FabricIndex fabricIndex = device->GetSecureSession().Value()->GetFabricIndex();
return InteractionModelWriter::WriteGroupAttribute(groupId, fabricIndex, clusterId, attributeId, value, dataVersion);
}
template <class T>
CHIP_ERROR SendCommand(const char * identity, chip::EndpointId endpointId, chip::ClusterId clusterId, chip::CommandId commandId,
const T & value, chip::Optional<uint16_t> timedInteractionTimeoutMs = chip::NullOptional,
const chip::Optional<bool> & suppressResponse = chip::NullOptional)
{
chip::DeviceProxy * device = GetDevice(identity);
VerifyOrReturnError(device != nullptr, CHIP_ERROR_INCORRECT_STATE);
return InteractionModelCommands::SendCommand(device, endpointId, clusterId, commandId, value, timedInteractionTimeoutMs,
suppressResponse);
}
template <class T>
CHIP_ERROR SendGroupCommand(const char * identity, chip::GroupId groupId, chip::ClusterId clusterId, chip::CommandId commandId,
const T & value)
{
chip::DeviceProxy * device = GetDevice(identity);
VerifyOrReturnError(device != nullptr, CHIP_ERROR_INCORRECT_STATE);
chip::FabricIndex fabricIndex = device->GetSecureSession().Value()->GetFabricIndex();
return InteractionModelCommands::SendGroupCommand(groupId, fabricIndex, clusterId, commandId, value);
}
void Shutdown();
/////////// ReadClient Callback Interface /////////
void OnAttributeData(const chip::app::ConcreteDataAttributePath & path, chip::TLV::TLVReader * data,
const chip::app::StatusIB & status) override;
void OnEventData(const chip::app::EventHeader & eventHeader, chip::TLV::TLVReader * data,
const chip::app::StatusIB * status) override;
void OnError(CHIP_ERROR error) override;
void OnDone(chip::app::ReadClient * aReadClient) override;
void OnSubscriptionEstablished(chip::SubscriptionId subscriptionId) override;
void OnDeallocatePaths(chip::app::ReadPrepareParams && aReadPrepareParams) override;
/////////// WriteClient Callback Interface /////////
void OnResponse(const chip::app::WriteClient * client, const chip::app::ConcreteDataAttributePath & path,
chip::app::StatusIB status) override;
void OnError(const chip::app::WriteClient * client, CHIP_ERROR error) override;
void OnDone(chip::app::WriteClient * client) override;
/////////// CommandSender Callback Interface /////////
void OnResponse(chip::app::CommandSender * client, const chip::app::ConcreteCommandPath & path,
const chip::app::StatusIB & status, chip::TLV::TLVReader * data) override;
void OnError(const chip::app::CommandSender * client, CHIP_ERROR error) override;
void OnDone(chip::app::CommandSender * client) override;
};