Skip to content

Commit fac56c3

Browse files
andy31415j-ororke
authored andcommitted
Add an Invoke implementation in CodegenDataModel (project-chip#34220)
* Ember invoke implementation with unit tests inside DataModel * Code review comments * Update src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp
1 parent 4e8f6ad commit fac56c3

11 files changed

+170
-348
lines changed

src/app/BUILD.gn

+18-2
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ static_library("interaction-model") {
207207

208208
public_deps = [
209209
":app_config",
210-
":command-handler",
210+
":command-handler-impl",
211211
":constants",
212212
":paths",
213213
":subscription-info-provider",
@@ -333,16 +333,32 @@ source_set("status-response") {
333333
]
334334
}
335335

336-
source_set("command-handler") {
336+
source_set("command-handler-interface") {
337337
sources = [
338338
"CommandHandler.cpp",
339339
"CommandHandler.h",
340340
"CommandHandlerExchangeInterface.h",
341+
]
342+
343+
public_deps = [
344+
":paths",
345+
"${chip_root}/src/access:types",
346+
"${chip_root}/src/app/data-model",
347+
"${chip_root}/src/lib/core",
348+
"${chip_root}/src/lib/support",
349+
"${chip_root}/src/messaging",
350+
"${chip_root}/src/protocols/interaction_model",
351+
]
352+
}
353+
354+
source_set("command-handler-impl") {
355+
sources = [
341356
"CommandHandlerImpl.cpp",
342357
"CommandHandlerImpl.h",
343358
]
344359

345360
public_deps = [
361+
":command-handler-interface",
346362
":paths",
347363
":required-privileges",
348364
":status-response",

src/app/codegen-data-model/CodegenDataModel.cpp

+13-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include <app-common/zap-generated/attribute-type.h>
2020
#include <app/RequiredPrivilege.h>
21+
#include <app/util/IMClusterCommandHandler.h>
2122
#include <app/util/attribute-storage.h>
2223
#include <app/util/endpoint-config-api.h>
2324
#include <lib/core/DataModelTypes.h>
@@ -232,10 +233,19 @@ bool CodegenDataModel::EmberCommandListIterator::Exists(const CommandId * list,
232233
}
233234

234235
CHIP_ERROR CodegenDataModel::Invoke(const InteractionModel::InvokeRequest & request, TLV::TLVReader & input_arguments,
235-
InteractionModel::InvokeReply & reply)
236+
CommandHandler * handler)
236237
{
237-
// TODO: this needs an implementation
238-
return CHIP_ERROR_NOT_IMPLEMENTED;
238+
// TODO: CommandHandlerInterface support is currently
239+
// residing in InteractionModelEngine itself. We may want to separate this out
240+
// into its own registry, similar to attributes, so that IM is decoupled from actual storage of things.
241+
//
242+
// Open issue at https://github.com/project-chip/connectedhomeip/issues/34258
243+
244+
// Ember dispatching automatically uses `handler` to set an appropriate result or status
245+
// This never fails (as handler error is encoded as needed).
246+
DispatchSingleClusterCommand(request.path, input_arguments, handler);
247+
248+
return CHIP_NO_ERROR;
239249
}
240250

241251
EndpointId CodegenDataModel::FirstEndpoint()

src/app/codegen-data-model/CodegenDataModel.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class CodegenDataModel : public chip::app::InteractionModel::DataModel
7171
CHIP_ERROR ReadAttribute(const InteractionModel::ReadAttributeRequest & request, AttributeValueEncoder & encoder) override;
7272
CHIP_ERROR WriteAttribute(const InteractionModel::WriteAttributeRequest & request, AttributeValueDecoder & decoder) override;
7373
CHIP_ERROR Invoke(const InteractionModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments,
74-
InteractionModel::InvokeReply & reply) override;
74+
CommandHandler * handler) override;
7575

7676
/// attribute tree iteration
7777
EndpointId FirstEndpoint() override;

src/app/codegen-data-model/tests/BUILD.gn

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ source_set("ember_extra_files") {
2424
"${chip_root}/src/app/util/ember-io-storage.cpp",
2525
"AttributeReportIBEncodeDecode.cpp",
2626
"AttributeReportIBEncodeDecode.h",
27+
"EmberInvokeOverride.cpp",
28+
"EmberInvokeOverride.h",
2729
"EmberReadWriteOverride.cpp",
2830
"EmberReadWriteOverride.h",
2931
"InteractionModelTemporaryOverrides.cpp",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (c) 2024 Project CHIP Authors
3+
* All rights reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
#include "EmberInvokeOverride.h"
18+
19+
#include <app/util/IMClusterCommandHandler.h>
20+
21+
namespace {
22+
23+
chip::app::ConcreteCommandPath gLastDispatchPath;
24+
uint32_t gDispatchCount = 0;
25+
26+
} // namespace
27+
28+
namespace chip {
29+
namespace Test {
30+
31+
app::ConcreteCommandPath GetLastDispatchPath()
32+
{
33+
return gLastDispatchPath;
34+
}
35+
36+
uint32_t DispatchCount()
37+
{
38+
return gDispatchCount;
39+
}
40+
41+
} // namespace Test
42+
} // namespace chip
43+
44+
namespace chip {
45+
namespace app {
46+
47+
void DispatchSingleClusterCommand(const ConcreteCommandPath & aRequestCommandPath, chip::TLV::TLVReader & aReader,
48+
CommandHandler * apCommandObj)
49+
{
50+
gLastDispatchPath = aRequestCommandPath;
51+
gDispatchCount++;
52+
}
53+
54+
} // namespace app
55+
} // namespace chip
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright (c) 2024 Project CHIP Authors
3+
* All rights reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
#pragma once
18+
19+
#include <app/ConcreteCommandPath.h>
20+
21+
namespace chip {
22+
namespace Test {
23+
24+
/// what was the last path on which DispatchSingleClusterCommand was called
25+
app::ConcreteCommandPath GetLastDispatchPath();
26+
27+
/// How many times was DispatchSingleClusterCommand called
28+
uint32_t DispatchCount();
29+
30+
} // namespace Test
31+
} // namespace chip

src/app/codegen-data-model/tests/InteractionModelTemporaryOverrides.cpp

-6
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,6 @@ CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescr
7474
return CHIP_ERROR_NOT_IMPLEMENTED;
7575
}
7676

77-
void DispatchSingleClusterCommand(const ConcreteCommandPath & aRequestCommandPath, chip::TLV::TLVReader & aReader,
78-
CommandHandler * apCommandObj)
79-
{
80-
// TODO: total hardcoded noop
81-
}
82-
8377
} // namespace app
8478
} // namespace chip
8579

src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp

+46
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
1616
*/
17+
#include "app/ConcreteCommandPath.h"
1718
#include <app/codegen-data-model/CodegenDataModel.h>
1819

1920
#include <app/codegen-data-model/tests/AttributeReportIBEncodeDecode.h>
21+
#include <app/codegen-data-model/tests/EmberInvokeOverride.h>
2022
#include <app/codegen-data-model/tests/EmberReadWriteOverride.h>
2123

2224
#include <access/AccessControl.h>
@@ -69,6 +71,9 @@ constexpr NodeId kTestNodeId = 0xFFFF'1234'ABCD'4321;
6971
constexpr AttributeId kAttributeIdReadOnly = 0x3001;
7072
constexpr AttributeId kAttributeIdTimedWrite = 0x3002;
7173

74+
constexpr CommandId kMockCommandId1 = 0x1234;
75+
constexpr CommandId kMockCommandId2 = 0x1122;
76+
7277
constexpr EndpointId kEndpointIdThatIsMissing = kMockEndpointMin - 1;
7378

7479
constexpr AttributeId kReadOnlyAttributeId = 0x5001;
@@ -2430,6 +2435,47 @@ TEST(TestCodegenModelViaMocks, EmberWriteAttributeAccessInterfaceTest)
24302435
TestEmberScalarNullWrite<int64_t, ZCL_INT64S_ATTRIBUTE_TYPE>();
24312436
}
24322437

