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., LoggingMySensorDevice).*this to the base constructor.DeviceFactory or unit tests without requiring external wiring.Spec-Pure Directory Layout & Extracted Capabilities:
devices/ 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.MySensorDevice.h)Derive your class from SingleEndpointDevice (or EndpointDevice if managing sub-endpoints). 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 <devices/interface/SingleEndpointDevice.h> #include <lib/support/TimerDelegate.h> namespace chip::app { class MySensorDevice : public SingleEndpointDevice { public: MySensorDevice(TimerDelegate & timerDelegate, Clusters::IdentifyDelegate & identifyDelegate); ~MySensorDevice() 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(); } private: TimerDelegate & mTimerDelegate; Clusters::IdentifyDelegate & mIdentifyDelegate; LazyRegisteredServerCluster<Clusters::IdentifyCluster> mIdentifyCluster; LazyRegisteredServerCluster<Clusters::MySensorCluster> mMySensorCluster; }; } // namespace chip::app
MySensorDevice.cpp)In Register(), wire up your mandatory delegates using the .WithDelegate() helper when creating the cluster instances:
#include "MySensorDevice.h" #include <devices/Types.h> #include <lib/support/logging/CHIPLogging.h> using namespace chip::app::Clusters; namespace chip::app { MySensorDevice::MySensorDevice(TimerDelegate & timerDelegate, Clusters::IdentifyDelegate & identifyDelegate) : SingleEndpointDevice(Span<const DataModel::DeviceTypeEntry>(&Device::Type::kMySensor, 1)), mTimerDelegate(timerDelegate), mIdentifyDelegate(identifyDelegate) {} CHIP_ERROR MySensorDevice::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())); ReturnErrorOnFailure(provider.AddEndpoint(mEndpointRegistration)); transaction.Commit(); return CHIP_NO_ERROR; } void MySensorDevice::Unregister(CodeDrivenDataModelProvider & provider) { UnregisterDescriptor(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/LoggingMySensorDevice.h & .cpp)To provide a self-contained simulator variant ready for DeviceFactory, implement the self-delegate logging mock under the impl/ subfolder:
impl/LoggingMySensorDevice.h)#pragma once #include <devices/my-sensor/MySensorDevice.h> #include <lib/support/logging/CHIPLogging.h> namespace chip::app { class LoggingMySensorDevice : public MySensorDevice, public Clusters::IdentifyDelegate { public: explicit LoggingMySensorDevice(TimerDelegate & timerDelegate) : MySensorDevice(timerDelegate, *this) {} ~LoggingMySensorDevice() 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/devices/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 = [ "MySensorDevice.cpp", "MySensorDevice.h", ] public_deps = [ "${chip_root}/examples/all-devices-app/all-devices-common/devices/interface: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 <devices/my-sensor/impl/LoggingMySensorDevice.h>
Register the Creator inside the DeviceFactory constructor:
if constexpr (ALL_DEVICES_ENABLE_MY_SENSOR) { RegisterCreator("my-sensor", [this]() { VerifyOrDie(mContext.has_value()); return std::make_unique<LoggingMySensorDevice>(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.
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}/devices/my-sensor/MySensorDevice.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 )
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/devices/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/devices/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.