Manufacturer Specific Extensions (MS Extensions) are defined in Section 7.19 of
the Matter Specification. Manufacturer-specific clusters are identified
by a Manufacturer Extension Identifier (MEI). This guide covers
the implementation of the samplemei
cluster, available in the
Matter SDK.
This guide should be performed in a Linux host environment running a Debian-based distribution.
Get started
Clone the Matter repository in your host workstation:
$ git clone https://github.com/project-chip/connectedhomeip.git $ cd connectedhomeip
Install linux prerequisites:
$ sudo apt-get install git gcc g++ pkg-config libssl-dev libdbus-1-dev \ libglib2.0-dev libavahi-client-dev ninja-build python3-venv python3-dev \ python3-pip unzip libgirepository1.0-dev libcairo2-dev libreadline-dev
Initialize the Matter repository:
$ source scripts/bootstrap.sh
Define your MS Extension Data Model
The first step to adding a custom feature is to define your data model
in an XML file. In our sample, we've created the file
sample-mei-cluster.xml
.
You may use the
TestCluster
as an exhaustive example of possible extensions, including structs and lists.
In case you want to define your own new cluster, copy sample-mei-cluster.xml
and place your data model file to the same folder:
src/app/zap-templates/zcl/data-model/chip
.
The XML definition file can be broken down in 5 parts:
Header: The file is defined as an XML version 1.0, with the main tag
<configurator>
and specifying the domain to CHIP. Other domains may be possible, but haven't been tested.<?xml version="1.0"?> <!-- Copyright (c) 2023 Project CHIP Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <configurator> <domain name="CHIP"/>
General properties:
a.
<name>
and<define>
: Define a name for your new cluster and apply it consistently. Several scripts generate source code based on the naming convention defined here. Be sure to follow the same naming conventions as in the sample. In different places you're expected to use the same pattern that already exists in the samples, either:- Pascal Case:
MyCluster
- Space Separated:
My Cluster
- Screaming Snake Case:
MY_CLUSTER
a.
<client|server tick>
: The tick mechanism would schedule periodic calls from the scheduler, so your cluster can perform timed or delayed operations. The sample cluster for this guide doesn't use those. In case your cluster needs to use timed calls, it's often preferable to use schedule your own delayed executions usingDeviceLayer::SystemLayer().StartTimer().
a.
code
: This is a concatenation of your Vendor ID (0xFFF1
, test VID in our case) with the Cluster ID (0xFC20
in our case). Remember to change these values to your own production VID and chosen Cluster IDs when developing your product beyond an initial proof-of-concept.<cluster> <domain>General</domain> <name>Sample MEI</name> <code>0xFFF1FC20</code> <define>SAMPLE_MEI_CLUSTER</define> <description>The Sample MEI cluster showcases cluster manufacturer extensions</description>
- Pascal Case:
Attributes: Define your custom Matter attributes. Make sure the
define
XML attributes are in Screaming Snake Case format. The XML content has a matching name in Pascal case.<!-- Attributes --> <!-- A simple boolean attribute that flips or flops --> <attribute side="server" code="0x0000" define="FLIP_FLOP" type="BOOLEAN" writable="true" default="false" optional="false">FlipFlop</attribute>
Commands: Define both commands and command responses, such as
AddArgumentsResponse
. In this case theAddArguments
command can be invoked with two arguments, and returns aAddArgumentsResponse
with their sum. Ping is a command with no arguments.<!-- Command Responses --> <command source="server" code="0x01" name="AddArgumentsResponse" optional="false" disableDefaultResponse="true"> <description> Response for AddArguments that returns the sum. </description> <arg name="returnValue" type="INT8U"/> </command> <!-- Commands --> <command source="client" code="0x02" name="AddArguments" response="AddArgumentsResponse" optional="false"> <description> Command that takes two uint8 arguments and returns their sum. </description> <arg name="arg1" type="INT8U"/> <arg name="arg2" type="INT8U"/> </command> <command source="client" code="0x00" name="Ping" optional="false"> <description> Simple command without any parameters and without a response. </description> </command>
Events: No events are defined in this cluster.
<!-- Events --> </cluster> </configurator>
Create the MS Source Code
The source code of your cluster should be placed in
src/app/clusters/<cluster-name>
. It builds whenever your device's
.zap/.matter
files include the cluster in any of its endpoints.
src/app/clusters/sample-mei-server/sample-mei-server.h
is the header of
MEI's cluster's classes. You may implement your cluster in any way you prefer.
samplemei
shows an example of how that may be done. We have declared
a class within it that holds the same Attributes previously defined in the XML
file. samplemei
has a single Attribute: bool flipflop
.
(...)
*****************************************************************************
// SampleMeiContent has the stateful attributes of the cluster: its endpoint
// and attributes
class SampleMeiContent
{
public:
EndpointId endpoint;
// Attribute List
bool flipflop; /* Attributes::FlipFlop::Id */
SampleMeiContent(EndpointId endpoint);
SampleMeiContent();
};
Meanwhile, your main class for the server cluster should inherit from
AttributeAccessInterface
and CommandHandlerInterface
and override the
Read()
, Write()
and InvokeCommand()
methods to perform Attributes R/W, as
well as be called when commands are received.
// *****************************************************************************
// SampleMeiServer implements both Attributes and Commands
class SampleMeiServer : public AttributeAccessInterface, public CommandHandlerInterface
{
public:
(...)
// Attributes
CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override;
CHIP_ERROR Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder) override;
// Commands
void InvokeCommand(HandlerContext & ctx) override;
// Attribute storage
#if SAMPLE_MEI_NUM_SUPPORTED_ENDPOINTS > 0
SampleMeiContent content[kNumSupportedEndpoints];
#else
SampleMeiContent* content = nullptr;
#endif
(...)
};
src/app/clusters/sample-mei-server/sample-mei-server.cpp
implements the
samplemei
cluster class. In your cluster, each supported command should be
included in the cases handled by the switch statement of InvokeCommand()
.
void SampleMeiServer::InvokeCommand(HandlerContext & ctxt)
{
(...)
switch (ctxt.mRequestPath.mCommandId)
{
case Commands::Ping::Id:
HandleCommand<Commands::Ping::DecodableType>(ctxt, [endpoint](HandlerContext & ctx, const auto & req) {
ChipLogProgress(Zcl,"Ping Command on endpoint %d", endpoint);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Protocols::InteractionModel::Status::Success);
});
return;
(...)
}
}
The Read()
, Write()
and InvokeCommand()
methods must be defined, along
with any other class methods that have been declared.
CHIP_ERROR SampleMeiServer::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder)
{
(...)
switch (aPath.mAttributeId)
{
case Attributes::FlipFlop::Id:
ChipLogProgress(Zcl,"Read Attribute flip-flop from endpoint %d index %zu value %d", endpoint, endpointIndex, content[endpointIndex].flipflop);
err = aEncoder.Encode(content[endpointIndex].flipflop);
break;
default:
break;
}
return err;
}
CHIP_ERROR SampleMeiServer::Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder)
{
(...)
switch (aPath.mAttributeId)
{
case Attributes::FlipFlop::Id:
{
auto oldValue = content[endpointIndex].flipflop;
ReturnErrorOnFailure(aDecoder.Decode(content[endpointIndex].flipflop));
ChipLogProgress(Zcl,"Write Attribute flip-flop on endpoint %d index %zu newValue %d oldValue %d", endpoint, endpointIndex, content[endpointIndex].flipflop, oldValue);
}
break;
default:
break;
}
return err;
}
References to your cluster from other files
Other files need to be changed whenever a new cluster is defined. You don't need
to change these files if you are just making changes to samplemei
cluster.
.github/workflows/tests.yaml
implements XML parsing tests for the .xml
cluster definitions:
./scripts/run_in_build_env.sh \
"./scripts/py_matter_idl/matter_idl/xml_parser.py \
--no-print \
--log-level info \
src/app/zap-templates/zcl/data-model/chip/global-attributes.xml \
(...)
+ src/app/zap-templates/zcl/data-model/chip/sample-mei-cluster.xml \
"
scripts/rules.matterlint
also performs lint checks:
+load "../src/app/zap-templates/zcl/data-model/chip/sample-mei-cluster.xml";
src/app/common/templates/config-data.yaml
controls some of the code
generation flags. CommandHandlerInterfaceOnlyClusters
ensures some C-style
callbacks for command handlers are not automatically called, and thus would need
to be defined by your code.
CommandHandlerInterfaceOnlyClusters:
# List of clusters that are implemented entirely with
# CommandHandlerInterface and hence do not need generated command dispatch.
# This uses asUpperCamelCase versions of the cluster name.
- NetworkCommissioning
- Scenes
(...)
+ - SampleMei
(...)
src/app/zap-templates/zcl/data-model/all.xml
<?xml version="1.0"?>
<all xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="chip/access-control-cluster.xml" />
(...)
+ <xi:include href="chip/sample-mei-cluster.xml" />
</all>
src/app/zap-templates/zcl/zcl-with-test-extensions.json
is used by the ZAP
GUI. It draws from this list the set of available clusters when a device is
being created. This list includes all the MS clusters.
"xmlFile": [
"access-control-definitions.xml",
(...)
+ "sample-mei-cluster.xml",
(...)
],
src/app/zap-templates/zcl/zcl.json
is also used by the ZAP GUI. It
alternatively draws from this list the set of available clusters when a device
is being created. This list shouldn't include the MS clusters, but temporarily
it does. Make sure you also define your MS Cluster here.
"xmlFile": [
"access-control-definitions.xml",
(...)
+ "sample-mei-cluster.xml",
(...)
],
src/app/zap_cluster_list.json
flags to the build system to include your
server cluster source directory (src/app/clusters/sample-mei-server/
) to the
list of source code to be selectively compiled whenever they exist in your
device's data model: the .zap
and .matter
files. That is also true for
client clusters, but those are not implemented in samplemei
. If your cluster
also implements client logic, make sure to also add it to the
ClientDirectorieslist
.
{
"ClientDirectories": {
"ACCESS_CONTROL_CLUSTER": [],
(...)
"SAMPLE_MEI_CLUSTER": [],
(...)
},
"ServerDirectories": {
"ACCESS_CONTROL_CLUSTER": ["access-control-server"],
(...)
"SAMPLE_MEI_CLUSTER": ["sample-mei-server"],
(...)
}
}
src/controller/data_model/BUILD.gn
needs to include Android JNI
generation for the new cluster:
if (current_os == "android" || matter_enable_java_compilation) {
config("java-build-config") {
if (matter_enable_java_compilation) {
include_dirs = java_matter_controller_dependent_paths
}
}
chip_codegen("java-jni-generate") {
input = "controller-clusters.matter"
generator = "java-jni"
outputs = [
"jni/AccessControlClient-InvokeSubscribeImpl.cpp",
(...)
+ "jni/SampleMeiClient-InvokeSubscribeImpl.cpp",
+ "jni/SampleMeiClient-ReadImpl.cpp",
(...)
]
deps = [
":data_model",
"${chip_root}/src/platform:platform_buildconfig",
]
public_configs = [ ":java-build-config" ]
}
(...)
Regenerate all code
Once all changes have been made, regenerate all code by running:
$ scripts/tools/zap_regen_all.py
Reformat your changes to adhere to the Matter project coding style by running:
$ scripts/helpers/restyle-diff.sh
Add the MS Cluster to controller-clusters.zap
From the Matter root folder to open the ZAP GUI run:
$ cd <chip_root>
$ scripts/tools/zap/run_zaptool.sh src/controller/data_model/controller-clusters.zap
Once the UI is loaded, click Endpoint - 0:
Search for your cluster by entering its name in the Search Clusters field. Once located, select Not Enabled and change it to Client.
Click the
to configure the cluster:In the Attributes tab, ensure all attributes are enabled:
In the Commands tab ensure all commands are enabled:
Leave the cluster configuration window by clicking on the
button.Save your the controller configuration by selecting File > Save or use
Ctrl+S
.
Exit ZAP by selecting File > Exit or use Ctrl+Q
.
Regenerate for consistency the ZAP and codegen files with:
$ scripts/tools/zap_regen_all.py