Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement MTRBaseCluster invokes on top of MTRBaseDevice. #29554

Merged
merged 3 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 0 additions & 165 deletions src/darwin/Framework/CHIP/MTRBaseClusterUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -263,169 +263,4 @@ void MTRReadAttribute(MTRReadParams * _Nonnull params,
std::move(*callbackBridge).DispatchAction(device);
}

/**
* Utility functions base clusters use for doing commands.
*/
template <typename InvokeBridgeType, typename ResponseType>
class MTRInvokeCallback : public chip::app::CommandSender::Callback {
public:
MTRInvokeCallback(InvokeBridgeType * _Nonnull bridge, typename InvokeBridgeType::SuccessCallbackType _Nonnull onResponse,
MTRErrorCallback _Nonnull onError)
: mBridge(bridge)
, mOnResponse(onResponse)
, mOnError(onError)
{
}

~MTRInvokeCallback() {}

void AdoptCommandSender(chip::Platform::UniquePtr<chip::app::CommandSender> commandSender)
{
mCommandSender = std::move(commandSender);
}

protected:
// We need to have different OnResponse implementations depending on whether
// ResponseType is DataModel::NullObjectType or not. Since template class methods
// can't be partially specialized (either you have to partially specialize
// the class template, or you have to fully specialize the method), use
// enable_if to deal with this.
void OnResponse(chip::app::CommandSender * commandSender, const chip::app::ConcreteCommandPath & commandPath,
const chip::app::StatusIB & status, chip::TLV::TLVReader * reader) override
{
HandleResponse(commandSender, commandPath, status, reader);
}

/**
* Response handler for data responses.
*/
template <typename T = ResponseType, std::enable_if_t<!std::is_same<T, chip::app::DataModel::NullObjectType>::value, int> = 0>
void HandleResponse(chip::app::CommandSender * commandSender, const chip::app::ConcreteCommandPath & commandPath,
const chip::app::StatusIB & status, chip::TLV::TLVReader * reader)
{
if (mCalledCallback) {
return;
}
mCalledCallback = true;

ResponseType response;
CHIP_ERROR err = CHIP_NO_ERROR;

//
// We're expecting response data in this variant of OnResponse. Consequently, reader should always be
// non-null. If it is, it means we received a success status code instead, which is not what was expected.
//
VerifyOrExit(reader != nullptr, err = CHIP_ERROR_SCHEMA_MISMATCH);

//
// Validate that the data response we received matches what we expect in terms of its cluster and command IDs.
//
VerifyOrExit(
commandPath.mClusterId == ResponseType::GetClusterId() && commandPath.mCommandId == ResponseType::GetCommandId(),
err = CHIP_ERROR_SCHEMA_MISMATCH);

err = chip::app::DataModel::Decode(*reader, response);
SuccessOrExit(err);

mOnResponse(mBridge, response);

exit:
if (err != CHIP_NO_ERROR) {
mOnError(mBridge, err);
}
}

/**
* Response handler for status responses.
*/
template <typename T = ResponseType, std::enable_if_t<std::is_same<T, chip::app::DataModel::NullObjectType>::value, int> = 0>
void HandleResponse(chip::app::CommandSender * commandSender, const chip::app::ConcreteCommandPath & commandPath,
const chip::app::StatusIB & status, chip::TLV::TLVReader * reader)
{
if (mCalledCallback) {
return;
}
mCalledCallback = true;

//
// If we got a valid reader, it means we received response data that we were not expecting to receive.
//
if (reader != nullptr) {
mOnError(mBridge, CHIP_ERROR_SCHEMA_MISMATCH);
return;
}

chip::app::DataModel::NullObjectType nullResp;
mOnResponse(mBridge, nullResp);
}

void OnError(const chip::app::CommandSender * commandSender, CHIP_ERROR error) override
{
if (mCalledCallback) {
return;
}
mCalledCallback = true;

mOnError(mBridge, error);
}

void OnDone(chip::app::CommandSender * commandSender) override
{
if (!mCalledCallback) {
// This can happen if the server sends a response with an empty
// InvokeResponses list. Since we are not sending wildcard command
// paths, that's not a valid response and we should treat it as an
// error. Use the error we would have gotten if we in fact expected
// a nonempty list.
OnError(commandSender, CHIP_END_OF_TLV);
}

chip::Platform::Delete(this);
}

InvokeBridgeType * _Nonnull mBridge;

typename InvokeBridgeType::SuccessCallbackType mOnResponse;
MTRErrorCallback mOnError;
chip::Platform::UniquePtr<chip::app::CommandSender> mCommandSender;
// For reads, we ensure that we make only one data/error callback to our consumer.
bool mCalledCallback = false;
};

