Custom Product Baseline Guide#

A production Matter device requires a fixed application architecture rather than the CLI-configured simulator approach.

This guide explains adapting all-devices-app patterns to build a standalone product application.


1. Architectural Differences#

When transitioning to a product application, replace dynamic configurations with static definitions:

Component / Layer

all-devices-app

Custom Application

Data Model Structure

Dynamic, parsed entirely from CLI --device arguments at boot.

Static / Fixed, defined explicitly in your application initialization code.

Device Registry

Uses DeviceFactory singleton to map string names to device creators.

Removed. Devices are instantiated directly as members.

Build Source List

enabled_devices.cmake/.gni compiling all device types.

Only compiles the C++ device classes required by the product.



3. Extracting the Product Baseline Code#

Rather than using DeviceFactory, implement a DeviceManager to own Matter endpoints and Interaction Model clusters.

The Device Manager Header (MyBulbDeviceManager.h)#

Instantiate device classes (such as LoggingDimmableLightDevice) directly.

#pragma once

#include <app/persistence/DefaultAttributePersistenceProvider.h>
#include <data-model-providers/codedriven/CodeDrivenDataModelProvider.h>
#include <devices/dimmable-light/impl/LoggingDimmableLightDevice.h>
#include <lib/core/CHIPError.h>
#include <platform/DefaultTimerDelegate.h>

namespace chip::app {

class MyBulbDeviceManager
{
public:
    MyBulbDeviceManager() = default;
    ~MyBulbDeviceManager() = default;

    static MyBulbDeviceManager & Instance()
    {
        static MyBulbDeviceManager sInstance;
        return sInstance;
    }

    // Explicit runtime initialization hook called during application boot
    CHIP_ERROR Init(PersistentStorageDelegate & storageDelegate, Credentials::GroupDataProvider & groupDataProvider, FabricTable & fabricTable);

    // Exact tear down contract
    void Shutdown();

    CodeDrivenDataModelProvider & DataModelProvider() { return *mDataModelProvider; }

private:
    DefaultAttributePersistenceProvider mAttributePersistence;
    std::unique_ptr<CodeDrivenDataModelProvider> mDataModelProvider;
    std::unique_ptr<DeviceInterface> mMainLightEndpoint;
    DeviceLayer::DefaultTimerDelegate mTimerDelegate;
};

} // namespace chip::app

The Device Manager Source (MyBulbDeviceManager.cpp)#

Instantiate endpoints and bind them to hardcoded EndpointId values.

#include "MyBulbDeviceManager.h"
#include <lib/support/CodeUtils.h>

namespace chip::app {

CHIP_ERROR MyBulbDeviceManager::Init(PersistentStorageDelegate & storageDelegate, Credentials::GroupDataProvider & groupDataProvider, FabricTable & fabricTable)
{
    // 1. Initialize attribute persistence and instantiate our CodeDriven Data Model Provider
    ReturnErrorOnFailure(mAttributePersistence.Init(&storageDelegate));
    mDataModelProvider = std::make_unique<CodeDrivenDataModelProvider>(storageDelegate, mAttributePersistence);

    // 2. Instantiate the precise C++ functional device object for our Smart Bulb
    mMainLightEndpoint = std::make_unique<LoggingDimmableLightDevice>(LoggingDimmableLightDevice::Context{
        .groupDataProvider = groupDataProvider,
        .fabricTable       = fabricTable,
        .timerDelegate     = mTimerDelegate,
    });

    // 3. Register the endpoint and its encapsulated server clusters into our Data Model Provider
    ReturnErrorOnFailure(mMainLightEndpoint->Register(EndpointId(1), *mDataModelProvider));

    return CHIP_NO_ERROR;
}

void MyBulbDeviceManager::Shutdown()
{
    if (mMainLightEndpoint)
    {
        VerifyOrDie(mDataModelProvider != nullptr);
        mMainLightEndpoint->Unregister(*mDataModelProvider);
        mMainLightEndpoint.reset();
    }
    mDataModelProvider.reset();
}

} // namespace chip::app

4. Hooking into Application Entry Points#

In the platform entrypoint (main.cpp or AppTask::Init()), initialize the static configuration after the Matter stack initializes:

#include "MyBulbDeviceManager.h"
#include <platform/PlatformManager.h>

void ApplicationResumedHook(PersistentStorageDelegate & storageDelegate)
{
    // Fetch SDK core dependencies
    auto & groupDataProvider = *Credentials::GetGroupDataProvider();
    auto & fabricTable       = Server::GetInstance().GetFabricTable();

    // Boot our fixed production device topology
    VerifyOrDie(chip::app::MyBulbDeviceManager::Instance().Init(storageDelegate, groupDataProvider, fabricTable) == CHIP_NO_ERROR);
}

void ApplicationShutdownHook()
{
    // Safely unregister endpoints before the underlying stack halts
    chip::app::MyBulbDeviceManager::Instance().Shutdown();
}

5. Commercial Firmware Guidelines#

  1. Link Specific Devices: In BUILD.gn or CMakeLists.txt, link directly against required device modules (e.g., all-devices-common/devices/dimmable-light) rather than device-factory to minimize RAM and Flash usage.

  2. Statically Define Topologies: Configure fixed EndpointId parameters instead of using auto-incrementing IDs to ensure a fixed data model.

  3. Persist Hardware State: Replace simulated cluster implementations with hardware drivers (e.g., binding a PWM driver to WriteAttribute callbacks) and persist calibration data via storage delegates.