2438+
TEST(TestCodegenModelViaMocks, EmberInvokeTest)
2439+
{
2440+
// Ember invoke is fully code-generated - there is a single function for Dispatch
2441+
// that will do a `switch` on the path elements and invoke a corresponding `emberAf*`
2442+
// callback.
2443+
//
2444+
// The only thing that can be validated is that this `DispatchSingleClusterCommand`
2445+
// is actually invoked.
2446+
2447+
UseMockNodeConfig config(gTestNodeConfig);
2448+
chip::app::CodegenDataModel model;
2449+
2450+
{
2451+
const ConcreteCommandPath kCommandPath(kMockEndpoint1, MockClusterId(1), kMockCommandId1);
2452+
const InvokeRequest kInvokeRequest{ .path = kCommandPath };
2453+
chip::TLV::TLVReader tlvReader;
2454+
2455+
const uint32_t kDispatchCountPre = chip::Test::DispatchCount();
2456+
2457+
// Using a handler set to nullptr as it is not used by the impl
2458+
ASSERT_EQ(model.Invoke(kInvokeRequest, tlvReader, /* handler = */ nullptr), CHIP_NO_ERROR);
2459+
2460+
EXPECT_EQ(chip::Test::DispatchCount(), kDispatchCountPre + 1); // single dispatch
2461+
EXPECT_EQ(chip::Test::GetLastDispatchPath(), kCommandPath); // for the right path
2462+
}
2463+
2464+
{
2465+
const ConcreteCommandPath kCommandPath(kMockEndpoint1, MockClusterId(1), kMockCommandId2);
2466+
const InvokeRequest kInvokeRequest{ .path = kCommandPath };
2467+
chip::TLV::TLVReader tlvReader;
2468+
2469+
const uint32_t kDispatchCountPre = chip::Test::DispatchCount();
2470+
2471+
// Using a handler set to nullpotr as it is not used by the impl
2472+
ASSERT_EQ(model.Invoke(kInvokeRequest, tlvReader, /* handler = */ nullptr), CHIP_NO_ERROR);
2473+
2474+
EXPECT_EQ(chip::Test::DispatchCount(), kDispatchCountPre + 1); // single dispatch
2475+
EXPECT_EQ(chip::Test::GetLastDispatchPath(), kCommandPath); // for the right path
2476+
}
2477+
}
2478+
24332479
TEST(TestCodegenModelViaMocks, EmberWriteAttributeAccessInterfaceReturningError)
24342480
{
24352481
UseMockNodeConfig config(gTestNodeConfig);

src/app/data-model-interface/BUILD.gn

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ source_set("data-model-interface") {
2020
"DataModel.h",
2121
"DataModelChangeListener.h",
2222
"EventsGenerator.h",
23-
"InvokeResponder.h",
2423
"MetadataTypes.cpp",
2524
"MetadataTypes.h",
2625
"OperationTypes.h",
@@ -29,6 +28,7 @@ source_set("data-model-interface") {
2928
public_deps = [
3029
"${chip_root}/src/access:types",
3130
"${chip_root}/src/app:attribute-access",
31+
"${chip_root}/src/app:command-handler-interface",
3232
"${chip_root}/src/app:events",
3333
"${chip_root}/src/app:paths",
3434
"${chip_root}/src/app/MessageDef",

src/app/data-model-interface/DataModel.h

+3-19
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121

2222
#include <app/AttributeValueDecoder.h>
2323
#include <app/AttributeValueEncoder.h>
24+
#include <app/CommandHandler.h>
2425

2526
#include <app/data-model-interface/Context.h>
26-
#include <app/data-model-interface/InvokeResponder.h>
2727
#include <app/data-model-interface/MetadataTypes.h>
2828
#include <app/data-model-interface/OperationTypes.h>
2929

@@ -99,25 +99,9 @@ class DataModel : public DataModelMetadataTree
9999
/// - `NeedsTimedInteraction` for writes that are not timed however are required to be so
100100
virtual CHIP_ERROR WriteAttribute(const WriteAttributeRequest & request, AttributeValueDecoder & decoder) = 0;
101101

102-
/// `reply` is used to send back the reply.
103-
/// - calling Reply() or ReplyAsync() will let the application control the reply
104-
/// - returning a CHIP_NO_ERROR without reply/reply_async implies a Status::Success reply without data
102+
/// `handler` is used to send back the reply.
105103
/// - returning a value other than CHIP_NO_ERROR implies an error reply (error and data are mutually exclusive)
106-
///
107-
/// See InvokeReply/AutoCompleteInvokeResponder for details on how to send back replies and expected
108-
/// error handling. If you need to know weather a response was successfully sent, use the underlying
109-
/// `reply` object instead of returning an error code from Invoke.
110-
///
111-
/// Return codes
112-
/// CHIP_IM_GLOBAL_STATUS(code):
113-
/// - error codes that are translatable to specific IM codes
114-
/// - in particular, the following codes are interesting/expected
115-
/// - `UnsupportedEndpoint` for invalid endpoint
116-
/// - `UnsupportedCluster` for no such cluster on the endpoint
117-
/// - `UnsupportedCommand` for no such command in the cluster
118-
/// - `UnsupportedAccess` for permission errors (ACL or fabric scoped with invalid fabric)
119-
/// - `NeedsTimedInteraction` if the invoke requires timed interaction support
120-
virtual CHIP_ERROR Invoke(const InvokeRequest & request, chip::TLV::TLVReader & input_arguments, InvokeReply & reply) = 0;
104+
virtual CHIP_ERROR Invoke(const InvokeRequest & request, chip::TLV::TLVReader & input_arguments, CommandHandler * handler) = 0;
121105

122106
private:
123107
InteractionModelContext mContext = { nullptr };

0 commit comments

Comments
 (0)