Skip to content

Commit

Permalink
Add and Enable IM subscription support (#9510)
Browse files Browse the repository at this point in the history
* [testsuite] Refactor test suite simulated cluster command

* Add initial IM subscriber server change

--Extend IM ReadHandler with subscribe capability, and update the
corresponding reporting engine, which can process multiple subscriber,
process subscribe request and generate subscribe response.
--Hook setDirty API to ember so that ember can generate the change set
and send it out via IM report.

* Add initial IM subscription client change

--Extends IM read client with subscribe capability and it can send
subscribe request and process subscribe process and further maintain
subscription with livess check.
--Disable path filter for IM read/subscribe request.
--Update templates to use interaction model APIs for sending subscribe
requests.
--Update DeviceControllerInteractionModelDelegate to handle report
messages from subscription.
--Update ChipCallback Mgr to bridge subscribe responses to existing
callbacks, and handle TLV message just as read responses.
--Update TestSuite for basic Subscription protocol tests, test routing
looks like follows:
Send subscribe request, wait for response.
Wait for a few seconds, and execute "kick" commands.
The test is expected to receive the same number of Reports as the
number of kick commands.
The test will fail when not receiving enough report data from server
in global timeout.
--Update python script test to (in cirque) include a similar test
routine as test suite.
--Add unit test for positive and negative subscribe test
--Add integrated cirque test for subscribe

* Update test cases, add darwin test

* run codegen

Co-authored-by: Song Guo <[email protected]>
  • Loading branch information
yunhanw-google and erjiaqing authored Sep 13, 2021
1 parent 7c00fa2 commit ec5f5dc
Show file tree
Hide file tree
Showing 78 changed files with 3,460 additions and 2,678 deletions.
21 changes: 11 additions & 10 deletions examples/bridge-app/esp32/main/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -314,25 +314,25 @@ void HandleDeviceStatusChanged(Device * dev, Device::Changed_t itemChangedMask)
if (itemChangedMask & Device::kChanged_Reachable)
{
uint8_t reachable = dev->IsReachable() ? 1 : 0;
emberAfReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_BRIDGED_DEVICE_BASIC_CLUSTER_ID,
ZCL_REACHABLE_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, 0, ZCL_BOOLEAN_ATTRIBUTE_TYPE,
&reachable);
InteractionModelReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_BRIDGED_DEVICE_BASIC_CLUSTER_ID,
ZCL_REACHABLE_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, 0,
ZCL_BOOLEAN_ATTRIBUTE_TYPE, &reachable);
}

if (itemChangedMask & Device::kChanged_State)
{
uint8_t isOn = dev->IsOn() ? 1 : 0;
emberAfReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_ON_OFF_CLUSTER_ID, ZCL_ON_OFF_ATTRIBUTE_ID,
CLUSTER_MASK_SERVER, 0, ZCL_BOOLEAN_ATTRIBUTE_TYPE, &isOn);
InteractionModelReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_ON_OFF_CLUSTER_ID, ZCL_ON_OFF_ATTRIBUTE_ID,
CLUSTER_MASK_SERVER, 0, ZCL_BOOLEAN_ATTRIBUTE_TYPE, &isOn);
}

if (itemChangedMask & Device::kChanged_Name)
{
uint8_t zclName[kUserLabelSize];
ToZclCharString(zclName, dev->GetName(), kUserLabelSize - 1);
emberAfReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_BRIDGED_DEVICE_BASIC_CLUSTER_ID,
ZCL_USER_LABEL_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, 0, ZCL_CHAR_STRING_ATTRIBUTE_TYPE,
zclName);
InteractionModelReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_BRIDGED_DEVICE_BASIC_CLUSTER_ID,
ZCL_USER_LABEL_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, 0,
ZCL_CHAR_STRING_ATTRIBUTE_TYPE, zclName);
}
if (itemChangedMask & Device::kChanged_Location)
{
Expand All @@ -343,8 +343,9 @@ void HandleDeviceStatusChanged(Device * dev, Device::Changed_t itemChangedMask)

EncodeFixedLabel("room", dev->GetLocation(), buffer, sizeof(buffer), &am);

emberAfReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_FIXED_LABEL_CLUSTER_ID, ZCL_LABEL_LIST_ATTRIBUTE_ID,
CLUSTER_MASK_SERVER, 0, ZCL_ARRAY_ATTRIBUTE_TYPE, buffer);
InteractionModelReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_FIXED_LABEL_CLUSTER_ID,
ZCL_LABEL_LIST_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, 0,
ZCL_ARRAY_ATTRIBUTE_TYPE, buffer);
}
}

Expand Down
21 changes: 11 additions & 10 deletions examples/bridge-app/linux/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,26 +207,26 @@ void HandleDeviceStatusChanged(Device * dev, Device::Changed_t itemChangedMask)
if (itemChangedMask & Device::kChanged_Reachable)
{
uint8_t reachable = dev->IsReachable() ? 1 : 0;
emberAfReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_BRIDGED_DEVICE_BASIC_CLUSTER_ID,
ZCL_REACHABLE_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, 0, ZCL_BOOLEAN_ATTRIBUTE_TYPE,
&reachable);
InteractionModelReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_BRIDGED_DEVICE_BASIC_CLUSTER_ID,
ZCL_REACHABLE_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, 0,
ZCL_BOOLEAN_ATTRIBUTE_TYPE, &reachable);
}

if (itemChangedMask & Device::kChanged_State)
{
uint8_t isOn = dev->IsOn() ? 1 : 0;
emberAfReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_ON_OFF_CLUSTER_ID, ZCL_ON_OFF_ATTRIBUTE_ID,
CLUSTER_MASK_SERVER, 0, ZCL_BOOLEAN_ATTRIBUTE_TYPE, &isOn);
InteractionModelReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_ON_OFF_CLUSTER_ID, ZCL_ON_OFF_ATTRIBUTE_ID,
CLUSTER_MASK_SERVER, 0, ZCL_BOOLEAN_ATTRIBUTE_TYPE, &isOn);
}

if (itemChangedMask & Device::kChanged_Name)
{
uint8_t zclName[kUserLabelSize];
MutableByteSpan zclNameSpan(zclName);
MakeZclCharString(zclNameSpan, dev->GetName());
emberAfReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_BRIDGED_DEVICE_BASIC_CLUSTER_ID,
ZCL_USER_LABEL_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, 0, ZCL_CHAR_STRING_ATTRIBUTE_TYPE,
zclNameSpan.data());
InteractionModelReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_BRIDGED_DEVICE_BASIC_CLUSTER_ID,
ZCL_USER_LABEL_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, 0,
ZCL_CHAR_STRING_ATTRIBUTE_TYPE, zclNameSpan.data());
}

if (itemChangedMask & Device::kChanged_Location)
Expand All @@ -238,8 +238,9 @@ void HandleDeviceStatusChanged(Device * dev, Device::Changed_t itemChangedMask)

EncodeFixedLabel("room", dev->GetLocation(), buffer, sizeof(buffer), &am);

emberAfReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_FIXED_LABEL_CLUSTER_ID, ZCL_LABEL_LIST_ATTRIBUTE_ID,
CLUSTER_MASK_SERVER, 0, ZCL_ARRAY_ATTRIBUTE_TYPE, buffer);
InteractionModelReportingAttributeChangeCallback(dev->GetEndpointId(), ZCL_FIXED_LABEL_CLUSTER_ID,
ZCL_LABEL_LIST_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, 0,
ZCL_ARRAY_ATTRIBUTE_TYPE, buffer);
}
}

