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 matter_bridged_device.h header file and find the MatterBridgedDevice class constructor. Note the constructor signature, it will be used in the child class implemented in the next steps.
Add a new
PressureSensorDeviceclass inheriting MatterBridgedDevice, 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 MatterBridgedDevice class fields in the
PressureSensorDeviceclass constructor.The Pressure Sensor device requires the
Descriptor,Bridged Device Basic InformationandIdentifyclusters, which can be declared using helper macros from the matter_bridged_device.h header 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 matter_bridged_device.h header file again to see which methods of the MatterBridgedDevice class 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 to
0x0305. 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 the
pressure_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 the
Bridge 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 bridged_device_data_provider.h header file and find the BridgedDeviceDataProvider class constructor. Note the constructor signature, it will be used in the child class implemented in the next steps.
Add a new
SimulatedPressureSensorDataProviderclass inheriting BridgedDeviceDataProvider, 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 bridged_device_data_provider.h header file again to see which methods of the BridgedDeviceDataProvider class 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 the
Bridge 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 the
Bridge 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. Edit the CMakeLists.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
PressureSensorDeviceandSimulatedPressureSensorDataProviderobject creation. The Matter Bridge application uses a SimulatedBridgedDeviceFactory factory 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 the simulated_bridged_device_factory.h and simulated_bridged_device_factory.cpp files as follows:simulated_bridged_device_factory.h
#include "pressure_sensor.h" #include "simulated_pressure_sensor_data_provider.h"
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); } },
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.