blob: 25a0be188ad7af6ef64993f994ba4434055b45e9 [file] [log] [blame]
/*
*
* Copyright (c) 2023 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.
*/
#include <app/AttributeAccessInterface.h>
#include <app/AttributeAccessInterfaceRegistry.h>
#include <app/CommandHandlerInterface.h>
#include <app/ConcreteAttributePath.h>
#include <app/ConcreteClusterPath.h>
#include <app/InteractionModelEngine.h>
#include <app/SafeAttributePersistenceProvider.h>
#include <app/clusters/resource-monitoring-server/resource-monitoring-cluster-objects.h>
#include <app/clusters/resource-monitoring-server/resource-monitoring-server.h>
#include <app/data-model/Nullable.h>
#include <app/reporting/reporting.h>
#include <app/util/attribute-storage.h>
#include <lib/core/CHIPError.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/TypeTraits.h>
#include <protocols/interaction_model/StatusCode.h>
// using namespace std;
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::ResourceMonitoring;
using chip::Protocols::InteractionModel::Status;
namespace chip {
namespace app {
namespace Clusters {
namespace ResourceMonitoring {
Instance::Instance(Delegate * aDelegate, EndpointId aEndpointId, ClusterId aClusterId, uint32_t aFeatureMap,
ResourceMonitoring::Attributes::DegradationDirection::TypeInfo::Type aDegradationDirection,
bool aResetConditionCommandSupported) :
CommandHandlerInterface(Optional<EndpointId>(aEndpointId), aClusterId),
AttributeAccessInterface(Optional<EndpointId>(aEndpointId), aClusterId), mDelegate(aDelegate), mEndpointId(aEndpointId),
mClusterId(aClusterId), mDegradationDirection(aDegradationDirection), mFeatureMap(aFeatureMap),
mResetConditionCommandSupported(aResetConditionCommandSupported)
{
mDelegate->SetInstance(this);
};
Instance::~Instance()
{
InteractionModelEngine::GetInstance()->UnregisterCommandHandler(this);
unregisterAttributeAccessOverride(this);
}
CHIP_ERROR Instance::Init()
{
ChipLogDetail(Zcl, "ResourceMonitoring: Init");
// Check if the cluster has been selected in zap
VerifyOrDie(emberAfContainsServer(mEndpointId, mClusterId));
LoadPersistentAttributes();
ReturnErrorOnFailure(chip::app::InteractionModelEngine::GetInstance()->RegisterCommandHandler(this));
VerifyOrReturnError(registerAttributeAccessOverride(this), CHIP_ERROR_INCORRECT_STATE);
ChipLogDetail(Zcl, "ResourceMonitoring: calling mDelegate->Init()");
ReturnErrorOnFailure(mDelegate->Init());
return CHIP_NO_ERROR;
}
bool Instance::HasFeature(ResourceMonitoring::Feature aFeature) const
{
return ((mFeatureMap & to_underlying(aFeature)) != 0);
}
chip::Protocols::InteractionModel::Status Instance::UpdateCondition(uint8_t aNewCondition)
{
auto oldCondition = mCondition;
mCondition = aNewCondition;
if (mCondition != oldCondition)
{
MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::Condition::Id);
}
return Protocols::InteractionModel::Status::Success;
}
chip::Protocols::InteractionModel::Status
Instance::UpdateChangeIndication(chip::app::Clusters::ResourceMonitoring::ChangeIndicationEnum aNewChangeIndication)
{
if (aNewChangeIndication == chip::app::Clusters::ResourceMonitoring::ChangeIndicationEnum::kWarning)
{
if (!HasFeature(ResourceMonitoring::Feature::kWarning))
{
return Protocols::InteractionModel::Status::InvalidValue;
}
}
auto oldChangeIndication = mChangeIndication;
mChangeIndication = aNewChangeIndication;
if (mChangeIndication != oldChangeIndication)
{
MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::ChangeIndication::Id);
}
return Protocols::InteractionModel::Status::Success;
}
chip::Protocols::InteractionModel::Status Instance::UpdateInPlaceIndicator(bool aNewInPlaceIndicator)
{
auto oldInPlaceIndicator = mInPlaceIndicator;
mInPlaceIndicator = aNewInPlaceIndicator;
if (mInPlaceIndicator != oldInPlaceIndicator)
{
MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::InPlaceIndicator::Id);
}
return Protocols::InteractionModel::Status::Success;
}
chip::Protocols::InteractionModel::Status Instance::UpdateLastChangedTime(DataModel::Nullable<uint32_t> aNewLastChangedTime)
{
auto oldLastchangedTime = mLastChangedTime;
mLastChangedTime = aNewLastChangedTime;
if (mLastChangedTime != oldLastchangedTime)
{
chip::app::GetSafeAttributePersistenceProvider()->WriteScalarValue(
ConcreteAttributePath(mEndpointId, mClusterId, Attributes::LastChangedTime::Id), mLastChangedTime);
MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::LastChangedTime::Id);
}
return Protocols::InteractionModel::Status::Success;
}
void Instance::SetReplacementProductListManagerInstance(ReplacementProductListManager * aReplacementProductListManager)
{
mReplacementProductListManager = aReplacementProductListManager;
}
uint8_t Instance::GetCondition() const
{
return mCondition;
}
chip::app::Clusters::ResourceMonitoring::ChangeIndicationEnum Instance::GetChangeIndication() const
{
return mChangeIndication;
}
chip::app::Clusters::ResourceMonitoring::DegradationDirectionEnum Instance::GetDegradationDirection() const
{
return mDegradationDirection;
}
bool Instance::GetInPlaceIndicator() const
{
return mInPlaceIndicator;
}
DataModel::Nullable<uint32_t> Instance::GetLastChangedTime() const
{
return mLastChangedTime;
}
ReplacementProductListManager * Instance::GetReplacementProductListManagerInstance()
{
return mReplacementProductListManager;
}
// This method is called by the interaction model engine when a command destined for this instance is received.
void Instance::InvokeCommand(HandlerContext & handlerContext)
{
ChipLogDetail(Zcl, "ResourceMonitoring Instance::InvokeCommand");
switch (handlerContext.mRequestPath.mCommandId)
{
case ResourceMonitoring::Commands::ResetCondition::Id:
ChipLogDetail(Zcl, "ResourceMonitoring::Commands::ResetCondition");
HandleCommand<ResourceMonitoring::Commands::ResetCondition::DecodableType>(
handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleResetCondition(ctx, commandData); });
break;
}
}
// List the commands supported by this instance.
CHIP_ERROR Instance::EnumerateAcceptedCommands(const ConcreteClusterPath & cluster,
CommandHandlerInterface::CommandIdCallback callback, void * context)
{
ChipLogDetail(Zcl, "resourcemonitoring: EnumerateAcceptedCommands");
if (mResetConditionCommandSupported)
{
callback(ResourceMonitoring::Commands::ResetCondition::Id, context);
}
return CHIP_NO_ERROR;
}
CHIP_ERROR Instance::ReadReplaceableProductList(AttributeValueEncoder & aEncoder)
{
CHIP_ERROR err = CHIP_NO_ERROR;
if (HasFeature(ResourceMonitoring::Feature::kReplacementProductList))
{
ReplacementProductListManager * productListManagerInstance = GetReplacementProductListManagerInstance();
if (nullptr == productListManagerInstance)
{
aEncoder.EncodeEmptyList();
return CHIP_NO_ERROR;
}
productListManagerInstance->Reset();
err = aEncoder.EncodeList([productListManagerInstance](const auto & encoder) -> CHIP_ERROR {
ReplacementProductStruct replacementProductStruct;
CHIP_ERROR iteratorError = productListManagerInstance->Next(replacementProductStruct);
while (CHIP_NO_ERROR == iteratorError)
{
ReturnErrorOnFailure(encoder.Encode(replacementProductStruct));
iteratorError = productListManagerInstance->Next(replacementProductStruct);
}
return (CHIP_ERROR_PROVIDER_LIST_EXHAUSTED == iteratorError) ? CHIP_NO_ERROR : iteratorError;
});
}
return err;
}
// Implements the read functionality for non-standard attributes.
CHIP_ERROR Instance::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder)
{
switch (aPath.mAttributeId)
{
case Attributes::Condition::Id: {
ReturnErrorOnFailure(aEncoder.Encode(mCondition));
break;
}
case Attributes::FeatureMap::Id: {
ReturnErrorOnFailure(aEncoder.Encode(mFeatureMap));
break;
}
case Attributes::DegradationDirection::Id: {
ReturnErrorOnFailure(aEncoder.Encode(mDegradationDirection));
break;
}
case Attributes::ChangeIndication::Id: {
ReturnErrorOnFailure(aEncoder.Encode(mChangeIndication));
break;
}
case Attributes::InPlaceIndicator::Id: {
ReturnErrorOnFailure(aEncoder.Encode(mInPlaceIndicator));
break;
}
case Attributes::LastChangedTime::Id: {
ReturnErrorOnFailure(aEncoder.Encode(mLastChangedTime));
break;
}
case Attributes::ReplacementProductList::Id: {
return ReadReplaceableProductList(aEncoder);
break;
}
}
return CHIP_NO_ERROR;
}
// Implements checking before attribute writes.
CHIP_ERROR Instance::Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder)
{
switch (aPath.mAttributeId)
{
case Attributes::LastChangedTime::Id: {
DataModel::Nullable<uint32_t> newLastChangedTime;
ReturnErrorOnFailure(aDecoder.Decode(newLastChangedTime));
UpdateLastChangedTime(newLastChangedTime);
break;
}
}
return CHIP_NO_ERROR;
}
template <typename RequestT, typename FuncT>
void Instance::HandleCommand(HandlerContext & handlerContext, FuncT func)
{
ChipLogDetail(Zcl, "ResourceMonitoring: HandleCommand");
if (handlerContext.mCommandHandled || (handlerContext.mRequestPath.mCommandId != RequestT::GetCommandId()))
{
return;
}
RequestT requestPayload;
// If the command matches what the caller is looking for, let's mark this as being handled
// even if errors happen after this. This ensures that we don't execute any fall-back strategies
// to handle this command since at this point, the caller is taking responsibility for handling
// the command in its entirety, warts and all.
handlerContext.SetCommandHandled();
if (DataModel::Decode(handlerContext.mPayload, requestPayload) != CHIP_NO_ERROR)
{
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Protocols::InteractionModel::Status::InvalidCommand);
return;
}
func(handlerContext, requestPayload);
}
void Instance::LoadPersistentAttributes()
{
CHIP_ERROR err = chip::app::GetSafeAttributePersistenceProvider()->ReadScalarValue(
ConcreteAttributePath(mEndpointId, mClusterId, Attributes::LastChangedTime::Id), mLastChangedTime);
if (err == CHIP_NO_ERROR)
{
if (mLastChangedTime.IsNull())
{
ChipLogDetail(Zcl, "ResourceMonitoring: Loaded LastChangedTime as null");
}
else
{
ChipLogDetail(Zcl, "ResourceMonitoring: Loaded LastChangedTime as %lu",
(long unsigned int) mLastChangedTime.Value()); // on some platforms uint32_t is a long, cast it to
// unsigned long on all platforms to prevent CI errors
}
}
else
{
// If we cannot find the previous LastChangedTime, we will assume it to be null.
ChipLogDetail(Zcl, "ResourceMonitoring: Unable to load the LastChangedTime from the KVS. Assuming null");
}
}
void Instance::HandleResetCondition(HandlerContext & ctx,
const ResourceMonitoring::Commands::ResetCondition::DecodableType & commandData)
{
Status resetConditionStatus = mDelegate->OnResetCondition();
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, resetConditionStatus);
}
Status Delegate::OnResetCondition()
{
ChipLogDetail(Zcl, "ResourceMonitoringServer::OnResetCondition()");
// call application specific pre reset logic,
// anything other than Success will cause the command to fail, and not do any of the resets
auto status = PreResetCondition();
if (status != Status::Success)
{
return status;
}
// Handle the reset of the condition attribute, if supported
if (emberAfContainsAttribute(mInstance->GetEndpointId(), mInstance->GetClusterId(), Attributes::Condition::Id))
{
if (mInstance->GetDegradationDirection() == DegradationDirectionEnum::kDown)
{
mInstance->UpdateCondition(100);
}
else if (mInstance->GetDegradationDirection() == DegradationDirectionEnum::kUp)
{
mInstance->UpdateCondition(0);
}
}
// handle the reset of the ChangeIndication attribute, mandatory
mInstance->UpdateChangeIndication(ChangeIndicationEnum::kOk);
// Handle the reset of the LastChangedTime attribute, if supported
if (emberAfContainsAttribute(mInstance->GetEndpointId(), mInstance->GetClusterId(), Attributes::LastChangedTime::Id))
{
System::Clock::Milliseconds64 currentUnixTimeMS;
CHIP_ERROR err = System::SystemClock().GetClock_RealTimeMS(currentUnixTimeMS);
if (err == CHIP_NO_ERROR)
{
System::Clock::Seconds32 currentUnixTime = std::chrono::duration_cast<System::Clock::Seconds32>(currentUnixTimeMS);
mInstance->UpdateLastChangedTime(DataModel::MakeNullable(currentUnixTime.count()));
}
}
// call application specific post reset logic
status = PostResetCondition();
return status;
}
Status Delegate::PreResetCondition()
{
ChipLogDetail(Zcl, "ResourceMonitoringServer::PreResetCondition()");
return Status::Success;
}
Status Delegate::PostResetCondition()
{
ChipLogDetail(Zcl, "ResourceMonitoringServer::PostResetCondition()");
return Status::Success;
}
} // namespace ResourceMonitoring
} // namespace Clusters
} // namespace app
} // namespace chip