Skip to content

Commit

Permalink
Dynamic Command Dispatch (Server-side) (#11339)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrjerryjohns authored and pull[bot] committed Feb 16, 2023
1 parent 981bde4 commit 5276258
Show file tree
Hide file tree
Showing 14 changed files with 652 additions and 33 deletions.
4 changes: 4 additions & 0 deletions config/standalone/CHIPProjectConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,8 @@

#define CHIP_CONFIG_DATA_MANAGEMENT_CLIENT_EXPERIMENTAL 1

#ifndef DYNAMIC_ENDPOINT_COUNT
#define DYNAMIC_ENDPOINT_COUNT 4
#endif

#endif /* CHIPPROJECTCONFIG_H */
12 changes: 6 additions & 6 deletions examples/bridge-app/esp32/main/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,27 +74,27 @@ static Device gLight4("Light 4", "Den");

// Declare On/Off cluster attributes
DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(onOffAttrs)
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_ON_OFF_ATTRIBUTE_ID, BOOLEAN, 1, 0) /* on/off */
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_ON_OFF_ATTRIBUTE_ID, BOOLEAN, 1, 0), /* on/off */
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();

// Declare Descriptor cluster attributes
DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(descriptorAttrs)
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_DEVICE_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0), /* device list */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_SERVER_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0), /* server list */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_CLIENT_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0), /* client list */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_PARTS_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0) /* parts list */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_PARTS_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0), /* parts list */
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();

// Declare Bridged Device Basic information cluster attributes
DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(bridgedDeviceBasicAttrs)
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_USER_LABEL_ATTRIBUTE_ID, CHAR_STRING, kUserLabelSize, 0), /* UserLabel */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_REACHABLE_ATTRIBUTE_ID, BOOLEAN, 1, 0) /* Reachable */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_REACHABLE_ATTRIBUTE_ID, BOOLEAN, 1, 0), /* Reachable */
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();

// Declare Fixed Label cluster attributes
DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(fixedLabelAttrs)
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_LABEL_LIST_ATTRIBUTE_ID, ARRAY, kFixedLabelAttributeArraySize, 0) /* label list */
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_LABEL_LIST_ATTRIBUTE_ID, ARRAY, kFixedLabelAttributeArraySize, 0), /* label list */
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();

// Declare Cluster List for Bridged Light endpoint
DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN(bridgedLightClusters)
Expand Down
28 changes: 14 additions & 14 deletions examples/bridge-app/linux/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,27 +94,27 @@ static Device * gDevices[DYNAMIC_ENDPOINT_COUNT];

// Declare On/Off cluster attributes
DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(onOffAttrs)
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_ON_OFF_ATTRIBUTE_ID, BOOLEAN, 1, 0) /* on/off */
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_ON_OFF_ATTRIBUTE_ID, BOOLEAN, 1, 0), /* on/off */
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();

// Declare Descriptor cluster attributes
DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(descriptorAttrs)
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_DEVICE_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0), /* device list */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_SERVER_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0), /* server list */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_CLIENT_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0), /* client list */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_PARTS_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0) /* parts list */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_PARTS_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0), /* parts list */
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();

// Declare Bridged Device Basic information cluster attributes
DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(bridgedDeviceBasicAttrs)
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_USER_LABEL_ATTRIBUTE_ID, CHAR_STRING, kUserLabelSize, 0), /* UserLabel */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_REACHABLE_ATTRIBUTE_ID, BOOLEAN, 1, 0) /* Reachable */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_REACHABLE_ATTRIBUTE_ID, BOOLEAN, 1, 0), /* Reachable */
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();

// Declare Fixed Label cluster attributes
DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(fixedLabelAttrs)
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_LABEL_LIST_ATTRIBUTE_ID, ARRAY, kFixedLabelAttributeArraySize, 0) /* label list */
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_LABEL_LIST_ATTRIBUTE_ID, ARRAY, kFixedLabelAttributeArraySize, 0), /* label list */
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();

// Declare Cluster List for Bridged Light endpoint
DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN(bridgedLightClusters)
Expand All @@ -135,30 +135,30 @@ DECLARE_DYNAMIC_ENDPOINT(bridgedLightEndpoint, bridgedLightClusters);

// Declare Switch cluster attributes
DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(switchAttrs)
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_NUMBER_OF_POSITIONS_ATTRIBUTE_ID, INT8U, 1, 0), /* NumberOfPositions */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_CURRENT_POSITION_ATTRIBUTE_ID, INT8U, 1, 0), /* CurrentPosition */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_MULTI_PRESS_MAX_ATTRIBUTE_ID, INT8U, 1, 0), /* MultiPressMax */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_FEATURE_MAP_SERVER_ATTRIBUTE_ID, BITMAP32, 4, 0) /* FeatureMap */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_NUMBER_OF_POSITIONS_ATTRIBUTE_ID, INT8U, 1, 0), /* NumberOfPositions */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_CURRENT_POSITION_ATTRIBUTE_ID, INT8U, 1, 0), /* CurrentPosition */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_MULTI_PRESS_MAX_ATTRIBUTE_ID, INT8U, 1, 0), /* MultiPressMax */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_FEATURE_MAP_SERVER_ATTRIBUTE_ID, BITMAP32, 4, 0), /* FeatureMap */
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();