Expand Down
78 changes: 78 additions & 0 deletions examples/chip-tool/templates/partials/process_response_value.zapt
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
{{#chip_tests_item_response_parameters}}
{{#if hasExpectedValue}}
{{#if isList}}
if (count != {{expectedValue.length}})
{
ChipLogError(chipTool, "Error: Value mismatch. Expected: '%s'", "{{expectedValue}}");
runner->SetCommandExitStatus(CHIP_ERROR_INTERNAL);
return;
}
{{else}}
{{#if (isString type)}}
{{chipType}} {{asLowerCamelCase name}}Argument = chip::ByteSpan(chip::Uint8::from_const_char("{{expectedValue}}"), strlen("{{expectedValue}}"));
if (!{{asLowerCamelCase name}}.data_equal({{asLowerCamelCase name}}Argument))
{{else}}
if ({{asLowerCamelCase name}} != {{expectedValue}}{{asTypeLiteralSuffix chipType}})
{{/if}}
{
ChipLogError(chipTool, "Error: Value mismatch. Expected: '%s'", "{{expectedValue}}");
runner->SetCommandExitStatus(CHIP_ERROR_INTERNAL);
return;
}
{{/if}}
{{/if}}
{{#if hasExpectedConstraints}}
{{#if expectedConstraints.type}}
ChipLogError(chipTool, "Warning: {{asLowerCamelCase name}} type checking is not implemented yet. Expected type: '%s'", "{{expectedConstraints.type}}");
{{/if}}

{{#if expectedConstraints.format}}
ChipLogError(chipTool, "Warning: {{asLowerCamelCase name}} format checking is not implemented yet. Expected format: '%s'", "{{expectedConstraints.format}}");
{{/if}}

{{#if expectedConstraints.minLength}}
if ({{asLowerCamelCase name}}.size() < {{expectedConstraints.minLength}})
{
ChipLogError(chipTool, "Error: {{asLowerCamelCase name}} is too short. Min size is {{expectedConstraints.minLength}} but got '%zu'", {{asLowerCamelCase name}}.size());
runner->SetCommandExitStatus(CHIP_ERROR_INTERNAL);
return;
}
{{/if}}

{{#if expectedConstraints.maxLength}}
if ({{asLowerCamelCase name}}.size() > {{expectedConstraints.maxLength}})
{
ChipLogError(chipTool, "Error: {{asLowerCamelCase name}} is too long. Max size is {{expectedConstraints.maxLength}} but got '%zu'", {{asLowerCamelCase name}}.size());
runner->SetCommandExitStatus(CHIP_ERROR_INTERNAL);
return;
}
{{/if}}

{{#if expectedConstraints.minValue}}
if ({{asLowerCamelCase name}} < {{expectedConstraints.minValue}})
{
ChipLogError(chipTool, "Error: {{asLowerCamelCase name}} is lower than expected. Min value is {{expectedConstraints.minValue}} but got '%d'", {{asLowerCamelCase name}});
runner->SetCommandExitStatus(CHIP_ERROR_INTERNAL);
return;
}
{{/if}}

{{#if expectedConstraints.maxValue}}
if ({{asLowerCamelCase name}} > {{expectedConstraints.maxValue}})
{
ChipLogError(chipTool, "Error: {{asLowerCamelCase name}} is higher than expected. Max value is {{expectedConstraints.maxValue}} but got '%d'", {{asLowerCamelCase name}});
runner->SetCommandExitStatus(CHIP_ERROR_INTERNAL);
return;
}
{{/if}}

{{#if expectedConstraints.notValue}}
if ({{asLowerCamelCase name}} == {{expectedConstraints.notValue}})
{
ChipLogError(chipTool, "Error: {{asLowerCamelCase name}} was not expected to be '{{expectedConstraints.notValue}}' due to notValue constraint");
runner->SetCommandExitStatus(CHIP_ERROR_INTERNAL);
return;
}
{{/if}}
{{/if}}
{{/chip_tests_item_response_parameters}}
114 changes: 30 additions & 84 deletions examples/chip-tool/templates/partials/test_cluster.zapt
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,22 @@ class {{filename}}: public TestCommand

{{#chip_tests_items}}
{{#if (isTestOnlyCluster cluster)}}
CHIP_ERROR TestSendCluster{{asUpperCamelCase cluster}}Command{{asUpperCamelCase command}}_{{index}}()
{
ChipLogProgress(chipTool, "{{cluster}} - {{label}}");

return {{command}}({{#chip_tests_item_parameters}}{{#not_first}}, {{/not_first}}{{definedValue}}{{/chip_tests_item_parameters}});
}
{{> (asTestSuiteSimulatedClusterCommandPartial command) }}
{{else}}
// Test {{label}}
using SuccessCallback_{{index}} = void (*)(void * context{{#chip_tests_item_response_parameters}}, {{#if isList}}uint16_t count, {{/if}}{{chipType}} {{#if isList}}* {{/if}}{{asLowerCamelCase name}}{{/chip_tests_item_response_parameters}});
chip::Callback::Callback<SuccessCallback_{{index}}> mOnSuccessCallback_{{index}} { OnTestSendCluster{{asUpperCamelCase cluster}}Command{{asUpperCamelCase command}}_{{index}}_SuccessResponse, this };
chip::Callback::Callback<DefaultFailureCallback> mOnFailureCallback_{{index}} { OnTestSendCluster{{asUpperCamelCase cluster}}Command{{asUpperCamelCase command}}_{{index}}_FailureResponse, this };
{{#if isSubscribeAttribute}}
chip::Callback::Callback<DefaultSuccessCallback> mOnSubscriptionEstablishedCallback_{{index}} { SubscribeAttribute_{{ index }}_OnSubscriptionEstablishedCallback, this };
{{/if}}

bool mIsFailureExpected_{{index}} = {{response.error}};

{{#if isSubscribeAttribute}}
bool mReceivedReport_{{index}} = false;
{{/if}}

CHIP_ERROR TestSendCluster{{asUpperCamelCase cluster}}Command{{asUpperCamelCase command}}_{{index}}()
{
ChipLogProgress(chipTool, "{{cluster}} - {{label}}: Sending command...");
Expand All @@ -77,6 +80,9 @@ class {{filename}}: public TestCommand
{{/if}}
{{/chip_tests_item_parameters}}
err = cluster.{{asUpperCamelCase command}}(mOnSuccessCallback_{{index}}.Cancel(), mOnFailureCallback_{{index}}.Cancel(){{#chip_tests_item_parameters}}, {{asLowerCamelCase name}}Argument{{/chip_tests_item_parameters}});
{{else if isSubscribeAttribute}}
cluster.ReportAttribute{{asUpperCamelCase attribute}}(mOnSuccessCallback_{{index}}.Cancel());
err = cluster.ConfigureAttribute{{asUpperCamelCase attribute}}(mOnSubscriptionEstablishedCallback_{{index}}.Cancel(), mOnFailureCallback_{{index}}.Cancel(), {{minInterval}}, {{maxInterval}});
{{else if isReadAttribute}}
err = cluster.ReadAttribute{{asUpperCamelCase attribute}}(mOnSuccessCallback_{{index}}.Cancel(), mOnFailureCallback_{{index}}.Cancel());
{{else if isWriteAttribute}}
Expand All @@ -95,6 +101,19 @@ class {{filename}}: public TestCommand
return err;
}

{{#if isSubscribeAttribute }}
static void SubscribeAttribute_{{ index }}_OnSubscriptionEstablishedCallback(void * context)
{
{{parent.filename}} * runner = reinterpret_cast<{{parent.filename}} *>(context);
if (!runner->mReceivedReport_{{index}}) {
ChipLogError(chipTool, "Error: Initial report not received!");
runner->SetCommandExitStatus(CHIP_ERROR_INTERNAL);
return;
}
runner->NextTest();
}
{{/if}}

static void OnTestSendCluster{{asUpperCamelCase cluster}}Command{{asUpperCamelCase command}}_{{index}}_FailureResponse(void * context, uint8_t status)
{
ChipLogProgress(chipTool, "{{cluster}} - {{label}}: Failure Response");
Expand Down Expand Up @@ -130,86 +149,13 @@ class {{filename}}: public TestCommand
return;
}

{{#chip_tests_item_response_parameters}}
{{#if hasExpectedValue}}
{{#if isList}}
if (count != {{expectedValue.length}})
{
ChipLogError(chipTool, "Error: Value mismatch. Expected: '%s'", "{{expectedValue}}");
runner->SetCommandExitStatus(CHIP_ERROR_INTERNAL);
return;
}
{{else}}
{{#if (isString type)}}
{{chipType}} {{asLowerCamelCase name}}Argument = chip::ByteSpan(chip::Uint8::from_const_char("{{expectedValue}}"), strlen("{{expectedValue}}"));
if (!{{asLowerCamelCase name}}.data_equal({{asLowerCamelCase name}}Argument))
{{else}}
if ({{asLowerCamelCase name}} != {{expectedValue}}{{asTypeLiteralSuffix chipType}})
{{/if}}
{
ChipLogError(chipTool, "Error: Value mismatch. Expected: '%s'", "{{expectedValue}}");
runner->SetCommandExitStatus(CHIP_ERROR_INTERNAL);
return;
}
{{/if}}
{{/if}}
{{#if hasExpectedConstraints}}
{{#if expectedConstraints.type}}
ChipLogError(chipTool, "Warning: {{asLowerCamelCase name}} type checking is not implemented yet. Expected type: '%s'", "{{expectedConstraints.type}}");
{{/if}}

{{#if expectedConstraints.format}}
ChipLogError(chipTool, "Warning: {{asLowerCamelCase name}} format checking is not implemented yet. Expected format: '%s'", "{{expectedConstraints.format}}");
{{/if}}

{{#if expectedConstraints.minLength}}
if ({{asLowerCamelCase name}}.size() < {{expectedConstraints.minLength}})
{
ChipLogError(chipTool, "Error: {{asLowerCamelCase name}} is too short. Min size is {{expectedConstraints.minLength}} but got '%zu'", {{asLowerCamelCase name}}.size());
runner->SetCommandExitStatus(CHIP_ERROR_INTERNAL);
return;
}
{{/if}}

{{#if expectedConstraints.maxLength}}
if ({{asLowerCamelCase name}}.size() > {{expectedConstraints.maxLength}})
{
ChipLogError(chipTool, "Error: {{asLowerCamelCase name}} is too long. Max size is {{expectedConstraints.maxLength}} but got '%zu'", {{asLowerCamelCase name}}.size());
runner->SetCommandExitStatus(CHIP_ERROR_INTERNAL);
return;
}
{{/if}}

{{#if expectedConstraints.minValue}}
if ({{asLowerCamelCase name}} < {{expectedConstraints.minValue}})
{
ChipLogError(chipTool, "Error: {{asLowerCamelCase name}} is lower than expected. Min value is {{expectedConstraints.minValue}} but got '%d'", {{asLowerCamelCase name}});
runner->SetCommandExitStatus(CHIP_ERROR_INTERNAL);
return;
}
{{/if}}
{{> process_response_value}}

{{#if expectedConstraints.maxValue}}
if ({{asLowerCamelCase name}} > {{expectedConstraints.maxValue}})
{
ChipLogError(chipTool, "Error: {{asLowerCamelCase name}} is higher than expected. Max value is {{expectedConstraints.maxValue}} but got '%d'", {{asLowerCamelCase name}});
runner->SetCommandExitStatus(CHIP_ERROR_INTERNAL);
return;
}
{{/if}}

{{#if expectedConstraints.notValue}}
if ({{asLowerCamelCase name}} == {{expectedConstraints.notValue}})
{
ChipLogError(chipTool, "Error: {{asLowerCamelCase name}} was not expected to be '{{expectedConstraints.notValue}}' due to notValue constraint");
runner->SetCommandExitStatus(CHIP_ERROR_INTERNAL);
return;
}
{{/if}}
{{#if isSubscribeAttribute}}
runner->mReceivedReport_{{index}} = true;
{{else}}
runner->NextTest();
{{/if}}
{{/chip_tests_item_response_parameters}}

runner->NextTest();
}

{{/if}}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// The callback should be called atleast once
{{#chip_tests_WaitForAttributeReport_attribute_info}}
using OnReportCallback_{{parent.index}} = void (*)(void * context{{#chip_tests_item_response_parameters}}, {{#if isList}}uint16_t count, {{/if}}{{chipType}} {{#if isList}}* {{/if}}{{asLowerCamelCase name}}{{/chip_tests_item_response_parameters}});
chip::Callback::Callback<OnReportCallback_{{ parent.index }}> mOnReportCallback_{{parent.index}} { SubscribeAttribute_{{ parent.index }}_OnReportCallback, this };
{{/chip_tests_WaitForAttributeReport_attribute_info}}

bool mReceivedReport_{{index}} = false;

CHIP_ERROR TestSendCluster{{asUpperCamelCase cluster}}Command{{asUpperCamelCase command}}_{{index}}()
{
ChipLogProgress(chipTool, "{{cluster}} - {{asUpperCamelCase command}} - {{label}}");
{{#chip_tests_WaitForAttributeReport_attribute_info}}
chip::Controller::{{asUpperCamelCase cluster}}Cluster cluster;
cluster.Associate(mDevice, {{endpoint}});
return cluster.ReportAttribute{{asUpperCamelCase attribute}}(mOnReportCallback_{{parent.index}}.Cancel());
{{/chip_tests_WaitForAttributeReport_attribute_info}}
}

{{#chip_tests_WaitForAttributeReport_attribute_info}}
static void SubscribeAttribute_{{ parent.index }}_OnReportCallback(void * context{{#chip_tests_item_response_parameters}}, {{#if isList}}uint16_t count, {{/if}}{{chipType}} {{#if isList}}* {{/if}}{{asLowerCamelCase name}}{{/chip_tests_item_response_parameters}})
{
ChipLogProgress(chipTool, "On/Off - Subscribe {{asUpperCamelCase attribute}} Attribute: Report Data");
{{parent.parent.filename}} * runner = reinterpret_cast<{{parent.parent.filename}} *>(context);

if (runner->mReceivedReport_{{parent.index}})
{
// Receiving attribute more than once is not an issue, since the following handler will override previous handlers.
return;
}

{{> process_response_value}}

runner->mReceivedReport_{{parent.index}} = true;
ChipLogProgress(chipTool, "On/Off - report received.");
runner->NextTest();
}

{{/chip_tests_WaitForAttributeReport_attribute_info}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CHIP_ERROR TestSendCluster{{asUpperCamelCase cluster}}Command{{asUpperCamelCase command}}_{{index}}()
{
ChipLogProgress(chipTool, "{{cluster}} - {{asUpperCamelCase command}} - {{label}}");

return {{command}}({{#chip_tests_item_parameters}}{{#not_first}}, {{/not_first}}{{definedValue}}{{/chip_tests_item_parameters}});
}
2 changes: 1 addition & 1 deletion examples/chip-tool/templates/reporting-commands.zapt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public:
{{#chip_client_clusters}}
{{#chip_server_cluster_attributes}}
{{#if isReportableAttribute}}
callbacksMgr.AddReportCallback(GetExecContext()->storage->GetRemoteNodeId(), endpointId, {{asHex parent.code 4}}, {{asHex code 4}}, onReport{{asUpperCamelCase parent.name}}{{asUpperCamelCase name}}Callback->Cancel());
callbacksMgr.AddReportCallback(GetExecContext()->storage->GetRemoteNodeId(), endpointId, {{asHex parent.code 4}}, {{asHex code 4}}, onReport{{asUpperCamelCase parent.name}}{{asUpperCamelCase name}}Callback->Cancel(), BasicAttributeFilter<{{chipCallback.name}}AttributeCallback>);
{{/if}}
{{/chip_server_cluster_attributes}}
{{/chip_client_clusters}}
Expand Down
Loading

0 comments on commit ec5f5dc

Please sign in to comment.