/**
* timedInvokeTimeoutMs, if provided, is how long the server will wait for us to
* send the invoke after we sent the Timed Request message.
*
* invokeTimeout, if provided, will have possible MRP latency added to it and
* the result is how long we will wait for the server to respond.
*/
template <typename BridgeType, typename RequestDataType>
CHIP_ERROR MTRStartInvokeInteraction(BridgeType * _Nonnull bridge, const RequestDataType & requestData,
chip::Messaging::ExchangeManager & exchangeManager, const chip::SessionHandle & session,
typename BridgeType::SuccessCallbackType successCb, MTRErrorCallback failureCb, chip::EndpointId endpoint,
chip::Optional<uint16_t> timedInvokeTimeoutMs, chip::Optional<chip::System::Clock::Timeout> invokeTimeout)
{
auto callback = chip::Platform::MakeUnique<MTRInvokeCallback<BridgeType, typename RequestDataType::ResponseType>>(
bridge, successCb, failureCb);
VerifyOrReturnError(callback != nullptr, CHIP_ERROR_NO_MEMORY);

auto commandSender
= chip::Platform::MakeUnique<chip::app::CommandSender>(callback.get(), &exchangeManager, timedInvokeTimeoutMs.HasValue());
VerifyOrReturnError(commandSender != nullptr, CHIP_ERROR_NO_MEMORY);

chip::app::CommandPathParams commandPath(endpoint, 0, RequestDataType::GetClusterId(), RequestDataType::GetCommandId(),
chip::app::CommandPathFlags::kEndpointIdValid);
ReturnErrorOnFailure(commandSender->AddRequestData(commandPath, requestData, timedInvokeTimeoutMs));

if (invokeTimeout.HasValue()) {
invokeTimeout.SetValue(session->ComputeRoundTripTimeout(invokeTimeout.Value()));
}
ReturnErrorOnFailure(commandSender->SendCommandRequest(session, invokeTimeout));

callback->AdoptCommandSender(std::move(commandSender));
callback.release();

return CHIP_NO_ERROR;
};

NS_ASSUME_NONNULL_END
42 changes: 42 additions & 0 deletions src/darwin/Framework/CHIP/MTRBaseDevice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#import "MTRSetupPayload_Internal.h"
#import "NSDataSpanConversion.h"
#import "NSStringSpanConversion.h"
#import "zap-generated/MTRCommandPayloads_Internal.h"

#include "app/ConcreteAttributePath.h"
#include "app/ConcreteCommandPath.h"
Expand Down Expand Up @@ -1308,6 +1309,47 @@ - (void)_invokeCommandWithEndpointID:(NSNumber *)endpointID
std::move(*bridge).DispatchAction(self);
}

- (void)_invokeKnownCommandWithEndpointID:(NSNumber *)endpointID
clusterID:(NSNumber *)clusterID
commandID:(NSNumber *)commandID
commandPayload:(id)commandPayload
timedInvokeTimeout:(NSNumber * _Nullable)timeout
serverSideProcessingTimeout:(NSNumber * _Nullable)serverSideProcessingTimeout
responseClass:(Class _Nullable)responseClass
queue:(dispatch_queue_t)queue
completion:(void (^)(id _Nullable response, NSError * _Nullable error))completion
{
NSError * encodingError;
auto * commandFields = [commandPayload _encodeAsDataValue:&encodingError];
if (commandFields == nil) {
dispatch_async(queue, ^{
completion(nil, encodingError);
});
return;
}

auto responseHandler = ^(NSArray<NSDictionary<NSString *, id> *> * _Nullable values, NSError * _Nullable error) {
id _Nullable response = nil;
if (error == nil) {
if (values.count != 1) {
error = [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeSchemaMismatch userInfo:nil];
} else if (responseClass != nil) {
response = [[responseClass alloc] initWithResponseValue:values[0] error:&error];
}
}
completion(response, error);
};

[self _invokeCommandWithEndpointID:endpointID
clusterID:clusterID
commandID:commandID
commandFields:commandFields
timedInvokeTimeout:timeout
serverSideProcessingTimeout:serverSideProcessingTimeout
queue:queue
completion:responseHandler];
}