// Declare Descriptor cluster attributes
DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(switchDescriptorAttrs)
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_DEVICE_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0), /* device list */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_SERVER_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0), /* server list */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_CLIENT_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0), /* client list */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_PARTS_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0) /* parts list */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_PARTS_LIST_ATTRIBUTE_ID, ARRAY, kDescriptorAttributeArraySize, 0), /* parts list */
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();

// Declare Bridged Device Basic information cluster attributes
DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(switchBridgedDeviceBasicAttrs)
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_USER_LABEL_ATTRIBUTE_ID, CHAR_STRING, kUserLabelSize, 0), /* UserLabel */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_REACHABLE_ATTRIBUTE_ID, BOOLEAN, 1, 0) /* Reachable */
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_REACHABLE_ATTRIBUTE_ID, BOOLEAN, 1, 0), /* Reachable */
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();

// Declare Fixed Label cluster attributes
DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(switchFixedLabelAttrs)
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_LABEL_LIST_ATTRIBUTE_ID, ARRAY, kFixedLabelAttributeArraySize, 0) /* label list */
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();
DECLARE_DYNAMIC_ATTRIBUTE(ZCL_LABEL_LIST_ATTRIBUTE_ID, ARRAY, kFixedLabelAttributeArraySize, 0), /* label list */
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();

// Declare Cluster List for Bridged Switch endpoint
DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN(bridgedSwitchClusters)
Expand Down
2 changes: 1 addition & 1 deletion src/app/AttributeAccessInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ class AttributeAccessInterface
* specific endpoint. This is used to clean up overrides registered for an
* endpoint that becomes disabled.
*/
bool MatchesExactly(EndpointId aEndpointId) const { return mEndpointId.HasValue() && mEndpointId.Value() == aEndpointId; }
bool MatchesEndpoint(EndpointId aEndpointId) const { return mEndpointId.HasValue() && mEndpointId.Value() == aEndpointId; }

/**
* Check whether another AttributeAccessInterface wants to handle the same set of
Expand Down
6 changes: 3 additions & 3 deletions src/app/CommandHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ void CommandHandler::Close()

if (mpCallback)
{
mpCallback->OnDone(this);
mpCallback->OnDone(*this);
}
}

Expand Down Expand Up @@ -171,7 +171,7 @@ CHIP_ERROR CommandHandler::ProcessCommandDataIB(CommandDataIB::Parser & aCommand

err = commandPath.GetEndpointId(&endpointId);
SuccessOrExit(err);
VerifyOrExit(ServerClusterCommandExists(ConcreteCommandPath(endpointId, clusterId, commandId)),
VerifyOrExit(mpCallback->CommandExists(ConcreteCommandPath(endpointId, clusterId, commandId)),
err = CHIP_ERROR_INVALID_PROFILE_ID);
err = aCommandElement.GetData(&commandDataReader);
if (CHIP_END_OF_TLV == err)
Expand All @@ -189,7 +189,7 @@ CHIP_ERROR CommandHandler::ProcessCommandDataIB(CommandDataIB::Parser & aCommand
endpointId, ChipLogValueMEI(clusterId), ChipLogValueMEI(commandId));
const ConcreteCommandPath concretePath(endpointId, clusterId, commandId);
SuccessOrExit(MatterPreCommandReceivedCallback(concretePath));
DispatchSingleClusterCommand(concretePath, commandDataReader, this);
mpCallback->DispatchCommand(*this, ConcreteCommandPath(endpointId, clusterId, commandId), commandDataReader);
MatterPostCommandReceivedCallback(concretePath);
}

Expand Down
15 changes: 14 additions & 1 deletion src/app/CommandHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,19 @@ class CommandHandler : public Command
* Method that signals to a registered callback that this object
* has completed doing useful work and is now safe for release/destruction.
*/
virtual void OnDone(CommandHandler * apCommandObj) = 0;
virtual void OnDone(CommandHandler & apCommandObj) = 0;

/*
* Upon processing of a CommandDataIB, this method is invoked to dispatch the command
* to the right server-side handler provided by the application.
*/
virtual void DispatchCommand(CommandHandler & apCommandObj, const ConcreteCommandPath & aCommandPath,
TLV::TLVReader & apPayload) = 0;

/*
* Check to see if a command implementation exists for a specific concrete command path.
*/
virtual bool CommandExists(const ConcreteCommandPath & aCommandPath) = 0;
};

