blob: 87d971e8df4efc400c7524e95ffa471e118eb242 [file] [log] [blame]
/*
*
* Copyright (c) 2024 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 "actions-server.h"
#include <app-common/zap-generated/cluster-objects.h>
#include <app-common/zap-generated/ids/Attributes.h>
#include <app/AttributeAccessInterfaceRegistry.h>
#include <app/CommandHandlerInterfaceRegistry.h>
#include <app/EventLogging.h>
#include <app/util/attribute-storage.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/logging/CHIPLogging.h>
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::Actions;
using namespace chip::app::Clusters::Actions::Attributes;
using namespace chip::Protocols::InteractionModel;
ActionsServer::~ActionsServer()
{
Shutdown();
}
void ActionsServer::Shutdown()
{
TEMPORARY_RETURN_IGNORED CommandHandlerInterfaceRegistry::Instance().UnregisterCommandHandler(this);
AttributeAccessInterfaceRegistry::Instance().Unregister(this);
}
CHIP_ERROR ActionsServer::Init()
{
ReturnErrorOnFailure(CommandHandlerInterfaceRegistry::Instance().RegisterCommandHandler(this));
VerifyOrReturnError(AttributeAccessInterfaceRegistry::Instance().Register(this), CHIP_ERROR_INCORRECT_STATE);
return CHIP_NO_ERROR;
}
void ActionsServer::OnStateChanged(EndpointId aEndpoint, uint16_t aActionId, uint32_t aInvokeId, ActionStateEnum aActionState)
{
ChipLogProgress(Zcl, "ActionsServer: OnStateChanged");
// Generate StateChanged event
EventNumber eventNumber;
Events::StateChanged::Type event{ aActionId, aInvokeId, aActionState };
if (CHIP_NO_ERROR != LogEvent(event, aEndpoint, eventNumber))
{
ChipLogError(Zcl, "ActionsServer: Failed to generate OnStateChanged event");
}
}
void ActionsServer::OnActionFailed(EndpointId aEndpoint, uint16_t aActionId, uint32_t aInvokeId, ActionStateEnum aActionState,
ActionErrorEnum aActionError)
{
ChipLogProgress(Zcl, "ActionsServer: OnActionFailed");
// Generate ActionFailed event
EventNumber eventNumber;
Events::ActionFailed::Type event{ aActionId, aInvokeId, aActionState, aActionError };
if (CHIP_NO_ERROR != LogEvent(event, aEndpoint, eventNumber))
{
ChipLogError(Zcl, "ActionsServer: Failed to generate OnActionFailed event");
}
}
CHIP_ERROR ActionsServer::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder)
{
VerifyOrDie(aPath.mClusterId == Actions::Id);
switch (aPath.mAttributeId)
{
case ActionList::Id: {
ReturnErrorOnFailure(aEncoder.EncodeList(
[this, aPath](const auto & encoder) -> CHIP_ERROR { return this->ReadActionListAttribute(aPath, encoder); }));
return CHIP_NO_ERROR;
}
case EndpointLists::Id: {
ReturnErrorOnFailure(aEncoder.EncodeList(
[this, aPath](const auto & encoder) -> CHIP_ERROR { return this->ReadEndpointListAttribute(aPath, encoder); }));
return CHIP_NO_ERROR;
}
default:
break;
}
return CHIP_NO_ERROR;
}
CHIP_ERROR ActionsServer::ReadActionListAttribute(const ConcreteReadAttributePath & aPath,
const AttributeValueEncoder::ListEncodeHelper & aEncoder)
{
for (uint16_t i = 0; i < kMaxActionListLength; i++)
{
ActionStructStorage action;
CHIP_ERROR err = mDelegate.ReadActionAtIndex(i, action);
if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED)
{
return CHIP_NO_ERROR;
}
ReturnErrorOnFailure(aEncoder.Encode(action));
}
return CHIP_NO_ERROR;
}
CHIP_ERROR ActionsServer::ReadEndpointListAttribute(const ConcreteReadAttributePath & aPath,
const AttributeValueEncoder::ListEncodeHelper & aEncoder)
{
for (uint16_t i = 0; i < kMaxEndpointListLength; i++)
{
EndpointListStorage epList;
CHIP_ERROR err = mDelegate.ReadEndpointListAtIndex(i, epList);
if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED)
{
return CHIP_NO_ERROR;
}
ReturnErrorOnFailure(aEncoder.Encode(epList));
}
return CHIP_NO_ERROR;
}
bool ActionsServer::HaveActionWithId(EndpointId aEndpointId, uint16_t aActionId, uint16_t & aActionIndex)
{
return mDelegate.HaveActionWithId(aActionId, aActionIndex);
}
template <typename RequestT, typename FuncT>
void ActionsServer::HandleCommand(HandlerContext & handlerContext, FuncT func)
{
if (!handlerContext.mCommandHandled && (handlerContext.mRequestPath.mCommandId == RequestT::GetCommandId()))
{
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;
}
uint16_t actionIndex = kMaxActionListLength;
if (!HaveActionWithId(handlerContext.mRequestPath.mEndpointId, requestPayload.actionID, actionIndex))
{
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Protocols::InteractionModel::Status::NotFound);
return;
}
if (actionIndex != kMaxActionListLength)
{
ActionStructStorage action;
TEMPORARY_RETURN_IGNORED mDelegate.ReadActionAtIndex(actionIndex, action);
// Check if the command bit is set in the SupportedCommands of an ations.
if (!(action.supportedCommands.Raw() & (1 << handlerContext.mRequestPath.mCommandId)))
{
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath,
Protocols::InteractionModel::Status::InvalidCommand);
return;
}
}
func(handlerContext, requestPayload);
}
}
void ActionsServer::HandleInstantAction(HandlerContext & ctx, const Commands::InstantAction::DecodableType & commandData)
{
Status status = mDelegate.HandleInstantAction(commandData.actionID, commandData.invokeID);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
}
void ActionsServer::HandleInstantActionWithTransition(HandlerContext & ctx,
const Commands::InstantActionWithTransition::DecodableType & commandData)
{
Status status =
mDelegate.HandleInstantActionWithTransition(commandData.actionID, commandData.transitionTime, commandData.invokeID);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
}
void ActionsServer::HandleStartAction(HandlerContext & ctx, const Commands::StartAction::DecodableType & commandData)
{
Status status = mDelegate.HandleStartAction(commandData.actionID, commandData.invokeID);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
}
void ActionsServer::HandleStartActionWithDuration(HandlerContext & ctx,
const Commands::StartActionWithDuration::DecodableType & commandData)
{
Status status = mDelegate.HandleStartActionWithDuration(commandData.actionID, commandData.duration, commandData.invokeID);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
}
void ActionsServer::HandleStopAction(HandlerContext & ctx, const Commands::StopAction::DecodableType & commandData)
{
Status status = mDelegate.HandleStopAction(commandData.actionID, commandData.invokeID);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
}
void ActionsServer::HandlePauseAction(HandlerContext & ctx, const Commands::PauseAction::DecodableType & commandData)
{
Status status = mDelegate.HandlePauseAction(commandData.actionID, commandData.invokeID);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
}
void ActionsServer::HandlePauseActionWithDuration(HandlerContext & ctx,
const Commands::PauseActionWithDuration::DecodableType & commandData)
{
Status status = mDelegate.HandlePauseActionWithDuration(commandData.actionID, commandData.duration, commandData.invokeID);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
}
void ActionsServer::HandleResumeAction(HandlerContext & ctx, const Commands::ResumeAction::DecodableType & commandData)
{
Status status = mDelegate.HandleResumeAction(commandData.actionID, commandData.invokeID);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
}
void ActionsServer::HandleEnableAction(HandlerContext & ctx, const Commands::EnableAction::DecodableType & commandData)
{
Status status = mDelegate.HandleEnableAction(commandData.actionID, commandData.invokeID);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
}
void ActionsServer::HandleEnableActionWithDuration(HandlerContext & ctx,
const Commands::EnableActionWithDuration::DecodableType & commandData)
{
Status status = mDelegate.HandleEnableActionWithDuration(commandData.actionID, commandData.duration, commandData.invokeID);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
}
void ActionsServer::HandleDisableAction(HandlerContext & ctx, const Commands::DisableAction::DecodableType & commandData)
{
Status status = mDelegate.HandleDisableAction(commandData.actionID, commandData.invokeID);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
}
void ActionsServer::HandleDisableActionWithDuration(HandlerContext & ctx,
const Commands::DisableActionWithDuration::DecodableType & commandData)
{
Status status = mDelegate.HandleDisableActionWithDuration(commandData.actionID, commandData.duration, commandData.invokeID);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
}
void ActionsServer::InvokeCommand(HandlerContext & handlerContext)
{
switch (handlerContext.mRequestPath.mCommandId)
{
case Actions::Commands::InstantAction::Id:
HandleCommand<Commands::InstantAction::DecodableType>(
handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleInstantAction(ctx, commandData); });
return;
case Actions::Commands::InstantActionWithTransition::Id:
HandleCommand<Commands::InstantActionWithTransition::DecodableType>(
handlerContext,
[this](HandlerContext & ctx, const auto & commandData) { HandleInstantActionWithTransition(ctx, commandData); });
return;
case Actions::Commands::StartAction::Id:
HandleCommand<Commands::StartAction::DecodableType>(
handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleStartAction(ctx, commandData); });
return;
case Actions::Commands::StartActionWithDuration::Id:
HandleCommand<Commands::StartActionWithDuration::DecodableType>(
handlerContext,
[this](HandlerContext & ctx, const auto & commandData) { HandleStartActionWithDuration(ctx, commandData); });
return;
case Actions::Commands::StopAction::Id:
HandleCommand<Commands::StopAction::DecodableType>(
handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleStopAction(ctx, commandData); });
return;
case Actions::Commands::PauseAction::Id:
HandleCommand<Commands::PauseAction::DecodableType>(
handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandlePauseAction(ctx, commandData); });
return;
case Actions::Commands::PauseActionWithDuration::Id:
HandleCommand<Commands::PauseActionWithDuration::DecodableType>(
handlerContext,
[this](HandlerContext & ctx, const auto & commandData) { HandlePauseActionWithDuration(ctx, commandData); });
return;
case Actions::Commands::ResumeAction::Id:
HandleCommand<Commands::ResumeAction::DecodableType>(
handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleResumeAction(ctx, commandData); });
return;
case Actions::Commands::EnableAction::Id:
HandleCommand<Commands::EnableAction::DecodableType>(
handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleEnableAction(ctx, commandData); });
return;
case Actions::Commands::EnableActionWithDuration::Id:
HandleCommand<Commands::EnableActionWithDuration::DecodableType>(
handlerContext,
[this](HandlerContext & ctx, const auto & commandData) { HandleEnableActionWithDuration(ctx, commandData); });
return;
case Actions::Commands::DisableAction::Id:
HandleCommand<Commands::DisableAction::DecodableType>(
handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleDisableAction(ctx, commandData); });
return;
case Actions::Commands::DisableActionWithDuration::Id:
HandleCommand<Commands::DisableActionWithDuration::DecodableType>(
handlerContext,
[this](HandlerContext & ctx, const auto & commandData) { HandleDisableActionWithDuration(ctx, commandData); });
return;
}
}
void ActionsServer::ActionListModified(EndpointId aEndpoint)
{
MarkDirty(aEndpoint, Attributes::ActionList::Id);
}
void ActionsServer::EndpointListModified(EndpointId aEndpoint)
{
MarkDirty(aEndpoint, Attributes::EndpointLists::Id);
}
void MatterActionsPluginServerInitCallback() {}
void MatterActionsPluginServerShutdownCallback() {}