Writing and Updating Clusters

This guide provides a comprehensive walkthrough for creating a new Matter cluster implementation, referred to as a “code-driven” cluster.

Overview of the Process

Writing a new cluster involves the following key stages:

  1. Define the Cluster: Generate or update the cluster definition XML based on the Matter specification.
  2. Implement the Cluster: Write the C++ implementation for the cluster's logic and data management.
  3. Integrate with Build System: Add the necessary files to integrate the new cluster into the build process.
  4. Integrate with Application: Connect the cluster to an application's code generation configuration.
  5. Test: Add unit and integration tests to verify the cluster's functionality.

Part 1: Cluster Definition (XML)

Clusters are defined based on the Matter specification. The C++ code for them is generated from XML definitions located in src/app/zap-templates/zcl/data-model/chip.

  • Generate XML: To create or update a cluster XML, use Alchemy to parse the specification's asciidoc. Manual editing of XML is discouraged, as it is error-prone.

  • Run Code Generation: Once the XML is ready, run the code generation script. It's often sufficient to run:

    ./scripts/run_in_build_env.sh 'scripts/tools/zap_regen_all.py'
    

    For more details, see the code generation guide.


Part 2: C++ Implementation

File Structure

Create a new directory for your cluster at src/app/clusters/<cluster-name>/. This directory will house the cluster implementation and its unit tests.

Recommended Modular Layout

For better testability and maintainability, we recommend splitting the implementation into logical components. The Software Diagnostics cluster is a good example of this pattern.

  • ClusterLogic:
    • A type-safe class containing the core business logic of the cluster.
    • Manages all attribute storage.
    • Should be thoroughly unit-tested.
  • ClusterImplementation:
    • Implements the ServerClusterInterface (often by deriving from DefaultServerCluster).
    • Acts as a translation layer between the data model (encoders/decoders) and the ClusterLogic.
  • ClusterDriver (or Delegate):
    • An optional interface providing callbacks to the application for cluster interactions. We recommend the term Driver to avoid confusion with the overloaded term Delegate.

Implementation Details

Attribute and Feature Handling

Your implementation must correctly report which attributes and commands are available based on the enabled features and optional items.

  • Use a feature map to control elements dependent on features.
  • Use boolean flags or BitFlags for purely optional elements.
  • Ensure your unit tests cover different combinations of enabled features and optional attributes/commands.

Attribute Change Notifications

For subscriptions to work correctly, you must notify the system whenever an attribute's value changes.

  • The Startup method of your cluster receives a ServerClusterContext.
  • Use the context to call interactionContext->dataModelChangeListener->MarkDirty(path). A NotifyAttributeChanged helper exists for paths managed by this cluster.
    • For write implementations, you can use NotifyAttributeChangedIfSuccess together with a separate WriteImpl such that any successful attribute write will notify.

Persistent Storage

  • Attributes: For scalar attribute values, use AttributePersistence from src/app/persistence/AttributePersistence.h. The ServerClusterContext provides an AttributePersistenceProvider.
  • General Storage: For non-attribute data, the context provides a PersistentStorageDelegate.

Optimizing for Flash/RAM

For common or large clusters, you may need to optimize for resource usage. Consider using C++ templates to compile-time select features and attributes, which can significantly reduce flash and RAM footprint.

Unit Testing

  • Unit tests should reside in src/app/clusters/<cluster-name>/tests/.
  • At a minimum, ClusterLogic should be fully tested, including its behavior with different feature configurations.
  • ClusterImplementation can also be unit-tested if its logic is complex. Otherwise, integration tests should provide sufficient coverage.

Part 3: Build and Application Integration

Build System Integration

The build system maps cluster names to their source directories. Add your new cluster to this mapping:

  • Edit src/app/zap_cluster_list.json and add an entry for your cluster, pointing to the directory you created.

Application Integration (CodegenIntegration.cpp)

To integrate your cluster with an application's .zap file configuration, you need to bridge the gap between the statically generated code and your C++ implementation.

  1. Create CodegenIntegration.cpp: This file will contain the integration logic.
  2. Create Build Files: Add app_config_dependent_sources.gni and app_config_dependent_sources.cmake to your cluster directory. These files should list CodegenIntegration.cpp and its dependencies. See existing clusters for examples.
  3. Use Generated Configuration: The code generator creates a header file at <app/static-cluster-config/<cluster-name>.h that provides static, application-specific configuration. Use this to initialize your cluster correctly for each endpoint.
  4. Implement Callbacks: Implement Matter<Cluster>ClusterInitCallback(EndpointId) and Matter<Cluster>ClusterShutdownCallback(EndpointId) in your CodegenIntegration.cpp.
  5. Update config-data.yaml: To enable these callbacks, add your cluster to the CodeDrivenClusters array in src/app/common/templates/config-data.yaml.
  6. Update ZAP Configuration: To prevent the Ember framework from allocating memory for your cluster's attributes (which are now managed by your ClusterLogic), you must:
    • In src/app/common/templates/config-data.yaml, consider adding your cluster to CommandHandlerInterfaceOnlyClusters if it does not need Ember command dispatch.
    • In src/app/zap-templates/zcl/zcl.json and zcl-with-test-extensions.json, add all non-list attributes of your cluster to attributeAccessInterfaceAttributes. This marks them as externally handled.

Part 4: Example Application and Integration Testing

  • Write unit tests to ensure cluster test coverage
  • Integrate into an Example: Add your cluster to an example application, such as the all-clusters-app, to test it in a real-world scenario.
    • use tools such as chip-tool or matter-repl to manually validate the cluster
  • Add Integration Tests: Write integration tests to validate the end-to-end functionality of your cluster against the example application.