diff --git a/applications/matter_bridge/CMakeLists.txt b/applications/matter_bridge/CMakeLists.txt index f374bb7b803..566207979bc 100644 --- a/applications/matter_bridge/CMakeLists.txt +++ b/applications/matter_bridge/CMakeLists.txt @@ -44,7 +44,7 @@ target_sources(app PRIVATE src/main.cpp src/bridge_shell.cpp ${COMMON_ROOT}/src/bridge/bridge_manager.cpp - ${COMMON_ROOT}/src/bridge/bridged_device.cpp + ${COMMON_ROOT}/src/bridge/matter_bridged_device.cpp ${COMMON_ROOT}/src/bridge/bridge_storage_manager.cpp ${COMMON_ROOT}/src/bridge/bridged_device_data_provider.cpp src/zap-generated/IMClusterCommandHandler.cpp @@ -63,6 +63,25 @@ if(CONFIG_BRIDGE_ONOFF_LIGHT_BRIDGED_DEVICE) ) endif() # CONFIG_BRIDGE_ONOFF_LIGHT_BRIDGED_DEVICE +if(CONFIG_BRIDGE_HUMIDITY_SENSOR_BRIDGED_DEVICE) + target_sources(app PRIVATE + src/bridged_device_types/humidity_sensor.cpp + ) +endif() # CONFIG_BRIDGE_HUMIDITY_SENSOR_BRIDGED_DEVICE + +if(CONFIG_BRIDGE_TEMPERATURE_SENSOR_BRIDGED_DEVICE) + target_sources(app PRIVATE + src/bridged_device_types/temperature_sensor.cpp + ) +endif() # CONFIG_BRIDGE_TEMPERATURE_SENSOR_BRIDGED_DEVICE + +# Assume it makes no sense to support BLE environmental sensor without all Matter counterparts +if(CONFIG_BRIDGE_TEMPERATURE_SENSOR_BRIDGED_DEVICE AND CONFIG_BRIDGE_HUMIDITY_SENSOR_BRIDGED_DEVICE) + target_sources(app PRIVATE + src/bridged_device_types/ble_environmental_data_provider.cpp + ) +endif() # CONFIG_BRIDGE_TEMPERATURE_SENSOR_BRIDGED_DEVICE AND CONFIG_BRIDGE_HUMIDITY_SENSOR_BRIDGED_DEVICE + else() if(CONFIG_BRIDGE_ONOFF_LIGHT_BRIDGED_DEVICE) target_sources(app PRIVATE diff --git a/applications/matter_bridge/Kconfig b/applications/matter_bridge/Kconfig index f2d01fc90f1..2e21e7379d8 100644 --- a/applications/matter_bridge/Kconfig +++ b/applications/matter_bridge/Kconfig @@ -11,11 +11,11 @@ config BRIDGE_ONOFF_LIGHT_BRIDGED_DEVICE config BRIDGE_TEMPERATURE_SENSOR_BRIDGED_DEVICE bool "Support for Temperature Sensor bridged device" - default y if BRIDGED_DEVICE_SIMULATED + default y config BRIDGE_HUMIDITY_SENSOR_BRIDGED_DEVICE bool "Support for Humidity Sensor bridged device" - default y if BRIDGED_DEVICE_SIMULATED + default y choice BRIDGED_DEVICE_IMPLEMENTATION prompt "Bridged Device implementation" @@ -53,7 +53,7 @@ config BT_SCAN_FILTER_ENABLE # Configure how many Bluetooth LE service UUIDs the Matter bridge can scan. config BT_SCAN_UUID_CNT - default 1 + default 2 config BT_GATT_CLIENT default y @@ -61,6 +61,10 @@ config BT_GATT_CLIENT config BT_GATT_DM default y +config BRIDGE_BLE_DEVICE_POLLING_INTERVAL + int "BLE humidity measurement readout polling interval in ms" + default 3000 + endif if BRIDGE_TEMPERATURE_SENSOR_BRIDGED_DEVICE diff --git a/applications/matter_bridge/README.rst b/applications/matter_bridge/README.rst index ac279e226de..329a45ef564 100644 --- a/applications/matter_bridge/README.rst +++ b/applications/matter_bridge/README.rst @@ -23,8 +23,12 @@ The application supports the following development kits: To test the Matter bridge application with the :ref:`Bluetooth LE bridged device `, you also need the following: -* An additional development kit compatible with the :ref:`peripheral_lbs` sample. -* A micro USB cable to connect the development kit to the PC. +* An additional development kit compatible with one of the following Bluetooth LE samples: + + * :ref:`peripheral_lbs` + * :ref:`peripheral_esp` + +* A micro-USB cable to connect the development kit to the PC. To commission the Matter bridge device and control it remotely through a Wi-Fi network, you also need a Matter controller device :ref:`configured on PC or smartphone `. This requires additional hardware depending on your setup. @@ -182,6 +186,7 @@ Adding a Bluetooth LE bridged device to the Matter bridge The argument is mandatory and accepts the following values: * ``256`` - On/Off Light. + * ``291`` - Environmental Sensor (a combination of Temperature Sensor and Humidity Sensor). * ** is the Bluetooth LE device index on the list returned by the ``scan`` command. The argument is mandatory and accepts only the values returned by the ``scan`` command. @@ -329,8 +334,12 @@ After building the sample and programming it to your development kit, complete t .. group-tab:: Testing with Bluetooth LE bridged devices - a. Build and program the :ref:`peripheral_lbs` sample to an additional development kit compatible with the sample. - #. Connect the development kit that is running the :ref:`peripheral_lbs` sample to the PC. + a. Build and program the one of the following Bluetooth LE samples to an additional development kit compatible with the sample: + + * :ref:`peripheral_lbs` + * :ref:`peripheral_esp` + + #. Connect the development kit that is running the Bluetooth LE sample to the PC. #. Using the terminal emulator connected to the bridge, run the following :ref:`Matter CLI command ` to scan for available Bluetooth LE devices: .. code-block:: console @@ -373,6 +382,8 @@ After building the sample and programming it to your development kit, complete t I: Adding OnOff Light bridged device I: Added device to dynamic endpoint 3 (index=0) + For the Environmental Sensor, two endpoints are created: one implements the Temperature Sensor, and the other implements the Humidity Sensor. + #. Write down the value for the bridged device dynamic endpoint ID. This is going to be used in the next steps (**). #. Use the :doc:`CHIP Tool ` to read the value of an attribute from the bridged device endpoint. @@ -383,6 +394,22 @@ After building the sample and programming it to your development kit, complete t ./chip-tool onoff read on-off ** ** + In case of the Environmental Sensor, the current temperature and humidity measurements forwarded by the Bluetooth LE Environmental Sensor can be read as follows: + + * temperature: + + .. parsed-literal:: + :class: highlight + + ./chip-tool temperaturemeasurement read measured-value ** ** + + * humidity: + + .. parsed-literal:: + :class: highlight + + ./chip-tool relativehumiditymeasurement read measured-value ** ** + Enabling remote control ======================= diff --git a/applications/matter_bridge/src/app_task.cpp b/applications/matter_bridge/src/app_task.cpp index b6b55bb82a2..500769ed1e0 100644 --- a/applications/matter_bridge/src/app_task.cpp +++ b/applications/matter_bridge/src/app_task.cpp @@ -61,7 +61,8 @@ bool sHaveBLEConnections = false; #ifdef CONFIG_BRIDGED_DEVICE_BT static bt_uuid *sUuidLbs = BT_UUID_LBS; -static bt_uuid *sUuidServices[] = { sUuidLbs }; +static bt_uuid *sUuidEs = BT_UUID_ESS; +static bt_uuid *sUuidServices[] = { sUuidLbs, sUuidEs }; static constexpr uint8_t kUuidServicesNumber = ARRAY_SIZE(sUuidServices); #endif /* CONFIG_BRIDGED_DEVICE_BT */ diff --git a/applications/matter_bridge/src/bridge_shell.cpp b/applications/matter_bridge/src/bridge_shell.cpp index 828e045b63b..e73abe9454c 100644 --- a/applications/matter_bridge/src/bridge_shell.cpp +++ b/applications/matter_bridge/src/bridge_shell.cpp @@ -21,7 +21,7 @@ LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); static BridgedDeviceDataProvider *CreateSimulatedProvider(int deviceType) { return BridgeFactory::GetSimulatedDataProviderFactory().Create( - static_cast(deviceType), BridgeManager::HandleUpdate); + static_cast(deviceType), BridgeManager::HandleUpdate); } #endif /* CONFIG_BRIDGED_DEVICE_SIMULATED */ @@ -29,14 +29,14 @@ static BridgedDeviceDataProvider *CreateSimulatedProvider(int deviceType) static BridgedDeviceDataProvider *CreateBleProvider(int deviceType) { - return BridgeFactory::GetBleDataProviderFactory().Create(static_cast(deviceType), - BridgeManager::HandleUpdate); + return BridgeFactory::GetBleDataProviderFactory().Create( + static_cast(deviceType), BridgeManager::HandleUpdate); } struct BluetoothConnectionContext { const struct shell *shell; int deviceType; - char nodeLabel[BridgedDevice::kNodeLabelSize]; + char nodeLabel[MatterBridgedDevice::kNodeLabelSize]; BLEBridgedDeviceProvider *provider; }; #endif /* CONFIG_BRIDGED_DEVICE_BT */ @@ -44,21 +44,43 @@ struct BluetoothConnectionContext { static void AddDevice(const struct shell *shell, int deviceType, const char *nodeLabel, BridgedDeviceDataProvider *provider) { - VerifyOrReturn(provider != nullptr, - shell_fprintf(shell, SHELL_INFO, "Cannot allocate data provider of given type\n")); + VerifyOrReturn(provider != nullptr, shell_fprintf(shell, SHELL_INFO, "No valid data provider!\n")); - auto *newBridgedDevice = BridgeFactory::GetBridgedDeviceFactory().Create( - static_cast(deviceType), nodeLabel); + CHIP_ERROR err; + if (deviceType == MatterBridgedDevice::DeviceType::EnvironmentalSensor) { + /* Handle special case for environmental sensor */ + auto *temperatureBridgedDevice = BridgeFactory::GetBridgedDeviceFactory().Create( + MatterBridgedDevice::DeviceType::TemperatureSensor, nodeLabel); - VerifyOrReturn(newBridgedDevice != nullptr, delete provider, - shell_fprintf(shell, SHELL_INFO, "Cannot allocate Matter device of given type\n")); + VerifyOrReturn(temperatureBridgedDevice != nullptr, delete provider, + shell_fprintf(shell, SHELL_INFO, "Cannot allocate Matter device of given type\n")); + + auto *humidityBridgedDevice = BridgeFactory::GetBridgedDeviceFactory().Create( + MatterBridgedDevice::DeviceType::HumiditySensor, nodeLabel); + + VerifyOrReturn(humidityBridgedDevice != nullptr, delete provider, delete temperatureBridgedDevice, + shell_fprintf(shell, SHELL_INFO, "Cannot allocate Matter device of given type\n")); + + MatterBridgedDevice *newBridgedDevices[] = { temperatureBridgedDevice, humidityBridgedDevice }; + err = BridgeManager::Instance().AddBridgedDevices(newBridgedDevices, provider, + ARRAY_SIZE(newBridgedDevices)); + + } else { + auto *newBridgedDevice = BridgeFactory::GetBridgedDeviceFactory().Create( + static_cast(deviceType), nodeLabel); + + MatterBridgedDevice *newBridgedDevices[] = { newBridgedDevice }; + VerifyOrReturn(newBridgedDevice != nullptr, delete provider, + shell_fprintf(shell, SHELL_INFO, "Cannot allocate Matter device of given type\n")); + err = BridgeManager::Instance().AddBridgedDevices(newBridgedDevices, provider, + ARRAY_SIZE(newBridgedDevices)); + } - CHIP_ERROR err = BridgeManager::Instance().AddBridgedDevices(newBridgedDevice, provider); if (err == CHIP_NO_ERROR) { shell_fprintf(shell, SHELL_INFO, "Done\n"); } else if (err == CHIP_ERROR_INVALID_STRING_LENGTH) { shell_fprintf(shell, SHELL_ERROR, "Error: too long node label (max %d)\n", - BridgedDevice::kNodeLabelSize); + MatterBridgedDevice::kNodeLabelSize); } else if (err == CHIP_ERROR_NO_MEMORY) { shell_fprintf(shell, SHELL_ERROR, "Error: no memory\n"); } else if (err == CHIP_ERROR_INVALID_ARGUMENT) { @@ -119,6 +141,7 @@ static int AddBridgedDeviceHandler(const struct shell *shell, size_t argc, char BridgedDeviceDataProvider *provider = CreateBleProvider(contextPtr->deviceType); if (!provider) { + shell_fprintf(shell, SHELL_INFO, "Cannot allocate data provider of given type\n"); return -ENOMEM; } @@ -193,13 +216,14 @@ static int ScanBridgedDeviceHandler(const struct shell *shell, size_t argc, char SHELL_STATIC_SUBCMD_SET_CREATE( sub_matter_bridge, #ifdef CONFIG_BRIDGED_DEVICE_BT - SHELL_CMD_ARG(add, NULL, - "Adds bridged device. \n" - "Usage: add [node_label]\n" - "* bridged_device_type - the bridged device's type, e.g. 256 - OnOff Light\n" - "* ble_device_index - the Bluetooth LE device's index on the list returned by the scan command\n" - "* node_label - the optional bridged device's node label\n", - AddBridgedDeviceHandler, 3, 1), + SHELL_CMD_ARG( + add, NULL, + "Adds bridged device. \n" + "Usage: add [node_label]\n" + "* bridged_device_type - the bridged device's type, e.g. 256 - OnOff Light, 291 - EnvironmentalSensor\n" + "* ble_device_index - the Bluetooth LE device's index on the list returned by the scan command\n" + "* node_label - the optional bridged device's node label\n", + AddBridgedDeviceHandler, 3, 1), #else SHELL_CMD_ARG( add, NULL, diff --git a/applications/matter_bridge/src/bridged_device_factory.h b/applications/matter_bridge/src/bridged_device_factory.h index 9ea70494395..b6df77a6a89 100644 --- a/applications/matter_bridge/src/bridged_device_factory.h +++ b/applications/matter_bridge/src/bridged_device_factory.h @@ -7,8 +7,8 @@ #pragma once #include "bridge_util.h" -#include "bridged_device.h" #include "bridged_device_data_provider.h" +#include "matter_bridged_device.h" #include #ifdef CONFIG_BRIDGE_HUMIDITY_SENSOR_BRIDGED_DEVICE @@ -35,11 +35,17 @@ #endif #endif +#ifdef CONFIG_BRIDGED_DEVICE_BT +#if defined(CONFIG_BRIDGE_HUMIDITY_SENSOR_BRIDGED_DEVICE) && defined(CONFIG_BRIDGE_TEMPERATURE_SENSOR_BRIDGED_DEVICE) +#include "ble_environmental_data_provider.h" +#endif +#endif + namespace BridgeFactory { using UpdateAttributeCallback = BridgedDeviceDataProvider::UpdateAttributeCallback; -using DeviceType = BridgedDevice::DeviceType; -using BridgedDeviceFactory = DeviceFactory; +using DeviceType = MatterBridgedDevice::DeviceType; +using BridgedDeviceFactory = DeviceFactory; #ifdef CONFIG_BRIDGED_DEVICE_SIMULATED using SimulatedDataProviderFactory = DeviceFactory; @@ -51,7 +57,7 @@ using BleDataProviderFactory = DeviceFactory BridgedDevice * { + [](const char *nodeLabel) -> MatterBridgedDevice * { if (!checkLabel(nodeLabel)) { return nullptr; } @@ -71,7 +77,7 @@ inline BridgedDeviceFactory &GetBridgedDeviceFactory() #endif #ifdef CONFIG_BRIDGE_ONOFF_LIGHT_BRIDGED_DEVICE { DeviceType::OnOffLight, - [](const char *nodeLabel) -> BridgedDevice * { + [](const char *nodeLabel) -> MatterBridgedDevice * { if (!checkLabel(nodeLabel)) { return nullptr; } @@ -79,8 +85,8 @@ inline BridgedDeviceFactory &GetBridgedDeviceFactory() } }, #endif #ifdef CONFIG_BRIDGE_TEMPERATURE_SENSOR_BRIDGED_DEVICE - { BridgedDevice::DeviceType::TemperatureSensor, - [](const char *nodeLabel) -> BridgedDevice * { + { MatterBridgedDevice::DeviceType::TemperatureSensor, + [](const char *nodeLabel) -> MatterBridgedDevice * { if (!checkLabel(nodeLabel)) { return nullptr; } @@ -121,10 +127,16 @@ inline SimulatedDataProviderFactory &GetSimulatedDataProviderFactory() #ifdef CONFIG_BRIDGED_DEVICE_BT inline BleDataProviderFactory &GetBleDataProviderFactory() { - static BleDataProviderFactory sDeviceDataProvider{ + static BleDataProviderFactory sDeviceDataProvider + { #ifdef CONFIG_BRIDGE_ONOFF_LIGHT_BRIDGED_DEVICE { DeviceType::OnOffLight, [](UpdateAttributeCallback clb) { return chip::Platform::New(clb); } }, +#endif +#if defined(CONFIG_BRIDGE_TEMPERATURE_SENSOR_BRIDGED_DEVICE) && defined(CONFIG_BRIDGE_HUMIDITY_SENSOR_BRIDGED_DEVICE) + { DeviceType::EnvironmentalSensor, [](UpdateAttributeCallback clb) { + return chip::Platform::New(clb); + } }, #endif }; return sDeviceDataProvider; diff --git a/applications/matter_bridge/src/bridged_device_types/ble_environmental_data_provider.cpp b/applications/matter_bridge/src/bridged_device_types/ble_environmental_data_provider.cpp new file mode 100644 index 00000000000..cb6faf1b23f --- /dev/null +++ b/applications/matter_bridge/src/bridged_device_types/ble_environmental_data_provider.cpp @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "ble_environmental_data_provider.h" + +#include +#include +#include + +#include + +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); + +using namespace ::chip; +using namespace ::chip::app; + +static bt_uuid *sServiceUuid = BT_UUID_ESS; +static bt_uuid *sUuidTemperature = BT_UUID_TEMPERATURE; +static bt_uuid *sUuidHumidity = BT_UUID_HUMIDITY; +static bt_uuid *sUuidCcc = BT_UUID_GATT_CCC; + +bt_gatt_read_params BleEnvironmentalDataProvider::sHumidityReadParams{}; + +BleEnvironmentalDataProvider *GetProvider(bt_conn *conn) +{ + BLEBridgedDevice *device = BLEConnectivityManager::Instance().FindBLEBridgedDevice(conn); + VerifyOrReturnValue(device, nullptr); + return reinterpret_cast(device->mProvider); +} + +bt_uuid *BleEnvironmentalDataProvider::GetServiceUuid() +{ + return sServiceUuid; +} + +uint8_t BleEnvironmentalDataProvider::GattTemperatureNotifyCallback(bt_conn *conn, bt_gatt_subscribe_params *params, + const void *data, uint16_t length) +{ + BleEnvironmentalDataProvider *provider = GetProvider(conn); + VerifyOrExit(provider, ); + VerifyOrExit(data, ); + VerifyOrExit(length == sizeof(mTemperatureValue), ); + + /* Save data received in notification. */ + memcpy(&provider->mTemperatureValue, data, length); + DeviceLayer::PlatformMgr().ScheduleWork(NotifyTemperatureAttributeChange, reinterpret_cast(provider)); + +exit: + + return BT_GATT_ITER_CONTINUE; +} + +uint8_t BleEnvironmentalDataProvider::GattHumidityNotifyCallback(bt_conn *conn, bt_gatt_subscribe_params *params, + const void *data, uint16_t length) +{ + BleEnvironmentalDataProvider *provider = GetProvider(conn); + VerifyOrExit(provider, ); + VerifyOrExit(data, ); + VerifyOrExit(length == sizeof(mHumidityValue), ); + + /* Save data received in notification. */ + memcpy(&provider->mHumidityValue, data, length); + DeviceLayer::PlatformMgr().ScheduleWork(NotifyHumidityAttributeChange, reinterpret_cast(provider)); + +exit: + + return BT_GATT_ITER_CONTINUE; +} + +void BleEnvironmentalDataProvider::Init() +{ + /* Configure subscription for the temperature characteristic */ + mGattTemperatureSubscribeParams.ccc_handle = mCccTemperatureHandle; + mGattTemperatureSubscribeParams.value_handle = mTemperatureCharacteristicHandle; + mGattTemperatureSubscribeParams.value = BT_GATT_CCC_NOTIFY; + mGattTemperatureSubscribeParams.notify = BleEnvironmentalDataProvider::GattTemperatureNotifyCallback; + + /* Configure subscription for the humidity characteristic */ + mGattHumiditySubscribeParams.ccc_handle = mCccHumidityHandle; + mGattHumiditySubscribeParams.value_handle = mHumidityCharacteristicHandle; + mGattHumiditySubscribeParams.value = BT_GATT_CCC_NOTIFY; + mGattHumiditySubscribeParams.notify = BleEnvironmentalDataProvider::GattHumidityNotifyCallback; + + Subscribe(); +} + +void BleEnvironmentalDataProvider::NotifyUpdateState(chip::ClusterId clusterId, chip::AttributeId attributeId, + void *data, size_t dataSize) +{ + if (mUpdateAttributeCallback) { + mUpdateAttributeCallback(*this, clusterId, attributeId, data, dataSize); + } +} + +CHIP_ERROR BleEnvironmentalDataProvider::UpdateState(chip::ClusterId clusterId, chip::AttributeId attributeId, + uint8_t *) +{ + return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; +} + +int BleEnvironmentalDataProvider::ParseDiscoveredData(bt_gatt_dm *discoveredData) +{ + VerifyOrReturnError(CHIP_NO_ERROR == ParseTemperatureCharacteristic(discoveredData), -ENXIO); + VerifyOrReturnError(CHIP_NO_ERROR == ParseHumidityCharacteristic(discoveredData), -ENXIO); + + return 0; +} + +CHIP_ERROR BleEnvironmentalDataProvider::ParseTemperatureCharacteristic(bt_gatt_dm *discoveredData) +{ + const bt_gatt_dm_attr *gatt_chrc = bt_gatt_dm_char_by_uuid(discoveredData, sUuidTemperature); + VerifyOrReturnError(gatt_chrc, CHIP_ERROR_INTERNAL, LOG_ERR("No temperature characteristic found.")); + + const bt_gatt_dm_attr *gatt_desc = bt_gatt_dm_desc_by_uuid(discoveredData, gatt_chrc, sUuidTemperature); + VerifyOrReturnError(gatt_desc, CHIP_ERROR_INTERNAL, LOG_ERR("No temperature characteristic value found.")); + + mTemperatureCharacteristicHandle = gatt_desc->handle; + + gatt_desc = bt_gatt_dm_desc_by_uuid(discoveredData, gatt_chrc, sUuidCcc); + VerifyOrReturnError(gatt_desc, CHIP_ERROR_INTERNAL, LOG_ERR("No temperature CCC descriptor found.")); + + mCccTemperatureHandle = gatt_desc->handle; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR BleEnvironmentalDataProvider::ParseHumidityCharacteristic(bt_gatt_dm *discoveredData) +{ + const bt_gatt_dm_attr *gatt_chrc = bt_gatt_dm_char_by_uuid(discoveredData, sUuidHumidity); + VerifyOrReturnError(gatt_chrc, CHIP_ERROR_INTERNAL, LOG_ERR("No humidity characteristic found.")); + + const bt_gatt_dm_attr *gatt_desc = bt_gatt_dm_desc_by_uuid(discoveredData, gatt_chrc, sUuidHumidity); + VerifyOrReturnError(gatt_desc, CHIP_ERROR_INTERNAL, LOG_ERR("No humidity characteristic value found.")); + + mHumidityCharacteristicHandle = gatt_desc->handle; + + gatt_desc = bt_gatt_dm_desc_by_uuid(discoveredData, gatt_chrc, sUuidCcc); + /* This is acceptable because we will emulate the subscription for humidity sensor. */ + VerifyOrReturnError(gatt_desc, CHIP_NO_ERROR, LOG_INF("No humidity CCC descriptor found.")); + + mCccHumidityHandle = gatt_desc->handle; + + return CHIP_NO_ERROR; +} + +void BleEnvironmentalDataProvider::Subscribe() +{ + VerifyOrReturn(mDevice && mDevice->mConn, LOG_ERR("Invalid connection object")); + + if (CheckSubscriptionParameters(&mGattTemperatureSubscribeParams)) { + int err = bt_gatt_subscribe(mDevice->mConn, &mGattTemperatureSubscribeParams); + if (err) { + LOG_ERR("Subscribe to temperature characteristic failed with error %d", err); + } + } else { + LOG_ERR("Invalid temperature subscription parameters provided"); + } + + if (CheckSubscriptionParameters(&mGattHumiditySubscribeParams)) { + int err = bt_gatt_subscribe(mDevice->mConn, &mGattHumiditySubscribeParams); + if (err) { + LOG_ERR("Subscribe to humidity characteristic failed with error %d", err); + } + } else { + LOG_INF("Invalid humidity subscription parameters provided, starting emulated subscription"); + /* The fallback is based on polling for the current GATT humidity value */ + sHumidityReadParams.func = BleEnvironmentalDataProvider::HumidityGATTReadCallback; + sHumidityReadParams.handle_count = 0; + sHumidityReadParams.by_uuid.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + sHumidityReadParams.by_uuid.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + sHumidityReadParams.by_uuid.uuid = sUuidHumidity; + StartHumidityTimer(); + } +} + +void BleEnvironmentalDataProvider::Unsubscribe() +{ + VerifyOrReturn(mDevice && mDevice->mConn, LOG_ERR("Invalid connection object")); + + int err = bt_gatt_unsubscribe(mDevice->mConn, &mGattTemperatureSubscribeParams); + if (err) { + LOG_INF("Cannot unsubscribe from temperature characteristic (error %d)", err); + } + + err = bt_gatt_unsubscribe(mDevice->mConn, &mGattHumiditySubscribeParams); + if (err) { + LOG_INF("Cannot unsubscribe from humidity characteristic (error %d)", err); + } + StopHumidityTimer(); +} + +bool BleEnvironmentalDataProvider::CheckSubscriptionParameters(bt_gatt_subscribe_params *params) +{ + /* If any of these is not met, the bt_gatt_subscribe() generates an assert at runtime */ + VerifyOrReturnValue(params && params->notify, false); + VerifyOrReturnValue(params->value, false); + VerifyOrReturnValue(params->ccc_handle, false); + + return true; +} + +void BleEnvironmentalDataProvider::NotifyTemperatureAttributeChange(intptr_t context) +{ + BleEnvironmentalDataProvider *provider = reinterpret_cast(context); + + provider->NotifyUpdateState(Clusters::TemperatureMeasurement::Id, + Clusters::TemperatureMeasurement::Attributes::MeasuredValue::Id, + &provider->mTemperatureValue, sizeof(provider->mTemperatureValue)); +} + +void BleEnvironmentalDataProvider::NotifyHumidityAttributeChange(intptr_t context) +{ + BleEnvironmentalDataProvider *provider = reinterpret_cast(context); + + provider->NotifyUpdateState(Clusters::RelativeHumidityMeasurement::Id, + Clusters::RelativeHumidityMeasurement::Attributes::MeasuredValue::Id, + &provider->mHumidityValue, sizeof(provider->mHumidityValue)); +} + +void BleEnvironmentalDataProvider::StartHumidityTimer() +{ + k_timer_init(&mHumidityTimer, BleEnvironmentalDataProvider::HumidityTimerTimeoutCallback, nullptr); + k_timer_user_data_set(&mHumidityTimer, this); + k_timer_start(&mHumidityTimer, K_MSEC(kMeasurementsIntervalMs), K_MSEC(kMeasurementsIntervalMs)); +} + +void BleEnvironmentalDataProvider::HumidityTimerTimeoutCallback(k_timer *timer) +{ + VerifyOrReturn(timer && timer->user_data, LOG_ERR("Invalid context")); + + BleEnvironmentalDataProvider *provider = reinterpret_cast(timer->user_data); + VerifyOrReturn(provider, LOG_ERR("Invalid provider object")); + VerifyOrReturn(provider->mDevice && provider->mDevice->mConn, LOG_ERR("Invalid connection object")); + + DeviceLayer::PlatformMgr().ScheduleWork(ReadGATTHumidity, reinterpret_cast(provider)); +} + +void BleEnvironmentalDataProvider::ReadGATTHumidity(intptr_t context) +{ + BleEnvironmentalDataProvider *provider = reinterpret_cast(context); + + int err = bt_gatt_read(provider->mDevice->mConn, &sHumidityReadParams); + if (err) { + LOG_INF("GATT read failed (err %d)", err); + } +} + +uint8_t BleEnvironmentalDataProvider::HumidityGATTReadCallback(bt_conn *conn, uint8_t att_err, + bt_gatt_read_params *params, const void *data, + uint16_t read_len) +{ + BleEnvironmentalDataProvider *provider = GetProvider(conn); + VerifyOrReturnValue(provider, BT_GATT_ITER_STOP, LOG_ERR("Invalid provider object")); + + if (!att_err && (read_len == sizeof(provider->mHumidityValue))) { + const uint16_t newValue = *(static_cast(data)); + if (newValue != provider->mHumidityValue) { + provider->mHumidityValue = newValue; + DeviceLayer::PlatformMgr().ScheduleWork(NotifyHumidityAttributeChange, + reinterpret_cast(provider)); + } + } else { + LOG_ERR("Unsuccessful GATT read operation (err %d)", att_err); + } + + return BT_GATT_ITER_STOP; +} diff --git a/applications/matter_bridge/src/bridged_device_types/ble_environmental_data_provider.h b/applications/matter_bridge/src/bridged_device_types/ble_environmental_data_provider.h new file mode 100644 index 00000000000..ce4854846ee --- /dev/null +++ b/applications/matter_bridge/src/bridged_device_types/ble_environmental_data_provider.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#pragma once + +#include "ble_bridged_device.h" +#include "ble_connectivity_manager.h" +#include "bridged_device_data_provider.h" + +#include + +class BleEnvironmentalDataProvider : public BLEBridgedDeviceProvider { +public: + explicit BleEnvironmentalDataProvider(UpdateAttributeCallback callback) : BLEBridgedDeviceProvider(callback) {} + ~BleEnvironmentalDataProvider() { Unsubscribe(); } + + 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; + bt_uuid *GetServiceUuid() override; + int MatchBleDevice(BLEBridgedDevice *device) override + { + if (!device) { + return -EINVAL; + } + + mDevice = device; + mDevice->mProvider = this; + + return 0; + } + int ParseDiscoveredData(bt_gatt_dm *discoveredData) override; + +private: + static constexpr uint32_t kMeasurementsIntervalMs{ CONFIG_BRIDGE_BLE_DEVICE_POLLING_INTERVAL }; + + void StartHumidityTimer(); + void StopHumidityTimer() { k_timer_stop(&mHumidityTimer); } + void Subscribe(); + void Unsubscribe(); + bool CheckSubscriptionParameters(bt_gatt_subscribe_params *params); + + CHIP_ERROR ParseTemperatureCharacteristic(bt_gatt_dm *discoveredData); + CHIP_ERROR ParseHumidityCharacteristic(bt_gatt_dm *discoveredData); + + static uint8_t GattTemperatureNotifyCallback(bt_conn *conn, bt_gatt_subscribe_params *params, const void *data, + uint16_t length); + static uint8_t GattHumidityNotifyCallback(bt_conn *conn, bt_gatt_subscribe_params *params, const void *data, + uint16_t length); + static void NotifyTemperatureAttributeChange(intptr_t context); + static void NotifyHumidityAttributeChange(intptr_t context); + + static void ReadGATTHumidity(intptr_t context); + static void HumidityTimerTimeoutCallback(k_timer *timer); + static uint8_t HumidityGATTReadCallback(bt_conn *conn, uint8_t att_err, bt_gatt_read_params *params, + const void *data, uint16_t read_len); + + uint16_t mTemperatureValue{}; + uint16_t mHumidityValue{}; + + uint16_t mTemperatureCharacteristicHandle{}; + uint16_t mHumidityCharacteristicHandle{}; + + bt_gatt_subscribe_params mGattTemperatureSubscribeParams{}; + bt_gatt_subscribe_params mGattHumiditySubscribeParams{}; + + uint16_t mCccTemperatureHandle{}; + uint16_t mCccHumidityHandle{}; + + k_timer mHumidityTimer; + + static bt_gatt_read_params sHumidityReadParams; +}; diff --git a/applications/matter_bridge/src/bridged_device_types/humidity_sensor.cpp b/applications/matter_bridge/src/bridged_device_types/humidity_sensor.cpp index eac37018219..6488e7708e1 100644 --- a/applications/matter_bridge/src/bridged_device_types/humidity_sensor.cpp +++ b/applications/matter_bridge/src/bridged_device_types/humidity_sensor.cpp @@ -33,15 +33,15 @@ DECLARE_DYNAMIC_CLUSTER(Clusters::RelativeHumidityMeasurement::Id, humiSensorAtt DECLARE_DYNAMIC_ENDPOINT(bridgedHumidityEndpoint, bridgedHumidityClusters); static constexpr EmberAfDeviceType kBridgedHumidityDeviceTypes[] = { - { static_cast(BridgedDevice::DeviceType::HumiditySensor), - BridgedDevice::kDefaultDynamicEndpointVersion }, - { static_cast(BridgedDevice::DeviceType::BridgedNode), - BridgedDevice::kDefaultDynamicEndpointVersion } + { static_cast(MatterBridgedDevice::DeviceType::HumiditySensor), + MatterBridgedDevice::kDefaultDynamicEndpointVersion }, + { static_cast(MatterBridgedDevice::DeviceType::BridgedNode), + MatterBridgedDevice::kDefaultDynamicEndpointVersion } }; static constexpr uint8_t kHumidityDataVersionSize = ArraySize(bridgedHumidityClusters); -HumiditySensorDevice::HumiditySensorDevice(const char *nodeLabel) : BridgedDevice(nodeLabel) +HumiditySensorDevice::HumiditySensorDevice(const char *nodeLabel) : MatterBridgedDevice(nodeLabel) { mDataVersionSize = kHumidityDataVersionSize; mEp = &bridgedHumidityEndpoint; diff --git a/applications/matter_bridge/src/bridged_device_types/humidity_sensor.h b/applications/matter_bridge/src/bridged_device_types/humidity_sensor.h index 80b67f37159..90b02683e75 100644 --- a/applications/matter_bridge/src/bridged_device_types/humidity_sensor.h +++ b/applications/matter_bridge/src/bridged_device_types/humidity_sensor.h @@ -6,9 +6,9 @@ #pragma once -#include "bridged_device.h" +#include "matter_bridged_device.h" -class HumiditySensorDevice : public BridgedDevice { +class HumiditySensorDevice : public MatterBridgedDevice { public: static constexpr uint16_t kRelativeHumidityMeasurementClusterRevision = 1; static constexpr uint32_t kRelativeHumidityMeasurementFeatureMap = 0; @@ -21,7 +21,10 @@ class HumiditySensorDevice : public BridgedDevice { uint16_t GetRelativeHumidityMeasurementClusterRevision() { return kRelativeHumidityMeasurementClusterRevision; } uint32_t GetRelativeHumidityMeasurementFeatureMap() { return kRelativeHumidityMeasurementFeatureMap; } - BridgedDevice::DeviceType GetDeviceType() const override { return BridgedDevice::DeviceType::HumiditySensor; } + MatterBridgedDevice::DeviceType GetDeviceType() const override + { + return MatterBridgedDevice::DeviceType::HumiditySensor; + } CHIP_ERROR HandleRead(chip::ClusterId clusterId, chip::AttributeId attributeId, uint8_t *buffer, uint16_t maxReadLength) override; CHIP_ERROR HandleReadRelativeHumidityMeasurement(chip::AttributeId attributeId, uint8_t *buffer, diff --git a/applications/matter_bridge/src/bridged_device_types/onoff_light.cpp b/applications/matter_bridge/src/bridged_device_types/onoff_light.cpp index c03f1cc36ac..8146df45023 100644 --- a/applications/matter_bridge/src/bridged_device_types/onoff_light.cpp +++ b/applications/matter_bridge/src/bridged_device_types/onoff_light.cpp @@ -38,15 +38,15 @@ DECLARE_DYNAMIC_CLUSTER(Clusters::OnOff::Id, onOffAttrs, onOffIncomingCommands, DECLARE_DYNAMIC_ENDPOINT(bridgedLightEndpoint, bridgedLightClusters); static constexpr EmberAfDeviceType kBridgedOnOffDeviceTypes[] = { - { static_cast(BridgedDevice::DeviceType::OnOffLight), - BridgedDevice::kDefaultDynamicEndpointVersion }, - { static_cast(BridgedDevice::DeviceType::BridgedNode), - BridgedDevice::kDefaultDynamicEndpointVersion } + { static_cast(MatterBridgedDevice::DeviceType::OnOffLight), + MatterBridgedDevice::kDefaultDynamicEndpointVersion }, + { static_cast(MatterBridgedDevice::DeviceType::BridgedNode), + MatterBridgedDevice::kDefaultDynamicEndpointVersion } }; static constexpr uint8_t kLightDataVersionSize = ArraySize(bridgedLightClusters); -OnOffLightDevice::OnOffLightDevice(const char *nodeLabel) : BridgedDevice(nodeLabel) +OnOffLightDevice::OnOffLightDevice(const char *nodeLabel) : MatterBridgedDevice(nodeLabel) { mDataVersionSize = kLightDataVersionSize; mEp = &bridgedLightEndpoint; diff --git a/applications/matter_bridge/src/bridged_device_types/onoff_light.h b/applications/matter_bridge/src/bridged_device_types/onoff_light.h index afab1506194..45d5af7d708 100644 --- a/applications/matter_bridge/src/bridged_device_types/onoff_light.h +++ b/applications/matter_bridge/src/bridged_device_types/onoff_light.h @@ -6,9 +6,9 @@ #pragma once -#include "bridged_device.h" +#include "matter_bridged_device.h" -class OnOffLightDevice : public BridgedDevice { +class OnOffLightDevice : public MatterBridgedDevice { public: static constexpr uint16_t kOnOffClusterRevision = 1; static constexpr uint32_t kOnOffFeatureMap = 0; @@ -20,7 +20,10 @@ class OnOffLightDevice : public BridgedDevice { uint16_t GetOnOffClusterRevision() { return kOnOffClusterRevision; } uint32_t GetOnOffFeatureMap() { return kOnOffFeatureMap; } - BridgedDevice::DeviceType GetDeviceType() const override { return BridgedDevice::DeviceType::OnOffLight; } + MatterBridgedDevice::DeviceType GetDeviceType() const override + { + return MatterBridgedDevice::DeviceType::OnOffLight; + } CHIP_ERROR HandleRead(chip::ClusterId clusterId, chip::AttributeId attributeId, uint8_t *buffer, uint16_t maxReadLength) override; CHIP_ERROR HandleReadOnOff(chip::AttributeId attributeId, uint8_t *buffer, uint16_t maxReadLength); diff --git a/applications/matter_bridge/src/bridged_device_types/simulated_humidity_sensor_data_provider.cpp b/applications/matter_bridge/src/bridged_device_types/simulated_humidity_sensor_data_provider.cpp index 4a1fd7a793e..4269d679fbf 100644 --- a/applications/matter_bridge/src/bridged_device_types/simulated_humidity_sensor_data_provider.cpp +++ b/applications/matter_bridge/src/bridged_device_types/simulated_humidity_sensor_data_provider.cpp @@ -33,8 +33,6 @@ void SimulatedHumiditySensorDataProvider::NotifyUpdateState(chip::ClusterId clus CHIP_ERROR SimulatedHumiditySensorDataProvider::UpdateState(chip::ClusterId clusterId, chip::AttributeId attributeId, uint8_t *buffer) { - LOG_INF("Updating state of the SimulatedHumiditySensorDataProvider, cluster ID: %u, attribute ID: %u. Dropping, currently not supported.", - clusterId, attributeId); return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; } diff --git a/applications/matter_bridge/src/bridged_device_types/simulated_temperature_sensor_data_provider.cpp b/applications/matter_bridge/src/bridged_device_types/simulated_temperature_sensor_data_provider.cpp index d02a8ebe8b0..5954894281e 100644 --- a/applications/matter_bridge/src/bridged_device_types/simulated_temperature_sensor_data_provider.cpp +++ b/applications/matter_bridge/src/bridged_device_types/simulated_temperature_sensor_data_provider.cpp @@ -33,8 +33,6 @@ void SimulatedTemperatureSensorDataProvider::NotifyUpdateState(chip::ClusterId c CHIP_ERROR SimulatedTemperatureSensorDataProvider::UpdateState(chip::ClusterId clusterId, chip::AttributeId attributeId, uint8_t *buffer) { - LOG_INF("Updating state of the SimulatedTemperatureSensorDataProvider, cluster ID: %u, attribute ID: %u. Dropping, currently not supported.", - clusterId, attributeId); return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; } diff --git a/applications/matter_bridge/src/bridged_device_types/temperature_sensor.cpp b/applications/matter_bridge/src/bridged_device_types/temperature_sensor.cpp index c9daf6fd6a9..0254792ba00 100644 --- a/applications/matter_bridge/src/bridged_device_types/temperature_sensor.cpp +++ b/applications/matter_bridge/src/bridged_device_types/temperature_sensor.cpp @@ -31,15 +31,15 @@ DECLARE_DYNAMIC_CLUSTER(Clusters::TemperatureMeasurement::Id, tempSensorAttrs, n DECLARE_DYNAMIC_ENDPOINT(bridgedTemperatureEndpoint, bridgedTemperatureClusters); static constexpr EmberAfDeviceType kBridgedTemperatureDeviceTypes[] = { - { static_cast(BridgedDevice::DeviceType::TemperatureSensor), - BridgedDevice::kDefaultDynamicEndpointVersion }, - { static_cast(BridgedDevice::DeviceType::BridgedNode), - BridgedDevice::kDefaultDynamicEndpointVersion } + { static_cast(MatterBridgedDevice::DeviceType::TemperatureSensor), + MatterBridgedDevice::kDefaultDynamicEndpointVersion }, + { static_cast(MatterBridgedDevice::DeviceType::BridgedNode), + MatterBridgedDevice::kDefaultDynamicEndpointVersion } }; static constexpr uint8_t kTemperatureDataVersionSize = ArraySize(bridgedTemperatureClusters); -TemperatureSensorDevice::TemperatureSensorDevice(const char *nodeLabel) : BridgedDevice(nodeLabel) +TemperatureSensorDevice::TemperatureSensorDevice(const char *nodeLabel) : MatterBridgedDevice(nodeLabel) { mDataVersionSize = kTemperatureDataVersionSize; mEp = &bridgedTemperatureEndpoint; diff --git a/applications/matter_bridge/src/bridged_device_types/temperature_sensor.h b/applications/matter_bridge/src/bridged_device_types/temperature_sensor.h index 74befce4a77..11143fb6cd5 100644 --- a/applications/matter_bridge/src/bridged_device_types/temperature_sensor.h +++ b/applications/matter_bridge/src/bridged_device_types/temperature_sensor.h @@ -6,9 +6,9 @@ #pragma once -#include "bridged_device.h" +#include "matter_bridged_device.h" -class TemperatureSensorDevice : public BridgedDevice { +class TemperatureSensorDevice : public MatterBridgedDevice { public: static constexpr uint16_t kTemperatureMeasurementClusterRevision = 1; static constexpr uint32_t kTemperatureMeasurementFeatureMap = 0; @@ -21,9 +21,9 @@ class TemperatureSensorDevice : public BridgedDevice { uint16_t GetTemperatureMeasurementClusterRevision() { return kTemperatureMeasurementClusterRevision; } uint32_t GetTemperatureMeasurementFeatureMap() { return kTemperatureMeasurementFeatureMap; } - BridgedDevice::DeviceType GetDeviceType() const override + MatterBridgedDevice::DeviceType GetDeviceType() const override { - return BridgedDevice::DeviceType::TemperatureSensor; + return MatterBridgedDevice::DeviceType::TemperatureSensor; } CHIP_ERROR HandleRead(chip::ClusterId clusterId, chip::AttributeId attributeId, uint8_t *buffer, uint16_t maxReadLength) override; diff --git a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst index a2e9cc28554..4f908b72435 100644 --- a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst +++ b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst @@ -254,7 +254,10 @@ Matter Bridge ------------- * Added the :ref:`Matter bridge ` application. -* Added support for the Bluetooth LE bridged devices. +* Added: + + * Support for the Bluetooth LE bridged devices. + * Support for bridging of the Bluetooth LE Environmental Sensor (ESP). Samples ======= diff --git a/samples/matter/common/src/bridge/bridge_manager.cpp b/samples/matter/common/src/bridge/bridge_manager.cpp index e5c413458ab..31624f59edb 100644 --- a/samples/matter/common/src/bridge/bridge_manager.cpp +++ b/samples/matter/common/src/bridge/bridge_manager.cpp @@ -21,7 +21,7 @@ using namespace ::chip::app; void BridgeManager::Init() { /* The first dynamic endpoint is the last fixed endpoint + 1. */ - mFirstDynamicEndpointId = static_cast( + mFirstDynamicEndpointId = static_cast( static_cast(emberAfEndpointFromIndex(static_cast(emberAfFixedEndpointCount() - 1))) + 1); mCurrentDynamicEndpointId = mFirstDynamicEndpointId; @@ -31,32 +31,58 @@ void BridgeManager::Init() false); } -CHIP_ERROR BridgeManager::AddBridgedDevices(BridgedDevice *aDevice, BridgedDeviceDataProvider *aDataProvider) +CHIP_ERROR BridgeManager::AddBridgedDevices(MatterBridgedDevice *devices[], BridgedDeviceDataProvider *dataProvider, + uint8_t deviceListSize) { - aDataProvider->Init(); - CHIP_ERROR err = AddDevices(aDevice, aDataProvider); + VerifyOrReturnError(devices && dataProvider, CHIP_ERROR_INTERNAL); - return err; + dataProvider->Init(); + + /* Wrap input data into unique_ptr to avoid memory leakage in case any error occurs. */ + Platform::UniquePtr devicesPtr[deviceListSize]; + for (auto i = 0; i < deviceListSize; ++i) { + devicesPtr[i] = std::move(Platform::UniquePtr(devices[i])); + } + Platform::UniquePtr dataProviderPtr(dataProvider); + + /* Maximum number of Matter bridged devices is controlled inside mDevicesMap, + but the data providers may be created independently, so let's ensure we do not + violate the maximum number of supported instances. */ + VerifyOrReturnError(mDevicesMap.FreeSlots() >= deviceListSize, CHIP_ERROR_NO_MEMORY, + LOG_ERR("Not enough free slots in the map")); + VerifyOrReturnError(mNumberOfProviders + 1 <= kMaxDataProviders, CHIP_ERROR_NO_MEMORY, + LOG_ERR("Maximum number of providers exceeded")); + mNumberOfProviders++; + + bool cumulativeStatus{ true }; + bool status{ true }; + for (auto i = 0; i < deviceListSize; ++i) { + MatterBridgedDevice *device = devicesPtr[i].get(); + status = AddSingleDevice(device, dataProviderPtr.get()); + if (status) { + devicesPtr[i].release(); + } + cumulativeStatus &= status; + } + + if (cumulativeStatus) { + dataProviderPtr.release(); + return CHIP_NO_ERROR; + } + return CHIP_ERROR_NO_MEMORY; } CHIP_ERROR BridgeManager::RemoveBridgedDevice(uint16_t endpoint) { uint8_t index = 0; - while (index < kMaxBridgedDevices) { if (mDevicesMap.Contains(index)) { - const DevicePair *devicesPtr = mDevicesMap[index]; - if (devicesPtr->mDevice->GetEndpointId() == endpoint) { + auto &devicePair = mDevicesMap[index]; + if (devicePair.mDevice->GetEndpointId() == endpoint) { LOG_INF("Removed dynamic endpoint %d (index=%d)", endpoint, index); /* Free dynamically allocated memory */ emberAfClearDynamicEndpoint(index); - if (mDevicesMap.Erase(index)) { - mNumberOfProviders--; - return CHIP_NO_ERROR; - } else { - LOG_ERR("Cannot remove bridged devices under index=%d", index); - return CHIP_ERROR_NOT_FOUND; - } + return SafelyRemoveDevice(index); } } index++; @@ -64,63 +90,84 @@ CHIP_ERROR BridgeManager::RemoveBridgedDevice(uint16_t endpoint) return CHIP_ERROR_NOT_FOUND; } -CHIP_ERROR BridgeManager::AddDevices(BridgedDevice *aDevice, BridgedDeviceDataProvider *aDataProvider) +CHIP_ERROR BridgeManager::SafelyRemoveDevice(uint8_t index) { - uint8_t index = 0; - - Platform::UniquePtr device(aDevice); - Platform::UniquePtr provider(aDataProvider); - VerifyOrReturnError(device && provider, CHIP_ERROR_INTERNAL); - - /* Maximum number of Matter bridged devices is controlled inside mDevicesMap, - but the data providers may be created independently, so let's ensure we do not - violate the maximum number of supported instances. */ - VerifyOrReturnError(!mDevicesMap.IsFull(), CHIP_ERROR_INTERNAL); - VerifyOrReturnError(mNumberOfProviders + 1 <= kMaxDataProviders, CHIP_ERROR_INTERNAL); - mNumberOfProviders++; + uint16_t duplicatedItemKeys[kMaxBridgedDevices]; + uint8_t size; + bool removeProvider = true; + + auto &devicePair = mDevicesMap[index]; + if (mDevicesMap.ValueDuplicated(devicePair, duplicatedItemKeys, size)) { + for (uint8_t i = 0; i < size; i++) { + /* We must consider only true duplicates, so skip the current index, as + * it would lead to comparing the same values each time. */ + if (duplicatedItemKeys[i] != index) { + auto &duplicate = mDevicesMap[duplicatedItemKeys[i]]; + if (duplicate.mDevice == devicePair.mDevice) { + devicePair.mDevice = nullptr; + } + if (duplicate.mProvider == devicePair.mProvider) { + devicePair.mProvider = nullptr; + removeProvider = false; + } + /* At least one real duplicate found. */ + break; + } + } + } + if (mDevicesMap.Erase(index)) { + if (removeProvider) { + mNumberOfProviders--; + } + return CHIP_NO_ERROR; + } else { + LOG_ERR("Cannot remove bridged devices under index=%d", index); + return CHIP_ERROR_NOT_FOUND; + } +} +bool BridgeManager::AddSingleDevice(MatterBridgedDevice *device, BridgedDeviceDataProvider *dataProvider) +{ + uint8_t index{ 0 }; while (index < kMaxBridgedDevices) { /* Find the first empty index in the bridged devices list */ if (!mDevicesMap.Contains(index)) { - mDevicesMap.Insert(index, DevicePair(std::move(device), std::move(provider))); - EmberAfStatus ret; - while (true) { - if (!mDevicesMap[index]) { - LOG_ERR("Cannot retrieve bridged device from index %d", index); - return CHIP_ERROR_INTERNAL; - } - auto &storedDevice = mDevicesMap[index]->mDevice; - ret = emberAfSetDynamicEndpoint( - index, mCurrentDynamicEndpointId, storedDevice->mEp, - Span(storedDevice->mDataVersion, storedDevice->mDataVersionSize), - Span(storedDevice->mDeviceTypeList, - storedDevice->mDeviceTypeListSize)); - - if (ret == EMBER_ZCL_STATUS_SUCCESS) { - LOG_INF("Added device to dynamic endpoint %d (index=%d)", - mCurrentDynamicEndpointId, index); - storedDevice->Init(mCurrentDynamicEndpointId); - return CHIP_NO_ERROR; - } else if (ret != EMBER_ZCL_STATUS_DUPLICATE_EXISTS) { - LOG_ERR("Failed to add dynamic endpoint: Internal error!"); - RemoveBridgedDevice(mCurrentDynamicEndpointId); // TODO: check if this is ok, we - // need to cleanup the unused - // devices - return CHIP_ERROR_INTERNAL; - } - - /* Handle wrap condition */ - if (++mCurrentDynamicEndpointId < mFirstDynamicEndpointId) { - mCurrentDynamicEndpointId = mFirstDynamicEndpointId; + if (mDevicesMap.Insert(index, BridgedDevicePair(device, dataProvider))) { + while (true) { + auto *storedDevice = mDevicesMap[index].mDevice; + /* Allocate endpoints for provided devices */ + EmberAfStatus ret = emberAfSetDynamicEndpoint( + index, mCurrentDynamicEndpointId, storedDevice->mEp, + Span(storedDevice->mDataVersion, + storedDevice->mDataVersionSize), + Span(storedDevice->mDeviceTypeList, + storedDevice->mDeviceTypeListSize)); + + if (ret == EMBER_ZCL_STATUS_SUCCESS) { + LOG_INF("Added device to dynamic endpoint %d (index=%d)", + mCurrentDynamicEndpointId, index); + storedDevice->Init(mCurrentDynamicEndpointId); + return true; + } else if (ret != EMBER_ZCL_STATUS_DUPLICATE_EXISTS) { + LOG_ERR("Failed to add dynamic endpoint: Internal error!"); + if (CHIP_NO_ERROR != SafelyRemoveDevice(index)) { + LOG_ERR("Cannot remove device from the map!"); + } + return false; + } + + /* Handle wrap condition */ + if (++mCurrentDynamicEndpointId < mFirstDynamicEndpointId) { + mCurrentDynamicEndpointId = mFirstDynamicEndpointId; + } } } } index++; } - LOG_ERR("Failed to add dynamic endpoint: No endpoints available!"); - - return CHIP_ERROR_NO_MEMORY; + LOG_ERR("Failed to add dynamic endpoint: No endpoints or indexes available!"); + return false; } CHIP_ERROR BridgeManager::HandleRead(uint16_t index, ClusterId clusterId, @@ -130,7 +177,7 @@ CHIP_ERROR BridgeManager::HandleRead(uint16_t index, ClusterId clusterId, VerifyOrReturnError(attributeMetadata && buffer, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnValue(Instance().mDevicesMap.Contains(index), CHIP_ERROR_INTERNAL); - auto &device = Instance().mDevicesMap[index]->mDevice; + auto *device = Instance().mDevicesMap[index].mDevice; return device->HandleRead(clusterId, attributeMetadata->attributeId, buffer, maxReadLength); } @@ -140,32 +187,29 @@ CHIP_ERROR BridgeManager::HandleWrite(uint16_t index, ClusterId clusterId, VerifyOrReturnError(attributeMetadata && buffer, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnValue(Instance().mDevicesMap.Contains(index), CHIP_ERROR_INTERNAL); - auto &device = Instance().mDevicesMap[index]->mDevice; + auto *device = Instance().mDevicesMap[index].mDevice; CHIP_ERROR err = device->HandleWrite(clusterId, attributeMetadata->attributeId, buffer); - /* After updating Matter BridgedDevice state, forward request to the non-Matter device. */ + /* After updating MatterBridgedDevice state, forward request to the non-Matter device. */ if (err == CHIP_NO_ERROR) { - return Instance().mDevicesMap[index]->mProvider->UpdateState(clusterId, attributeMetadata->attributeId, - buffer); + return Instance().mDevicesMap[index].mProvider->UpdateState(clusterId, attributeMetadata->attributeId, + buffer); } return err; } -void BridgeManager::HandleUpdate(BridgedDeviceDataProvider &dataProvider, chip::ClusterId clusterId, - chip::AttributeId attributeId, void *data, size_t dataSize) +void BridgeManager::HandleUpdate(BridgedDeviceDataProvider &dataProvider, ClusterId clusterId, AttributeId attributeId, + void *data, size_t dataSize) { VerifyOrReturn(data); - /* The state update was triggered by non-Matter device, find related Matter Bridged Device to update it as - well. - */ + /* The state update was triggered by non-Matter device, find bridged Matter device to update it as well. */ for (auto &item : Instance().mDevicesMap.mMap) { - if (item.value.mProvider.get() == &dataProvider) { + if (item.value.mProvider == &dataProvider) { /* If the Bridged Device state was updated successfully, schedule sending Matter data report. */ - if (CHIP_NO_ERROR == - item.value.mDevice->HandleAttributeChange(clusterId, attributeId, data, dataSize)) { - MatterReportingAttributeChangeCallback(item.value.mDevice->GetEndpointId(), clusterId, - attributeId); + auto *device = item.value.mDevice; + if (CHIP_NO_ERROR == device->HandleAttributeChange(clusterId, attributeId, data, dataSize)) { + MatterReportingAttributeChangeCallback(device->GetEndpointId(), clusterId, attributeId); } } } diff --git a/samples/matter/common/src/bridge/bridge_manager.h b/samples/matter/common/src/bridge/bridge_manager.h index f7602151a95..e6a8e6fd8aa 100644 --- a/samples/matter/common/src/bridge/bridge_manager.h +++ b/samples/matter/common/src/bridge/bridge_manager.h @@ -7,13 +7,23 @@ #pragma once #include "bridge_util.h" -#include "bridged_device.h" #include "bridged_device_data_provider.h" +#include "matter_bridged_device.h" class BridgeManager { public: void Init(); - CHIP_ERROR AddBridgedDevices(BridgedDevice *aDevice, BridgedDeviceDataProvider *aDataProvider); + /** + * @brief Add devices which are supposed to be bridged to the Bridge Manager + * + * @param devices Matter devices to be bridged (the maximum size of this array may depend on the + * application and can be memory constrained) + * @param dataProvider data provider which is going to be bridged with Matter devices + * @param deviceListSize number of Matter devices which are going to be bridged with data provider + * @return CHIP_NO_ERROR in case of success, specific CHIP_ERROR code otherwise + */ + CHIP_ERROR AddBridgedDevices(MatterBridgedDevice *devices[], BridgedDeviceDataProvider *dataProvider, + uint8_t deviceListSize); CHIP_ERROR RemoveBridgedDevice(uint16_t endpoint); static CHIP_ERROR HandleRead(uint16_t index, chip::ClusterId clusterId, const EmberAfAttributeMetadata *attributeMetadata, uint8_t *buffer, @@ -30,31 +40,63 @@ class BridgeManager { } private: - static constexpr uint8_t kMaxBridgedDevices = CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; - static constexpr uint8_t kMaxDataProviders = CONFIG_BRIDGE_MAX_BRIDGED_DEVICES_NUMBER; + struct BridgedDevicePair { + BridgedDevicePair() : mDevice(nullptr), mProvider(nullptr) {} + BridgedDevicePair(MatterBridgedDevice *device, BridgedDeviceDataProvider *dataProvider) + : mDevice(device), mProvider(dataProvider) + { + } - using DevicePtr = chip::Platform::UniquePtr; - using ProviderPtr = chip::Platform::UniquePtr; - struct DevicePair { - DevicePair() : mDevice(nullptr), mProvider(nullptr) {} - DevicePair(DevicePtr device, ProviderPtr provider) - : mDevice(std::move(device)), mProvider(std::move(provider)) + ~BridgedDevicePair() { + chip::Platform::Delete(mDevice); + chip::Platform::Delete(mProvider); + mDevice = nullptr; + mProvider = nullptr; + } + + /* Disable copy semantics and implement move semantics. */ + BridgedDevicePair(const BridgedDevicePair &other) = delete; + BridgedDevicePair &operator=(const BridgedDevicePair &other) = delete; + + BridgedDevicePair(BridgedDevicePair &&other) + { + mDevice = other.mDevice; + mProvider = other.mProvider; + + other.mDevice = nullptr; + other.mProvider = nullptr; } - DevicePair &operator=(DevicePair &&other) + + BridgedDevicePair &operator=(BridgedDevicePair &&other) { - mDevice = std::move(other.mDevice); - mProvider = std::move(other.mProvider); + if (this != &other) { + this->~BridgedDevicePair(); + mDevice = other.mDevice; + mProvider = other.mProvider; + other.mDevice = nullptr; + other.mProvider = nullptr; + } return *this; } - operator bool() const { return mDevice && mProvider; } - DevicePair &operator=(const DevicePair &other) = delete; - DevicePtr mDevice; - ProviderPtr mProvider; + + operator bool() const { return mDevice || mProvider; } + bool operator==(const BridgedDevicePair &other) + { + return (mDevice == other.mDevice) || (mProvider == other.mProvider); + } + + MatterBridgedDevice *mDevice; + BridgedDeviceDataProvider *mProvider; }; - using DeviceMap = FiniteMap; - CHIP_ERROR AddDevices(BridgedDevice *aDevice, BridgedDeviceDataProvider *aDataProvider); + static constexpr uint8_t kMaxBridgedDevices = CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; + static constexpr uint8_t kMaxDataProviders = CONFIG_BRIDGE_MAX_BRIDGED_DEVICES_NUMBER; + + using DeviceMap = FiniteMap; + + bool AddSingleDevice(MatterBridgedDevice *device, BridgedDeviceDataProvider *dataProvider); + CHIP_ERROR SafelyRemoveDevice(uint8_t index); DeviceMap mDevicesMap; uint16_t mNumberOfProviders{ 0 }; diff --git a/samples/matter/common/src/bridge/bridge_storage_manager.cpp b/samples/matter/common/src/bridge/bridge_storage_manager.cpp index 1d32b6b87c6..53e002abcdf 100644 --- a/samples/matter/common/src/bridge/bridge_storage_manager.cpp +++ b/samples/matter/common/src/bridge/bridge_storage_manager.cpp @@ -121,7 +121,7 @@ bool BridgeStorageManager::RemoveBridgedDeviceType(uint8_t bridgedDeviceIndex) return PersistentStorage::Instance().Remove(&id); } -bool BridgeStorageManager::StoreBridgedDevice(const BridgedDevice *device, uint8_t index) +bool BridgeStorageManager::StoreBridgedDevice(const MatterBridgedDevice *device, uint8_t index) { if (!device) { return false; diff --git a/samples/matter/common/src/bridge/bridge_storage_manager.h b/samples/matter/common/src/bridge/bridge_storage_manager.h index ad867bc1b85..18fcb027810 100644 --- a/samples/matter/common/src/bridge/bridge_storage_manager.h +++ b/samples/matter/common/src/bridge/bridge_storage_manager.h @@ -6,7 +6,7 @@ #pragma once -#include "bridged_device.h" +#include "matter_bridged_device.h" #include "persistent_storage_util.h" #ifdef CONFIG_BRIDGED_DEVICE_BT @@ -217,7 +217,7 @@ class BridgeStorageManager { * @return true if key has been written successfully * @return false an error occurred */ - bool StoreBridgedDevice(const BridgedDevice *device, uint8_t index); + bool StoreBridgedDevice(const MatterBridgedDevice *device, uint8_t index); #ifdef CONFIG_BRIDGED_DEVICE_BT diff --git a/samples/matter/common/src/bridge/bridged_device.cpp b/samples/matter/common/src/bridge/matter_bridged_device.cpp similarity index 75% rename from samples/matter/common/src/bridge/bridged_device.cpp rename to samples/matter/common/src/bridge/matter_bridged_device.cpp index 7a20cc6d10b..fd236f4dafa 100644 --- a/samples/matter/common/src/bridge/bridged_device.cpp +++ b/samples/matter/common/src/bridge/matter_bridged_device.cpp @@ -4,13 +4,14 @@ * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause */ -#include "bridged_device.h" +#include "matter_bridged_device.h" #include using namespace ::chip; using namespace ::chip::app; -CHIP_ERROR BridgedDevice::CopyAttribute(void *attribute, size_t attributeSize, void *buffer, uint16_t maxBufferSize) +CHIP_ERROR MatterBridgedDevice::CopyAttribute(void *attribute, size_t attributeSize, void *buffer, + uint16_t maxBufferSize) { if (maxBufferSize < attributeSize) { return CHIP_ERROR_INVALID_ARGUMENT; @@ -21,22 +22,23 @@ CHIP_ERROR BridgedDevice::CopyAttribute(void *attribute, size_t attributeSize, v return CHIP_NO_ERROR; } -CHIP_ERROR BridgedDevice::HandleWriteDeviceBasicInformation(chip::ClusterId clusterId, chip::AttributeId attributeId, - void *data, size_t dataSize) +CHIP_ERROR MatterBridgedDevice::HandleWriteDeviceBasicInformation(chip::ClusterId clusterId, + chip::AttributeId attributeId, void *data, + size_t dataSize) { switch (attributeId) { case Clusters::BridgedDeviceBasicInformation::Attributes::Reachable::Id: if (data && dataSize == sizeof(bool)) { - SetIsReachable(*reinterpret_cast(data)); + SetIsReachable(*reinterpret_cast(data)); return CHIP_NO_ERROR; } - default: + default: return CHIP_ERROR_INVALID_ARGUMENT; } } -CHIP_ERROR BridgedDevice::HandleReadBridgedDeviceBasicInformation(AttributeId attributeId, uint8_t *buffer, - uint16_t maxReadLength) +CHIP_ERROR MatterBridgedDevice::HandleReadBridgedDeviceBasicInformation(AttributeId attributeId, uint8_t *buffer, + uint16_t maxReadLength) { switch (attributeId) { case Clusters::BridgedDeviceBasicInformation::Attributes::Reachable::Id: { @@ -60,7 +62,7 @@ CHIP_ERROR BridgedDevice::HandleReadBridgedDeviceBasicInformation(AttributeId at } } -CHIP_ERROR BridgedDevice::HandleReadDescriptor(AttributeId attributeId, uint8_t *buffer, uint16_t maxReadLength) +CHIP_ERROR MatterBridgedDevice::HandleReadDescriptor(AttributeId attributeId, uint8_t *buffer, uint16_t maxReadLength) { switch (attributeId) { case Clusters::Descriptor::Attributes::ClusterRevision::Id: { diff --git a/samples/matter/common/src/bridge/bridged_device.h b/samples/matter/common/src/bridge/matter_bridged_device.h similarity index 88% rename from samples/matter/common/src/bridge/bridged_device.h rename to samples/matter/common/src/bridge/matter_bridged_device.h index 0fcee5c26cf..120b59b8cb4 100644 --- a/samples/matter/common/src/bridge/bridged_device.h +++ b/samples/matter/common/src/bridge/matter_bridged_device.h @@ -14,20 +14,20 @@ #define DESCRIPTOR_CLUSTER_ATTRIBUTES(descriptorAttrs) \ DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(descriptorAttrs) \ DECLARE_DYNAMIC_ATTRIBUTE(chip::app::Clusters::Descriptor::Attributes::DeviceTypeList::Id, ARRAY, \ - BridgedDevice::kDescriptorAttributeArraySize, 0), /* device list */ \ + MatterBridgedDevice::kDescriptorAttributeArraySize, 0), /* device list */ \ DECLARE_DYNAMIC_ATTRIBUTE(chip::app::Clusters::Descriptor::Attributes::ServerList::Id, ARRAY, \ - BridgedDevice::kDescriptorAttributeArraySize, 0), /* server list */ \ + MatterBridgedDevice::kDescriptorAttributeArraySize, 0), /* server list */ \ DECLARE_DYNAMIC_ATTRIBUTE(chip::app::Clusters::Descriptor::Attributes::ClientList::Id, ARRAY, \ - BridgedDevice::kDescriptorAttributeArraySize, 0), /* client list */ \ + MatterBridgedDevice::kDescriptorAttributeArraySize, 0), /* client list */ \ DECLARE_DYNAMIC_ATTRIBUTE(chip::app::Clusters::Descriptor::Attributes::PartsList::Id, ARRAY, \ - BridgedDevice::kDescriptorAttributeArraySize, 0), /* parts list */ \ + MatterBridgedDevice::kDescriptorAttributeArraySize, 0), /* parts list */ \ DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(); /* Declare Bridged Device Basic Information cluster attributes */ #define BRIDGED_DEVICE_BASIC_INFORMATION_CLUSTER_ATTRIBUTES(bridgedDeviceBasicAttrs) \ DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(bridgedDeviceBasicAttrs) \ DECLARE_DYNAMIC_ATTRIBUTE(chip::app::Clusters::BridgedDeviceBasicInformation::Attributes::NodeLabel::Id, \ - CHAR_STRING, BridgedDevice::kNodeLabelSize, 0), /* NodeLabel */ \ + CHAR_STRING, MatterBridgedDevice::kNodeLabelSize, 0), /* NodeLabel */ \ DECLARE_DYNAMIC_ATTRIBUTE( \ chip::app::Clusters::BridgedDeviceBasicInformation::Attributes::Reachable::Id, BOOLEAN, 1, \ 0), /* Reachable */ \ @@ -36,26 +36,27 @@ 0), /* feature map */ \ DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(); -class BridgedDevice { +class MatterBridgedDevice { public: enum DeviceType : uint16_t { BridgedNode = 0x0013, OnOffLight = 0x0100, TemperatureSensor = 0x0302, HumiditySensor = 0x0307, + EnvironmentalSensor = 0x123 }; static constexpr uint8_t kDefaultDynamicEndpointVersion = 1; static constexpr uint8_t kNodeLabelSize = 32; static constexpr uint8_t kDescriptorAttributeArraySize = 254; - explicit BridgedDevice(const char *nodeLabel) + explicit MatterBridgedDevice(const char *nodeLabel) { if (nodeLabel) { memcpy(mNodeLabel, nodeLabel, strlen(nodeLabel)); } } - virtual ~BridgedDevice() { chip::Platform::MemoryFree(mDataVersion); } + virtual ~MatterBridgedDevice() { chip::Platform::MemoryFree(mDataVersion); } void Init(chip::EndpointId endpoint) { mEndpointId = endpoint; } chip::EndpointId GetEndpointId() const { return mEndpointId; } diff --git a/samples/matter/common/src/bridge/util/bridge_util.h b/samples/matter/common/src/bridge/util/bridge_util.h index fa00182c8cb..f08972b301f 100644 --- a/samples/matter/common/src/bridge/util/bridge_util.h +++ b/samples/matter/common/src/bridge/util/bridge_util.h @@ -6,7 +6,9 @@ #pragma once -#include "bridged_device.h" +#include "matter_bridged_device.h" + +#include #include #include @@ -14,10 +16,10 @@ /* DeviceFactory template container allows to instantiate a map which - binds supported BridgedDevice::DeviceType types with corresponding + binds supported MatterBridgedDevice::DeviceType types with corresponding creation method (e.g. constructor invocation). DeviceFactory can only be constructed by passing a user-defined initialized list with - pairs. + pairs. Then, Create() method can be used to obtain an instance of demanded device type with all passed arguments forwarded to the underlying ConcreteDeviceCreator. @@ -25,9 +27,9 @@ template class DeviceFactory { public: using ConcreteDeviceCreator = std::function; - using CreationMap = std::map; + using CreationMap = std::map; - DeviceFactory(std::initializer_list> init) + DeviceFactory(std::initializer_list> init) { for (auto &pair : init) { mCreationMap.insert(pair); @@ -41,7 +43,7 @@ template class DeviceFactory { DeviceFactory &operator=(DeviceFactory &&) = delete; ~DeviceFactory() = default; - T *Create(BridgedDevice::DeviceType deviceType, Args... params) + T *Create(MatterBridgedDevice::DeviceType deviceType, Args... params) { if (mCreationMap.find(deviceType) == mCreationMap.end()) { return nullptr; @@ -54,73 +56,94 @@ template class DeviceFactory { }; /* - FiniteMap template container allows to wrap mappings between uint16_t type key and T type value. + FiniteMap template container allows to wrap mappings between uint16_t-type key and T-type value. The size or the container is predefined at compilation time, therefore the maximum number of stored elements must be well known. FiniteMap owns inserted values, meaning that once the user inserts a value it will not longer be valid outside of the container, i.e. FiniteMap cannot return stored object by copy. FiniteMap's API offers basic operations like: - * inserting a new value under provided key (Insert) - * erasing existing item under given key (Erase) - * retrieving value stored under provided key ([] operator) + * inserting new values under provided key (Insert) + * erasing existing item under given key (Erase) - this operation takes care about the duplicated items, + meaning that pointer under given key is released only if the same address is not present elsewhere in the map + * retrieving values stored under provided key ([] operator) * checking if the map contains a non-null value under given key (Contains) + * retrieving a number of free slots available in the map (FreeSlots) * iterating though stored item via publicly available mMap member - Prerequisites: - * T must have =operator(&&) and bool()operator implemented + Prerequisites: + * T must have move semantics and bool()/==operators implemented */ template struct FiniteMap { - struct Pair { - uint16_t key; + static constexpr uint16_t kInvalidKey{ N + 1 }; + struct Item { + /* Initialize with invalid key (0 is a valid key) */ + uint16_t key{ kInvalidKey }; T value; }; - void Insert(uint16_t key, T &&value) + bool Insert(uint16_t key, T &&value) { if (Contains(key)) { /* The key with sane value already exists in the map, return prematurely. */ - return; + return false; } else if (mElementsCount < N) { mMap[key].key = key; mMap[key].value = std::move(value); mElementsCount++; } + return true; } bool Erase(uint16_t key) { const auto &it = std::find_if(std::begin(mMap), std::end(mMap), - [key](const Pair &pair) { return pair.key == key; }); + [key](const Item &item) { return item.key == key; }); if (it != std::end(mMap) && it->value) { - /* Invalidate the value but leave the key unchanged */ - it->value = T{}; + it->value.~T(); + it->key = kInvalidKey; mElementsCount--; return true; } return false; } - /* TODO: refactor to return a reference (Constains() check will be always needed) */ - const T *operator[](uint16_t key) const + /* Always use Constains() before using operator[]. */ + T &operator[](uint16_t key) { + static T dummyObject; for (auto &it : mMap) { if (key == it.key) - return &(it.value); + return it.value; } - return nullptr; + return dummyObject; } bool Contains(uint16_t key) { - const T *stored = (*this)[key]; - if (stored && *stored) { - /* The key with sane value found in the map */ - return true; + for (auto &it : mMap) { + if (key == it.key) + return true; } return false; } + std::size_t FreeSlots() { return N - mElementsCount; } - bool IsFull() { return mElementsCount >= N; } + bool ValueDuplicated(const T &value, uint16_t *key, uint8_t &numberOfDuplicates) + { + /* Find the first duplicated item and return its key, + so that the application can handle the duplicate by itself. */ + *key = kInvalidKey; + numberOfDuplicates = 0; + for (auto it = std::begin(mMap); it != std::end(mMap); ++it) { + if (it->value == value) { + *(key++) = it->key; + numberOfDuplicates++; + } + } + + /* We must find at least two matching items. */ + return (numberOfDuplicates > 1) ? true : false; + } - Pair mMap[N]; + Item mMap[N]; uint16_t mElementsCount{ 0 }; };