This guide explains how to implement a simulated Matter device in the all-devices-app.
We will walk through creating a device named MySensor (--device my-sensor), implementing its data model, registering it in the device factory, expanding build targets, and executing certification tests.
data_model/{version}/device_types/{DeviceType}.xml for quick lookup. Note: XMLs are static snapshots of a certifiable spec version; they may need augmentation for next-version features.0x0302 for Temperature Sensor).chip-test-plans repository (accessible to CSA members) to understand cluster verification steps.src/python_testing/ for TC_{CLUSTER}_*.py or src/app/tests/suites/ for YAML tests corresponding to the implemented clusters.=== BEGIN CI TEST ARGUMENTS === block of the python script or to the YAML test suites, passing --device {your-device}.When implementing your device, keep these key architectural patterns in mind to make your code compile-time safe, highly reusable across different platforms, and easy to test:
Abstract Hardware Interactions:
Prefer References (Delegate &) for Mandatory Delegates:
OnOffDelegate for a light, or IdentifyDelegate for a device), always require it as a C++ reference (&) in the constructor.Delegate * defaulting to nullptr) only for clusters that are optional based on feature flags or configuration.Expose Public Cluster Getters:
OnOffCluster(), IdentifyCluster()) to expose the underlying cluster instances.Symmetrical Logging Mocks (impl/ subfolder):
impl/ subfolder (e.g., LoggingMySensor).*this to the base constructor.DeviceFactory or unit tests without requiring external wiring.Spec-Pure Directory Layout & Extracted Capabilities:
device/types/ directory pure: it should contain only real, spec-defined Matter Device Types (like dimmable-light, fan, air-purifier).device/capabilities/<capability-name>/ (e.g., device/capabilities/dimmable-load/). Concrete leaf devices then inherit publicly from this capability base.Registration & Teardown Ordering:
RegisterDescriptor() first (to construct the descriptor cluster and initialize endpoint metadata), create and add all domain and optional clusters via provider.AddCluster(), and finally register the endpoint via provider.AddEndpoint(mEndpointRegistration). Once started, CodeDrivenDataModelProvider rejects adding clusters to an already-registered endpoint (CHIP_ERROR_INCORRECT_STATE).UnregisterDescriptor(provider) first, before removing or destroying any domain or optional clusters. UnregisterDescriptor() removes the endpoint from the provider via provider.RemoveEndpoint(). Once started, CodeDrivenDataModelProvider prevents non-atomic endpoint modifications and returns CHIP_ERROR_INCORRECT_STATE if provider.RemoveCluster() is called while the endpoint is still registered.MySensor.h)Derive your class from SingleEndpoint (or DeviceInterface if managing composite / multi-endpoint devices). Require all mandatory delegates as references in the constructor and declare public getters to expose the underlying clusters:
#pragma once #include <app/clusters/identify-server/IdentifyCluster.h> #include <app/clusters/my-sensor-server/MySensorServerCluster.h> // Example code-driven cluster #include <data-model-providers/codedriven/CodeDrivenDataModelProvider.h> #include <device/api/SingleEndpoint.h> #include <lib/support/TimerDelegate.h> namespace chip::app { class MySensor : public SingleEndpoint { public: MySensor(TimerDelegate & timerDelegate, Clusters::IdentifyDelegate & identifyDelegate); ~MySensor() override = default; // DeviceInterface pure virtual lifecycle hooks CHIP_ERROR Register(chip::EndpointId endpoint, CodeDrivenDataModelProvider & provider, EndpointComposition composition = {}) override; void Unregister(CodeDrivenDataModelProvider & provider) override; // Public cluster getters for programmatic control Clusters::IdentifyCluster & IdentifyCluster() { return mIdentifyCluster.Cluster(); } Clusters::MySensorCluster & MySensorCluster() { return mMySensorCluster.Cluster(); } protected: // Optional cluster extension hooks for subclasses (impl/ or hardware targets) virtual CHIP_ERROR RegisterAdditionalClusters(chip::EndpointId endpoint, CodeDrivenDataModelProvider & provider) { return CHIP_NO_ERROR; } virtual void UnregisterAdditionalClusters(CodeDrivenDataModelProvider & provider) {} private: TimerDelegate & mTimerDelegate; Clusters::IdentifyDelegate & mIdentifyDelegate; LazyRegisteredServerCluster<Clusters::IdentifyCluster> mIdentifyCluster; LazyRegisteredServerCluster<Clusters::MySensorCluster> mMySensorCluster; }; } // namespace chip::app
MySensor.cpp)In Register(), wire up mandatory clusters and invoke RegisterAdditionalClusters() before calling provider.AddEndpoint() so subclasses can attach optional clusters within the atomic registration transaction:
#include "MySensor.h" #include <devices/Types.h> #include <lib/support/logging/CHIPLogging.h> using namespace chip::app::Clusters; namespace chip::app { MySensor::MySensor(TimerDelegate & timerDelegate, Clusters::IdentifyDelegate & identifyDelegate) : SingleEndpoint(Span<const DataModel::DeviceTypeEntry>(&Device::Type::kMySensor, 1)), mTimerDelegate(timerDelegate), mIdentifyDelegate(identifyDelegate) {} CHIP_ERROR MySensor::Register(chip::EndpointId endpoint, CodeDrivenDataModelProvider & provider, EndpointComposition composition) { VerifyOrReturnError(mEndpointId == kInvalidEndpointId, CHIP_ERROR_INCORRECT_STATE); DeviceRegistrationTransaction transaction(*this, provider); ReturnErrorOnFailure(RegisterDescriptor(endpoint, provider, composition)); // Wire up the mandatory identify delegate mIdentifyCluster.Create(IdentifyCluster::Config(endpoint, mTimerDelegate).WithDelegate(&mIdentifyDelegate)); ReturnErrorOnFailure(provider.AddCluster(mIdentifyCluster.Registration())); mMySensorCluster.Create(endpoint); ReturnErrorOnFailure(provider.AddCluster(mMySensorCluster.Registration())); // Allow subclasses to register optional clusters before committing the endpoint ReturnErrorOnFailure(RegisterAdditionalClusters(endpoint, provider)); ReturnErrorOnFailure(provider.AddEndpoint(mEndpointRegistration)); transaction.Commit(); return CHIP_NO_ERROR; } void MySensor::Unregister(CodeDrivenDataModelProvider & provider) { // UnregisterDescriptor MUST be called first to remove the endpoint from the provider. // Once started, calling provider.RemoveCluster while the endpoint is still registered returns CHIP_ERROR_INCORRECT_STATE. UnregisterDescriptor(provider); UnregisterAdditionalClusters(provider); if (mMySensorCluster.IsConstructed()) { LogErrorOnFailure(provider.RemoveCluster(&mMySensorCluster.Cluster())); mMySensorCluster.Destroy(); } if (mIdentifyCluster.IsConstructed()) { LogErrorOnFailure(provider.RemoveCluster(&mIdentifyCluster.Cluster())); mIdentifyCluster.Destroy(); } } } // namespace chip::app
impl/LoggingMySensor.h & .cpp)To provide a self-contained simulator variant ready for DeviceFactory, implement the logging subclass under impl/.
Note the inheritance order: delegate base classes (private Clusters::IdentifyDelegate) are listed before public MySensor so the delegate subobject is constructed before being passed as *this to MySensor:
impl/LoggingMySensor.h)#pragma once #include <device/types/my-sensor/MySensor.h> #include <lib/support/logging/CHIPLogging.h> namespace chip::app { class LoggingMySensor : private Clusters::IdentifyDelegate, public MySensor { public: explicit LoggingMySensor(TimerDelegate & timerDelegate) : MySensor(timerDelegate, *this) {} ~LoggingMySensor() override = default; // IdentifyDelegate implementation void OnIdentifyStart(Clusters::IdentifyCluster & cluster) override { ChipLogProgress(DeviceLayer, "MySensor: OnIdentifyStart"); } void OnIdentifyStop(Clusters::IdentifyCluster & cluster) override { ChipLogProgress(DeviceLayer, "MySensor: OnIdentifyStop"); } void OnTriggerEffect(Clusters::IdentifyCluster & cluster) override { ChipLogProgress(DeviceLayer, "MySensor: OnTriggerEffect"); } bool IsTriggerEffectEnabled() const override { return true; } }; } // namespace chip::app
BUILD.gn)Create all-devices-common/device/types/my-sensor/BUILD.gn to define your standalone source set:
# Copyright (c) 2026 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. import("//build_overrides/chip.gni") source_set("my-sensor") { sources = [ "MySensor.cpp", "MySensor.h", ] public_deps = [ "${chip_root}/examples/all-devices-app/all-devices-common/device/api:single-endpoint-device", "${chip_root}/src/app/clusters/identify-server", # Add public_deps for your specific cluster servers here "${chip_root}/src/data-model-providers/codedriven", "${chip_root}/src/lib/core", ] }
DeviceFactoryTo enable runtime initialization via the --device my-sensor CLI flag, register your self-contained logging mock in all-devices-common/device-factory/DeviceFactory.h.
Include your Logging Mock Header (keep the include list sorted alphabetically):
#include <device/types/my-sensor/impl/LoggingMySensor.h>
Register the Creator inside the DeviceFactory constructor:
if constexpr (ALL_DEVICES_ENABLE_MY_SENSOR) { RegisterCreator("my-sensor", [this]() { VerifyOrDie(mContext.has_value()); return MakeDevice<LoggingMySensor>(mContext->timerDelegate); }); }
Note: We instantiate the Logging mock version in the factory so it runs completely out-of-the-box with self-contained console logging, keeping the platform main clean.
(Optional) Register Out-of-Band & Named Pipe Support:
OOBAccessors.h/.cpp (platform-neutral) and NamedPipeTranslators.h/.cpp (POSIX-only).<device/types/<name>/OOBAccessors.h> in all-devices-common/oob-accessors/OOBAccessorHook.h and <device/types/<name>/NamedPipeTranslators.h> in posix/named_pipe/Hook.h so the SFINAE detection hooks (HasOOBAccessors, HasNamedPipeTranslators) discover the registration overloads at compile-time.Next, register your implementation files across our core build scripts.
[!NOTE] To keep project lists clean, our build systems enforce alphabetical sorting. Please insert your new device entries in alphabetical order to ensure automated CI formatting checks pass successfully.
all-devices-common/device-factory/enabled_devices_config.h.in)Add the compile-time CMake hook:
#cmakedefine01 ALL_DEVICES_ENABLE_MY_SENSOR
all-devices-common/device-factory/enabled_devices.gni)Add the device entry to _available_devices (keep sorted):
_available_devices = [ ... [ "my-sensor", "MY_SENSOR", ], ... ]
all-devices-common/device-factory/enabled_devices.cmake)Add your .cpp source file to ALL_DEVICES_DEVICE_SOURCES (keep sorted):
set(ALL_DEVICES_DEVICE_SOURCES # keep-sorted: start ... "${ALL_DEVICES_COMMON_DIR}/device/types/my-sensor/MySensor.cpp" ... # keep-sorted: end )
Also add your key to the activation loop in the same file (keep sorted):
foreach(_key # keep-sorted: start ... my-sensor ... # keep-sorted: end )
Also add a bool entry to examples/all-devices-app/zephyr/Kconfig.devices inside the menu "Matter device types" block (keep sorted):
config ALL_DEVICES_DEVICE_MY_SENSOR bool "My sensor"
all-devices-common/device-factory/BUILD.gn)Add your new device target to the public_deps of device-factory (keep sorted):
public_deps = [ ... "${chip_root}/examples/all-devices-app/all-devices-common/device/types/my-sensor", ... ]
BUILD.gn files)Add the target dependency to the relevant platform executable targets where all-devices-app is built (e.g., examples/all-devices-app/posix/BUILD.gn, and embedded platform builds such as Silicon Labs or ESP32):
"${chip_root}/examples/all-devices-app/all-devices-common/device/types/my-sensor",
To support building automated variants of the application with your device enabled by default, expand the Python build targets.
targets.pyOpen scripts/build/build/targets.py and append your device string to _ALL_DEVICES_APP_DEVICES (keep sorted):
_ALL_DEVICES_APP_DEVICES = [ # keep-sorted: start ... 'my-sensor', ... ]
Our automated PR workflows verify that build variants match a golden snapshot. Updating targets.py will temporarily cause scripts/build/test.py to fail until you regenerate this golden file:
scripts/run_in_build_env.sh "python3 scripts/build/test.py"
cp all_targets_linux_x64.txt.actual scripts/build/testdata/all_targets_linux_x64.txt rm all_targets_linux_x64.txt.actual
OK):scripts/run_in_build_env.sh "python3 scripts/build/test.py"
Fully validate your new device integration using end-to-end testing tools.
Build the executable using the standard Python build scripts:
source scripts/activate.sh ./scripts/build/build_examples.py --target linux-x64-all-devices-clang build
Activate the automated test virtual environment and run the core device basic composition certification test. This instructs the binary to dynamically spawn your new device on a child endpoint:
source out/venv/bin/activate ./scripts/tests/run_python_test.py \ --factory-reset \ --app ./out/linux-x64-all-devices-clang/all-devices-app \ --app-args "--device my-sensor:2 --discriminator 1234 --KVS kvs1" \ --script src/python_testing/TC_DeviceBasicComposition.py \ --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021"
Confirm that all test cases execute and pass successfully.
chip-toolFor manual interactive validation, execute chip-tool (refer to the chip-tool-testing skill for detailed commissioning setups):
Compile chip-tool from source:
source scripts/activate.sh ./scripts/build/build_examples.py --target linux-x64-chip-tool-clang build
Start your application configured with your new device:
./out/linux-x64-all-devices-clang/all-devices-app --device my-sensor
Commission the app and execute interaction model read/write commands or custom cluster commands against your new device endpoint to verify functional data model operations.
src/python_testing/ or YAML test cases in src/app/tests/suites/ that match the specific clusters implemented by your device.run_python_test.py to guarantee full compliance with the Matter Specification.After implementing and registering your device, manually update the tracking documentation:
supported_device_types.md: Move your device type entry from the Unimplemented table to the Implemented table, and increment the total count.supported_clusters.md: If your device introduced support for a new cluster, update its Used in All-Devices status to Yes and list your device type in the Notes/Devices column.README.md: Update the Supported Devices list and the --device CLI help options example block under the Running the Application section.