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 Measurementcluster for the endpoint2in thesrc/bridge.zapfile and re-generate the files located in thesrc/zap-generateddirectory.To learn how to modify the
.zapfile and re-generate thezap-generateddirectory, see the Edit clusters using the ZAP tool section in the Adding clusters to Matter application user guide.Implement the
Matter Bridged Devicerole.Create the
pressure_sensor.cppandpressure_sensor.hfiles in thesrc/bridged_device_typesdirectory.Open the
nrf/applications/matter_bridge/src/core/matter_bridged_device.hheader file and find theMatterBridgedDeviceclass constructor. Note the constructor signature, it will be used in the child class implemented in the next steps.Add a new
PressureSensorDeviceclass inheritingMatterBridgedDevice, and implement its constructor in thepressure_sensor.cppandpressure_sensor.hfiles.pressure_sensor.h#pragma once #include "matter_bridged_device.h" class PressureSensorDevice : public Nrf::MatterBridgedDevice { public: PressureSensorDevice(const char *uniqueID, const char *nodeLabel); static constexpr uint16_t kPressureSensorDeviceTypeId = 0x0305; };
pressure_sensor.cpp#include "pressure_sensor.h" PressureSensorDevice::PressureSensorDevice(const char *uniqueID, const char *nodeLabel) : MatterBridgedDevice(uniqueID, nodeLabel) {}
Declare all clusters that are mandatory for the Pressure Sensor device type, according to the Matter device library specification, and fill the appropriate
MatterBridgedDeviceclass fields in thePressureSensorDeviceclass constructor.The Pressure Sensor device requires the
Descriptor,Bridged Device Basic InformationandIdentifyclusters, which can be declared using helper macros from thenrf/applications/matter_bridge/src/core/matter_bridged_device.hheader file, and thePressure Measurementcluster, which has to be defined in the application. Edit thepressure_sensor.cppfile 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 *uniqueID, const char *nodeLabel) : MatterBridgedDevice(uniqueID, nodeLabel) { mDataVersionSize = kPressureDataVersionSize; mEp = &bridgedPressureEndpoint; mDeviceTypeList = kBridgedPressureDeviceTypes; mDeviceTypeListSize = ARRAY_SIZE(kBridgedPressureDeviceTypes); mDataVersion = static_cast<DataVersion *>(chip::Platform::MemoryAlloc(sizeof(DataVersion) * mDataVersionSize)); }
Open the
nrf/applications/matter_bridge/src/core/matter_bridged_device.hheader file again to see which methods of theMatterBridgedDeviceclass are purely virtual (assigned with=0) and have to be overridden by thePressureSensorDeviceclass.Edit the
PressureSensorDeviceclass in thepressure_sensor.hheader 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, size_t size) 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.cppfile 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 InformationandIdentifyclusters, which are common to all devices, are handled in a common bridge module. The read operations for thePressure Measurementcluster are the only ones that need to be handled in the application.To provide support for reading attributes for the Pressure Sensor device, edit the
pressure_sensor.handpressure_sensor.cppfiles as follows:pressure_sensor.h,PressureSensorDeviceclassint16_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.cppCHIP_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.cppfile 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 Managerto notify that data was changed by theBridged Device Data Providerand the local state should be updated.Edit the
pressure_sensor.handpressure_sensor.cppfiles as follows:pressure_sensor.hvoid SetMeasuredValue(int16_t value) { mMeasuredValue = value; }
pressure_sensor.cppCHIP_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 Providerrole.Create the
simulated_pressure_sensor_data_provider.cppandsimulated_pressure_sensor_data_provider.hfiles in thesrc/simulated_providersdirectory.Open the
nrf/applications/matter_bridge/src/core/bridged_device_data_provider.hheader file and find theBridgedDeviceDataProviderclass constructor. Note the constructor signature, it will be used in the child class implemented in the next steps.Add a new
SimulatedPressureSensorDataProviderclass inheritingBridgedDeviceDataProvider, and implement its constructor in thesimulated_pressure_sensor_data_provider.hheader 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/applications/matter_bridge/src/core/bridged_device_data_provider.hheader file again to see which methods of theBridgedDeviceDataProviderclass are purely virtual (assigned with=0) and have to be overridden by theSimulatedPressureSensorDataProviderclass.Edit the
SimulatedPressureSensorDataProviderclass in thesimulated_pressure_sensor_data_provider.hheader 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.handsimulated_pressure_sensor_data_provider.cppfiles as follows:simulated_pressure_sensor_data_provider.h,SimulatedPressureSensorDataProviderclassstatic 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 Managerand 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.cppfile 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 Managerto 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.cppfile as follows:CHIP_ERROR SimulatedPressureSensorDataProvider::UpdateState(chip::ClusterId clusterId, chip::AttributeId attributeId, uint8_t *buffer) { return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; }
Add the
PressureSensorDeviceandSimulatedPressureSensorDataProviderimplementations created in previous steps to the compilation process. To do that, edit theCMakeLists.txtfile 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
PressureSensorDeviceandSimulatedPressureSensorDataProviderobject creation. The Matter Bridge application uses aSimulatedBridgedDeviceFactoryfactory module that creates pairedMatter Bridged DeviceandBridged Device Data Providerobjects matching a specific Matter device type ID.To add support for creating the
PressureSensorDeviceandSimulatedPressureSensorDataProviderobjects when the Pressure Sensor device type ID is used, edit thesrc/simulated_providers/simulated_bridged_device_factory.handsrc/simulated_providers/simulated_bridged_device_factory.cppfiles 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, [checkUniqueID, checkLabel](const char* uniqueID, const char* nodeLabel) -> Nrf::MatterBridgedDevice * { if (!checkUniqueID(uniqueID) || !checkLabel(nodeLabel)) { return nullptr; } return chip::Platform::New<PressureSensorDevice>(uniqueID, 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.