This document provides a step-by-step guide for converting an existing Ember-based cluster to a modern, code-driven implementation. It is recommended to first read the guide on Writing Clusters for a general overview of the code-driven cluster architecture.
This checklist provides a granular, step-by-step process for migrating an Ember cluster to a code-driven implementation.
Before you begin the migration, consider structuring your changes into multiple, smaller pull requests. This approach significantly simplifies the review process, allowing reviewers to approve preliminary changes quickly. We recommend the following sequence of PRs:
-
PR 1: File Renames and Moves Only.
-
If your migration involves renaming files, submit a PR containing only the renames and file moves. This helps to isolate structural changes from logic changes, making the review process easier.
-
.cppfile rename:- Rename
<name>-server.cppto<Name>Cluster.cpp. This is often a good starting point as much of the implementation can be reused. Renaming allowsgit diffto track changes more effectively than a delete/add operation.
- Rename
-
.hfile renames and refactoring:- The legacy header (
<name>-server.h) often contains a mix of legacy API functions, delegate classes, and other definitions. To maintain backward compatibility while moving to a code-driven model, we recommend the following: - Create a new
CodegenIntegration.hheader and move the implementation portions of<name>-server.hinto it. This new file will be responsible for maintaining the legacy API integration logic that interacts with the generated code. - If
<name>-server.hcontains delegate classes or other distinct components (e.g.,class FooDelegate), move them into their own header files (e.g.,FooDelegate.h). A good rule of thumb is thatclass Barshould live inBar.h. - After moving the implementation out of
<name>-server.h, update<name>-server.hitself to be a wrapper that simply includes the new headers (CodegenIntegration.h,FooDelegate.h, etc.). This preserves the include paths for existing code while allowing the implementation to evolve.
- The legacy header (
-
Why: This prevents
git difffrom becoming confused and showing the entire file as deleted and recreated, making the actual code changes impossible to review. Separating file moves from logic changes is a critical step for an efficient code review. -
This type of PR can be reviewed and merged very quickly.
-
-
PR 2: Code Movement Only.
- If you plan to reorder functions or move code blocks (e.g., moving helper functions to an anonymous namespace), submit a PR with only these movements. Do not change any logic.
- Why: Reviewers can use tools like
git diff --color-movedto verify that code has only been moved, not altered. This allows for a rapid review of structural changes. - This type of PR can also be fast-tracked.
-
PR 3: The Core Logic Changes.
- This PR should contain the actual migration logic: implementing the new cluster class, moving attribute storage, and converting command handlers.
- Why: With renames and code movements already handled, this PR will be much smaller and focused, allowing the reviewer to concentrate solely on the correctness of the migration logic.
This structure respects the reviewer's time and helps get your changes merged faster. You can ask for an expedited review of the preliminary PRs in the project's Slack channel.
-
1.1: Understand the Existing Implementation:
- Identify if the cluster uses a pure Ember implementation,
AttributeAccessInterface(AAI), orCommandHandlerInterface(CHI).- Pure Ember Implementation: For simple attributes, the Ember
framework in
src/app/utilhandles data storage and access. In a code-driven implementation, this data must be moved into member variables within your new cluster class. - AAI and CHI: For more complex logic,
AAItranslates to theReadAttributeandWriteAttributemethods, whileCHItranslates to theInvokeCommandmethod.
- Pure Ember Implementation: For simple attributes, the Ember
framework in
- Determine how attributes are stored (e.g.,
persist,ram,callback). This will inform how you handle data in the new implementation.- If the value was
persist, the new cluster should handle persistence (load inStartupand store during writes). - If the value was
ramand loaded from the ZAP UI,CodegenIntegration.cppshould load the value from ZAP via the generatedAccessors.h. A common example is theFeatureMap. - If the value is internal to the cluster (e.g.,
ClusterRevision), it should be marked asExternalby adding it toattributeAccessInterfaceAttributesinzcl.json.
- If the value was
- Identify if the cluster uses a pure Ember implementation,
-
1.2: Choose an Implementation Pattern:
- Decide between a combined or modular implementation based on the cluster's complexity and resource constraints. See the Writing Clusters guide for more details.
-
1.3: Create the File Structure:
- Use an existing directory for the cluster or create a new one if
missing at
src/app/clusters/<name>/.- Note: If the cluster was a pure Ember implementation, this
directory may not exist. After creating it, you must add a mapping
to
src/app/zap_cluster_list.jsonunder theServerDirectorieskey.
- Note: If the cluster was a pure Ember implementation, this
directory may not exist. After creating it, you must add a mapping
to
- Add the following files:
<Name>Cluster.h<Name>Cluster.cppCodegenIntegration.cpptests/Test<Name>Cluster.cppBUILD.gntests/BUILD.gnapp_config_dependent_sources.gniapp_config_dependent_sources.cmake
- Use an existing directory for the cluster or create a new one if
missing at
-
2.1: Implement the Cluster Class:
- Define the
<Name>Clusterclass, inheriting fromDefaultServerCluster. - Add member variables for all attributes previously handled by Ember.
- Implement the
StartupandShutdownmethods for resource management.
- Define the
-
2.2: Implement Attribute Logic:
- Implement the
ReadAttributeandWriteAttributemethods, translating any existingAAIlogic. - For persisted attributes, use the
AttributePersistencehelper to load inStartupand save on writes. - Crucially, after a successful write, call a notification
function (e.g.,
NotifyAttributeChangedIfSuccess) to ensure subscriptions work correctly.
- Implement the
-
2.3: Implement Command Logic:
-
Implement the
InvokeCommandmethod, translating any existingCHIoremberAf...Callbacklogic.-
Example: An
emberAf<CLUSTER>Cluster<COMMAND>Callbackfunction becomes acasein theInvokeCommand'sswitchstatement:// This: emberAfAccessControlClusterReviewFabricRestrictionsCallback(...); // Becomes this in the `InvokeCommand` implementation: switch (request.path.mCommandId) { // ... case AccessControl::Commands::ReviewFabricRestrictions::Id: // ... }
-
-
Use a
switchstatement on the command ID to handle different commands.
-
-
2.4: Implement Event Logic:
- Replace any calls to
LogEventwithmContext->interactionContext.eventsGenerator.GenerateEventto make events unit-testable. - Check if any events have non-default access permissions (e.g.,
require Administrator access) and implement the
EventInfomethod if necessary.
- Replace any calls to
-
3.1: Update Build Files:
- Configure
BUILD.gnandtests/BUILD.gnto include the new source files. - Configure
app_config_dependent_sources.gniandapp_config_dependent_sources.cmaketo integrate the cluster into the build system.
- Configure
-
3.2: Implement Codegen Integration:
- In
CodegenIntegration.cpp, use theCodegenClusterIntegrationhelper to read configuration values from the generated code. -
Note: The
CodegenClusterIntegrationhelper for optional attributes only supports attribute IDs up to 31. For clusters with higher attribute IDs, you will need a custom implementation.
- In
-
3.3: Update ZAP Configuration:
- In
src/app/common/templates/config-data.yaml, add the cluster to theCodeDrivenClustersarray and remove it fromCommandHandlerInterfaceOnlyClustersif it exists there. - In
src/app/zap-templates/zcl/zcl.jsonandzcl-with-test-extensions.json, add all non-list attributes to theattributeAccessInterfaceAttributeslist. - Run the ZAP regeneration script:
./scripts/run_in_build_env.sh 'scripts/tools/zap_regen_all.py'
- In
-
4.1: Write Unit Tests:
- In
tests/Test<Name>Cluster.cpp, add unit tests for the new implementation. - Ensure all attributes, commands, and feature combinations are tested.
- In
-
4.2: Perform Integration Testing:
- Integrate the cluster into an example application (e.g.,
all-clusters-app). - Manually validate the cluster's functionality using
chip-toolormatter-repl.
- Integrate the cluster into an example application (e.g.,
Cluster migrations are often not a full re-write of the code but rather the minimum necessary to make the code testable and decoupled from Ember/ZAP. Therefore, the suggestions in this section are optional.
Some legacy cluster implementations contain build time switches (#ifdef)
throughout the code. This is undesirable because it makes it difficult to change
the behavior of an application in runtime.
When possible, avoid copying this pattern and think of new ways the same thing could be achieved. For example, use configuration injected via constructor or implement separate sub classes if code size increase is a concern and the build time options cause a significant difference in the cluster behavior.