Adding support for a new Matter device type
The Matter Bridge application supports bridging only a few Matter device types due to practical reasons. However, you can select any of the available Matter device types and add support to it in the application.
You will need to implement the Matter Bridged Device
and Bridged Device Data Provider
roles based on the Matter Bridge architecture for the newly added Matter device type.
The Matter Bridge application supports simulated and Bluetooth LE bridged device configurations.
In this guide, the simulated provider example is presented, but the process is similar for the Bluetooth LE provider as well.
The following steps show how to add support for a new Matter device type, using the Pressure Sensor device type as an example.
Enable the
Pressure Measurement
cluster for the endpoint2
in thesrc/bridge.zap
file and re-generate the files located in thesrc/zap-generated
directory.To learn how to modify the
.zap
file and re-generate thezap-generated
directory, see the Edit clusters using the ZAP tool section in the Adding clusters to Matter application user guide.Implement the
Matter Bridged Device
role.Create the
pressure_sensor.cpp
andpressure_sensor.h
files in thesrc/bridged_device_types
directory.Open the
nrf/samples/matter/common/src/bridge/matter_bridged_device.h
header file and find theMatterBridgedDevice
class constructor. Note the constructor signature, it will be used in the child class implemented in the next steps.Add a new
PressureSensorDevice
class inheritingMatterBridgedDevice
, and implement its constructor in thepressure_sensor.cpp
andpressure_sensor.h
files.pressure_sensor.h
#pragma once #include "matter_bridged_device.h" class PressureSensorDevice : public Nrf::MatterBridgedDevice { public: PressureSensorDevice(const char *nodeLabel); static constexpr uint16_t kPressureSensorDeviceTypeId = 0x0305; };
pressure_sensor.cpp
#include "pressure_sensor.h" PressureSensorDevice::PressureSensorDevice(const char *nodeLabel) : MatterBridgedDevice(nodeLabel) {}
Declare all clusters that are mandatory for the Pressure Sensor device type, according to the Matter device library specification, and fill the appropriate
MatterBridgedDevice
class fields in thePressureSensorDevice
class constructor.The Pressure Sensor device requires the
Descriptor
,Bridged Device Basic Information
andIdentify
clusters, which can be declared using helper macros from thenrf/samples/matter/common/src/bridge/matter_bridged_device.h
header file, and thePressure Measurement
cluster, which has to be defined in the application. Edit thepressure_sensor.cpp
file as follows:Add:
namespace { DESCRIPTOR_CLUSTER_ATTRIBUTES(descriptorAttrs); BRIDGED_DEVICE_BASIC_INFORMATION_CLUSTER_ATTRIBUTES(bridgedDeviceBasicAttrs); IDENTIFY_CLUSTER_ATTRIBUTES(identifyAttrs); }; /* namespace */ using namespace ::chip; using namespace ::chip::app; using namespace Nrf; DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(pressureSensorAttrs) DECLARE_DYNAMIC_ATTRIBUTE(Clusters::PressureMeasurement::Attributes::MeasuredValue::Id, INT16S, 2, 0), DECLARE_DYNAMIC_ATTRIBUTE(Clusters::PressureMeasurement::Attributes::MinMeasuredValue::Id, INT16S, 2, 0), DECLARE_DYNAMIC_ATTRIBUTE(Clusters::PressureMeasurement::Attributes::MaxMeasuredValue::Id, INT16S, 2, 0), DECLARE_DYNAMIC_ATTRIBUTE(Clusters::PressureMeasurement::Attributes::FeatureMap::Id, BITMAP32, 4, 0), DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(); DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN(bridgedPressureClusters) DECLARE_DYNAMIC_CLUSTER(Clusters::PressureMeasurement::Id, pressureSensorAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr), DECLARE_DYNAMIC_CLUSTER(Clusters::Descriptor::Id, descriptorAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr), DECLARE_DYNAMIC_CLUSTER(Clusters::BridgedDeviceBasicInformation::Id, bridgedDeviceBasicAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr), DECLARE_DYNAMIC_CLUSTER(Clusters::Identify::Id, identifyAttrs, ZAP_CLUSTER_MASK(SERVER), sIdentifyIncomingCommands, nullptr) DECLARE_DYNAMIC_CLUSTER_LIST_END; DECLARE_DYNAMIC_ENDPOINT(bridgedPressureEndpoint, bridgedPressureClusters); static constexpr uint8_t kBridgedPressureEndpointVersion = 2; static constexpr EmberAfDeviceType kBridgedPressureDeviceTypes[] = { { static_cast<chip::DeviceTypeId>(PressureSensorDevice::kPressureSensorDeviceTypeId), kBridgedPressureEndpointVersion }, { static_cast<chip::DeviceTypeId>(MatterBridgedDevice::DeviceType::BridgedNode), MatterBridgedDevice::kDefaultDynamicEndpointVersion } }; static constexpr uint8_t kPressureDataVersionSize = ArraySize(bridgedPressureClusters);
Modify the constructor:
PressureSensorDevice::PressureSensorDevice(const char *nodeLabel) : MatterBridgedDevice(nodeLabel) { mDataVersionSize = kPressureDataVersionSize; mEp = &bridgedPressureEndpoint; mDeviceTypeList = kBridgedPressureDeviceTypes; mDeviceTypeListSize = ARRAY_SIZE(kBridgedPressureDeviceTypes); mDataVersion = static_cast<DataVersion *>(chip::Platform::MemoryAlloc(sizeof(DataVersion) * mDataVersionSize)); }
Open the
nrf/samples/matter/common/src/bridge/matter_bridged_device.h
header file again to see which methods of theMatterBridgedDevice
class are purely virtual (assigned with=0
) and have to be overridden by thePressureSensorDevice
class.Edit the
PressureSensorDevice
class in thepressure_sensor.h
header file to declare the required methods as follows:uint16_t GetDeviceType() const override; CHIP_ERROR HandleRead(chip::ClusterId clusterId, chip::AttributeId attributeId, uint8_t *buffer, uint16_t maxReadLength) override; CHIP_ERROR HandleWrite(chip::ClusterId clusterId, chip::AttributeId attributeId, uint8_t *buffer) override; CHIP_ERROR HandleAttributeChange(chip::ClusterId clusterId, chip::AttributeId attributeId, void *data, size_t dataSize) override;
Implement the body of the
GetDeviceType()
method so that it can return the device type ID for the Pressure Sensor device type, which is equal to0x0305
. To check the device type ID for specific type of device, see Matter Device Library Specification.Edit the
pressure_sensor.cpp
file as follows:uint16_t PressureSensorDevice::GetDeviceType() const { return PressureSensorDevice::kPressureSensorDeviceTypeId; }
Implement the body of the
HandleRead()
method to handle reading data operations for all supported attributes.The read operations for the
Descriptor
,Bridged Device Basic Information
andIdentify
clusters, which are common to all devices, are handled in a common bridge module. The read operations for thePressure Measurement
cluster are the only ones to that need to be handled in the application.To provide support for reading attributes for the Pressure Sensor device, edit the
pressure_sensor.h
andpressure_sensor.cpp
files as follows:pressure_sensor.h
,PressureSensorDevice
classint16_t GetMeasuredValue() { return mMeasuredValue; } int16_t GetMinMeasuredValue() { return 95; } int16_t GetMaxMeasuredValue() { return 101; } uint16_t GetPressureMeasurementClusterRevision() { return 3; } uint32_t GetPressureMeasurementFeatureMap() { return 0; } CHIP_ERROR HandleReadPressureMeasurement(chip::AttributeId attributeId, uint8_t *buffer, uint16_t maxReadLength); uint16_t mMeasuredValue = 0;
pressure_sensor.cpp
CHIP_ERROR PressureSensorDevice::HandleRead(ClusterId clusterId, AttributeId attributeId, uint8_t *buffer, uint16_t maxReadLength) { switch (clusterId) { case Clusters::PressureMeasurement::Id: return HandleReadPressureMeasurement(attributeId, buffer, maxReadLength); default: return CHIP_ERROR_INVALID_ARGUMENT; } } CHIP_ERROR PressureSensorDevice::HandleReadPressureMeasurement(AttributeId attributeId, uint8_t *buffer, uint16_t maxReadLength) { switch (attributeId) { case Clusters::PressureMeasurement::Attributes::MeasuredValue::Id: { int16_t value = GetMeasuredValue(); return CopyAttribute(&value, sizeof(value), buffer, maxReadLength); } case Clusters::PressureMeasurement::Attributes::MinMeasuredValue::Id: { int16_t value = GetMinMeasuredValue(); return CopyAttribute(&value, sizeof(value), buffer, maxReadLength); } case Clusters::PressureMeasurement::Attributes::MaxMeasuredValue::Id: { int16_t value = GetMaxMeasuredValue(); return CopyAttribute(&value, sizeof(value), buffer, maxReadLength); } case Clusters::PressureMeasurement::Attributes::ClusterRevision::Id: { uint16_t clusterRevision = GetPressureMeasurementClusterRevision(); return CopyAttribute(&clusterRevision, sizeof(clusterRevision), buffer, maxReadLength); } case Clusters::PressureMeasurement::Attributes::FeatureMap::Id: { uint32_t featureMap = GetPressureMeasurementFeatureMap(); return CopyAttribute(&featureMap, sizeof(featureMap), buffer, maxReadLength); } default: return CHIP_ERROR_INVALID_ARGUMENT; } }
Implement the body of the
HandleWrite()
method, which handles write data operations for all supported attributes. In this case, there is no attribute supporting write operations, so edit thepressure_sensor.cpp
file as follows:CHIP_ERROR PressureSensorDevice::HandleWrite(chip::ClusterId clusterId, chip::AttributeId attributeId, uint8_t *buffer) { return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; }
Implement the body of the
HandleAttributeChange()
method. This will be called by theBridge Manager
to notify that data was changed by theBridged Device Data Provider
and the local state should be updated.Edit the
pressure_sensor.h
andpressure_sensor.cpp
files as follows:pressure_sensor.h
void SetMeasuredValue(int16_t value) { mMeasuredValue = value; }
pressure_sensor.cpp
CHIP_ERROR PressureSensorDevice::HandleAttributeChange(chip::ClusterId clusterId, chip::AttributeId attributeId, void *data, size_t dataSize) { CHIP_ERROR err = CHIP_NO_ERROR; if (!data) { return CHIP_ERROR_INVALID_ARGUMENT; } switch (clusterId) { case Clusters::BridgedDeviceBasicInformation::Id: return HandleWriteDeviceBasicInformation(clusterId, attributeId, data, dataSize); case Clusters::Identify::Id: return HandleWriteIdentify(attributeId, data, dataSize); case Clusters::PressureMeasurement::Id: { switch (attributeId) { case Clusters::PressureMeasurement::Attributes::MeasuredValue::Id: { int16_t value; err = CopyAttribute(data, dataSize, &value, sizeof(value)); if (err != CHIP_NO_ERROR) { return err; } SetMeasuredValue(value); break; } default: return CHIP_ERROR_INVALID_ARGUMENT; } break; } default: return CHIP_ERROR_INVALID_ARGUMENT; } return err; }
Implement the
Bridged Device Data Provider
role.Create the
simulated_pressure_sensor_data_provider.cpp
andsimulated_pressure_sensor_data_provider.h
files in thesrc/simulated_providers
directory.Open the
nrf/samples/matter/common/src/bridge/bridged_device_data_provider.h
header file and find theBridgedDeviceDataProvider
class constructor. Note the constructor signature, it will be used in the child class implemented in the next steps.Add a new
SimulatedPressureSensorDataProvider
class inheritingBridgedDeviceDataProvider
, and implement its constructor in thesimulated_pressure_sensor_data_provider.h
header file.#pragma once #include "bridged_device_data_provider.h" #include <zephyr/kernel.h> class SimulatedPressureSensorDataProvider : public Nrf::BridgedDeviceDataProvider { public: SimulatedPressureSensorDataProvider(UpdateAttributeCallback updateCallback, InvokeCommandCallback commandCallback) : Nrf::BridgedDeviceDataProvider(updateCallback, commandCallback) {} ~SimulatedPressureSensorDataProvider() {} };
Open the
nrf/samples/matter/common/src/bridge/bridged_device_data_provider.h
header file again to see which methods of theBridgedDeviceDataProvider
class are purely virtual (assigned with=0
) and have to be overridden by theSimulatedPressureSensorDataProvider
class.Edit the
SimulatedPressureSensorDataProvider
class in thesimulated_pressure_sensor_data_provider.h
header file to declare the required methods as follows:void Init() override; void NotifyUpdateState(chip::ClusterId clusterId, chip::AttributeId attributeId, void *data, size_t dataSize) override; CHIP_ERROR UpdateState(chip::ClusterId clusterId, chip::AttributeId attributeId, uint8_t *buffer) override;
Implement the body of the
Init()
method so that it can prepare the data provider for further operation. In this case, the pressure measurements will be simulated by changing data in a random manner and updating it at fixed time intervals.To initialize the timer and perform measurement updates, edit the
simulated_pressure_sensor_data_provider.h
andsimulated_pressure_sensor_data_provider.cpp
files as follows:simulated_pressure_sensor_data_provider.h
,SimulatedPressureSensorDataProvider
classstatic constexpr uint16_t kMeasurementsIntervalMs = 10000; static constexpr int16_t kMinRandomPressure = 95; static constexpr int16_t kMaxRandomPressure = 101; static void TimerTimeoutCallback(k_timer *timer); k_timer mTimer; int16_t mPressure = 0;
simulated_pressure_sensor_data_provider.cpp
#include "simulated_pressure_sensor_data_provider.h" using namespace ::chip; using namespace ::chip::app; using namespace Nrf; void SimulatedPressureSensorDataProvider::Init() { k_timer_init(&mTimer, SimulatedPressureSensorDataProvider::TimerTimeoutCallback, nullptr); k_timer_user_data_set(&mTimer, this); k_timer_start(&mTimer, K_MSEC(kMeasurementsIntervalMs), K_MSEC(kMeasurementsIntervalMs)); } void SimulatedPressureSensorDataProvider::TimerTimeoutCallback(k_timer *timer) { if (!timer || !timer->user_data) { return; } DeviceLayer::PlatformMgr().ScheduleWork( [](intptr_t p) { SimulatedPressureSensorDataProvider *provider = reinterpret_cast<SimulatedPressureSensorDataProvider *>(p); /* Get some random data to emulate sensor measurements. */ provider->mPressure = chip::Crypto::GetRandU16() % (kMaxRandomPressure - kMinRandomPressure) + kMinRandomPressure; provider->NotifyUpdateState(Clusters::PressureMeasurement::Id, Clusters::PressureMeasurement::Attributes::MeasuredValue::Id, &provider->mPressure, sizeof(provider->mPressure)); }, reinterpret_cast<intptr_t>(timer->user_data)); }
Implement the body of the
NotifyUpdateState()
method that shall be called after every data change related to the Pressure Sensor device. It is used to inform theBridge Manager
and Matter Data Model that an attribute value should be updated.To make the method invoke the appropriate callback, edit the
simulated_pressure_sensor_data_provider.cpp
file as follows:void SimulatedPressureSensorDataProvider::NotifyUpdateState(chip::ClusterId clusterId, chip::AttributeId attributeId, void *data, size_t dataSize) { if (mUpdateAttributeCallback) { mUpdateAttributeCallback(*this, Clusters::PressureMeasurement::Id, Clusters::PressureMeasurement::Attributes::MeasuredValue::Id, data, dataSize); } }
Implement the body of the
UpdateState()
method. This will be called by theBridge Manager
to inform that data in Matter Data Model was changed and request propagating this information to the end device.In this case, there is no attribute supporting write operations and sending data to end device is not required, so edit the
simulated_pressure_sensor_data_provider.cpp
file as follows:CHIP_ERROR SimulatedPressureSensorDataProvider::UpdateState(chip::ClusterId clusterId, chip::AttributeId attributeId, uint8_t *buffer) { return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; }
Add the
PressureSensorDevice
andSimulatedPressureSensorDataProvider
implementations created in previous steps to the compilation process. To do that, edit theCMakeLists.txt
file as follows:target_sources(app PRIVATE src/bridged_device_types/pressure_sensor.cpp src/simulated_providers/simulated_pressure_sensor_data_provider.cpp )
Provide allocators for
PressureSensorDevice
andSimulatedPressureSensorDataProvider
object creation. The Matter Bridge application uses aSimulatedBridgedDeviceFactory
factory module that creates pairedMatter Bridged Device
andBridged Device Data Provider
objects matching a specific Matter device type ID.To add support for creating the
PressureSensorDevice
andSimulatedPressureSensorDataProvider
objects when the Pressure Sensor device type ID is used, edit thesrc/simulated_providers/simulated_bridged_device_factory.h
andsrc/simulated_providers/simulated_bridged_device_factory.cpp
files as follows:src/simulated_providers/simulated_bridged_device_factory.h
#include "pressure_sensor.h" #include "simulated_pressure_sensor_data_provider.h"
src/simulated_providers/simulated_bridged_device_factory.cpp
,GetBridgedDeviceFactory()
method{ PressureSensorDevice::kPressureSensorDeviceTypeId, [checkLabel](const char *nodeLabel) -> Nrf::MatterBridgedDevice * { if (!checkLabel(nodeLabel)) { return nullptr; } return chip::Platform::New<PressureSensorDevice>(nodeLabel); } },
src/simulated_providers/simulated_bridged_device_factory.cpp
,GetDataProviderFactory()
method{ PressureSensorDevice::kPressureSensorDeviceTypeId, [](UpdateAttributeCallback updateClb, InvokeCommandCallback commandClb) { return chip::Platform::New<SimulatedPressureSensorDataProvider>(updateClb, commandClb); } },
Compile the target and test it following the steps from the Matter Bridge application testing section.