When designing new clusters, consider the following approach:
General approach:
Class proposal:
The ClusterServerClass is a Very light wrapper over ClusterLogic. It translates Interaction Model wire format handling into API calls for cluster logic methods.
This class implements both the AttributeAccessInterface and the CommandHandler interfaces so ClusterLogic properly handles data dependencies between commands and attributes.
An example code snippet showing the translation of the TLV into API calls to the ClusterLogic class:
CHIP_ERROR DiscoBallServer::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) { DiscoBallClusterLogic * cluster = FindEndpoint(aPath.mEndpointId); VerifyOrReturnError(cluster != nullptr, CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint)); switch (aPath.mAttributeId) { case Clusters::DiscoBall::Attributes::Run::Id: return aEncoder.Encode(cluster->GetRunAttribute()); … } }
The ClusterLogic class is for all the code that is SHARED between platforms. It does NOT include any TLV parsing or direct calls to Ember/IM/LogEvent etc.
The class should include attribute getters/setters and handlers for all commands.
The class receives “plain data” Matter requests from ClusterServer class, performs required common actions, and calls driver class to perform platform- or hardware-specific actions. It also receives driver updates (e.g. application-driven value changes) from the ClusterDriver class and updates state as appropriate.
The class should handle spec-requirements for:
- Range checking (CONSTRAINT_ERROR) - Attribute and metadata storage (persistent or in-memory) - Data dependencies between commands and attributes - Event generation / dirty attributes (callback to server) - Calling driver when platform or hardware interactions are required
API recommendation:
- Maintain all cluster state in a separate data-only class. - Provider getters/setters for application logic to use. - Implement handlers for all commands, conditional on features. - Let the caller provide (inject) dependencies. Avoid explicit memory management.
Implements hardware or platform-specific actions required on cluster interactions or when application wants to report state changes.
The ClusterLogic class must not directly use global resource because they cannot be isolated for testing. Instead, the MatterContext holds pointers to Matter stack objects, which can be be injected / faked for testing. This includes - Wrapper over IM Engine interface functions for marking attributes dirty, and logging events. - Storage - Anything you would normally access with Server::GetInstance()
The ClusterDriver is called by the ClusterLogic class and is used to translate attribute changes and commands into application actions. It also reports external changes back to the ClusterLogic class.
The API design for this class will vary by the cluster, but it is generally recommended to use a generic API where possible, so the API ports easily to other platforms, for example an attribute changed callback with the changes listed. It is important to be careful about the design and revisit this early if issues arise.
The unit test instantiates the ClusterLogic, provides MatterContext and ClusterDriver instance with fakes/mocks for testing.
Unit test against the API, and check the fakes/mocks to ensure they are being called as appropriate.
As with all unit tests, prefer testing for behavior rather than implementation details.
Important tests to consider: