| /* |
| * |
| * 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 |