Code-Driven Cluster Migration
1. When to Invoke This Skill
Invoke this skill when you are converting an existing cluster under
src/app/clusters/<folder>/ that does not yet contain a
CodegenIntegration.cpp file (note: the folder naming convention is
inconsistent — it may be <name>-server or simply <name>). If the cluster is
already migrated, use code-driven-cluster-development instead.
All rules from code-driven-cluster-development apply to the converted cluster.
This skill specifies the additional migration workflow: PR structure, backward
compatibility, and integration wiring.
2. The Three-PR Migration Workflow
Conversion must be split into at least three pull requests to ensure
reviewability. Mixing these phases is a common cause for review rejection.
| PR |
Contents |
Validation Method |
| 1 |
File renames and compat-stub creation only. |
git diff --color-moved |
| 2 |
Code reordering / anonymous-namespace moves only. |
git diff --color-moved=dimmed-zebra |
| 3 |
Substantive conversion: new class, integration, tests, regen. |
Functional review + ninja check |
3. Phase 0: Discovery & Strategy
3.1 Baseline & Map Blast Radius
- Inventory Attributes: Identify Mandatory (
M), Optional (O), Fixed
(F), and Nullable attributes from the cluster XML and generated
Attributes.h.
- Map Consumers: Search
examples/ for references to the legacy
<name>-server.h or class <Name>Server. This determines your
backward-compat strategy.
3.2 Backward-Compat Strategy
The standard approach preserves full include and API compatibility:
- CodegenIntegration.cpp: Keep any legacy public APIs alive here as thin
forwarders to the new cluster class. This is the preferred location for all
backward-compat shims.
- Legacy header (
<name>-server.h): If it existed, update it to
#include the new CodegenIntegration.h. This preserves include-path
compatibility for application code without duplicating logic.
- Breaking changes: If direct Ember RAM writes (
Attributes::X::Set) are
used by apps and cannot be shimmed, you must update those apps in PR #3
and document the change in a cluster README.md.
3.3 Pick a Reference PR
Identify the "shape" of your cluster and study the corresponding reference PR's
diff end-to-end.
| Your cluster looks like... |
Reference PR |
| Nullable measurement + min/max/tolerance |
#71424 Relative Humidity |
| Mandatory list + delegate + commands |
#43471 Actions |
| Multi-instance (per-endpoint state) |
#43720 Closure Dimension |
| Singleton, node-scoped |
#40422 Basic Information |
| Command-heavy with delegate |
#42331 Chime |
| Writable scalar + features |
#42968 Switch |
| Runtime-only (no Ember defaults) |
#71552 Flow Measurement |
| Identify/timer-driven |
#41232 Identify |
4. Implementation Phases
4.1 Phase 1: Renames (PR #1)
- Rename
.cpp and .h to PascalCase (<Name>Cluster.cpp).
- Create a one-line compat stub at the old header path.
- Update
BUILD.gn with renamed files. No logic changes.
4.2 Phase 2: Moves (PR #2)
- Move helpers to anonymous namespaces or group command handlers. No logic
changes.
4.3 Phase 3: The Conversion (PR #3)
- Strip Shell: Remove
emberAf... calls and
MatterPostAttributeChangeCallback.
- Implement Class: Follow the patterns in
code-driven-cluster-development.
- Integration Layer: Create
CodegenIntegration.cpp to bridge ZAP defaults
to your new cluster.
- ZAP/Build Updates:
- Update
src/app/common/templates/config-data.yaml (add to
CodeDrivenClusters).
- Update
src/app/zap-templates/zcl/zcl.json
(attributeAccessInterfaceAttributes).
- Regeneration: Run
scripts/tools/zap_regen_all.py and commit all
generated files.
5. Unit Testing with ClusterTester
The ClusterTester helper class (in
src/app/server-cluster/testing/ClusterTester.h) is mandatory for testing
code-driven clusters. It abstracts TLV handling, memory management for views,
and fabric context.
5.1 Key Capabilities
- Automatic TLV Handling: Encodes C++ types for writes/commands and
decodes for reads.
- Memory Safety: Maintains ownership of TLV data for views (like
CharSpan or List) during the test scope.
- Type Safety: Uses generated types (e.g.,
Attributes::MyAttr::TypeInfo)
to ensure spec alignment.
5.2 Core Patterns
- Attributes: Use
tester.ReadAttribute(Id, outValue) and
tester.WriteAttribute(Id, inValue).
- Commands: Use
tester.Invoke(requestStruct). It returns an
InvokeResult containing both status and response data.
- Side Effects:
- Verify events via
tester.GetNextGeneratedEvent().
- Verify dirty markings (reporting) via
tester.GetDirtyList().
- Fabric Scoping: Use
tester.SetFabricIndex(idx) to simulate actions
from specific fabrics.
6. Ensuring Spec Compliance
Tests must not just "pass" — they must prove the cluster adheres to the Matter
Specification and Test Plans.
6.1 Consult the Source of Truth
Use the matter-specification-access skill to obtain and read the latest
specification and test plans.
- Validate Semantics: Ensure
ReadAttribute and WriteAttribute handle
constraints (nullable, min/max, fixed) exactly as defined in the spec.
- Mirror Test Plans: Use the
.adoc test plans (e.g.,
src/cluster/<name>.adoc in chip-test-plans repo) as the blueprint for
your unit tests. If a test plan requires a ConstraintError for a specific
value, your unit test must assert exactly that.
6.2 Mandatory Test Coverage
- Attribute Constraints: Below min, at min, at max, above max.
- Nullable Handling: Null sentinel rejection (e.g.,
0xFFFF for uint16)
for nullable numeric types.
- Feature Gating: Verify that optional attributes/commands are only
present when the corresponding FeatureMap bits are set (using
Attributes()
/ AcceptedCommands()).
7. Ember → Code-Driven Mapping Reference
| Ember / Legacy Construct |
Code-Driven Equivalent |
emberAfReadAttribute |
Member access + encoder.Encode |
Attributes::X::Set |
cluster->SetX(value) (Forwarded) |
PreAttributeChangedCallback |
Validation logic in WriteAttribute |
PostAttributeChangeCallback |
Automatic via SetAttributeValue |
class FooServer : public AAI |
class <Name>Cluster : public DefaultServerCluster |
8. Common Review Findings (Migration Specific)
- Mixed PRs: Never combine renames with logic changes.
- Missing Regen: Forgetting to commit files changed by
zap_regen_all.py
breaks the build for other apps.
- Double Notification: Migrated clusters often have redundant
NotifyAttributeChanged calls; use SetAttributeValue to avoid this.
- Incorrect Fallbacks: When reading ZAP defaults in
CodegenIntegration,
ensure you handle read failures with safe, spec-compliant defaults.
- Missing README: If you break backward compatibility, a
README.md
explaining the migration for app developers is mandatory.
9. References
- Implementation Patterns:
.agents/skills/code-driven-cluster-development/SKILL.md
- Migration Guide:
docs/guides/migrating_ember_cluster_to_code_driven.md
1---2name: code-driven-cluster-migration3description: Step-by-step procedure for migrating a legacy Ember-based Matter server cluster in src/app/clusters/ to the code-driven pattern. Use this skill when converting an existing cluster. All rules from code-driven-cluster-development still apply; this skill adds the migration-specific workflow on top.4---56# Code-Driven Cluster Migration78## 1. When to Invoke This Skill910Invoke this skill when you are converting an existing cluster under11`src/app/clusters/<folder>/` that does not yet contain a12`CodegenIntegration.cpp` file (note: the folder naming convention is13inconsistent — it may be `<name>-server` or simply `<name>`). If the cluster is14already migrated, use `code-driven-cluster-development` instead.1516All rules from `code-driven-cluster-development` apply to the converted cluster.17This skill specifies the additional migration workflow: PR structure, backward18compatibility, and integration wiring.1920---2122## 2. The Three-PR Migration Workflow2324Conversion **must** be split into at least three pull requests to ensure25reviewability. Mixing these phases is a common cause for review rejection.2627| PR | Contents | Validation Method |28| ----- | ------------------------------------------------------------- | ------------------------------------- |29| **1** | File renames and compat-stub creation **only**. | `git diff --color-moved` |30| **2** | Code reordering / anonymous-namespace moves **only**. | `git diff --color-moved=dimmed-zebra` |31| **3** | Substantive conversion: new class, integration, tests, regen. | Functional review + `ninja check` |3233---3435## 3. Phase 0: Discovery & Strategy3637### 3.1 Baseline & Map Blast Radius3839- **Inventory Attributes**: Identify Mandatory (`M`), Optional (`O`), Fixed40 (`F`), and Nullable attributes from the cluster XML and generated41 `Attributes.h`.42- **Map Consumers**: Search `examples/` for references to the legacy43 `<name>-server.h` or `class <Name>Server`. This determines your44 backward-compat strategy.4546### 3.2 Backward-Compat Strategy4748The standard approach preserves full include and API compatibility:4950- **CodegenIntegration.cpp**: Keep any legacy public APIs alive here as thin51 forwarders to the new cluster class. This is the preferred location for all52 backward-compat shims.53- **Legacy header (`<name>-server.h`)**: If it existed, update it to54 `#include` the new `CodegenIntegration.h`. This preserves include-path55 compatibility for application code without duplicating logic.56- **Breaking changes**: If direct Ember RAM writes (`Attributes::X::Set`) are57 used by apps and cannot be shimmed, you **must** update those apps in PR #358 and document the change in a cluster `README.md`.5960### 3.3 Pick a Reference PR6162Identify the "shape" of your cluster and study the corresponding reference PR's63diff end-to-end.6465| Your cluster looks like... | Reference PR |66| ---------------------------------------- | -------------------------------------------------------------------------------------- |67| Nullable measurement + min/max/tolerance | [#71424](https://github.com/project-chip/connectedhomeip/pull/71424) Relative Humidity |68| Mandatory list + delegate + commands | [#43471](https://github.com/project-chip/connectedhomeip/pull/43471) Actions |69| Multi-instance (per-endpoint state) | [#43720](https://github.com/project-chip/connectedhomeip/pull/43720) Closure Dimension |70| Singleton, node-scoped | [#40422](https://github.com/project-chip/connectedhomeip/pull/40422) Basic Information |71| Command-heavy with delegate | [#42331](https://github.com/project-chip/connectedhomeip/pull/42331) Chime |72| Writable scalar + features | [#42968](https://github.com/project-chip/connectedhomeip/pull/42968) Switch |73| Runtime-only (no Ember defaults) | [#71552](https://github.com/project-chip/connectedhomeip/pull/71552) Flow Measurement |74| Identify/timer-driven | [#41232](https://github.com/project-chip/connectedhomeip/pull/41232) Identify |7576---7778## 4. Implementation Phases7980### 4.1 Phase 1: Renames (PR #1)8182- Rename `.cpp` and `.h` to PascalCase (`<Name>Cluster.cpp`).83- Create a one-line compat stub at the old header path.84- Update `BUILD.gn` with renamed files. **No logic changes.**8586### 4.2 Phase 2: Moves (PR #2)8788- Move helpers to anonymous namespaces or group command handlers. **No logic89 changes.**9091### 4.3 Phase 3: The Conversion (PR #3)92931. **Strip Shell**: Remove `emberAf...` calls and94 `MatterPostAttributeChangeCallback`.952. **Implement Class**: Follow the patterns in96 `code-driven-cluster-development`.973. **Integration Layer**: Create `CodegenIntegration.cpp` to bridge ZAP defaults98 to your new cluster.994. **ZAP/Build Updates**:100 - Update `src/app/common/templates/config-data.yaml` (add to101 `CodeDrivenClusters`).102 - Update `src/app/zap-templates/zcl/zcl.json`103 (`attributeAccessInterfaceAttributes`).1045. **Regeneration**: Run `scripts/tools/zap_regen_all.py` and commit **all**105 generated files.106107---108109## 5. Unit Testing with ClusterTester110111The `ClusterTester` helper class (in112`src/app/server-cluster/testing/ClusterTester.h`) is mandatory for testing113code-driven clusters. It abstracts TLV handling, memory management for views,114and fabric context.115116### 5.1 Key Capabilities117118- **Automatic TLV Handling**: Encodes C++ types for writes/commands and119 decodes for reads.120- **Memory Safety**: Maintains ownership of TLV data for views (like121 `CharSpan` or `List`) during the test scope.122- **Type Safety**: Uses generated types (e.g., `Attributes::MyAttr::TypeInfo`)123 to ensure spec alignment.124125### 5.2 Core Patterns126127- **Attributes**: Use `tester.ReadAttribute(Id, outValue)` and128 `tester.WriteAttribute(Id, inValue)`.129- **Commands**: Use `tester.Invoke(requestStruct)`. It returns an130 `InvokeResult` containing both status and response data.131- **Side Effects**:132 - Verify events via `tester.GetNextGeneratedEvent()`.133 - Verify dirty markings (reporting) via `tester.GetDirtyList()`.134- **Fabric Scoping**: Use `tester.SetFabricIndex(idx)` to simulate actions135 from specific fabrics.136137---138139## 6. Ensuring Spec Compliance140141Tests must not just "pass" — they must prove the cluster adheres to the Matter142Specification and Test Plans.143144### 6.1 Consult the Source of Truth145146Use the `matter-specification-access` skill to obtain and read the latest147specification and test plans.148149- **Validate Semantics**: Ensure `ReadAttribute` and `WriteAttribute` handle150 constraints (nullable, min/max, fixed) exactly as defined in the spec.151- **Mirror Test Plans**: Use the `.adoc` test plans (e.g.,152 `src/cluster/<name>.adoc` in `chip-test-plans` repo) as the blueprint for153 your unit tests. If a test plan requires a `ConstraintError` for a specific154 value, your unit test must assert exactly that.155156### 6.2 Mandatory Test Coverage157158- **Attribute Constraints**: Below min, at min, at max, above max.159- **Nullable Handling**: Null sentinel rejection (e.g., `0xFFFF` for `uint16`)160 for nullable numeric types.161- **Feature Gating**: Verify that optional attributes/commands are only162 present when the corresponding FeatureMap bits are set (using `Attributes()`163 / `AcceptedCommands()`).164165---166167## 7. Ember → Code-Driven Mapping Reference168169| Ember / Legacy Construct | Code-Driven Equivalent |170| ------------------------------ | --------------------------------------------------- |171| `emberAfReadAttribute` | Member access + `encoder.Encode` |172| `Attributes::X::Set` | `cluster->SetX(value)` (Forwarded) |173| `PreAttributeChangedCallback` | Validation logic in `WriteAttribute` |174| `PostAttributeChangeCallback` | Automatic via `SetAttributeValue` |175| `class FooServer : public AAI` | `class <Name>Cluster : public DefaultServerCluster` |176177---178179## 8. Common Review Findings (Migration Specific)1801811. **Mixed PRs**: Never combine renames with logic changes.1822. **Missing Regen**: Forgetting to commit files changed by `zap_regen_all.py`183 breaks the build for other apps.1843. **Double Notification**: Migrated clusters often have redundant185 `NotifyAttributeChanged` calls; use `SetAttributeValue` to avoid this.1864. **Incorrect Fallbacks**: When reading ZAP defaults in `CodegenIntegration`,187 ensure you handle read failures with safe, spec-compliant defaults.1885. **Missing README**: If you break backward compatibility, a `README.md`189 explaining the migration for app developers is mandatory.190191---192193## 9. References194195- **Implementation Patterns**:196 `.agents/skills/code-driven-cluster-development/SKILL.md`197- **Migration Guide**: `docs/guides/migrating_ember_cluster_to_code_driven.md`