/*
Expand Down Expand Up @@ -137,5 +149,6 @@ class CommandHandler : public Command
bool mSuppressResponse = false;
bool mTimedRequest = false;
};

} // namespace app
} // namespace chip
181 changes: 181 additions & 0 deletions src/app/CommandHandlerInterface.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/*
*
* Copyright (c) 2021 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/CommandHandler.h>
#include <app/ConcreteCommandPath.h>
#include <app/data-model/Decode.h>
#include <app/data-model/List.h> // So we can encode lists

namespace chip {
namespace app {

/*
* This interface permits applications to register a server-side command handler
* at run-time for a given cluster. The handler can either be configured to handle all endpoints
* for the given cluster or only handle a specific endpoint.
*
* If a command is not handled through this interface, it will default to invoking the generated DispatchSingleClusterCommand
* instead.
*
*/
class CommandHandlerInterface
{
public:
struct HandlerContext
{
public:
HandlerContext(CommandHandler & commandHandler, const ConcreteCommandPath & requestPath, TLV::TLVReader & aReader) :
mCommandHandler(commandHandler), mRequestPath(requestPath), mPayload(aReader)
{}

void SetCommandHandled() { mCommandHandled = true; }
void SetCommandNotHandled() { mCommandHandled = false; }

/*
* Returns a TLVReader positioned at the TLV struct that contains the payload of the command.
*
* If the reader is requested from the context, then we can assume there is an intention
* to access the payload of this command and consequently, to handle this command.
*
* If this is not true, the application should call SetCommandNotHandled().
*
*/
TLV::TLVReader & GetReader()
{
SetCommandHandled();
return mPayload;
}

CommandHandler & mCommandHandler;
const ConcreteCommandPath & mRequestPath;
TLV::TLVReader & mPayload;
bool mCommandHandled = false;
};

/**
* aEndpointId can be Missing to indicate that this object is meant to be
* used with all endpoints.
*/
CommandHandlerInterface(Optional<EndpointId> aEndpointId, ClusterId aClusterId) :
mEndpointId(aEndpointId), mClusterId(aClusterId)
{}

virtual ~CommandHandlerInterface() {}

/**
* Callback that must be implemented to handle an invoke request.
*
* The callee is required to handle *all* errors that may occur during the handling of this command,
* including errors like those encountered during decode and encode of the payloads as
* well as application-specific errors. As part of handling the error, the callee is required
* to handle generation of an appropriate status response.
*
* The only exception to this rule is if the HandleCommand helper method is used below - it will
* help handle some of these cases (see below).
*
* @param [in] handlerContext Context that encapsulates the current invoke request.
* Handlers are responsible for correctly calling SetCommandHandled()
* on the context if they did handle the command.
*
* This is not necessary if the HandleCommand() method below is invoked.
*/
virtual void InvokeCommand(HandlerContext & handlerContext) = 0;

/**
* Mechanism for keeping track of a chain of CommandHandlerInterface.
*/
void SetNext(CommandHandlerInterface * aNext) { mNext = aNext; }
CommandHandlerInterface * GetNext() const { return mNext; }

/**
* Check whether a this CommandHandlerInterface is relevant for a
* particular endpoint+cluster. An CommandHandlerInterface will be used
* for an invoke from a particular cluster only when this function returns
* true.
*/
bool Matches(EndpointId aEndpointId, ClusterId aClusterId) const
{
return (!mEndpointId.HasValue() || mEndpointId.Value() == aEndpointId) && mClusterId == aClusterId;
}

/**
* Check whether an CommandHandlerInterface is relevant for a particular
* specific endpoint. This is used to clean up overrides registered for an
* endpoint that becomes disabled.
*/
bool MatchesEndpoint(EndpointId aEndpointId) const { return mEndpointId.HasValue() && mEndpointId.Value() == aEndpointId; }

/**
* Check whether another CommandHandlerInterface wants to handle the same set of
* commands as we do.
*/
bool Matches(const CommandHandlerInterface & aOther) const
{
return mClusterId == aOther.mClusterId &&
(!mEndpointId.HasValue() || !aOther.mEndpointId.HasValue() || mEndpointId.Value() == aOther.mEndpointId.Value());
}

protected:
/*
* Helper function to automatically de-serialize the data payload into a cluster object
* of type RequestT if the Cluster ID and Command ID in the context match. Upon successful
* de-serialization, the provided function is invoked and passed in a reference to the cluster object.
*
* Any errors encountered in this function prior to calling func result in the automatic generation of a status response.
* If `func` is called, the responsibility for doing so shifts to the callee to handle any further errors that are encountered.
*
* The provided function is expected to have the following signature:
* void Func(HandlerContext &handlerContext, const RequestT &requestPayload);
*/
template <typename RequestT, typename FuncT>
void HandleCommand(HandlerContext & handlerContext, FuncT func)
{
if (!handlerContext.mCommandHandled && (handlerContext.mRequestPath.mClusterId == RequestT::GetClusterId()) &&
(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;
}

func(handlerContext, requestPayload);
}
}

private:
Optional<EndpointId> mEndpointId;
ClusterId mClusterId;
CommandHandlerInterface * mNext = nullptr;
};

} // namespace app
} // namespace chip
Loading

0 comments on commit 5276258

Please sign in to comment.