- (void)subscribeToAttributesWithEndpointID:(NSNumber * _Nullable)endpointID
clusterID:(NSNumber * _Nullable)clusterID
attributeID:(NSNumber * _Nullable)attributeID
Expand Down
18 changes: 18 additions & 0 deletions src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,24 @@ static inline MTRTransportType MTRMakeTransportType(chip::Transport::Type type)
queue:(dispatch_queue_t)queue
completion:(MTRDeviceResponseHandler)completion;

/**
* Like the public invokeCommandWithEndpointID but:
*
* 1) Allows passing through a serverSideProcessingTimeout.
* 2) Expects one of the command payload structs as commandPayload
* 3) On success, returns an instance of responseClass via the completion (or
* nil if there is no responseClass, which indicates a status-only command).
*/
- (void)_invokeKnownCommandWithEndpointID:(NSNumber *)endpointID
clusterID:(NSNumber *)clusterID
commandID:(NSNumber *)commandID
commandPayload:(id)commandPayload
timedInvokeTimeout:(NSNumber * _Nullable)timeout
serverSideProcessingTimeout:(NSNumber * _Nullable)serverSideProcessingTimeout
responseClass:(Class _Nullable)responseClass
queue:(dispatch_queue_t)queue
completion:(void (^)(id _Nullable response, NSError * _Nullable error))completion;

@end

@interface MTRClusterPath ()
Expand Down
86 changes: 32 additions & 54 deletions src/darwin/Framework/CHIP/templates/MTRBaseClusters-src.zapt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#import "NSStringSpanConversion.h"
#import "NSDataSpanConversion.h"

