blob: a815e984c03005f1fa4782fec06bc31051d87d54 [file] [log] [blame]
/*
* Copyright (c) 2025 Project CHIP Authors
*
* 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 "closure-control-server.h"
#include <app/AttributeAccessInterface.h>
#include <app/AttributeAccessInterfaceRegistry.h>
#include <app/CommandHandlerInterfaceRegistry.h>
#include <app/ConcreteAttributePath.h>
#include <app/InteractionModelEngine.h>
#include <app/util/attribute-storage.h>
using namespace chip;
using namespace chip::app;
using namespace chip::app::DataModel;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::ClosureControl;
using namespace chip::app::Clusters::ClosureControl::Attributes;
using namespace chip::app::Clusters::ClosureControl::Commands;
using chip::Protocols::InteractionModel::Status;
namespace chip {
namespace app {
namespace Clusters {
namespace ClosureControl {
CHIP_ERROR Instance::Init()
{
ReturnErrorOnFailure(CommandHandlerInterfaceRegistry::Instance().RegisterCommandHandler(this));
VerifyOrReturnError(AttributeAccessInterfaceRegistry::Instance().Register(this), CHIP_ERROR_INCORRECT_STATE);
return CHIP_NO_ERROR;
}
void Instance::Shutdown()
{
CommandHandlerInterfaceRegistry::Instance().UnregisterCommandHandler(this);
AttributeAccessInterfaceRegistry::Instance().Unregister(this);
}
bool Instance::HasFeature(Feature aFeatures) const
{
return mFeatures.Has(aFeatures);
}
bool Instance::SupportsOptAttr(OptionalAttribute aOptionalAttrs) const
{
return mOptionalAttrs.Has(aOptionalAttrs);
}
bool Instance::IsSupportedState(MainStateEnum aMainState)
{
switch (aMainState)
{
case MainStateEnum::kCalibrating:
return HasFeature(Feature::kCalibration);
case MainStateEnum::kProtected:
return HasFeature(Feature::kProtection);
case MainStateEnum::kDisengaged:
return HasFeature(Feature::kManuallyOperable);
case MainStateEnum::kPendingFallback:
return HasFeature(Feature::kFallback);
default:
// Remaining MainState have Mandatory conformance,so will be supported.
return true;
}
return true;
}
CHIP_ERROR Instance::SetMainState(MainStateEnum aMainState)
{
if (!IsSupportedState(aMainState))
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
// If the Main State has changed, trigger the attribute change callback
if (mMainState != aMainState)
{
mMainState = aMainState;
MatterReportingAttributeChangeCallback(mDelegate.GetEndpointId(), ClosureControl::Id, Attributes::MainState::Id);
UpdateCountdownTimeFromClusterLogic();
}
return CHIP_NO_ERROR;
}
CHIP_ERROR Instance::SetOverallState(const GenericOverallState & aOverallState)
{
// If the overall state has changed, trigger the attribute change callback
if (!(mOverallState == aOverallState))
{
mOverallState = aOverallState;
MatterReportingAttributeChangeCallback(mDelegate.GetEndpointId(), ClosureControl::Id, Attributes::OverallState::Id);
}
return CHIP_NO_ERROR;
}
CHIP_ERROR Instance::SetOverallTarget(const GenericOverallTarget & aOverallTarget)
{
// If the overall target has changed, trigger the attribute change callback
if (!(mOverallTarget == aOverallTarget))
{
mOverallTarget = aOverallTarget;
MatterReportingAttributeChangeCallback(mDelegate.GetEndpointId(), ClosureControl::Id, Attributes::OverallTarget::Id);
}
return CHIP_NO_ERROR;
}
MainStateEnum Instance::GetMainState() const
{
return mMainState;
}
const GenericOverallState & Instance::GetOverallState() const
{
return mOverallState;
}
const GenericOverallTarget & Instance::GetOverallTarget() const
{
return mOverallTarget;
}
void Instance::UpdateCountdownTime(bool fromDelegate)
{
app::DataModel::Nullable<uint32_t> newCountdownTime = mDelegate.GetCountdownTime();
auto now = System::SystemClock().GetMonotonicTimestamp();
bool markDirty = false;
if (fromDelegate)
{
// Updates from delegate are reduce-reported to every 1s max (choice of this implementation), in addition
// to default change-from-null, change-from-zero and increment policy.
System::Clock::Milliseconds64 reportInterval = System::Clock::Milliseconds64(1000);
auto predicate = mCountdownTime.GetPredicateForSufficientTimeSinceLastDirty(reportInterval);
markDirty = (mCountdownTime.SetValue(newCountdownTime, now, predicate) == AttributeDirtyState::kMustReport);
}
else
{
auto predicate = [](const decltype(mCountdownTime)::SufficientChangePredicateCandidate &) -> bool { return true; };
markDirty = (mCountdownTime.SetValue(newCountdownTime, now, predicate) == AttributeDirtyState::kMustReport);
}
if (markDirty)
{
MatterReportingAttributeChangeCallback(mDelegate.GetEndpointId(), ClosureControl::Id, Attributes::CountdownTime::Id);
}
}
// AttributeAccessInterface
CHIP_ERROR Instance::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder)
{
VerifyOrDie(aPath.mClusterId == ClosureControl::Id);
switch (aPath.mAttributeId)
{
case CountdownTime::Id:
// Optional Attribute
if (SupportsOptAttr(OptionalAttribute::kCountdownTime))
{
return aEncoder.Encode(mDelegate.GetCountdownTime());
}
return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute);
case MainState::Id:
return aEncoder.Encode(GetMainState());
case CurrentErrorList::Id:
return aEncoder.EncodeList([this](const auto & encoder) -> CHIP_ERROR { return this->EncodeCurrentErrorList(encoder); });
case OverallState::Id:
return aEncoder.Encode(GetOverallState());
case OverallTarget::Id:
return aEncoder.Encode(GetOverallTarget());
/* FeatureMap - is held locally */
case FeatureMap::Id:
return aEncoder.Encode(mFeatures);
}
return CHIP_NO_ERROR;
}
CHIP_ERROR Instance::EncodeCurrentErrorList(const AttributeValueEncoder::ListEncodeHelper & encoder)
{
CHIP_ERROR err = CHIP_NO_ERROR;
ReturnErrorOnFailure(mDelegate.StartCurrentErrorListRead());
for (size_t i = 0; true; i++)
{
ClosureErrorEnum error;
err = mDelegate.GetCurrentErrorListAtIndex(i, error);
// Convert end of list to CHIP_NO_ERROR
VerifyOrExit(err != CHIP_ERROR_PROVIDER_LIST_EXHAUSTED, err = CHIP_NO_ERROR);
// Check if another error occurred before trying to encode
SuccessOrExit(err);
err = encoder.Encode(error);
SuccessOrExit(err);
}
exit:
// Tell the delegate the read is complete
ReturnErrorOnFailure(mDelegate.EndCurrentErrorListRead());
return err;
}
CHIP_ERROR Instance::Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder)
{
VerifyOrDie(aPath.mClusterId == ClosureControl::Id);
switch (aPath.mAttributeId)
{
case CountdownTime::Id:
if (SupportsOptAttr(OptionalAttribute::kCountdownTime))
{
return CHIP_IM_GLOBAL_STATUS(UnsupportedWrite);
}
return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute);
case MainState::Id:
case CurrentErrorList::Id:
case OverallState::Id:
case OverallTarget::Id:
return CHIP_IM_GLOBAL_STATUS(UnsupportedWrite);
default:
return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute);
}
}
// CommandHandlerInterface
void Instance::InvokeCommand(HandlerContext & handlerContext)
{
using namespace Commands;
switch (handlerContext.mRequestPath.mCommandId)
{
case Stop::Id:
if (!HasFeature(Feature::kInstantaneous))
{
HandleCommand<Stop::DecodableType>(
handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleStop(ctx, commandData); });
}
else
{
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::UnsupportedCommand);
}
break;
case MoveTo::Id:
HandleCommand<MoveTo::DecodableType>(
handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleMoveTo(ctx, commandData); });
break;
case Calibrate::Id:
if (HasFeature(Feature::kCalibration))
{
HandleCommand<Calibrate::DecodableType>(
handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleCalibrate(ctx, commandData); });
}
else
{
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::UnsupportedCommand);
}
break;
}
}
void Instance::HandleStop(HandlerContext & ctx, const Commands::Stop::DecodableType & commandData)
{
Status status = mDelegate.Stop();
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
}
void Instance::HandleMoveTo(HandlerContext & ctx, const Commands::MoveTo::DecodableType & commandData)
{
Status status = mDelegate.MoveTo(commandData.tag, commandData.latch, commandData.speed);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
}
void Instance::HandleCalibrate(HandlerContext & ctx, const Commands::Calibrate::DecodableType & commandData)
{
Status status = mDelegate.Calibrate();
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
}
} // namespace ClosureControl
} // namespace Clusters
} // namespace app
} // namespace chip
// -----------------------------------------------------------------------------
// Plugin initialization
void MatterClosureControlPluginServerInitCallback() {}
void MatterClosureControlPluginServerShutdownCallback() {}