Skip to content

Commit a7245f8

Browse files
authored
Updated the guides for code driven writing_clusters (project-chip#41409)
* Updated the guides for code driven writing_clusters - More checkmark-list for migration (easier to follow) - Added details for EventInfo and layout/design - Overall readability updates * Self-review: wording update * Add guidance on PRs for easy review. * More details on the renames. * Restyle.
1 parent 2f254b2 commit a7245f8

File tree

2 files changed

+258
-187
lines changed

2 files changed

+258
-187
lines changed

docs/guides/migrating_ember_cluster_to_code_driven.md

Lines changed: 185 additions & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -7,195 +7,193 @@ general overview of the code-driven cluster architecture.
77

88
---
99

10-
## Step 1: Evaluate the Existing Ember Implementation
11-
12-
Before writing new code, it's crucial to understand how the current Ember
13-
cluster is implemented. Ember clusters typically use a mix of the following
14-
patterns:
15-
16-
### Pure Ember Implementation (Simple Attributes)
17-
18-
For simple attributes, the Ember framework in `src/app/util` handles data
19-
storage and access. The application interacts with these attributes via the
20-
type-safe accessors in
21-
`zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h`.
22-
23-
In a code-driven implementation, this data must be moved into member variables
24-
within your new cluster class.
25-
26-
In some cases the cluster directory may not exist. In that case, create a new
27-
directory and add the mapping in
28-
[src/app/zap_cluster_list.json](https://github.com/project-chip/connectedhomeip/blob/master/src/app/zap_cluster_list.json)
29-
under the `ServerDirectories` key.
30-
31-
### `AttributeAccessInterface` (`AAI`) and `CommandHandlerInterface` (`CHI`)
32-
33-
When more complex logic is needed, Ember clusters use these interfaces.
34-
35-
- `AAI` can be directly translated to the `ReadAttribute` and `WriteAttribute`
36-
methods in your new cluster class.
37-
- `CHI` can be translated to the `InvokeCommand` method.
38-
39-
### Determine ember/zap storage
40-
41-
Ember storage should be moved from `persist/ram/callback` into `ram/callback`:
42-
43-
- if the value loaded from ZAP UI needs to be loaded, use `RAM` and have
44-
`CodegenIntegration.cpp` load the value from ZAP via `Accessors.h`. A common
45-
example here is `FeatureMap`
46-
47-
- if the value is internal to the cluster or does not need loading, set it as
48-
`External` by including it in the `attributeAccessInterfaceAttributes` in
49-
`zcl.json` and `zcl-with-test-extensions.json`. A common example here is
50-
`ClusterRevision`
51-
52-
- if the value used to be `persist` it is an indication that the cluster
53-
should handle persistence (load in `Startup` and store during writes).
54-
55-
## Step 2: Design the Code-Driven Implementation
56-
57-
### Class Layout and File Structure
58-
59-
When converting clusters, optimizing for flash usage is often a priority. For
60-
this reason, it's common to implement the cluster without separating the logic
61-
from the implementation class.
62-
63-
- **Recommendation:** Combine logic and data storage into a single
64-
`<Name>Cluster` class that implements the `ServerClusterInterface`.
65-
- **Trade-off:** This approach prioritizes a smaller flash footprint over the
66-
modular, more testable layout described in the
67-
[Writing Clusters](./writing_clusters.md) guide. The combined approach is
68-
often suitable for simpler clusters or when resource constraints are tight.
69-
70-
You will need to create the following files:
71-
72-
- `src/app/clusters/<name>/<Name>Cluster.h`
73-
- `src/app/clusters/<name>/<Name>Cluster.cpp`
74-
- `src/app/clusters/<name>/CodegenIntegration.cpp`
75-
- `src/app/clusters/<name>/tests/Test<Name>Cluster.cpp`
76-
- Build files (`BUILD.gn`, `tests/BUILD.gn`,
77-
`app_config_dependent_sources.gni`, `app_config_dependent_sources.cmake`)
78-
79-
### Attribute and Command Availability
80-
81-
Your new class must accurately report which attributes and commands are
82-
available based on the feature map and other configuration.
83-
84-
- Store the feature map in your cluster instance.
85-
- For optional attributes, use a `BitFlags` variable, an
86-
`OptionalAttributeSet`, or a `struct` of booleans to track which are
87-
enabled.
88-
89-
---
90-
91-
## Step 3: Implement the Cluster Logic
92-
93-
This step involves translating the logic from the old Ember patterns into the
94-
new code-driven class structure.
95-
96-
#### Attribute Storage and Persistence
97-
98-
- Attributes are now member variables of your cluster class.
99-
- If attributes were configured as `RAM` in `zap` to load defaults, ensure
100-
your `CodegenIntegration.cpp` reads these defaults and passes them to your
101-
cluster's constructor.
102-
- For persisted attributes, use the `AttributePersistence` helper, which is
103-
available via the `ServerClusterContext`. Load values in `Startup` and save
104-
them on writes.
105-
106-
#### Command Handling
107-
108-
- Translate `CommandHandlerInterface` calls or `emberAf...Callback` functions
109-
into logic inside your `InvokeCommand` method:
110-
111-
- ember calls of the form `emberAf<CLUSTER>Cluster<COMMAND>Callback` will
112-
be converted to a switch `case <CLUSTER>::Commands::<COMMAND>::Id: ...`
113-
implementation
114-
115-
Example:
116-
117-
```cpp
118-
// This
119-
emberAfAccessControlClusterReviewFabricRestrictionsCallback(...);
120-
121-
// Becomes this in the `InvokeCommand` implementation:
122-
switch (request.path.mCommandId) {
123-
// ...
124-
case AccessControl::Commands::ReviewFabricRestrictions::Id:
125-
// ...
126-
}
10+
## Migration Checklist
11+
12+
This checklist provides a granular, step-by-step process for migrating an Ember
13+
cluster to a code-driven implementation.
14+
15+
### Part 0: Optimizing for an Easier Review
16+
17+
Before you begin the migration, consider structuring your changes into multiple,
18+
smaller pull requests. This approach significantly simplifies the review
19+
process, allowing reviewers to approve preliminary changes quickly. We recommend
20+
the following sequence of PRs:
21+
22+
- [ ] **PR 1: File Renames Only.**
23+
24+
- If your migration involves renaming files, submit a PR containing _only_
25+
the renames. A typical rename is from `<name>-server.cpp` to
26+
`<Name>Cluster.cpp`.
27+
- **Note:** For backward compatibility with code generation, it is often
28+
best to **not** rename the header file.
29+
- **Why:** This prevents `git diff` from becoming confused and showing the
30+
entire file as deleted and recreated, making the actual code changes
31+
impossible to review.
32+
- _This type of PR can be reviewed and merged very quickly._
33+
34+
- [ ] **PR 2: Code Movement Only.**
35+
36+
- If you plan to reorder functions or move code blocks (e.g., moving
37+
helper functions to an anonymous namespace), submit a PR with _only_
38+
these movements. Do not change any logic.
39+
- **Why:** Reviewers can use tools like `git diff --color-moved` to verify
40+
that code has only been moved, not altered. This allows for a rapid
41+
review of structural changes.
42+
- _This type of PR can also be fast-tracked._
43+
44+
- [ ] **PR 3: The Core Logic Changes.**
45+
- This PR should contain the actual migration logic: implementing the new
46+
cluster class, moving attribute storage, and converting command
47+
handlers.
48+
- **Why:** With renames and code movements already handled, this PR will
49+
be much smaller and focused, allowing the reviewer to concentrate solely
50+
on the correctness of the migration logic.
51+
52+
This structure respects the reviewer's time and helps get your changes merged
53+
faster. You can ask for an expedited review of the preliminary PRs in the
54+
project's Slack channel.
55+
56+
### Part 1: Analysis and Design
57+
58+
- [ ] **1.1: Understand the Existing Implementation:**
59+
60+
- [ ] Identify if the cluster uses a pure Ember implementation,
61+
`AttributeAccessInterface` (`AAI`), or `CommandHandlerInterface`
62+
(`CHI`).
63+
- **Pure Ember Implementation:** For simple attributes, the Ember
64+
framework in `src/app/util` handles data storage and access. In a
65+
code-driven implementation, this data **must be moved into member
66+
variables** within your new cluster class.
67+
- **AAI and CHI:** For more complex logic, `AAI` translates to the
68+
`ReadAttribute` and `WriteAttribute` methods, while `CHI` translates
69+
to the `InvokeCommand` method.
70+
- [ ] Determine how attributes are stored (e.g., `persist`, `ram`,
71+
`callback`). This will inform how you handle data in the new
72+
implementation.
73+
- If the value was `persist`, the new cluster should handle
74+
persistence (load in `Startup` and store during writes).
75+
- If the value was `ram` and loaded from the ZAP UI,
76+
`CodegenIntegration.cpp` should load the value from ZAP via the
77+
generated `Accessors.h`. A common example is the `FeatureMap`.
78+
- If the value is internal to the cluster (e.g., `ClusterRevision`),
79+
it should be marked as `External` by adding it to
80+
`attributeAccessInterfaceAttributes` in `zcl.json`.
81+
82+
- [ ] **1.2: Choose an Implementation Pattern:**
83+
84+
- [ ] Decide between a combined or modular implementation based on the
85+
cluster's complexity and resource constraints. See the
86+
[Writing Clusters](./writing_clusters.md#choosing-the-right-implementation-pattern)
87+
guide for more details.
88+
89+
- [ ] **1.3: Create the File Structure:**
90+
- [ ] Use an existing directory for the cluster or create a new one if
91+
missing at `src/app/clusters/<name>/`.
92+
- **Note:** If the cluster was a pure Ember implementation, this
93+
directory may not exist. After creating it, you must add a mapping
94+
to `src/app/zap_cluster_list.json` under the `ServerDirectories`
95+
key.
96+
- [ ] Add the following files:
97+
- `<Name>Cluster.h`
98+
- `<Name>Cluster.cpp`
99+
- `CodegenIntegration.cpp`
100+
- `tests/Test<Name>Cluster.cpp`
101+
- `BUILD.gn`
102+
- `tests/BUILD.gn`
103+
- `app_config_dependent_sources.gni`
104+
- `app_config_dependent_sources.cmake`
105+
106+
### Part 2: Implementation
107+
108+
- [ ] **2.1: Implement the Cluster Class:**
109+
110+
- [ ] Define the `<Name>Cluster` class, inheriting from
111+
`DefaultServerCluster`.
112+
- [ ] Add member variables for all attributes previously handled by Ember.
113+
- [ ] Implement the `Startup` and `Shutdown` methods for resource
114+
management.
115+
116+
- [ ] **2.2: Implement Attribute Logic:**
117+
118+
- [ ] Implement the `ReadAttribute` and `WriteAttribute` methods,
119+
translating any existing `AAI` logic.
120+
- [ ] For persisted attributes, use the `AttributePersistence` helper to
121+
load in `Startup` and save on writes.
122+
- [ ] **Crucially,** after a successful write, call a notification
123+
function (e.g., `NotifyAttributeChangedIfSuccess`) to ensure
124+
subscriptions work correctly.
125+
126+
- [ ] **2.3: Implement Command Logic:**
127+
128+
- [ ] Implement the `InvokeCommand` method, translating any existing `CHI`
129+
or `emberAf...Callback` logic.
130+
131+
- **Example:** An `emberAf<CLUSTER>Cluster<COMMAND>Callback` function
132+
becomes a `case` in the `InvokeCommand`'s `switch` statement:
133+
134+
```cpp
135+
// This:
136+
emberAfAccessControlClusterReviewFabricRestrictionsCallback(...);
137+
138+
// Becomes this in the `InvokeCommand` implementation:
139+
switch (request.path.mCommandId) {
140+
// ...
141+
case AccessControl::Commands::ReviewFabricRestrictions::Id:
142+
// ...
143+
}
144+
```
145+
146+
- [ ] Use a `switch` statement on the command ID to handle different
147+
commands.
148+
149+
- [ ] **2.4: Implement Event Logic:**
150+
- [ ] Replace any calls to `LogEvent` with
151+
`mContext->interactionContext.eventsGenerator.GenerateEvent` to make
152+
events unit-testable.
153+
- [ ] Check if any events have non-default access permissions (e.g.,
154+
require Administrator access) and implement the `EventInfo` method
155+
if necessary.
156+
157+
### Part 3: Configuration and Integration
158+
159+
- [ ] **3.1: Update Build Files:**
160+
161+
- [ ] Configure `BUILD.gn` and `tests/BUILD.gn` to include the new source
162+
files.
163+
- [ ] Configure `app_config_dependent_sources.gni` and
164+
`app_config_dependent_sources.cmake` to integrate the cluster into
165+
the build system.
166+
167+
- [ ] **3.2: Implement Codegen Integration:**
168+
169+
- [ ] In `CodegenIntegration.cpp`, use the `CodegenClusterIntegration`
170+
helper to read configuration values from the generated code.
171+
- > **Note:** The `CodegenClusterIntegration` helper for optional
172+
> attributes only supports attribute IDs up to 31. For clusters with
173+
> higher attribute IDs, you will need a custom implementation.
174+
175+
- [ ] **3.3: Update ZAP Configuration:**
176+
- [ ] In `src/app/common/templates/config-data.yaml`, add the cluster to
177+
the `CommandHandlerInterfaceOnlyClusters` array.
178+
- [ ] In `src/app/zap-templates/zcl/zcl.json` and
179+
`zcl-with-test-extensions.json`, add all non-list attributes to the
180+
`attributeAccessInterfaceAttributes` list.
181+
- [ ] Run the ZAP regeneration script:
182+
```bash
183+
./scripts/run_in_build_env.sh 'scripts/tools/zap_regen_all.py'
127184
```
128185

129-
- Command Handler Interface logic translates directly: `CHI` has a switch
130-
on command ID inside its `InvokeCommand` call. You should have the same
131-
logic inside the `ServerClusterInterface` processing logic.
132-
133-
- The `InvokeCommand` method can return an `ActionReturnStatus` optional. For
134-
better readability, prefer returning a status code directly (e.g.,
135-
`return Status::Success;`) rather than using the command handler to set the
136-
status, unless you need to return a response with a value or handle the
137-
command asynchronously.
138-
139-
#### Attribute Access
140-
141-
- Translate `AAI` logic into your `ReadAttribute` and `WriteAttribute`
142-
methods.
143-
- **Important:** After successfully writing a new attribute value, you
144-
**must** explicitly call a notification function (e.g., via
145-
`interactionContext->dataModelChangeListener`) to inform the SDK of the
146-
change. This is required for subscriptions to work correctly.
147-
148-
#### Event Generation
149-
150-
- Replace any calls to `LogEvent` with
151-
`mContext->interactionContext.eventsGenerator.GenerateEvent`. This makes
152-
events unit-testable.
186+
### Part 4: Testing
153187

154-
---
155-
156-
## Step 4: Update Build and Codegen Configuration
157-
158-
#### Build Files
159-
160-
Create the necessary build files (`BUILD.gn`, `tests/BUILD.gn`,
161-
`app_config_dependent_sources.gni`, `app_config_dependent_sources.cmake`) to
162-
integrate your new cluster files into the build system.
163-
164-
#### Codegen Integration
165-
166-
In `CodegenIntegration.cpp`, use the `CodegenClusterIntegration` helper class to
167-
minimize the boilerplate needed to read configuration values (like feature maps
168-
and optional attribute lists) from the generated code.
169-
170-
> **Note:** The `CodegenClusterIntegration` helper for optional attributes only
171-
> supports attribute IDs up to 31. For clusters with higher attribute IDs (e.g.,
172-
> `On/Off`, `Color Control`), you will need a custom implementation.
173-
174-
#### ZAP Configuration
175-
176-
You must update the ZAP configuration to inform the code generator that your
177-
cluster is now code-driven. This prevents the Ember framework from managing its
178-
data.
179-
180-
1. **Update `config-data.yaml`:** In
181-
`src/app/common/templates/config-data.yaml`, add your cluster to the
182-
`CommandHandlerInterfaceOnlyClusters` array. This disables Ember's command
183-
dispatch for this cluster.
184-
2. **Update `zcl.json`:** In `src/app/zap-templates/zcl/zcl.json` and
185-
`zcl-with-test-extensions.json`, add all of your cluster's non-list
186-
attributes to the `attributeAccessInterfaceAttributes` list. This tells ZAP
187-
not to allocate RAM for these attributes, as your class now manages them.
188-
3. Re-run ZAP regeneration, like
189-
190-
```bash
191-
./scripts/run_in_build_env.sh 'scripts/tools/zap_regen_all.py'
192-
```
193-
194-
---
188+
- [ ] **4.1: Write Unit Tests:**
195189

196-
## Step 5: Add Unit Tests
190+
- [ ] In `tests/Test<Name>Cluster.cpp`, add unit tests for the new
191+
implementation.
192+
- [ ] Ensure all attributes, commands, and feature combinations are
193+
tested.
197194

198-
- Create `tests/Test<Name>Cluster.cpp` and add unit tests for your new
199-
implementation.
200-
- Ensure you test the cluster's logic with various feature combinations and
201-
for all supported attributes and commands.
195+
- [ ] **4.2: Perform Integration Testing:**
196+
- [ ] Integrate the cluster into an example application (e.g.,
197+
`all-clusters-app`).
198+
- [ ] Manually validate the cluster's functionality using `chip-tool` or
199+
`matter-repl`.

0 commit comments

Comments
 (0)