#include <app-common/zap-generated/cluster-objects.h>
#include <controller/CHIPCluster.h>
#include <lib/support/CHIPListUtils.h>
#include <platform/CHIPDeviceLayer.h>
Expand Down Expand Up @@ -53,7 +54,6 @@ using chip::System::Clock::Seconds16;
{{! This is used as the implementation for both the new-name and old-name bits, so check for both here. }}
{{#if (or (isSupported cluster command=command)
(isSupported (compatClusterNameRemapping parent.name) command=(compatCommandNameRemapping parent.name name)))}}
{{#*inline "callbackName"}}{{#if hasSpecificResponse}}{{cluster}}Cluster{{asUpperCamelCase responseName preserveAcronyms=true}}{{else}}CommandSuccess{{/if}}{{/inline}}
{{#*inline "paramsType"}}
{{#unless (isSupported cluster command=command)}}
MTR{{compatClusterNameRemapping parent.name}}Cluster{{compatCommandNameRemapping parent.name name}}Params
Expand All @@ -69,61 +69,39 @@ MTR{{cluster}}Cluster{{command}}Params
{{/unless}}
- (void){{asLowerCamelCase name}}WithParams: ({{> paramsType}} * {{#unless commandHasRequiredField}}_Nullable{{/unless}})params completion:({{>command_completion_type command=.}})completion
{
// Make a copy of params before we go async.
params = [params copy];
auto * bridge = new MTR{{>callbackName}}CallbackBridge(self.callbackQueue,
{{#if hasSpecificResponse}}
{{! This treats completion as taking an id for the data. This is
not great from a type-safety perspective, of course. }}
completion,
{{else}}
{{! For now, don't change the bridge API; instead just use an adapter
to invoke our completion handler. This is not great from a
type-safety perspective, of course. }}
^(id _Nullable value, NSError * _Nullable error) {
completion(error);
},
{{/if}}
^(ExchangeManager & exchangeManager, const SessionHandle & session, {{>callbackName}}CallbackType successCb, MTRErrorCallback failureCb, MTRCallbackBridgeBase * bridge) {
auto * typedBridge = static_cast<MTR{{>callbackName}}CallbackBridge *>(bridge);
Optional<uint16_t> timedInvokeTimeoutMs;
Optional<Timeout> invokeTimeout;
ListFreer listFreer;
{{asUpperCamelCase parent.name}}::Commands::{{asUpperCamelCase name}}::Type request;
if (params != nil) {
if (params.timedInvokeTimeoutMs != nil) {
params.timedInvokeTimeoutMs = MTRClampedNumber(params.timedInvokeTimeoutMs, @(1), @(UINT16_MAX));
timedInvokeTimeoutMs.SetValue(params.timedInvokeTimeoutMs.unsignedShortValue);
}
if (params.serverSideProcessingTimeout != nil) {
// Clamp to a number of seconds that will not overflow 32-bit
// int when converted to ms.
auto * serverSideProcessingTimeout = MTRClampedNumber(params.serverSideProcessingTimeout, @(0), @(UINT16_MAX));
invokeTimeout.SetValue(Seconds16(serverSideProcessingTimeout.unsignedShortValue));
}
}
{{#if mustUseTimedInvoke}}
if (!timedInvokeTimeoutMs.HasValue()) {
timedInvokeTimeoutMs.SetValue(10000);
}
if (params == nil) {
params = [[{{> paramsType}} alloc] init];
}

auto responseHandler = ^(id _Nullable response, NSError * _Nullable error) {
{{#if hasSpecificResponse}}
completion(response, error);
{{else}}
completion(error);
{{/if}}
{{#zcl_command_arguments}}
{{#first}}
{{#unless parent.commandHasRequiredField}}
if (params != nil) {
{{/unless}}
{{/first}}
{{>encode_value target=(concat "request." (asLowerCamelCase label)) source=(concat "params." (asStructPropertyName label)) cluster=parent.parent.name errorCode="return CHIP_ERROR_INVALID_ARGUMENT;" depth=0}}
{{#last}}
{{#unless parent.commandHasRequiredField}}
}
{{/unless}}
{{/last}}
{{/zcl_command_arguments}}
};

return MTRStartInvokeInteraction(typedBridge, request, exchangeManager, session, successCb, failureCb, self.endpoint, timedInvokeTimeoutMs, invokeTimeout);
});
std::move(*bridge).DispatchAction(self.device);
auto * timedInvokeTimeoutMs = params.timedInvokeTimeoutMs;
{{#if mustUseTimedInvoke}}
if (timedInvokeTimeoutMs == nil) {
timedInvokeTimeoutMs = @(10000);
}
{{/if}}

using RequestType = {{asUpperCamelCase parent.name}}::Commands::{{asUpperCamelCase name}}::Type;
[self.device _invokeKnownCommandWithEndpointID:@(self.endpoint)
clusterID:@(RequestType::GetClusterId())
commandID:@(RequestType::GetCommandId())
commandPayload:params
timedInvokeTimeout:timedInvokeTimeoutMs
serverSideProcessingTimeout:params.serverSideProcessingTimeout
{{#if hasSpecificResponse}}
responseClass:MTR{{cluster}}Cluster{{asUpperCamelCase responseName preserveAcronyms=true}}Params.class
{{else}}
responseClass:nil
{{/if}}
queue:self.callbackQueue
completion:responseHandler];
}
{{/if}}
{{/inline}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
#include <lib/support/TypeTraits.h>

{{#>MTRCallbackBridge partial-type="Status" }}DefaultSuccessCallback{{/MTRCallbackBridge}}
{{#>MTRCallbackBridge partial-type="CommandStatus" }}CommandSuccessCallback{{/MTRCallbackBridge}}
{{#>MTRCallbackBridge type="Octet_String" isNullable=false ns="chip"}}OctetStringAttributeCallback{{/MTRCallbackBridge}}
{{#>MTRCallbackBridge type="Octet_String" isNullable=true ns="chip"}}NullableOctetStringAttributeCallback{{/MTRCallbackBridge}}
{{#>MTRCallbackBridge type="Char_String" isNullable=false ns="chip"}}CharStringAttributeCallback{{/MTRCallbackBridge}}
Expand Down Expand Up @@ -57,14 +56,6 @@
{{/zcl_attributes_server}}
{{/zcl_clusters}}

{{#zcl_clusters}}
{{#zcl_command_responses}}
{{#if (isSupported (asUpperCamelCase ../name preserveAcronyms=true) command=(asUpperCamelCase name preserveAcronyms=true))}}
{{#>MTRCallbackBridge partial-type="Command" }}{{asUpperCamelCase ../../name preserveAcronyms=true}}Cluster{{asUpperCamelCase ../name preserveAcronyms=true}}Callback{{/MTRCallbackBridge}}
{{/if}}
{{/zcl_command_responses}}
{{/zcl_clusters}}

{{#zcl_clusters}}
{{#zcl_enums}}
{{#if (isSupported (asUpperCamelCase parent.name preserveAcronyms=true) enum=(asUpperCamelCase name preserveAcronyms=true))}}
Expand Down
Loading
Loading