diff --git a/.gitignore b/.gitignore index b37ecce25..fad277818 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,9 @@ docs/doxydocs .development + +# Editor/IDE (all directories) +.idea/ +.vscode/ + +# PlatformIO build +.pio/ diff --git a/examples/BLE_Beacon_Scanner/main.cpp b/examples/BLE_Beacon_Scanner/main.cpp new file mode 100644 index 000000000..0eac2c6cc --- /dev/null +++ b/examples/BLE_Beacon_Scanner/main.cpp @@ -0,0 +1,13 @@ +/* +* main.cpp + * + * Purpose: This file exists ONLY to satisfy PlatformIO's build system. + * The NimBLE Server example normally compiles fine in the Arduino IDE + * without needing a separate main.cpp. PlatformIO requires a main entry + * (setup() and loop()) for any build environment, so this file exists + * purely to make PIO happy. + */ + +#ifdef PLATFORMIO_BUILD + #include "BLE_Beacon_Scanner.ino" +#endif diff --git a/examples/BLE_EddystoneTLM_Beacon/main.cpp b/examples/BLE_EddystoneTLM_Beacon/main.cpp new file mode 100644 index 000000000..90559babe --- /dev/null +++ b/examples/BLE_EddystoneTLM_Beacon/main.cpp @@ -0,0 +1,13 @@ +/* +* main.cpp + * + * Purpose: This file exists ONLY to satisfy PlatformIO's build system. + * The NimBLE Server example normally compiles fine in the Arduino IDE + * without needing a separate main.cpp. PlatformIO requires a main entry + * (setup() and loop()) for any build environment, so this file exists + * purely to make PIO happy. + */ + +#ifdef PLATFORMIO_BUILD + #include "BLE_EddystoneTLM_Beacon.ino" +#endif diff --git a/examples/Bluetooth_5/NimBLE_extended_client/main.cpp b/examples/Bluetooth_5/NimBLE_extended_client/main.cpp new file mode 100644 index 000000000..381624efc --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_client/main.cpp @@ -0,0 +1,13 @@ +/* +* main.cpp + * + * Purpose: This file exists ONLY to satisfy PlatformIO's build system. + * The NimBLE Server example normally compiles fine in the Arduino IDE + * without needing a separate main.cpp. PlatformIO requires a main entry + * (setup() and loop()) for any build environment, so this file exists + * purely to make PIO happy. + */ + +#ifdef PLATFORMIO_BUILD + #include "NimBLE_extended_client.ino" +#endif diff --git a/examples/Bluetooth_5/NimBLE_extended_scan/main.cpp b/examples/Bluetooth_5/NimBLE_extended_scan/main.cpp new file mode 100644 index 000000000..fb4e10f57 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_scan/main.cpp @@ -0,0 +1,13 @@ +/* +* main.cpp + * + * Purpose: This file exists ONLY to satisfy PlatformIO's build system. + * The NimBLE Server example normally compiles fine in the Arduino IDE + * without needing a separate main.cpp. PlatformIO requires a main entry + * (setup() and loop()) for any build environment, so this file exists + * purely to make PIO happy. + */ + +#ifdef PLATFORMIO_BUILD + #include "NimBLE_extended_scan.ino" +#endif diff --git a/examples/Bluetooth_5/NimBLE_extended_server/main.cpp b/examples/Bluetooth_5/NimBLE_extended_server/main.cpp new file mode 100644 index 000000000..74d30acf5 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_server/main.cpp @@ -0,0 +1,13 @@ +/* +* main.cpp + * + * Purpose: This file exists ONLY to satisfy PlatformIO's build system. + * The NimBLE Server example normally compiles fine in the Arduino IDE + * without needing a separate main.cpp. PlatformIO requires a main entry + * (setup() and loop()) for any build environment, so this file exists + * purely to make PIO happy. + */ + +#ifdef PLATFORMIO_BUILD + #include "NimBLE_extended_server.ino" +#endif diff --git a/examples/Bluetooth_5/NimBLE_multi_advertiser/main.cpp b/examples/Bluetooth_5/NimBLE_multi_advertiser/main.cpp new file mode 100644 index 000000000..a169abc5e --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_multi_advertiser/main.cpp @@ -0,0 +1,13 @@ +/* +* main.cpp + * + * Purpose: This file exists ONLY to satisfy PlatformIO's build system. + * The NimBLE Server example normally compiles fine in the Arduino IDE + * without needing a separate main.cpp. PlatformIO requires a main entry + * (setup() and loop()) for any build environment, so this file exists + * purely to make PIO happy. + */ + +#ifdef PLATFORMIO_BUILD + #include "NimBLE_multi_advertiser.ino" +#endif diff --git a/examples/L2CAP/L2CAP_Client/main.cpp b/examples/L2CAP/L2CAP_Client/main.cpp new file mode 100644 index 000000000..1d5534a8c --- /dev/null +++ b/examples/L2CAP/L2CAP_Client/main.cpp @@ -0,0 +1,13 @@ +/* +* main.cpp + * + * Purpose: This file exists ONLY to satisfy PlatformIO's build system. + * The NimBLE Server example normally compiles fine in the Arduino IDE + * without needing a separate main.cpp. PlatformIO requires a main entry + * (setup() and loop()) for any build environment, so this file exists + * purely to make PIO happy. + */ + +#ifdef PLATFORMIO_BUILD + #include "L2CAP_Client.ino" +#endif diff --git a/examples/L2CAP/L2CAP_Server/main.cpp b/examples/L2CAP/L2CAP_Server/main.cpp new file mode 100644 index 000000000..7c8670243 --- /dev/null +++ b/examples/L2CAP/L2CAP_Server/main.cpp @@ -0,0 +1,13 @@ +/* +* main.cpp + * + * Purpose: This file exists ONLY to satisfy PlatformIO's build system. + * The NimBLE Server example normally compiles fine in the Arduino IDE + * without needing a separate main.cpp. PlatformIO requires a main entry + * (setup() and loop()) for any build environment, so this file exists + * purely to make PIO happy. + */ + +#ifdef PLATFORMIO_BUILD + #include "L2CAP_Server.ino" +#endif diff --git a/examples/NimBLE_Async_Client/main.cpp b/examples/NimBLE_Async_Client/main.cpp new file mode 100644 index 000000000..cb9a1eea6 --- /dev/null +++ b/examples/NimBLE_Async_Client/main.cpp @@ -0,0 +1,13 @@ +/* +* main.cpp + * + * Purpose: This file exists ONLY to satisfy PlatformIO's build system. + * The NimBLE Server example normally compiles fine in the Arduino IDE + * without needing a separate main.cpp. PlatformIO requires a main entry + * (setup() and loop()) for any build environment, so this file exists + * purely to make PIO happy. + */ + +#ifdef PLATFORMIO_BUILD + #include "NimBLE_Async_Client.ino" +#endif diff --git a/examples/NimBLE_Client/main.cpp b/examples/NimBLE_Client/main.cpp new file mode 100644 index 000000000..1535c5f3c --- /dev/null +++ b/examples/NimBLE_Client/main.cpp @@ -0,0 +1,13 @@ +/* +* main.cpp + * + * Purpose: This file exists ONLY to satisfy PlatformIO's build system. + * The NimBLE Server example normally compiles fine in the Arduino IDE + * without needing a separate main.cpp. PlatformIO requires a main entry + * (setup() and loop()) for any build environment, so this file exists + * purely to make PIO happy. + */ + +#ifdef PLATFORMIO_BUILD + #include "NimBLE_Client.ino" +#endif diff --git a/examples/NimBLE_Scan_Continuous/main.cpp b/examples/NimBLE_Scan_Continuous/main.cpp new file mode 100644 index 000000000..07edd3314 --- /dev/null +++ b/examples/NimBLE_Scan_Continuous/main.cpp @@ -0,0 +1,13 @@ +/* +* main.cpp + * + * Purpose: This file exists ONLY to satisfy PlatformIO's build system. + * The NimBLE Server example normally compiles fine in the Arduino IDE + * without needing a separate main.cpp. PlatformIO requires a main entry + * (setup() and loop()) for any build environment, so this file exists + * purely to make PIO happy. + */ + +#ifdef PLATFORMIO_BUILD + #include "NimBLE_Scan_Continuous.ino" +#endif diff --git a/examples/NimBLE_Scan_Whitelist/main.cpp b/examples/NimBLE_Scan_Whitelist/main.cpp new file mode 100644 index 000000000..077dcc683 --- /dev/null +++ b/examples/NimBLE_Scan_Whitelist/main.cpp @@ -0,0 +1,13 @@ +/* +* main.cpp + * + * Purpose: This file exists ONLY to satisfy PlatformIO's build system. + * The NimBLE Server example normally compiles fine in the Arduino IDE + * without needing a separate main.cpp. PlatformIO requires a main entry + * (setup() and loop()) for any build environment, so this file exists + * purely to make PIO happy. + */ + +#ifdef PLATFORMIO_BUILD + #include "NimBLE_Scan_whitelist.ino" +#endif diff --git a/examples/NimBLE_Secure_Client/main.cpp b/examples/NimBLE_Secure_Client/main.cpp new file mode 100644 index 000000000..e735b13dd --- /dev/null +++ b/examples/NimBLE_Secure_Client/main.cpp @@ -0,0 +1,13 @@ +/* +* main.cpp + * + * Purpose: This file exists ONLY to satisfy PlatformIO's build system. + * The NimBLE Server example normally compiles fine in the Arduino IDE + * without needing a separate main.cpp. PlatformIO requires a main entry + * (setup() and loop()) for any build environment, so this file exists + * purely to make PIO happy. + */ + +#ifdef PLATFORMIO_BUILD + #include "NimBLE_Secure_Client.ino" +#endif diff --git a/examples/NimBLE_Secure_Server/main.cpp b/examples/NimBLE_Secure_Server/main.cpp new file mode 100644 index 000000000..223f2d7ad --- /dev/null +++ b/examples/NimBLE_Secure_Server/main.cpp @@ -0,0 +1,13 @@ +/* +* main.cpp + * + * Purpose: This file exists ONLY to satisfy PlatformIO's build system. + * The NimBLE Server example normally compiles fine in the Arduino IDE + * without needing a separate main.cpp. PlatformIO requires a main entry + * (setup() and loop()) for any build environment, so this file exists + * purely to make PIO happy. + */ + +#ifdef PLATFORMIO_BUILD + #include "NimBLE_Secure_Server.ino" +#endif diff --git a/examples/NimBLE_Server/NimBLE_Server.ino b/examples/NimBLE_Server/NimBLE_Server.ino index 9d2802460..9c128e3ef 100644 --- a/examples/NimBLE_Server/NimBLE_Server.ino +++ b/examples/NimBLE_Server/NimBLE_Server.ino @@ -11,6 +11,8 @@ #include #include +#include "NimBLE2905.h" + static NimBLEServer* pServer; /** None of these are required as they will be handled by the library with defaults. ** @@ -123,7 +125,9 @@ class DescriptorCallbacks : public NimBLEDescriptorCallbacks { void setup(void) { Serial.begin(115200); + delay(500); Serial.printf("Starting NimBLE Server\n"); + delay(500); /** Initialize NimBLE and set the device name */ NimBLEDevice::init("NimBLE"); @@ -186,9 +190,70 @@ void setup(void) { pC01Ddsc->setValue("Send it back!"); pC01Ddsc->setCallbacks(&dscCallbacks); + NimBLEService* pCafeService = pServer->createService("CAFE"); + + NimBLECharacteristic* pBurgerIngredientsCharacteristic = + pCafeService->createCharacteristic("FEED", NIMBLE_PROPERTY::READ); + + NimBLE2904* pFormatName = pBurgerIngredientsCharacteristic->create2904(); + pFormatName->setFormat(0x19); + pFormatName->setUnit(0x2700); + + NimBLE2904* pFormatFatPercent = pBurgerIngredientsCharacteristic->create2904(); + pFormatFatPercent->setFormat(0x04); + pFormatFatPercent->setUnit(0x27AD); + + NimBLE2904* pFormatWeight = pBurgerIngredientsCharacteristic->create2904(); + pFormatWeight->setFormat(0x06); + pFormatWeight->setUnit(0x276F); + + /** + * 2905 “Aggregate Format” descriptor is a special case. When create2905() is called, + * it creates an instance of NimBLE2905 with the correct properties and size. + * We must then explicitly add the constituent 2904 descriptors that define the + * aggregate format (e.g., name, fat percent, weight) using add2904Descriptor(). + * This ensures the aggregate descriptor correctly references all its component descriptors. + */ + NimBLE2905* pFormatAggregate = pBurgerIngredientsCharacteristic->create2905(); + pFormatAggregate->add2904Descriptor(pFormatName); + pFormatAggregate->add2904Descriptor(pFormatFatPercent); + pFormatAggregate->add2904Descriptor(pFormatWeight); + + + const char* name = "Ground Beef Patty"; + const size_t nameLength = strlen(name); + uint8_t fatPercentage = 20; + uint16_t weightGrams = 227; + uint8_t rawValue[nameLength + 1 + sizeof(fatPercentage) + sizeof(weightGrams)]; + + size_t offset = 0; + // Copy name + memcpy(rawValue + offset, name, nameLength); + offset += nameLength; + + // Null terminator + rawValue[offset++] = 0; + + // Fat percentage (1 byte) + memcpy(rawValue + offset, &fatPercentage, sizeof(fatPercentage)); + offset += sizeof(fatPercentage); + // Weight (2 bytes) + memcpy(rawValue + offset, &weightGrams, sizeof(weightGrams)); + offset += sizeof(weightGrams); + pBurgerIngredientsCharacteristic->setValue(rawValue, offset); + + const char* description = "UTF-8 Name (null-terminated) + uint8 Fat % + uint16 Weight (grams)"; + NimBLEDescriptor* pUserDesc = pBurgerIngredientsCharacteristic->createDescriptor( + BLEUUID((uint16_t)0x2901), + NIMBLE_PROPERTY::READ, + strlen(description) + ); + pUserDesc->setValue(description); + /** Start the services when finished creating all Characteristics and Descriptors */ pDeadService->start(); pBaadService->start(); + pCafeService->start(); /** Create an advertising instance and add the services to the advertised data */ NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); diff --git a/examples/NimBLE_Server/main.cpp b/examples/NimBLE_Server/main.cpp new file mode 100644 index 000000000..a66c0725a --- /dev/null +++ b/examples/NimBLE_Server/main.cpp @@ -0,0 +1,13 @@ +/* +* main.cpp + * + * Purpose: This file exists ONLY to satisfy PlatformIO's build system. + * The NimBLE Server example normally compiles fine in the Arduino IDE + * without needing a separate main.cpp. PlatformIO requires a main entry + * (setup() and loop()) for any build environment, so this file exists + * purely to make PIO happy. + */ + +#ifdef PLATFORMIO_BUILD + #include "NimBLE_Server.ino" +#endif diff --git a/examples/NimBLE_Server_Whitelist/main.cpp b/examples/NimBLE_Server_Whitelist/main.cpp new file mode 100644 index 000000000..1cf083367 --- /dev/null +++ b/examples/NimBLE_Server_Whitelist/main.cpp @@ -0,0 +1,13 @@ +/* +* main.cpp + * + * Purpose: This file exists ONLY to satisfy PlatformIO's build system. + * The NimBLE Server example normally compiles fine in the Arduino IDE + * without needing a separate main.cpp. PlatformIO requires a main entry + * (setup() and loop()) for any build environment, so this file exists + * purely to make PIO happy. + */ + +#ifdef PLATFORMIO_BUILD + #include "NimBLE_Server_Whitelist.ino" +#endif diff --git a/examples/NimBLE_active_passive_scan/main.cpp b/examples/NimBLE_active_passive_scan/main.cpp new file mode 100644 index 000000000..822c5942c --- /dev/null +++ b/examples/NimBLE_active_passive_scan/main.cpp @@ -0,0 +1,13 @@ +/* +* main.cpp + * + * Purpose: This file exists ONLY to satisfy PlatformIO's build system. + * The NimBLE Server example normally compiles fine in the Arduino IDE + * without needing a separate main.cpp. PlatformIO requires a main entry + * (setup() and loop()) for any build environment, so this file exists + * purely to make PIO happy. + */ + +#ifdef PLATFORMIO_BUILD + #include "NimBLE_active_passive_scan.ino" +#endif diff --git a/examples/NimBLE_iBeacon/main.cpp b/examples/NimBLE_iBeacon/main.cpp new file mode 100644 index 000000000..5dd90584d --- /dev/null +++ b/examples/NimBLE_iBeacon/main.cpp @@ -0,0 +1,13 @@ +/* +* main.cpp + * + * Purpose: This file exists ONLY to satisfy PlatformIO's build system. + * The NimBLE Server example normally compiles fine in the Arduino IDE + * without needing a separate main.cpp. PlatformIO requires a main entry + * (setup() and loop()) for any build environment, so this file exists + * purely to make PIO happy. + */ + +#ifdef PLATFORMIO_BUILD + #include "NimBLE_iBeacon.ino" +#endif diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 000000000..7aa6e5c52 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,120 @@ +; PlatformIO Project Configuration File for NimBLE-Arduino +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[platformio] +default_envs = example_server + +; Common settings for all environments +[env] +framework = arduino +monitor_speed = 115200 +build_src_filter = +<*> + +build_flags = + -DCORE_DEBUG_LEVEL=0 + -DPLATFORMIO_BUILD + -DMYNEWT_VAL_BLE_MAX_CONNECTIONS=3 + -DMYNEWT_VAL_BLE_HS_LOG_LVL=5 + -DMYNEWT_VAL_NIMBLE_CPP_LOG_LEVEL=5 + -DMYNEWT_VAL_NIMBLE_CPP_DEBUG_ASSERT_ENABLED=1 + -DMYNEWT_VAL_NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT + -DMYNEWT_VAL_NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT + -DMYNEWT_VAL_NIMBLE_CPP_ENABLE_ADVERTISEMENT_TYPE_TEXT + +; ESP32 Development Board +[env:esp32dev] +platform = espressif32 +board = esp32dev +monitor_filters = esp32_exception_decoder + +; ESP32-S3 +[env:um_feathers3] +platform = espressif32@6.10.0 +board = um_feathers3 +board_build.mcu = esp32s3 +board_build.f_cpu = 240000000L + +[env:esp32-s3-devkitc-1] +platform = espressif32 +board = esp32-s3-devkitc-1 +monitor_filters = esp32_exception_decoder + +; ESP32-C3 +[env:esp32-c3-devkitm-1] +platform = espressif32 +board = esp32-c3-devkitm-1 +monitor_filters = esp32_exception_decoder + +; ESP32-C6 +[env:esp32-c6-devkitc-1] +platform = espressif32 +board = esp32-c6-devkitc-1 +monitor_filters = esp32_exception_decoder + +[env:base] +extends = + env + env:um_feathers3 + +; ========== Example Builds ========== + +; NimBLE Client Example +[env:example_client] +extends = env:base +build_src_filter = + ${env:base.build_src_filter} + + + +; BLE Beacon Scanner Example +[env:example_beacon_scanner] +extends = env:base +build_src_filter = + ${env:base.build_src_filter} + + + +; NimBLE Extended Server (Bluetooth 5) Example +[env:example_extended_server] +extends = env:base +build_src_filter = + ${env:base.build_src_filter} + + + +; NimBLE Secure Server Example +[env:example_secure_server] +extends = env:base +build_src_filter = + ${env:base.build_src_filter} + + + +; L2CAP Server Example +[env:example_l2cap_server] +extends = env:base +build_src_filter = + ${env:base.build_src_filter} + + + +; iBeacon Example +[env:example_ibeacon] +extends = env:base +build_src_filter = + ${env:base.build_src_filter} + + + +; NimBLE Server Example +[env:example_server] +extends = env:base +build_src_filter = + ${env:base.build_src_filter} + +<../examples/NimBLE_Server/> +build_flags = + ${env:base.build_flags} + -DMYNEWT_VAL_BLE_ROLE_CENTRAL=0 + -DMYNEWT_VAL_BLE_ROLE_OBSERVER=0 +; -DMYNEWT_VAL_BLE_ROLE_BROADCASTER=0 diff --git a/src/NimBLE2905.cpp b/src/NimBLE2905.cpp new file mode 100644 index 000000000..d88c02106 --- /dev/null +++ b/src/NimBLE2905.cpp @@ -0,0 +1,82 @@ +/* + * Copyright 2020-2025 Ryan Powell and + * esp-nimble-cpp, NimBLE-Arduino contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "NimBLE2905.h" +#include "NimBLELog.h" + +#include "NimBLECharacteristic.h" +#if CONFIG_BT_NIMBLE_ENABLED && MYNEWT_VAL(BLE_ROLE_PERIPHERAL) + +// Define default if not already defined +#ifndef NIMBLE_MAX_AGGREGATE_FORMAT_DESCRIPTORS + #define NIMBLE_MAX_AGGREGATE_FORMAT_DESCRIPTORS 5 // Default value +#else + // Ensure the value is within a valid range + #if NIMBLE_MAX_AGGREGATE_FORMAT_DESCRIPTORS < 1 || NIMBLE_MAX_AGGREGATE_FORMAT_DESCRIPTORS > 255 + #error "NIMBLE_MAX_AGGREGATE_FORMAT_DESCRIPTORS must be between 1 and 128" + #endif +#endif + +static const char* LOG_TAG = "NimBLE2905"; + +NimBLE2905::NimBLE2905(NimBLECharacteristic* pChr) + : NimBLEDescriptor(NimBLEUUID(static_cast(0x2905)), BLE_GATT_CHR_F_READ, NIMBLE_MAX_AGGREGATE_FORMAT_DESCRIPTORS * sizeof(uint16_t), pChr) { +} // NimBLE2905 + +void NimBLE2905::initValue() { + const size_t count = m_vAggregatedDescriptors.size(); + uint16_t aggregatedHandles[count]; + + for (size_t i = 0; i < count; ++i) { + auto* desc = m_vAggregatedDescriptors[i]; + uint16_t handle = desc->getHandle(); + + if (handle == 0) { + NIMBLE_LOGE(LOG_TAG, "Failed to initialize value: presentation format descriptor handle is not initialized"); + return; + } + + aggregatedHandles[i] = desc->getHandle(); + } // initValue + + setValue(reinterpret_cast(aggregatedHandles), sizeof(aggregatedHandles)); + + // Presentation formats no longer needed, let's free some memory + m_vAggregatedDescriptors.clear(); + m_vAggregatedDescriptors.shrink_to_fit(); +} // initValue + + +/** + * @brief Add presentation format descriptor. + * @param [in] presentationFormat The 2904 descriptor to aggregate. + */ +void NimBLE2905::add2904Descriptor(const NimBLE2904* presentationFormat) { + if (presentationFormat == nullptr) { + NIMBLE_LOGE(LOG_TAG, "Failed to add presentation format descriptor: nullptr"); + return; + } + + if (m_vAggregatedDescriptors.size() < NIMBLE_MAX_AGGREGATE_FORMAT_DESCRIPTORS) { + m_vAggregatedDescriptors.push_back(presentationFormat); + } else { + NIMBLE_LOGE(LOG_TAG, "Failed to add presentation format descriptor: maximum capacity reached"); + } +} // add2904Descriptor + + +#endif // CONFIG_BT_NIMBLE_ENABLED && MYNEWT_VAL(BLE_ROLE_PERIPHERAL) diff --git a/src/NimBLE2905.h b/src/NimBLE2905.h new file mode 100644 index 000000000..9c1f90762 --- /dev/null +++ b/src/NimBLE2905.h @@ -0,0 +1,56 @@ +/* + * Copyright 2020-2025 Ryan Powell and + * esp-nimble-cpp, NimBLE-Arduino contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NIMBLE_CPP_2905_H_ +#define NIMBLE_CPP_2905_H_ + +#include "syscfg/syscfg.h" +#if CONFIG_BT_NIMBLE_ENABLED && MYNEWT_VAL(BLE_ROLE_PERIPHERAL) + +# include "NimBLEDescriptor.h" + +/** + * @brief Characteristic Aggregate Format descriptor (UUID: 0x2905). + * + * @details Contains an ordered list of handles referencing 0x2904 Presentation Format + * descriptors that define the parent characteristic’s value. + */ +class NimBLE2905 : public NimBLEDescriptor { + public: + NimBLE2905(NimBLECharacteristic* pChr = nullptr); + + void add2904Descriptor(const NimBLE2904* presentationFormat); + private: + + /** + * @brief Descriptor for the Characteristic Aggregate Format (UUID: 0x2905). + * + * @details Contains an ordered list of handles referencing the 0x2904 + * Presentation Format descriptors that define the parent characteristic's value. + * + * @see NimBLEServer::start() + */ + void initValue(); + + friend class NimBLECharacteristic; + friend class NimBLEServer; + + std::vector m_vAggregatedDescriptors; +}; // NimBLE2904 + +#endif // CONFIG_BT_NIMBLE_ENABLED && MYNEWT_VAL(BLE_ROLE_PERIPHERAL) +#endif // NIMBLE_CPP_2904_H_ diff --git a/src/NimBLECharacteristic.cpp b/src/NimBLECharacteristic.cpp index 1ffa2bd97..2fb2d4fee 100644 --- a/src/NimBLECharacteristic.cpp +++ b/src/NimBLECharacteristic.cpp @@ -26,6 +26,7 @@ #endif # include "NimBLE2904.h" +# include "NimBLE2905.h" # include "NimBLEDevice.h" # include "NimBLELog.h" @@ -86,7 +87,10 @@ NimBLEDescriptor* NimBLECharacteristic::createDescriptor(const NimBLEUUID& uuid, if (uuid == NimBLEUUID(static_cast(0x2904))) { NIMBLE_LOGW(LOG_TAG, "0x2904 descriptor should be created with create2904()"); pDescriptor = create2904(); - } else { + } else if (uuid == NimBLEUUID(static_cast(0x2905))) { + NIMBLE_LOGW(LOG_TAG, "0x2905 descriptor should be created with create2905()"); + pDescriptor = create2905(); + } else{ pDescriptor = new NimBLEDescriptor(uuid, properties, maxLen, this); } @@ -104,6 +108,16 @@ NimBLE2904* NimBLECharacteristic::create2904() { return pDescriptor; } // create2904 +/** + * @brief Create a Characteristic Aggregate Format Descriptor for this characteristic. + * @return A pointer to a NimBLE2905 descriptor. + */ +NimBLE2905* NimBLECharacteristic::create2905() { + NimBLE2905* pDescriptor = new NimBLE2905(this); + addDescriptor(pDescriptor); + return pDescriptor; +} // create2905 + /** * @brief Add a descriptor to the characteristic. * @param [in] pDescriptor A pointer to the descriptor to add. diff --git a/src/NimBLECharacteristic.h b/src/NimBLECharacteristic.h index 789d21d7f..e26434f7c 100644 --- a/src/NimBLECharacteristic.h +++ b/src/NimBLECharacteristic.h @@ -26,6 +26,7 @@ class NimBLEService; class NimBLECharacteristic; class NimBLEDescriptor; class NimBLE2904; +class NimBLE2905; # include "NimBLELocalValueAttribute.h" @@ -68,6 +69,8 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute { uint32_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE, uint16_t maxLen = BLE_ATT_ATTR_MAX_LEN); NimBLE2904* create2904(); + NimBLE2905* create2905(); + NimBLEDescriptor* getDescriptorByUUID(const char* uuid) const; NimBLEDescriptor* getDescriptorByUUID(const NimBLEUUID& uuid) const; NimBLEDescriptor* getDescriptorByHandle(uint16_t handle) const; diff --git a/src/NimBLEServer.cpp b/src/NimBLEServer.cpp index 5e4f381b5..b8f1f19d0 100644 --- a/src/NimBLEServer.cpp +++ b/src/NimBLEServer.cpp @@ -18,6 +18,7 @@ #include "NimBLEServer.h" #if CONFIG_BT_NIMBLE_ENABLED && MYNEWT_VAL(BLE_ROLE_PERIPHERAL) +# include "NimBLE2905.h" # include "NimBLEDevice.h" # include "NimBLELog.h" @@ -207,10 +208,18 @@ void NimBLEServer::start() { } } - // Set the descriptor handles now as the stack does not set these when the service is started + // Phase 1: Set the descriptor handles (including those with duplicate UUIDs) now, + // as the stack does not initialize them when the service starts. for (const auto& chr : svc->m_vChars) { for (auto& desc : chr->m_vDescriptors) { - ble_gatts_find_dsc(svc->getUUID().getBase(), chr->getUUID().getBase(), desc->getUUID().getBase(), &desc->m_handle); + ble_gatts_find_dsc(svc->getUUID().getBase(), chr->getUUID().getBase(), desc->getUUID().getBase(), desc, &desc->m_handle); + } + + // Phase 2: Once all handles have been assigned, proceed to initialize the Aggregate Format Descriptor (0295) values. + for (auto& desc : chr->m_vDescriptors) { + if (desc->getUUID() == NimBLEUUID(static_cast(0x2905))) { + static_cast(desc)->initValue(); + } } } } diff --git a/src/nimble/nimble/host/include/host/ble_gatt.h b/src/nimble/nimble/host/include/host/ble_gatt.h index 133b81b8a..a672eaa36 100644 --- a/src/nimble/nimble/host/include/host/ble_gatt.h +++ b/src/nimble/nimble/host/include/host/ble_gatt.h @@ -1360,7 +1360,10 @@ int ble_gatts_find_chr(const ble_uuid_t *svc_uuid, const ble_uuid_t *chr_uuid, * * @param svc_uuid The UUID of the grandparent service. * @param chr_uuid The UUID of the parent characteristic. - * @param dsc_uuid The UUID of the descriptor ro look up. + * @param dsc_uuid The UUID of the descriptor to look up. + * @param dsc_arg Pointer to a NimBLEDescriptor instance. + * Used to identify a specific descriptor when + * multiple descriptors share the same UUID. * @param out_dsc_handle On success, populated with the handle * of the descriptor attribute. Pass null if * you don't need this value. @@ -1371,7 +1374,7 @@ int ble_gatts_find_chr(const ble_uuid_t *svc_uuid, const ble_uuid_t *chr_uuid, * found. */ int ble_gatts_find_dsc(const ble_uuid_t *svc_uuid, const ble_uuid_t *chr_uuid, - const ble_uuid_t *dsc_uuid, uint16_t *out_dsc_handle); + const ble_uuid_t *dsc_uuid, const void* dsc_arg, uint16_t *out_dsc_handle); /** Type definition for GATT service iteration callback function. */ typedef void (*ble_gatt_svc_foreach_fn)(const struct ble_gatt_svc_def *svc, diff --git a/src/nimble/nimble/host/src/ble_gatts.c b/src/nimble/nimble/host/src/ble_gatts.c index 31f9b00c9..39e2cecda 100644 --- a/src/nimble/nimble/host/src/ble_gatts.c +++ b/src/nimble/nimble/host/src/ble_gatts.c @@ -2740,7 +2740,7 @@ ble_gatts_find_chr(const ble_uuid_t *svc_uuid, const ble_uuid_t *chr_uuid, int ble_gatts_find_dsc(const ble_uuid_t *svc_uuid, const ble_uuid_t *chr_uuid, - const ble_uuid_t *dsc_uuid, uint16_t *out_handle) + const ble_uuid_t *dsc_uuid, const void* dsc_arg, uint16_t *out_handle) { struct ble_gatts_svc_entry *svc_entry; struct ble_att_svr_entry *att_chr; @@ -2772,7 +2772,10 @@ ble_gatts_find_dsc(const ble_uuid_t *svc_uuid, const ble_uuid_t *chr_uuid, return BLE_HS_ENOENT; } - if (ble_uuid_cmp(cur->ha_uuid, dsc_uuid) == 0) { + if (ble_uuid_cmp(cur->ha_uuid, dsc_uuid) == 0 && ( + dsc_arg == NULL || // Skip descriptor pointer comparison if none is provided + (cur->ha_cb_arg != NULL && ((struct ble_gatt_dsc_def*)cur->ha_cb_arg)->arg /* it is actually NimBLEDescriptor* */ == dsc_arg))) + { if (out_handle != NULL) { *out_handle = cur->ha_handle_id; return 0; diff --git a/src/nimconfig.h b/src/nimconfig.h index 5b230f0ab..3b90ba2e9 100644 --- a/src/nimconfig.h +++ b/src/nimconfig.h @@ -4,6 +4,8 @@ /*********************************************** * Arduino User Options * **********************************************/ +/** @brief Uncomment to change the maximum number of aggregated presentation format descriptors; 5 by default. */ +// #define NIMBLE_MAX_AGGREGATE_FORMAT_DESCRIPTORS 5 /** @brief Un-comment to change the number of simultaneous connections (esp controller max is 9) */ // #define MYNEWT_VAL_BLE_MAX_CONNECTIONS 3