diff --git a/CMakeLists.txt b/CMakeLists.txt index 23b1aa5..eb6349f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ idf_component_register( "src/NimBLEDevice.cpp" "src/NimBLEEddystoneTLM.cpp" "src/NimBLEEddystoneURL.cpp" + "src/NimBLEExtAdvertising.cpp" "src/NimBLEHIDDevice.cpp" "src/NimBLERemoteCharacteristic.cpp" "src/NimBLERemoteDescriptor.cpp" diff --git a/docs/Bluetooth 5 features.md b/docs/Bluetooth 5 features.md new file mode 100644 index 0000000..c0d03b5 --- /dev/null +++ b/docs/Bluetooth 5 features.md @@ -0,0 +1,29 @@ +# Bluetooth 5.x features + +## About extended advertising +Extended advertising allows for much more capability and flexibility. + +* Allows for 251 bytes of advertisement data and up to 1650 bytes when chained (configuration dependant) vs 31. + +* New PHY's (physical layers) that allow for faster data rate (2M PHY) or long range/slower data rates (CODED PHY) as well as the original 1M PHY. + +* New periodic advertising, allowing the scanning device to sync with the advertisements of a beacon. This allows for the scanning device to sleep or perform other tasks before the next expected advertisement is sent, preserving cpu cycles and power (To be implemented). +
+ +## Enabling extended advertising +Extended advertising is supported when enabled with the config option `CONFIG_BT_NIMBLE_EXT_ADV` set to a value of 1. This is done in menuconfig under `Component config > Bluetooth > NimBLE options > +Enable extended advertising`. + +When enabled the following will occur: +* `NimBLEScan::start` method will scan on both the 1M PHY and the coded PHY standards automatically. + +* `NimBLEClient::connect` will use the primary PHY the device is listening on, unless specified (see below). + +* `NimBLEClient::setConnectPhy` becomes available to specify the PHY's to connect with (default is all). + +* `NimBLEAdvertising` is no longer available for use and is replaced by `NimBLEExtAdvertising`. `NimBLEDevice::getAdvertising` will now return an instance of `NimBLEExtAdvertising`. + +* `NimBLEAdvertisementData` is no longer available for use and is replaced by `NimBLEExtAdvertisement`. This new class is where everything about the advertisement is configured, including the advertisement intervals and advertisement ended callback. + + + diff --git a/examples/Bluetooth_5/NimBLE_extended_client/CMakeLists.txt b/examples/Bluetooth_5/NimBLE_extended_client/CMakeLists.txt new file mode 100644 index 0000000..f46b44a --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_client/CMakeLists.txt @@ -0,0 +1,7 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(SUPPORTED_TARGETS esp32c3 esp32s3) +project(NimBLE_extended_client) diff --git a/examples/Bluetooth_5/NimBLE_extended_client/Makefile b/examples/Bluetooth_5/NimBLE_extended_client/Makefile new file mode 100644 index 0000000..2e4842d --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_client/Makefile @@ -0,0 +1,3 @@ +PROJECT_NAME := NimBLE_extended_client + +include $(IDF_PATH)/make/project.mk diff --git a/examples/Bluetooth_5/NimBLE_extended_client/main/CMakeLists.txt b/examples/Bluetooth_5/NimBLE_extended_client/main/CMakeLists.txt new file mode 100644 index 0000000..0a5a557 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_client/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "main.cpp") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/Bluetooth_5/NimBLE_extended_client/main/component.mk b/examples/Bluetooth_5/NimBLE_extended_client/main/component.mk new file mode 100644 index 0000000..a98f634 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_client/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/Bluetooth_5/NimBLE_extended_client/main/main.cpp b/examples/Bluetooth_5/NimBLE_extended_client/main/main.cpp new file mode 100644 index 0000000..56881d0 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_client/main/main.cpp @@ -0,0 +1,169 @@ + +/** NimBLE Extended Client Demo: + * + * Demonstrates the Bluetooth 5.x client capabilities. + * + * Created: on April 2 2022 + * Author: H2zero + * +*/ +#include + +extern "C" void app_main(void); + +void scanEndedCB(NimBLEScanResults results); + +#define SERVICE_UUID "ABCD" +#define CHARACTERISTIC_UUID "1234" + +static NimBLEAdvertisedDevice* advDevice; +static bool doConnect = false; +static uint32_t scanTime = 10; /* 0 = scan forever */ + +/* Define the PHY's to use when connecting to peer devices, can be 1, 2, or all 3 (default).*/ +static uint8_t connectPhys = BLE_GAP_LE_PHY_CODED_MASK | BLE_GAP_LE_PHY_1M_MASK /*| BLE_GAP_LE_PHY_2M_MASK */ ; + +/* Define a class to handle the callbacks for client connection events */ +class ClientCallbacks : public NimBLEClientCallbacks { + void onConnect(NimBLEClient* pClient) { + printf("Connected\n"); + }; + + void onDisconnect(NimBLEClient* pClient) { + printf("%s Disconnected - Starting scan\n", pClient->getPeerAddress().toString().c_str()); + NimBLEDevice::getScan()->start(scanTime, scanEndedCB); + }; +}; + + +/* Define a class to handle the callbacks when advertisements are received */ +class AdvertisedDeviceCallbacks: public NimBLEAdvertisedDeviceCallbacks { + + void onResult(NimBLEAdvertisedDevice* advertisedDevice) { + printf("Advertised Device found: %s\n", advertisedDevice->toString().c_str()); + if(advertisedDevice->isAdvertisingService(NimBLEUUID("ABCD"))) + { + printf("Found Our Service\n"); + /* Ready to connect now */ + doConnect = true; + /* Save the device reference in a global for the client to use*/ + advDevice = advertisedDevice; + /* stop scan before connecting */ + NimBLEDevice::getScan()->stop(); + } + }; +}; + + +/* Callback to process the results of the last scan or restart it */ +void scanEndedCB(NimBLEScanResults results){ + printf("Scan Ended\n"); + if (!doConnect) { /* Don't start the scan while connecting */ + NimBLEDevice::getScan()->start(scanTime, scanEndedCB); + } +} + + +/* Handles the provisioning of clients and connects / interfaces with the server */ +bool connectToServer() { + NimBLEClient* pClient = nullptr; + + pClient = NimBLEDevice::createClient(); + pClient->setClientCallbacks(new ClientCallbacks, false); + + /* Set the PHY's to use for this connection. This is a bitmask that represents the PHY's: + * * 0x01 BLE_GAP_LE_PHY_1M_MASK + * * 0x02 BLE_GAP_LE_PHY_2M_MASK + * * 0x04 BLE_GAP_LE_PHY_CODED_MASK + * Combine these with OR ("|"), eg BLE_GAP_LE_PHY_1M_MASK | BLE_GAP_LE_PHY_2M_MASK | BLE_GAP_LE_PHY_CODED_MASK; + */ + pClient->setConnectPhy(connectPhys); + + /** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */ + pClient->setConnectTimeout(10); + + if (!pClient->connect(advDevice)) { + /* Created a client but failed to connect, don't need to keep it as it has no data */ + NimBLEDevice::deleteClient(pClient); + printf("Failed to connect, deleted client\n"); + return false; + } + + printf("Connected to: %s RSSI: %d\n", + pClient->getPeerAddress().toString().c_str(), + pClient->getRssi()); + + /* Now we can read/write/subscribe the charateristics of the services we are interested in */ + NimBLERemoteService* pSvc = nullptr; + NimBLERemoteCharacteristic* pChr = nullptr; + + pSvc = pClient->getService(SERVICE_UUID); + + if (pSvc) { + pChr = pSvc->getCharacteristic(CHARACTERISTIC_UUID); + + if (pChr) { + // Read the value of the characteristic. + if (pChr->canRead()) { + std::string value = pChr->readValue(); + printf("Characteristic value: %s\n", value.c_str()); + } + } + + } else { + printf("ABCD service not found.\n"); + } + + NimBLEDevice::deleteClient(pClient); + printf("Done with this device!\n"); + return true; +} + +void connectTask (void * parameter){ + /* Loop here until we find a device we want to connect to */ + for (;;) { + if (doConnect) { + /* Found a device we want to connect to, do it now */ + if (connectToServer()) { + printf("Success!, scanning for more!\n"); + } else { + printf("Failed to connect, starting scan\n"); + } + + doConnect = false; + NimBLEDevice::getScan()->start(scanTime, scanEndedCB); + } + vTaskDelay(pdMS_TO_TICKS(10)); + } + + vTaskDelete(NULL); +} + +void app_main (void) { + printf("Starting NimBLE Client\n"); + /* Create a task to handle connecting to peers */ + xTaskCreate(connectTask, "connectTask", 5000, NULL, 1, NULL); + + /* Initialize NimBLE, no device name specified as we are not advertising */ + NimBLEDevice::init(""); + NimBLEScan* pScan = NimBLEDevice::getScan(); + + /* create a callback that gets called when advertisers are found */ + pScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks()); + + /* Set scan interval (how often) and window (how long) in milliseconds */ + pScan->setInterval(97); + pScan->setWindow(67); + + /* Active scan will gather scan response data from advertisers + * but will use more energy from both devices + */ + pScan->setActiveScan(true); + + /* Start scanning for advertisers for the scan time specified (in seconds) 0 = forever + * Optional callback for when scanning stops. + */ + pScan->start(scanTime, scanEndedCB); + + printf("Scanning for peripherals\n"); +} diff --git a/examples/Bluetooth_5/NimBLE_extended_server/CMakeLists.txt b/examples/Bluetooth_5/NimBLE_extended_server/CMakeLists.txt new file mode 100644 index 0000000..c58174a --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_server/CMakeLists.txt @@ -0,0 +1,7 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(SUPPORTED_TARGETS esp32c3 esp32s3) +project(NimBLE_extended_server) diff --git a/examples/Bluetooth_5/NimBLE_extended_server/Makefile b/examples/Bluetooth_5/NimBLE_extended_server/Makefile new file mode 100644 index 0000000..a18cf9f --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_server/Makefile @@ -0,0 +1,3 @@ +PROJECT_NAME := NimBLE_extended_server + +include $(IDF_PATH)/make/project.mk diff --git a/examples/Bluetooth_5/NimBLE_extended_server/main/CMakeLists.txt b/examples/Bluetooth_5/NimBLE_extended_server/main/CMakeLists.txt new file mode 100644 index 0000000..0a5a557 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_server/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "main.cpp") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/Bluetooth_5/NimBLE_extended_server/main/component.mk b/examples/Bluetooth_5/NimBLE_extended_server/main/component.mk new file mode 100644 index 0000000..a98f634 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_server/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/Bluetooth_5/NimBLE_extended_server/main/main.cpp b/examples/Bluetooth_5/NimBLE_extended_server/main/main.cpp new file mode 100644 index 0000000..096a281 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_server/main/main.cpp @@ -0,0 +1,139 @@ +/** NimBLE Extended Server Demo: + * + * Demonstrates the Bluetooth 5.x extended advertising capabilities. + * + * This demo will advertise a long data string on the CODED and 1M Phy's and + * starts a server allowing connection over either PHY's. It will advertise for + * 5 seconds then sleep for 20 seconds, if a client connects it will sleep once + * it has disconnected then repeats. + * + * Created: on April 2 2022 + * Author: H2zero + * +*/ + +#include "NimBLEDevice.h" +#include "esp_sleep.h" + +extern "C" void app_main(void); + +#define SERVICE_UUID "ABCD" +#define CHARACTERISTIC_UUID "1234" + +/* Time in milliseconds to advertise */ +static uint32_t advTime = 5000; + +/* Time to sleep between advertisements */ +static uint32_t sleepSeconds = 20; + +/* Primary PHY used for advertising, can be one of BLE_HCI_LE_PHY_1M or BLE_HCI_LE_PHY_CODED */ +static uint8_t primaryPhy = BLE_HCI_LE_PHY_CODED; + +/* Secondary PHY used for advertising and connecting, + * can be one of BLE_HCI_LE_PHY_1M, BLE_HCI_LE_PHY_2M or BLE_HCI_LE_PHY_CODED + */ +static uint8_t secondaryPhy = BLE_HCI_LE_PHY_1M; + + +/* Handler class for server events */ +class ServerCallbacks: public NimBLEServerCallbacks { + void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) { + printf("Client connected: %s\n", NimBLEAddress(desc->peer_ota_addr).toString().c_str()); + }; + + void onDisconnect(NimBLEServer* pServer) { + printf("Client disconnected - sleeping for %u seconds\n", sleepSeconds); + esp_deep_sleep_start(); + }; +}; + +/* Callback class to handle advertising events */ +class advertisingCallbacks: public NimBLEExtAdvertisingCallbacks { + void onStopped(NimBLEExtAdvertising* pAdv, int reason, uint8_t inst_id) { + /* Check the reason advertising stopped, don't sleep if client is connecting */ + printf("Advertising instance %u stopped\n", inst_id); + switch (reason) { + case 0: + printf("Client connecting\n"); + return; + case BLE_HS_ETIMEOUT: + printf("Time expired - sleeping for %u seconds\n", sleepSeconds); + break; + default: + break; + } + + esp_deep_sleep_start(); + } +}; + +void app_main (void) { + NimBLEDevice::init("Extended advertiser"); + + /* Create the server and add the services/characteristics/descriptors */ + NimBLEServer *pServer = NimBLEDevice::createServer(); + pServer->setCallbacks(new ServerCallbacks); + + NimBLEService *pService = pServer->createService(SERVICE_UUID); + NimBLECharacteristic *pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID, + NIMBLE_PROPERTY::READ | + NIMBLE_PROPERTY::WRITE | + NIMBLE_PROPERTY::NOTIFY); + + pCharacteristic->setValue("Hello World"); + + /* Start the services */ + pService->start(); + + /* + * Create an extended advertisement with the instance ID 0 and set the PHY's. + * Multiple instances can be added as long as the instance ID is incremented. + */ + NimBLEExtAdvertisement extAdv(primaryPhy, secondaryPhy); + + /* Set the advertisement as connectable */ + extAdv.setConnectable(true); + + /* As per Bluetooth specification, extended advertising cannot be both scannable and connectable */ + extAdv.setScannable(false); // The default is false, set here for demonstration. + + /* Extended advertising allows for 251 bytes (minus header bytes ~20) in a single advertisement or up to 1650 if chained */ + extAdv.setServiceData(NimBLEUUID(SERVICE_UUID), std::string("Extended Advertising Demo.\r\n" + "Extended advertising allows for " + "251 bytes of data in a single advertisement,\r\n" + "or up to 1650 bytes with chaining.\r\n" + "This example message is 226 bytes long " + "and is using CODED_PHY for long range.")); + + extAdv.setCompleteServices16({NimBLEUUID(SERVICE_UUID)}); + + /* When extended advertising is enabled `NimBLEDevice::getAdvertising` returns a pointer to `NimBLEExtAdvertising */ + NimBLEExtAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); + + /* Set the callbacks for advertising events */ + pAdvertising->setCallbacks(new advertisingCallbacks); + + /* + * NimBLEExtAdvertising::setInstanceData takes the instance ID and + * a reference to a `NimBLEExtAdvertisement` object. This sets the data + * that will be advertised for this instance ID, returns true if successful. + * + * Note: It is safe to create the advertisement as a local variable if setInstanceData + * is called before exiting the code block as the data will be copied. + */ + if (pAdvertising->setInstanceData(0, extAdv)) { + /* + * `NimBLEExtAdvertising::start` takes the advertisement instance ID to start + * and a duration in milliseconds or a max number of advertisements to send (or both). + */ + if (pAdvertising->start(0, advTime)) { + printf("Started advertising\n"); + } else { + printf("Failed to start advertising\n"); + } + } else { + printf("Failed to register advertisment data\n"); + } + + esp_sleep_enable_timer_wakeup(sleepSeconds * 1000000); +} diff --git a/examples/Bluetooth_5/NimBLE_multi_advertiser/CMakeLists.txt b/examples/Bluetooth_5/NimBLE_multi_advertiser/CMakeLists.txt new file mode 100644 index 0000000..7cfce86 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_multi_advertiser/CMakeLists.txt @@ -0,0 +1,7 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(SUPPORTED_TARGETS esp32c3 esp32s3) +project(NimBLE_multi_advertiser) diff --git a/examples/Bluetooth_5/NimBLE_multi_advertiser/Makefile b/examples/Bluetooth_5/NimBLE_multi_advertiser/Makefile new file mode 100644 index 0000000..501edc9 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_multi_advertiser/Makefile @@ -0,0 +1,3 @@ +PROJECT_NAME := NimBLE_multi_advertiser + +include $(IDF_PATH)/make/project.mk diff --git a/examples/Bluetooth_5/NimBLE_multi_advertiser/main/CMakeLists.txt b/examples/Bluetooth_5/NimBLE_multi_advertiser/main/CMakeLists.txt new file mode 100644 index 0000000..0a5a557 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_multi_advertiser/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "main.cpp") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/Bluetooth_5/NimBLE_multi_advertiser/main/component.mk b/examples/Bluetooth_5/NimBLE_multi_advertiser/main/component.mk new file mode 100644 index 0000000..a98f634 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_multi_advertiser/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/Bluetooth_5/NimBLE_multi_advertiser/main/main.cpp b/examples/Bluetooth_5/NimBLE_multi_advertiser/main/main.cpp new file mode 100644 index 0000000..fbbca34 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_multi_advertiser/main/main.cpp @@ -0,0 +1,170 @@ +/** NimBLE Multi Advertiser Demo: + * + * Demonstrates the Bluetooth 5.x extended advertising capabilities. + * + * This demo will advertise 2 advertisements, and extended scannable instance + * and a connectable legacy instance. They will advertise for 5 seconds then + * sleep for 20 seconds. The extended scannable instance will use the scan + * request callback to update it's data when a scan response is requested. + * + * Created: on April 9 2022 + * Author: H2zero + * +*/ + +#include "NimBLEDevice.h" +#include "esp_sleep.h" + +extern "C" void app_main(void); + +#define SERVICE_UUID "ABCD" +#define CHARACTERISTIC_UUID "1234" + +/* Time in milliseconds to advertise */ +static uint32_t advTime = 5000; + +/* Time to sleep between advertisements */ +static uint32_t sleepTime = 20; + +/* Primary PHY used for advertising, can be one of BLE_HCI_LE_PHY_1M or BLE_HCI_LE_PHY_CODED */ +static uint8_t primaryPhy = BLE_HCI_LE_PHY_CODED; + +/* Secondary PHY used for advertising and connecting, + * can be one of BLE_HCI_LE_PHY_1M, BLE_HCI_LE_PHY_2M or BLE_HCI_LE_PHY_CODED + */ +static uint8_t secondaryPhy = BLE_HCI_LE_PHY_1M; + + +/* Handler class for server events */ +class ServerCallbacks: public NimBLEServerCallbacks { + void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) { + printf("Client connected: %s\n", NimBLEAddress(desc->peer_ota_addr).toString().c_str()); + }; + + void onDisconnect(NimBLEServer* pServer) { + printf("Client disconnected\n"); + // if still advertising we won't sleep yet. + if (!pServer->getAdvertising()->isAdvertising()) { + printf("Sleeping for %u seconds\n", sleepTime); + esp_deep_sleep_start(); + } + }; +}; + +/* Callback class to handle advertising events */ +class advCallbacks: public NimBLEExtAdvertisingCallbacks { + void onStopped(NimBLEExtAdvertising* pAdv, int reason, uint8_t inst_id) { + /* Check the reason advertising stopped, don't sleep if client is connecting */ + printf("Advertising instance %u stopped\n", inst_id); + switch (reason) { + case 0: + printf(" client connecting\n"); + return; + case BLE_HS_ETIMEOUT: + printf("Time expired - sleeping for %u seconds\n", sleepTime); + break; + default: + break; + } + + esp_deep_sleep_start(); + } + + bool m_updatedSR = false; + + void onScanRequest(NimBLEExtAdvertising* pAdv, uint8_t inst_id, NimBLEAddress addr) { + printf("Scan request for instance %u\n", inst_id); + // if the data has already been updated we don't need to change it again. + if (!m_updatedSR) { + printf("Updating scan data\n"); + NimBLEExtAdvertisement sr; + sr.setServiceData(NimBLEUUID(SERVICE_UUID), std::string("Hello from scan response!")); + pAdv->setScanResponseData(inst_id, sr); + m_updatedSR = true; + } + } +}; + +void app_main (void) { + NimBLEDevice::init("Multi advertiser"); + + /* Create a server for our legacy advertiser */ + NimBLEServer *pServer = NimBLEDevice::createServer(); + pServer->setCallbacks(new ServerCallbacks); + + NimBLEService *pService = pServer->createService(SERVICE_UUID); + NimBLECharacteristic *pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID, + NIMBLE_PROPERTY::READ | + NIMBLE_PROPERTY::WRITE | + NIMBLE_PROPERTY::NOTIFY); + + pCharacteristic->setValue("Hello World"); + + /* Start the service */ + pService->start(); + + /* Create our multi advertising instances */ + + // extended scannable instance advertising on coded and 1m PHY's. + NimBLEExtAdvertisement extScannable(primaryPhy, secondaryPhy); + + // Legacy advertising as a connectable device. + NimBLEExtAdvertisement legacyConnectable; + + // Optional scan response data. + NimBLEExtAdvertisement legacyScanResponse; + + /* As per Bluetooth specification, extended advertising cannot be both scannable and connectable */ + extScannable.setScannable(true); + extScannable.setConnectable(false); + + /* Set the initial data */ + extScannable.setServiceData(NimBLEUUID(SERVICE_UUID), std::string("Scan me!")); + + /* enable the scan response callback, we will use this to update the data. */ + extScannable.enableScanRequestCallback(true); + + /* Optional custom address for this advertisment. */ + legacyConnectable.setAddress(NimBLEAddress("DE:AD:BE:EF:BA:AD")); + + /* Set the advertising data. */ + legacyConnectable.setName("Legacy"); + legacyConnectable.setCompleteServices16({NimBLEUUID(SERVICE_UUID)}); + + /* Set the legacy and connectable flags. */ + legacyConnectable.setLegacyAdvertising(true); + legacyConnectable.setConnectable(true); + + /* Put some data in the scan response if desired. */ + legacyScanResponse.setServiceData(NimBLEUUID(SERVICE_UUID), "Legacy SR"); + + /* Get the advertising ready */ + NimBLEExtAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); + + /* Set the callbacks to handle advertising events */ + pAdvertising->setCallbacks(new advCallbacks); + + /* Set instance data. + * Up to 5 instances can be used if configured in menuconfig, instance 0 is always available. + * + * We will set the extended scannable data on instance 0 and the legacy data on instance 1. + * Note that the legacy scan response data needs to be set to the same instance (1). + */ + if (pAdvertising->setInstanceData( 0, extScannable ) && + pAdvertising->setInstanceData( 1, legacyConnectable ) && + pAdvertising->setScanResponseData( 1, legacyScanResponse )) { + /* + * `NimBLEExtAdvertising::start` takes the advertisement instance ID to start + * and a duration in milliseconds or a max number of advertisements to send (or both). + */ + if (pAdvertising->start(0, advTime) && pAdvertising->start(1, advTime)) { + printf("Started advertising\n"); + } else { + printf("Failed to start advertising\n"); + } + } else { + printf("Failed to register advertisment data\n"); + } + + esp_sleep_enable_timer_wakeup(sleepTime * 1000000); +} diff --git a/src/NimBLEAdvertisedDevice.cpp b/src/NimBLEAdvertisedDevice.cpp index 01dd75d..29c9532 100644 --- a/src/NimBLEAdvertisedDevice.cpp +++ b/src/NimBLEAdvertisedDevice.cpp @@ -20,6 +20,8 @@ #include "NimBLEUtils.h" #include "NimBLELog.h" +#include + static const char* LOG_TAG = "NimBLEAdvertisedDevice"; @@ -69,7 +71,7 @@ uint8_t NimBLEAdvertisedDevice::getAdvType() { * @return The appearance of the advertised device. */ uint16_t NimBLEAdvertisedDevice::getAppearance() { - uint8_t data_loc = 0; + size_t data_loc = 0; if(findAdvField(BLE_HS_ADV_TYPE_APPEARANCE, 0, &data_loc) > 0) { ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc]; @@ -87,7 +89,7 @@ uint16_t NimBLEAdvertisedDevice::getAppearance() { * @return The advertisement interval in 0.625ms units. */ uint16_t NimBLEAdvertisedDevice::getAdvInterval() { - uint8_t data_loc = 0; + size_t data_loc = 0; if(findAdvField(BLE_HS_ADV_TYPE_ADV_ITVL, 0, &data_loc) > 0) { ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc]; @@ -105,7 +107,7 @@ uint16_t NimBLEAdvertisedDevice::getAdvInterval() { * @return The preferred min connection interval in 1.25ms units. */ uint16_t NimBLEAdvertisedDevice::getMinInterval() { - uint8_t data_loc = 0; + size_t data_loc = 0; if(findAdvField(BLE_HS_ADV_TYPE_SLAVE_ITVL_RANGE, 0, &data_loc) > 0) { ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc]; @@ -123,7 +125,7 @@ uint16_t NimBLEAdvertisedDevice::getMinInterval() { * @return The preferred max connection interval in 1.25ms units. */ uint16_t NimBLEAdvertisedDevice::getMaxInterval() { - uint8_t data_loc = 0; + size_t data_loc = 0; if(findAdvField(BLE_HS_ADV_TYPE_SLAVE_ITVL_RANGE, 0, &data_loc) > 0) { ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc]; @@ -141,7 +143,7 @@ uint16_t NimBLEAdvertisedDevice::getMaxInterval() { * @return The manufacturer data of the advertised device. */ std::string NimBLEAdvertisedDevice::getManufacturerData() { - uint8_t data_loc = 0; + size_t data_loc = 0; if(findAdvField(BLE_HS_ADV_TYPE_MFG_DATA, 0, &data_loc) > 0) { ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc]; @@ -159,7 +161,7 @@ std::string NimBLEAdvertisedDevice::getManufacturerData() { * @return The URI data. */ std::string NimBLEAdvertisedDevice::getURI() { - uint8_t data_loc = 0; + size_t data_loc = 0; if(findAdvField(BLE_HS_ADV_TYPE_URI, 0, &data_loc) > 0) { ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc]; @@ -177,7 +179,7 @@ std::string NimBLEAdvertisedDevice::getURI() { * @return The name of the advertised device. */ std::string NimBLEAdvertisedDevice::getName() { - uint8_t data_loc = 0; + size_t data_loc = 0; if(findAdvField(BLE_HS_ADV_TYPE_COMP_NAME, 0, &data_loc) > 0 || findAdvField(BLE_HS_ADV_TYPE_INCOMP_NAME, 0, &data_loc) > 0) @@ -214,7 +216,7 @@ NimBLEScan* NimBLEAdvertisedDevice::getScan() { * @brief Get the number of target addresses. * @return The number of addresses. */ -size_t NimBLEAdvertisedDevice::getTargetAddressCount() { +uint8_t NimBLEAdvertisedDevice::getTargetAddressCount() { uint8_t count = 0; count = findAdvField(BLE_HS_ADV_TYPE_PUBLIC_TGT_ADDR); @@ -232,7 +234,7 @@ size_t NimBLEAdvertisedDevice::getTargetAddressCount() { NimBLEAddress NimBLEAdvertisedDevice::getTargetAddress(uint8_t index) { ble_hs_adv_field *field = nullptr; uint8_t count = 0; - uint8_t data_loc = 0xFF; + size_t data_loc = ULONG_MAX; index++; count = findAdvField(BLE_HS_ADV_TYPE_PUBLIC_TGT_ADDR, index, &data_loc); @@ -242,7 +244,7 @@ NimBLEAddress NimBLEAdvertisedDevice::getTargetAddress(uint8_t index) { count = findAdvField(BLE_HS_ADV_TYPE_RANDOM_TGT_ADDR, index, &data_loc); } - if(count > 0 && data_loc != 0xFF) { + if(count > 0 && data_loc != ULONG_MAX) { field = (ble_hs_adv_field *)&m_payload[data_loc]; if(field->length < index * BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN) { index -= count - field->length / BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN; @@ -264,9 +266,9 @@ NimBLEAddress NimBLEAdvertisedDevice::getTargetAddress(uint8_t index) { std::string NimBLEAdvertisedDevice::getServiceData(uint8_t index) { ble_hs_adv_field *field = nullptr; uint8_t bytes; - uint8_t data_loc = findServiceData(index, &bytes); + size_t data_loc = findServiceData(index, &bytes); - if(data_loc != 0xFF) { + if(data_loc != ULONG_MAX) { field = (ble_hs_adv_field *)&m_payload[data_loc]; if(field->length > bytes) { return std::string((char*)(field->value + bytes), field->length - bytes - 1); @@ -286,9 +288,9 @@ std::string NimBLEAdvertisedDevice::getServiceData(const NimBLEUUID &uuid) { ble_hs_adv_field *field = nullptr; uint8_t bytes; uint8_t index = 0; - uint8_t data_loc = findServiceData(index, &bytes); + size_t data_loc = findServiceData(index, &bytes); + size_t plSize = m_payload.size() - 2; uint8_t uuidBytes = uuid.bitSize() / 8; - uint8_t plSize = m_payload.size() - 2; while(data_loc < plSize) { field = (ble_hs_adv_field *)&m_payload[data_loc]; @@ -313,9 +315,9 @@ std::string NimBLEAdvertisedDevice::getServiceData(const NimBLEUUID &uuid) { NimBLEUUID NimBLEAdvertisedDevice::getServiceDataUUID(uint8_t index) { ble_hs_adv_field *field = nullptr; uint8_t bytes; - uint8_t data_loc = findServiceData(index, &bytes); + size_t data_loc = findServiceData(index, &bytes); - if(data_loc != 0xFF) { + if(data_loc != ULONG_MAX) { field = (ble_hs_adv_field *)&m_payload[data_loc]; if(field->length >= bytes) { return NimBLEUUID(field->value, bytes, false); @@ -330,10 +332,10 @@ NimBLEUUID NimBLEAdvertisedDevice::getServiceDataUUID(uint8_t index) { * @brief Find the service data at the index. * @param [in] index The index of the service data to find. * @param [in] bytes A pointer to storage for the number of the bytes in the UUID. - * @return The index in the vector where the data is located, 0xFF if not found. + * @return The index in the vector where the data is located, ULONG_MAX if not found. */ -uint8_t NimBLEAdvertisedDevice::findServiceData(uint8_t index, uint8_t *bytes) { - uint8_t data_loc = 0; +size_t NimBLEAdvertisedDevice::findServiceData(uint8_t index, uint8_t *bytes) { + size_t data_loc = 0; uint8_t found = 0; *bytes = 0; @@ -358,7 +360,7 @@ uint8_t NimBLEAdvertisedDevice::findServiceData(uint8_t index, uint8_t *bytes) { return data_loc; } - return 0xFF; + return ULONG_MAX; } @@ -366,7 +368,7 @@ uint8_t NimBLEAdvertisedDevice::findServiceData(uint8_t index, uint8_t *bytes) { * @brief Get the count of advertised service data UUIDS * @return The number of service data UUIDS in the vector. */ -size_t NimBLEAdvertisedDevice::getServiceDataCount() { +uint8_t NimBLEAdvertisedDevice::getServiceDataCount() { uint8_t count = 0; count += findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID16); @@ -384,7 +386,7 @@ size_t NimBLEAdvertisedDevice::getServiceDataCount() { */ NimBLEUUID NimBLEAdvertisedDevice::getServiceUUID(uint8_t index) { uint8_t count = 0; - uint8_t data_loc = 0; + size_t data_loc = 0; uint8_t uuidBytes = 0; uint8_t type = BLE_HS_ADV_TYPE_INCOMP_UUIDS16; ble_hs_adv_field *field = nullptr; @@ -431,7 +433,7 @@ NimBLEUUID NimBLEAdvertisedDevice::getServiceUUID(uint8_t index) { * @brief Get the number of services advertised * @return The count of services in the advertising packet. */ -size_t NimBLEAdvertisedDevice::getServiceUUIDCount() { +uint8_t NimBLEAdvertisedDevice::getServiceUUIDCount() { uint8_t count = 0; count += findAdvField(BLE_HS_ADV_TYPE_INCOMP_UUIDS16); @@ -467,7 +469,7 @@ bool NimBLEAdvertisedDevice::isAdvertisingService(const NimBLEUUID &uuid) { * @return The TX Power of the advertised device. */ int8_t NimBLEAdvertisedDevice::getTXPower() { - uint8_t data_loc = 0; + size_t data_loc = 0; if(findAdvField(BLE_HS_ADV_TYPE_TX_PWR_LVL, 0, &data_loc) > 0) { ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc]; @@ -581,17 +583,60 @@ bool NimBLEAdvertisedDevice::haveTXPower() { } // haveTXPower -uint8_t NimBLEAdvertisedDevice::findAdvField(uint8_t type, uint8_t index, uint8_t *data_loc) { - ble_hs_adv_field *field = nullptr; - uint8_t data = 0; - uint8_t length = m_payload.size(); - uint8_t count = 0; +#if CONFIG_BT_NIMBLE_EXT_ADV +/** + * @brief Get the set ID of the extended advertisement. + * @return The set ID. + */ +uint8_t NimBLEAdvertisedDevice::getSetId() { + return m_sid; +} // getSetId - if(length < 2) { + +/** + * @brief Get the primary PHY used by this advertisement. + * @return The PHY type, one of: + * * BLE_HCI_LE_PHY_1M + * * BLE_HCI_LE_PHY_CODED + */ +uint8_t NimBLEAdvertisedDevice::getPrimaryPhy() { + return m_primPhy; +} // getPrimaryPhy + + +/** + * @brief Get the primary PHY used by this advertisement. + * @return The PHY type, one of: + * * BLE_HCI_LE_PHY_1M + * * BLE_HCI_LE_PHY_2M + * * BLE_HCI_LE_PHY_CODED + */ +uint8_t NimBLEAdvertisedDevice::getSecondaryPhy() { + return m_secPhy; +} // getSecondaryPhy + + +/** + * @brief Get the periodic interval of the advertisement. + * @return The periodic advertising interval, 0 if not periodic advertising. + */ +uint16_t NimBLEAdvertisedDevice::getPeriodicInterval() { + return m_periodicItvl; +} // getPeriodicInterval +#endif + + +uint8_t NimBLEAdvertisedDevice::findAdvField(uint8_t type, uint8_t index, size_t * data_loc) { + ble_hs_adv_field *field = nullptr; + size_t length = m_payload.size(); + size_t data = 0; + uint8_t count = 0; + + if (length < 3) { return count; } - while (length > 1) { + while (length > 2) { field = (ble_hs_adv_field*)&m_payload[data]; if (field->length >= length) { @@ -599,7 +644,7 @@ uint8_t NimBLEAdvertisedDevice::findAdvField(uint8_t type, uint8_t index, uint8_ } if (field->type == type) { - switch(type) { + switch (type) { case BLE_HS_ADV_TYPE_INCOMP_UUIDS16: case BLE_HS_ADV_TYPE_COMP_UUIDS16: count += field->length / 2; @@ -625,8 +670,8 @@ uint8_t NimBLEAdvertisedDevice::findAdvField(uint8_t type, uint8_t index, uint8_ break; } - if(data_loc != nullptr) { - if(index == 0 || count >= index) { + if (data_loc != nullptr) { + if (index == 0 || count >= index) { break; } } @@ -636,7 +681,7 @@ uint8_t NimBLEAdvertisedDevice::findAdvField(uint8_t type, uint8_t index, uint8_ data += 1 + field->length; } - if(data_loc != nullptr && field != nullptr) { + if (data_loc != nullptr && field != nullptr) { *data_loc = data; } @@ -657,8 +702,13 @@ void NimBLEAdvertisedDevice::setAddress(NimBLEAddress address) { * @brief Set the adFlag for this device. * @param [in] advType The advertisement flag data from the advertisement. */ -void NimBLEAdvertisedDevice::setAdvType(uint8_t advType) { +void NimBLEAdvertisedDevice::setAdvType(uint8_t advType, bool isLegacyAdv) { m_advType = advType; +#if CONFIG_BT_NIMBLE_EXT_ADV + m_isLegacyAdv = isLegacyAdv; +#else + (void)isLegacyAdv; +#endif } // setAdvType @@ -703,10 +753,10 @@ std::string NimBLEAdvertisedDevice::toString() { res += val; } - if(haveServiceData()) { - size_t count = getServiceDataCount(); + if (haveServiceData()) { + uint8_t count = getServiceDataCount(); res += "\nService Data:"; - for(size_t i = 0; i < count; i++) { + for(uint8_t i = 0; i < count; i++) { res += "\nUUID: " + std::string(getServiceDataUUID(i)); res += ", Data: " + getServiceData(i); } @@ -781,5 +831,34 @@ size_t NimBLEAdvertisedDevice::getPayloadLength() { return m_payload.size(); } // getPayloadLength + +/** + * @brief Check if this device is advertising as connectable. + * @return True if the device is connectable. + */ +bool NimBLEAdvertisedDevice::isConnectable() { +#if CONFIG_BT_NIMBLE_EXT_ADV + if (m_isLegacyAdv) { + return m_advType == BLE_HCI_ADV_RPT_EVTYPE_ADV_IND || + m_advType == BLE_HCI_ADV_RPT_EVTYPE_DIR_IND; + } +#endif + return (m_advType & BLE_HCI_ADV_CONN_MASK) || + (m_advType & BLE_HCI_ADV_DIRECT_MASK); +} // isConnectable + + +/** + * @brief Check if this advertisement is a legacy or extended type + * @return True if legacy (Bluetooth 4.x), false if extended (bluetooth 5.x). + */ +bool NimBLEAdvertisedDevice::isLegacyAdvertisement() { +#if CONFIG_BT_NIMBLE_EXT_ADV + return m_isLegacyAdv; +# else + return true; +#endif +} // isLegacyAdvertisement + #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */ diff --git a/src/NimBLEAdvertisedDevice.h b/src/NimBLEAdvertisedDevice.h index 39410e6..772bab9 100644 --- a/src/NimBLEAdvertisedDevice.h +++ b/src/NimBLEAdvertisedDevice.h @@ -71,7 +71,7 @@ public: std::string getName(); int getRSSI(); NimBLEScan* getScan(); - size_t getServiceDataCount(); + uint8_t getServiceDataCount(); std::string getServiceData(uint8_t index = 0); std::string getServiceData(const NimBLEUUID &uuid); @@ -111,9 +111,9 @@ public: NimBLEUUID getServiceDataUUID(uint8_t index = 0); NimBLEUUID getServiceUUID(uint8_t index = 0); - size_t getServiceUUIDCount(); + uint8_t getServiceUUIDCount(); NimBLEAddress getTargetAddress(uint8_t index = 0); - size_t getTargetAddressCount(); + uint8_t getTargetAddressCount(); int8_t getTXPower(); uint8_t* getPayload(); uint8_t getAdvLength(); @@ -133,16 +133,30 @@ public: bool haveTargetAddress(); bool haveURI(); std::string toString(); + bool isConnectable(); + bool isLegacyAdvertisement(); +#if CONFIG_BT_NIMBLE_EXT_ADV + uint8_t getSetId(); + uint8_t getPrimaryPhy(); + uint8_t getSecondaryPhy(); + uint16_t getPeriodicInterval(); +#endif private: friend class NimBLEScan; void setAddress(NimBLEAddress address); - void setAdvType(uint8_t advType); + void setAdvType(uint8_t advType, bool isLegacyAdv); void setPayload(const uint8_t *payload, uint8_t length, bool append); void setRSSI(int rssi); - uint8_t findAdvField(uint8_t type, uint8_t index = 0, uint8_t *data_loc = nullptr); - uint8_t findServiceData(uint8_t index, uint8_t* bytes); +#if CONFIG_BT_NIMBLE_EXT_ADV + void setSetId(uint8_t sid) { m_sid = sid; } + void setPrimaryPhy(uint8_t phy) { m_primPhy = phy; } + void setSecondaryPhy(uint8_t phy) { m_secPhy = phy; } + void setPeriodicInterval(uint16_t itvl) { m_periodicItvl = itvl; } +#endif + uint8_t findAdvField(uint8_t type, uint8_t index = 0, size_t * data_loc = nullptr); + size_t findServiceData(uint8_t index, uint8_t* bytes); NimBLEAddress m_address = NimBLEAddress(""); uint8_t m_advType; @@ -150,6 +164,13 @@ private: time_t m_timestamp; bool m_callbackSent; uint8_t m_advLength; +#if CONFIG_BT_NIMBLE_EXT_ADV + bool m_isLegacyAdv; + uint8_t m_sid; + uint8_t m_primPhy; + uint8_t m_secPhy; + uint16_t m_periodicItvl; +#endif std::vector m_payload; }; diff --git a/src/NimBLEAdvertising.cpp b/src/NimBLEAdvertising.cpp index a804130..d244f41 100644 --- a/src/NimBLEAdvertising.cpp +++ b/src/NimBLEAdvertising.cpp @@ -14,7 +14,9 @@ * */ #include "nimconfig.h" -#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) +#if (defined(CONFIG_BT_ENABLED) && \ + defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) && \ + !CONFIG_BT_NIMBLE_EXT_ADV) || defined(_DOXYGEN_) #if defined(CONFIG_NIMBLE_CPP_IDF) #include "services/gap/ble_svc_gap.h" @@ -412,7 +414,7 @@ bool NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv // If already advertising just return if(ble_gap_adv_active()) { NIMBLE_LOGW(LOG_TAG, "Advertising already active"); - return false; + return true; } // Save the duration incase of host reset so we can restart with the same params @@ -625,7 +627,7 @@ bool NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv &m_advParams, (pServer != nullptr) ? NimBLEServer::handleGapEvent : NimBLEAdvertising::handleGapEvent, - (pServer != nullptr) ? (void*)pServer : (void*)this); + (void*)this); #else rc = ble_gap_adv_start(NimBLEDevice::m_own_addr_type, NULL, duration, &m_advParams, NimBLEAdvertising::handleGapEvent, this); @@ -634,6 +636,10 @@ bool NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv case 0: break; + case BLE_HS_EALREADY: + NIMBLE_LOGI(LOG_TAG, "Advertisement Already active"); + break; + case BLE_HS_EINVAL: NIMBLE_LOGE(LOG_TAG, "Unable to advertise - Duration too long"); break; @@ -656,24 +662,26 @@ bool NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv } NIMBLE_LOGD(LOG_TAG, "<< Advertising start"); - return (rc == 0); + return (rc == 0 || rc == BLE_HS_EALREADY); } // start /** * @brief Stop advertising. + * @return True if advertising stopped successfully. */ -void NimBLEAdvertising::stop() { +bool NimBLEAdvertising::stop() { NIMBLE_LOGD(LOG_TAG, ">> stop"); int rc = ble_gap_adv_stop(); if (rc != 0 && rc != BLE_HS_EALREADY) { NIMBLE_LOGE(LOG_TAG, "ble_gap_adv_stop rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc)); - return; + return false; } NIMBLE_LOGD(LOG_TAG, "<< stop"); + return true; } // stop @@ -1026,4 +1034,4 @@ std::string NimBLEAdvertisementData::getPayload() { return m_payload; } // getPayload -#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_BROADCASTER */ +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_BROADCASTER && !CONFIG_BT_NIMBLE_EXT_ADV */ diff --git a/src/NimBLEAdvertising.h b/src/NimBLEAdvertising.h index 63a21d8..dd72ede 100644 --- a/src/NimBLEAdvertising.h +++ b/src/NimBLEAdvertising.h @@ -15,7 +15,9 @@ #ifndef MAIN_BLEADVERTISING_H_ #define MAIN_BLEADVERTISING_H_ #include "nimconfig.h" -#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) +#if (defined(CONFIG_BT_ENABLED) && \ + defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) && \ + !CONFIG_BT_NIMBLE_EXT_ADV) || defined(_DOXYGEN_) #if defined(CONFIG_NIMBLE_CPP_IDF) #include "host/ble_gap.h" @@ -89,7 +91,7 @@ public: void addServiceUUID(const char* serviceUUID); void removeServiceUUID(const NimBLEUUID &serviceUUID); bool start(uint32_t duration = 0, void (*advCompleteCB)(NimBLEAdvertising *pAdv) = nullptr); - void stop(); + bool stop(); void setAppearance(uint16_t appearance); void setName(const std::string &name); void setManufacturerData(const std::string &data); @@ -111,6 +113,7 @@ public: private: friend class NimBLEDevice; + friend class NimBLEServer; void onHostSync(); static int handleGapEvent(struct ble_gap_event *event, void *arg); @@ -134,5 +137,5 @@ private: std::vector m_uri; }; -#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_BROADCASTER */ +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_BROADCASTER && !CONFIG_BT_NIMBLE_EXT_ADV */ #endif /* MAIN_BLEADVERTISING_H_ */ diff --git a/src/NimBLEClient.cpp b/src/NimBLEClient.cpp index d822732..3619b44 100644 --- a/src/NimBLEClient.cpp +++ b/src/NimBLEClient.cpp @@ -65,6 +65,11 @@ NimBLEClient::NimBLEClient(const NimBLEAddress &peerAddress) : m_peerAddress(pee m_pTaskData = nullptr; m_connEstablished = false; m_lastErr = 0; +#if CONFIG_BT_NIMBLE_EXT_ADV + m_phyMask = BLE_GAP_LE_PHY_1M_MASK | + BLE_GAP_LE_PHY_2M_MASK | + BLE_GAP_LE_PHY_CODED_MASK; +#endif m_pConnParams.scan_itvl = 16; // Scan interval in 0.625ms units (NimBLE Default) m_pConnParams.scan_window = 16; // Scan window in 0.625ms units (NimBLE Default) @@ -220,9 +225,22 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) { * Loop on BLE_HS_EBUSY if the scan hasn't stopped yet. */ do { +#if CONFIG_BT_NIMBLE_EXT_ADV + rc = ble_gap_ext_connect(NimBLEDevice::m_own_addr_type, + &peerAddr_t, + m_connectTimeout, + m_phyMask, + &m_pConnParams, + &m_pConnParams, + &m_pConnParams, + NimBLEClient::handleGapEvent, + this); + +#else rc = ble_gap_connect(NimBLEDevice::m_own_addr_type, &peerAddr_t, m_connectTimeout, &m_pConnParams, NimBLEClient::handleGapEvent, this); +#endif switch (rc) { case 0: break; @@ -397,6 +415,21 @@ int NimBLEClient::disconnect(uint8_t reason) { } // disconnect +#if CONFIG_BT_NIMBLE_EXT_ADV +/** + * @brief Set the PHY types to use when connecting to a server. + * @param [in] mask A bitmask indicating what PHYS to connect with.\n + * The available bits are: + * * 0x01 BLE_GAP_LE_PHY_1M_MASK + * * 0x02 BLE_GAP_LE_PHY_2M_MASK + * * 0x04 BLE_GAP_LE_PHY_CODED_MASK + */ +void NimBLEClient::setConnectPhy(uint8_t mask) { + m_phyMask = mask; +} +#endif + + /** * @brief Set the connection paramaters to use when connecting to a server. * @param [in] minInterval The minimum connection interval in 1.25ms units. diff --git a/src/NimBLEClient.h b/src/NimBLEClient.h index 7c93c30..4aed02a 100644 --- a/src/NimBLEClient.h +++ b/src/NimBLEClient.h @@ -73,6 +73,9 @@ public: void discoverAttributes(); NimBLEConnInfo getConnInfo(); int getLastError(); +#if CONFIG_BT_NIMBLE_EXT_ADV + void setConnectPhy(uint8_t mask); +#endif private: NimBLEClient(const NimBLEAddress &peerAddress); @@ -98,6 +101,9 @@ private: NimBLEClientCallbacks* m_pClientCallbacks; ble_task_data_t* m_pTaskData; ble_npl_callout m_dcTimer; +#if CONFIG_BT_NIMBLE_EXT_ADV + uint8_t m_phyMask; +#endif std::vector m_servicesVector; diff --git a/src/NimBLEDevice.cpp b/src/NimBLEDevice.cpp index 8c205af..1c337b8 100644 --- a/src/NimBLEDevice.cpp +++ b/src/NimBLEDevice.cpp @@ -69,7 +69,11 @@ NimBLEServer* NimBLEDevice::m_pServer = nullptr; uint32_t NimBLEDevice::m_passkey = 123456; bool NimBLEDevice::m_synced = false; #if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) +# if CONFIG_BT_NIMBLE_EXT_ADV +NimBLEExtAdvertising* NimBLEDevice::m_bleAdvertising = nullptr; +# else NimBLEAdvertising* NimBLEDevice::m_bleAdvertising = nullptr; +# endif #endif gap_event_handler NimBLEDevice::m_customGapHandler = nullptr; @@ -114,6 +118,45 @@ uint8_t NimBLEDevice::m_scanFilterMode = CONFIG_BTDM_SCAN_DU #if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) +# if CONFIG_BT_NIMBLE_EXT_ADV +/** + * @brief Get the instance of the advertising object. + * @return A pointer to the advertising object. + */ +NimBLEExtAdvertising* NimBLEDevice::getAdvertising() { + if(m_bleAdvertising == nullptr) { + m_bleAdvertising = new NimBLEExtAdvertising(); + } + return m_bleAdvertising; +} + + +/** + * @brief Convenience function to begin advertising. + * @param [in] inst_id The extended advertisement instance ID to start. + * @param [in] duration How long to advertise for in milliseconds, 0 = forever (default). + * @param [in] max_events Maximum number of advertisement events to send, 0 = no limit (default). + * @return True if advertising started successfully. + */ +bool NimBLEDevice::startAdvertising(uint8_t inst_id, + int duration, + int max_events) { + return getAdvertising()->start(inst_id, duration, max_events); +} // startAdvertising + + +/** + * @brief Convenience function to stop advertising a data set. + * @param [in] inst_id The extended advertisement instance ID to stop advertising. + * @return True if advertising stopped successfully. + */ +bool NimBLEDevice::stopAdvertising(uint8_t inst_id) { + return getAdvertising()->stop(inst_id); +} // stopAdvertising + +# endif + +# if !CONFIG_BT_NIMBLE_EXT_ADV || defined(_DOXYGEN_) /** * @brief Get the instance of the advertising object. * @return A pointer to the advertising object. @@ -128,17 +171,19 @@ NimBLEAdvertising* NimBLEDevice::getAdvertising() { /** * @brief Convenience function to begin advertising. + * @return True if advertising started successfully. */ -void NimBLEDevice::startAdvertising() { - getAdvertising()->start(); +bool NimBLEDevice::startAdvertising() { + return getAdvertising()->start(); } // startAdvertising - +# endif /** - * @brief Convenience function to stop advertising. + * @brief Convenience function to stop all advertising. + * @return True if advertising stopped successfully. */ -void NimBLEDevice::stopAdvertising() { - getAdvertising()->stop(); +bool NimBLEDevice::stopAdvertising() { + return getAdvertising()->stop(); } // stopAdvertising #endif // #if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) diff --git a/src/NimBLEDevice.h b/src/NimBLEDevice.h index 94ad294..b7e804b 100644 --- a/src/NimBLEDevice.h +++ b/src/NimBLEDevice.h @@ -23,7 +23,11 @@ #endif #if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) -#include "NimBLEAdvertising.h" +# if CONFIG_BT_NIMBLE_EXT_ADV +# include "NimBLEExtAdvertising.h" +# else +# include "NimBLEAdvertising.h" +# endif #endif #if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) @@ -139,9 +143,18 @@ public: static void removeIgnored(const NimBLEAddress &address); #if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) - static NimBLEAdvertising* getAdvertising(); - static void startAdvertising(); - static void stopAdvertising(); +# if CONFIG_BT_NIMBLE_EXT_ADV + static NimBLEExtAdvertising* getAdvertising(); + static bool startAdvertising(uint8_t inst_id, + int duration = 0, + int max_events = 0); + static bool stopAdvertising(uint8_t inst_id); + static bool stopAdvertising(); +# else + static NimBLEAdvertising* getAdvertising(); + static bool startAdvertising(); + static bool stopAdvertising(); +# endif #endif #if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL) @@ -178,6 +191,10 @@ private: #if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) friend class NimBLEAdvertising; +# if CONFIG_BT_NIMBLE_EXT_ADV + friend class NimBLEExtAdvertising; + friend class NimBLEExtAdvertisement; +# endif #endif static void onReset(int reason); @@ -194,7 +211,11 @@ private: #endif #if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) +# if CONFIG_BT_NIMBLE_EXT_ADV + static NimBLEExtAdvertising* m_bleAdvertising; +# else static NimBLEAdvertising* m_bleAdvertising; +# endif #endif #if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL) diff --git a/src/NimBLEExtAdvertising.cpp b/src/NimBLEExtAdvertising.cpp new file mode 100644 index 0000000..b488e7f --- /dev/null +++ b/src/NimBLEExtAdvertising.cpp @@ -0,0 +1,870 @@ +/* + * NimBLEExtAdvertising.cpp + * + * Created: on February 6, 2022 + * Author H2zero + */ + +#include "nimconfig.h" +#if defined(CONFIG_BT_ENABLED) && \ + defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) && \ + CONFIG_BT_NIMBLE_EXT_ADV + +#if defined(CONFIG_NIMBLE_CPP_IDF) +#include "services/gap/ble_svc_gap.h" +#else +#include "nimble/nimble/host/services/gap/include/services/gap/ble_svc_gap.h" +#endif +#include "NimBLEExtAdvertising.h" +#include "NimBLEDevice.h" +#include "NimBLEServer.h" +#include "NimBLEUtils.h" +#include "NimBLELog.h" + +static NimBLEExtAdvertisingCallbacks defaultCallbacks; +static const char* LOG_TAG = "NimBLEExtAdvertising"; + + +/** + * @brief Destructor: deletes callback instances if requested. + */ +NimBLEExtAdvertising::~NimBLEExtAdvertising() { + if(m_deleteCallbacks && m_pCallbacks != &defaultCallbacks) { + delete m_pCallbacks; + } +} + + +/** + * @brief Register the extended advertisement data. + * @param [in] inst_id The extended advertisement instance ID to assign to this data. + * @param [in] adv The extended advertisement instance with the data to set. + * @return True if advertising started successfully. + */ +bool NimBLEExtAdvertising::setInstanceData(uint8_t inst_id, NimBLEExtAdvertisement& adv) { + adv.m_params.sid = inst_id; + + // Legacy advertising as connectable requires the scannable flag also. + if (adv.m_params.legacy_pdu && adv.m_params.connectable) { + adv.m_params.scannable = true; + } + + // If connectable or not scannable disable the callback for scan response requests + if (adv.m_params.connectable || !adv.m_params.scannable) { + adv.m_params.scan_req_notif = false; + } + +#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) + NimBLEServer* pServer = NimBLEDevice::getServer(); + if (pServer != nullptr) { + if (!pServer->m_gattsStarted) { + pServer->start(); + } + } + + int rc = ble_gap_ext_adv_configure(inst_id, + &adv.m_params, + NULL, + (pServer != nullptr) ? NimBLEServer::handleGapEvent : + NimBLEExtAdvertising::handleGapEvent, + NULL); +#else + int rc = ble_gap_ext_adv_configure(inst_id, + &data.m_params, + NULL, + NimBLEExtAdvertising::handleGapEvent, + NULL); +#endif + + if (rc != 0) { + NIMBLE_LOGE(LOG_TAG, "Advertising config error: rc = %d", rc); + } else { + os_mbuf *buf; + buf = os_msys_get_pkthdr(adv.m_payload.size(), 0); + if (!buf) { + NIMBLE_LOGE(LOG_TAG, "Data buffer allocation failed"); + return false; + } + + rc = os_mbuf_append(buf, &adv.m_payload[0], adv.m_payload.size()); + if (rc != 0) { + NIMBLE_LOGE(LOG_TAG, "Unable to copy data: rc = %d", rc); + return false; + } else { + if (adv.m_params.scannable && !adv.m_params.legacy_pdu) { + rc = ble_gap_ext_adv_rsp_set_data(inst_id, buf); + } else { + rc = ble_gap_ext_adv_set_data(inst_id, buf); + } + + if (rc != 0) { + NIMBLE_LOGE(LOG_TAG, "Invalid advertisement data: rc = %d", rc); + } else { + if (adv.m_advAddress != NimBLEAddress("")) { + ble_addr_t addr; + memcpy(&addr.val, adv.m_advAddress.getNative(), 6); + // Custom advertising address must be random. + addr.type = BLE_OWN_ADDR_RANDOM; + rc = ble_gap_ext_adv_set_addr(inst_id, &addr); + } + + if (rc != 0) { + NIMBLE_LOGE(LOG_TAG, "Error setting advertisement address: rc = %d", rc); + return false; + } + } + } + } + + return (rc == 0); +} + + +/** + * @brief Set the scan response data for a legacy advertisement. + * @param [in] inst_id The extended advertisement instance ID to assign to this data. + * @param [in] lsr A reference to a NimBLEExtAdvertisement that contains the data. + */ +bool NimBLEExtAdvertising::setScanResponseData(uint8_t inst_id, NimBLEExtAdvertisement & lsr) { + os_mbuf *buf = os_msys_get_pkthdr(lsr.m_payload.size(), 0); + if (!buf) { + NIMBLE_LOGE(LOG_TAG, "Data buffer allocation failed"); + return false; + } + + int rc = os_mbuf_append(buf, &lsr.m_payload[0], lsr.m_payload.size()); + + if (rc != 0) { + NIMBLE_LOGE(LOG_TAG, "Unable to copy scan data: rc = %d", rc); + return false; + } else { + rc = ble_gap_ext_adv_rsp_set_data(inst_id, buf); + } + return (rc == 0); +} + + +/** + * @brief Start extended advertising. + * @param [in] inst_id The extended advertisement instance ID to start. + * @param [in] duration How long to advertise for in milliseconds, 0 = forever (default). + * @param [in] max_events Maximum number of advertisement events to send, 0 = no limit (default). + * @return True if advertising started successfully. + */ +bool NimBLEExtAdvertising::start(uint8_t inst_id, int duration, int max_events) { + NIMBLE_LOGD(LOG_TAG, ">> Extended Advertising start"); + + // If Host is not synced we cannot start advertising. + if(!NimBLEDevice::m_synced) { + NIMBLE_LOGE(LOG_TAG, "Host reset, wait for sync."); + return false; + } + + int rc = ble_gap_ext_adv_start(inst_id, duration / 10, max_events); + + switch (rc) { + case 0: + m_advStatus[inst_id] = true; + break; + + case BLE_HS_EINVAL: + NIMBLE_LOGE(LOG_TAG, "Unable to advertise - Value Error"); + break; + + case BLE_HS_EALREADY: + NIMBLE_LOGI(LOG_TAG, "Advertisement Already active"); + break; + + case BLE_HS_ETIMEOUT_HCI: + case BLE_HS_EOS: + case BLE_HS_ECONTROLLER: + case BLE_HS_ENOTSYNCED: + NIMBLE_LOGE(LOG_TAG, "Unable to advertise - Host Reset"); + break; + + default: + NIMBLE_LOGE(LOG_TAG, "Error enabling advertising; rc=%d, %s", + rc, NimBLEUtils::returnCodeToString(rc)); + break; + } + + NIMBLE_LOGD(LOG_TAG, "<< Extended Advertising start"); + return (rc == 0 || rc == BLE_HS_EALREADY); +} // start + + +/** + * @brief Stop and remove this instance data from the advertisement set. + * @param [in] inst_id The extended advertisement instance to stop advertising. + * @return True if successful. + */ +bool NimBLEExtAdvertising::removeInstance(uint8_t inst_id) { + if (stop(inst_id)) { + int rc = ble_gap_ext_adv_remove(inst_id); + if (rc != 0 && rc != BLE_HS_EALREADY) { + NIMBLE_LOGE(LOG_TAG, "ble_gap_ext_adv_remove rc = %d %s", + rc, NimBLEUtils::returnCodeToString(rc)); + return false; + } + return true; + } + + return false; +} // removeInstance + + +/** + * @brief Stop and remove all advertising instance data. + * @return True if successful. + */ +bool NimBLEExtAdvertising::removeAll() { + if (stop()) { + int rc = ble_gap_ext_adv_clear(); + if (rc == 0 || rc == BLE_HS_EALREADY) { + return true; + } else { + NIMBLE_LOGE(LOG_TAG, "ble_gap_ext_adv_clear rc = %d %s", + rc, NimBLEUtils::returnCodeToString(rc)); + } + } + + return false; +} // removeAll + + +/** + * @brief Stop advertising this instance data. + * @param [in] inst_id The extended advertisement instance to stop advertising. + * @return True if successful. + */ +bool NimBLEExtAdvertising::stop(uint8_t inst_id) { + int rc = ble_gap_ext_adv_stop(inst_id); + if (rc != 0 && rc != BLE_HS_EALREADY) { + NIMBLE_LOGE(LOG_TAG, "ble_gap_ext_adv_stop rc = %d %s", + rc, NimBLEUtils::returnCodeToString(rc)); + return false; + } + + m_advStatus[inst_id] = false; + return true; +} // stop + + +/** + * @brief Stop all advertisements. + * @return True if successful. + */ +bool NimBLEExtAdvertising::stop() { + int rc = ble_gap_ext_adv_clear(); + if (rc != 0 && rc != BLE_HS_EALREADY) { + NIMBLE_LOGE(LOG_TAG, "ble_gap_ext_adv_stop rc = %d %s", + rc, NimBLEUtils::returnCodeToString(rc)); + return false; + } + + for(auto it : m_advStatus) { + it = false; + } + + return true; +} // stop + + +/** + * @brief Set a callback to call when the advertisement stops. + * @param [in] pCallbacks A pointer to a callback to be invoked when an advertisment stops. + * @param [in] deleteCallbacks if true callback class will be deleted when advertising is destructed. + */ +void NimBLEExtAdvertising::setCallbacks(NimBLEExtAdvertisingCallbacks* pCallbacks, + bool deleteCallbacks) { + if (pCallbacks != nullptr){ + m_pCallbacks = pCallbacks; + m_deleteCallbacks = deleteCallbacks; + } else { + m_pCallbacks = &defaultCallbacks; + } +} // setCallbacks + + +/** + * @brief Check if currently advertising. + * @param [in] inst_id The instance ID of the advertised data to get the status of. + * @return True if advertising is active. + */ +bool NimBLEExtAdvertising::isActive(uint8_t inst_id) { + return m_advStatus[inst_id]; +} // isAdvertising + + +/** + * @brief Check if any instances are currently advertising. + * @return True if any instance is active. + */ +bool NimBLEExtAdvertising::isAdvertising() { + for (auto it : m_advStatus) { + if (it) { + return true; + } + } + return false; +} // isAdvertising + + +/* + * Host reset seems to clear advertising data, + * we need clear the flag so it reloads it. + */ +void NimBLEExtAdvertising::onHostSync() { + NIMBLE_LOGD(LOG_TAG, "Host re-synced"); + for(auto it : m_advStatus) { + it = false; + } +} // onHostSync + + +/** + * @brief Handler for gap events when not using peripheral role. + * @param [in] event the event data. + * @param [in] arg pointer to the advertising instance. + */ +/*STATIC*/ +int NimBLEExtAdvertising::handleGapEvent(struct ble_gap_event *event, void *arg) { + (void)arg; + NimBLEExtAdvertising* pAdv = NimBLEDevice::getAdvertising(); + + switch (event->type) { + case BLE_GAP_EVENT_ADV_COMPLETE: { + switch (event->adv_complete.reason) { + // Don't call the callback if host reset, we want to + // preserve the active flag until re-sync to restart advertising. + case BLE_HS_ETIMEOUT_HCI: + case BLE_HS_EOS: + case BLE_HS_ECONTROLLER: + case BLE_HS_ENOTSYNCED: + NIMBLE_LOGC(LOG_TAG, "host reset, rc = %d", event->adv_complete.reason); + NimBLEDevice::onReset(event->adv_complete.reason); + return 0; + default: + break; + } + pAdv->m_advStatus[event->adv_complete.instance] = false; + pAdv->m_pCallbacks->onStopped(pAdv, event->adv_complete.reason, + event->adv_complete.instance); + break; + } + + case BLE_GAP_EVENT_SCAN_REQ_RCVD: { + pAdv->m_pCallbacks->onScanRequest(pAdv, event->scan_req_rcvd.instance, + NimBLEAddress(event->scan_req_rcvd.scan_addr)); + break; + } + } + + return 0; +} // handleGapEvent + + +/** Default callback handlers */ +void NimBLEExtAdvertisingCallbacks::onStopped(NimBLEExtAdvertising *pAdv, + int reason, uint8_t inst_id) { + NIMBLE_LOGD("NimBLEExtAdvertisingCallbacks", "onStopped: Default"); +} // onStopped + + +void NimBLEExtAdvertisingCallbacks::onScanRequest(NimBLEExtAdvertising *pAdv, + uint8_t inst_id, NimBLEAddress addr) { + NIMBLE_LOGD("NimBLEExtAdvertisingCallbacks", "onScanRequest: Default"); +} // onScanRequest + + +/** + * @brief Construct a BLE extended advertisement. + * @param [in] priPhy The primary Phy to advertise on, can be one of: + * * BLE_HCI_LE_PHY_1M + * * BLE_HCI_LE_PHY_CODED + * @param [in] secPhy The secondary Phy to advertise on, can be one of: + * * BLE_HCI_LE_PHY_1M + * * BLE_HCI_LE_PHY_2M + * * BLE_HCI_LE_PHY_CODED + */ +NimBLEExtAdvertisement::NimBLEExtAdvertisement(uint8_t priPhy, uint8_t secPhy) +: m_advAddress("") +{ + memset (&m_params, 0, sizeof(m_params)); + m_params.own_addr_type = NimBLEDevice::m_own_addr_type; + m_params.primary_phy = priPhy; + m_params.secondary_phy = secPhy; + m_params.tx_power = 127; +} // NimBLEExtAdvertisement + + +/** + * @brief Sets wether the advertisement should use legacy (BLE 4.0, 31 bytes max) advertising. + * @param [in] val true = using legacy advertising. + */ +void NimBLEExtAdvertisement::setLegacyAdvertising(bool val) { + m_params.legacy_pdu = val; +} // setLegacyAdvertising + + +/** + * @brief Sets wether the advertisement has scan response data available. + * @param [in] val true = scan response is available. + */ +void NimBLEExtAdvertisement::setScannable(bool val) { + m_params.scannable = val; +} // setScannable + + + +/** + * @brief Sets the transmission power level for this advertisement. + * @param [in] dbm the transmission power to use in dbm. + * @details The allowable value range depends on device hardware. \n + * The ESP32C3 and ESP32S3 have a range of -27 to +18. + */ +void NimBLEExtAdvertisement::setTxPower(int8_t dbm) { + m_params.tx_power = dbm; +} + + +/** + * @brief Sets wether this advertisement should advertise as a connectable device. + * @param [in] val True = connectable. + */ +void NimBLEExtAdvertisement::setConnectable(bool val) { +#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) + m_params.connectable = val; +#endif +} // setConnectable + + +/** + * @brief Set the address to use for this advertisement. + * @param [in] addr The address to use. + */ +void NimBLEExtAdvertisement::setAddress(const NimBLEAddress & addr) { + m_advAddress = addr; + // Must use random address type. + m_params.own_addr_type = BLE_OWN_ADDR_RANDOM; +} + + +/** + * @brief Sets The primary channels to advertise on. + * @param [in] ch37 Advertise on channel 37. + * @param [in] ch38 Advertise on channel 38. + * @param [in] ch39 Advertise on channel 39. + * @details This will set a bitmask using the input parameters to allow different \n + * combinations. If all inputs are false then all 3 channels will be used. + */ +void NimBLEExtAdvertisement::setPrimaryChannels(bool ch37, bool ch38, bool ch39) { + m_params.channel_map = (ch37 | (ch38 << 1) | (ch39 << 2)); +} // setPrimaryChannels + + +/** + * @brief Set the filtering for the scan filter. + * @param [in] scanRequestWhitelistOnly If true, only allow scan requests from those on the white list. + * @param [in] connectWhitelistOnly If true, only allow connections from those on the white list. + */ +void NimBLEExtAdvertisement::setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly) { + if (!scanRequestWhitelistOnly && !connectWhitelistOnly) { + m_params.filter_policy = BLE_HCI_ADV_FILT_NONE; + return; + } + if (scanRequestWhitelistOnly && !connectWhitelistOnly) { + m_params.filter_policy = BLE_HCI_ADV_FILT_SCAN; + return; + } + if (!scanRequestWhitelistOnly && connectWhitelistOnly) { + m_params.filter_policy = BLE_HCI_ADV_FILT_CONN; + return; + } + if (scanRequestWhitelistOnly && connectWhitelistOnly) { + m_params.filter_policy = BLE_HCI_ADV_FILT_BOTH; + return; + } +} // setScanFilter + + +/** + * @brief Sets the peer to directly advertise to. + * @param [in] addr The address of the peer to direct the advertisements. + */ +void NimBLEExtAdvertisement::setDirectedPeer(const NimBLEAddress & addr) { + ble_addr_t peerAddr; + memcpy(&peerAddr.val, addr.getNative(), 6); + peerAddr.type = addr.getType(); + m_params.peer = peerAddr; +} // setDirectedPeer + + +/** + * @brief Enable or disable direct advertisements to the peer set with `NimBLEExtAdvertisement::setDirectedPeer` + * @param [in] val true = send directed advertisements to peer. + * @param [in] high_duty true = use fast advertising rate, default - true. + */ +void NimBLEExtAdvertisement::setDirected(bool val, bool high_duty) { + m_params.directed = val; + m_params.high_duty_directed = high_duty; +} // setDirected + + +/** + * @brief Set the minimum advertising interval. + * @param [in] mininterval Minimum value for advertising interval in 0.625ms units, 0 = use default. + */ +void NimBLEExtAdvertisement::setMinInterval(uint32_t mininterval) { + m_params.itvl_min = mininterval; +} // setMinInterval + + +/** + * @brief Set the maximum advertising interval. + * @param [in] maxinterval Maximum value for advertising interval in 0.625ms units, 0 = use default. + */ +void NimBLEExtAdvertisement::setMaxInterval(uint32_t maxinterval) { + m_params.itvl_max = maxinterval; +} // setMaxInterval + + +/** + * @brief Set the primary advertising PHY to use + * @param [in] phy Can be one of following constants: + * * BLE_HCI_LE_PHY_1M + * * BLE_HCI_LE_PHY_CODED + */ +void NimBLEExtAdvertisement::setPrimaryPhy(uint8_t phy) { + m_params.primary_phy = phy; +} // setPrimaryPhy + + +/** + * @brief Set the secondary advertising PHY to use + * @param [in] phy Can be one of following constants: + * * BLE_HCI_LE_PHY_1M + * * BLE_HCI_LE_PHY_2M + * * BLE_HCI_LE_PHY_CODED + */ +void NimBLEExtAdvertisement::setSecondaryPhy(uint8_t phy) { + m_params.secondary_phy = phy; +} // setSecondaryPhy + + +/** + * @brief Sets whether the advertisement should be anonymous. + * @param [in] val Set to true to enable anonymous advertising. + * + * @details Anonymous advertising omits the device's address from the advertisement. + */ +void NimBLEExtAdvertisement::setAnonymous(bool val) { + m_params.anonymous = val; +} // setAnonymous + + +/** + * @brief Sets whether the scan response request callback should be called. + * @param [in] enable If true the scan response request callback will be called for this advertisement. + */ +void NimBLEExtAdvertisement::enableScanRequestCallback(bool enable) { + m_params.scan_req_notif = enable; +} // enableScanRequestCallback + + +/** + * @brief Clears the data stored in this instance, does not change settings. + * @details This will clear all data but preserves advertising parameter settings. + */ +void NimBLEExtAdvertisement::clearData() { + std::vector swap; + std::swap(m_payload, swap); +} + + +/** + * @brief Get the size of the current data. + */ +size_t NimBLEExtAdvertisement::getDataSize() { + return m_payload.size(); +} // getDataSize + + +/** + * @brief Set the advertisement data. + * @param [in] data The data to be set as the payload. + * @param [in] length The size of data. + * @details This will completely replace any data that was previously set. + */ +void NimBLEExtAdvertisement::setData(const uint8_t * data, size_t length) { + m_payload.assign(data, data + length); +} // setData + + +/** + * @brief Add data to the payload to be advertised. + * @param [in] data The data to be added to the payload. + */ +void NimBLEExtAdvertisement::addData(const std::string &data) { + addData((uint8_t*)data.data(), data.length()); +} // addData + + +/** + * @brief Add data to the payload to be advertised. + * @param [in] data The data to be added to the payload. + * @param [in] length The size of data to be added to the payload. + */ +void NimBLEExtAdvertisement::addData(const uint8_t * data, size_t length) { + m_payload.insert(m_payload.end(), data, data + length); +} // addData + + +/** + * @brief Set the appearance. + * @param [in] appearance The appearance code value. + * + * See also: + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.gap.appearance.xml + */ +void NimBLEExtAdvertisement::setAppearance(uint16_t appearance) { + char cdata[2]; + cdata[0] = 3; + cdata[1] = BLE_HS_ADV_TYPE_APPEARANCE; // 0x19 + addData(std::string(cdata, 2) + std::string((char*) &appearance, 2)); +} // setAppearance + + +/** + * @brief Set the advertisement flags. + * @param [in] flag The flags to be set in the advertisement. + * * BLE_HS_ADV_F_DISC_LTD + * * BLE_HS_ADV_F_DISC_GEN + * * BLE_HS_ADV_F_BREDR_UNSUP - must always use with NimBLE + */ +void NimBLEExtAdvertisement::setFlags(uint8_t flag) { + char cdata[3]; + cdata[0] = 2; + cdata[1] = BLE_HS_ADV_TYPE_FLAGS; // 0x01 + cdata[2] = flag | BLE_HS_ADV_F_BREDR_UNSUP; + addData(std::string(cdata, 3)); +} // setFlags + + +/** + * @brief Set manufacturer specific data. + * @param [in] data The manufacturer data to advertise. + */ +void NimBLEExtAdvertisement::setManufacturerData(const std::string &data) { + char cdata[2]; + cdata[0] = data.length() + 1; + cdata[1] = BLE_HS_ADV_TYPE_MFG_DATA ; // 0xff + addData(std::string(cdata, 2) + data); +} // setManufacturerData + + +/** + * @brief Set the URI to advertise. + * @param [in] uri The uri to advertise. + */ +void NimBLEExtAdvertisement::setURI(const std::string &uri) { + char cdata[2]; + cdata[0] = uri.length() + 1; + cdata[1] = BLE_HS_ADV_TYPE_URI; + addData(std::string(cdata, 2) + uri); +} // setURI + + +/** + * @brief Set the complete name of this device. + * @param [in] name The name to advertise. + */ +void NimBLEExtAdvertisement::setName(const std::string &name) { + char cdata[2]; + cdata[0] = name.length() + 1; + cdata[1] = BLE_HS_ADV_TYPE_COMP_NAME; // 0x09 + addData(std::string(cdata, 2) + name); +} // setName + + +/** + * @brief Set a single service to advertise as a complete list of services. + * @param [in] uuid The service to advertise. + */ +void NimBLEExtAdvertisement::setCompleteServices(const NimBLEUUID &uuid) { + setServices(true, uuid.bitSize(), {uuid}); +} // setCompleteServices + + +/** + * @brief Set the complete list of 16 bit services to advertise. + * @param [in] v_uuid A vector of 16 bit UUID's to advertise. + */ +void NimBLEExtAdvertisement::setCompleteServices16(const std::vector& v_uuid) { + setServices(true, 16, v_uuid); +} // setCompleteServices16 + + +/** + * @brief Set the complete list of 32 bit services to advertise. + * @param [in] v_uuid A vector of 32 bit UUID's to advertise. + */ +void NimBLEExtAdvertisement::setCompleteServices32(const std::vector& v_uuid) { + setServices(true, 32, v_uuid); +} // setCompleteServices32 + + +/** + * @brief Set a single service to advertise as a partial list of services. + * @param [in] uuid The service to advertise. + */ +void NimBLEExtAdvertisement::setPartialServices(const NimBLEUUID &uuid) { + setServices(false, uuid.bitSize(), {uuid}); +} // setPartialServices + + +/** + * @brief Set the partial list of services to advertise. + * @param [in] v_uuid A vector of 16 bit UUID's to advertise. + */ +void NimBLEExtAdvertisement::setPartialServices16(const std::vector& v_uuid) { + setServices(false, 16, v_uuid); +} // setPartialServices16 + + +/** + * @brief Set the partial list of services to advertise. + * @param [in] v_uuid A vector of 32 bit UUID's to advertise. + */ +void NimBLEExtAdvertisement::setPartialServices32(const std::vector& v_uuid) { + setServices(false, 32, v_uuid); +} // setPartialServices32 + + +/** + * @brief Utility function to create the list of service UUID's from a vector. + * @param [in] complete If true the vector is the complete set of services. + * @param [in] size The bit size of the UUID's in the vector. (16, 32, or 128). + * @param [in] v_uuid The vector of service UUID's to advertise. + */ +void NimBLEExtAdvertisement::setServices(const bool complete, const uint8_t size, + const std::vector &v_uuid) +{ + char cdata[2]; + cdata[0] = (size / 8) * v_uuid.size() + 1; + switch(size) { + case 16: + cdata[1] = complete ? BLE_HS_ADV_TYPE_COMP_UUIDS16 : BLE_HS_ADV_TYPE_INCOMP_UUIDS16; + break; + case 32: + cdata[1] = complete ? BLE_HS_ADV_TYPE_COMP_UUIDS32 : BLE_HS_ADV_TYPE_INCOMP_UUIDS32; + break; + case 128: + cdata[1] = complete ? BLE_HS_ADV_TYPE_COMP_UUIDS128 : BLE_HS_ADV_TYPE_INCOMP_UUIDS128; + break; + default: + return; + } + + std::string uuids; + + for(auto &it : v_uuid){ + if(it.bitSize() != size) { + NIMBLE_LOGE(LOG_TAG, "Service UUID(%d) invalid", size); + return; + } else { + switch(size) { + case 16: + uuids += std::string((char*)&it.getNative()->u16.value, 2); + break; + case 32: + uuids += std::string((char*)&it.getNative()->u32.value, 4); + break; + case 128: + uuids += std::string((char*)&it.getNative()->u128.value, 16); + break; + default: + return; + } + } + } + + addData(std::string(cdata, 2) + uuids); +} // setServices + + +/** + * @brief Set the service data (UUID + data) + * @param [in] uuid The UUID to set with the service data. + * @param [in] data The data to be associated with the service data advertised. + */ +void NimBLEExtAdvertisement::setServiceData(const NimBLEUUID &uuid, const std::string &data) { + char cdata[2]; + switch (uuid.bitSize()) { + case 16: { + // [Len] [0x16] [UUID16] data + cdata[0] = data.length() + 3; + cdata[1] = BLE_HS_ADV_TYPE_SVC_DATA_UUID16; // 0x16 + addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->u16.value, 2) + data); + break; + } + + case 32: { + // [Len] [0x20] [UUID32] data + cdata[0] = data.length() + 5; + cdata[1] = BLE_HS_ADV_TYPE_SVC_DATA_UUID32; // 0x20 + addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->u32.value, 4) + data); + break; + } + + case 128: { + // [Len] [0x21] [UUID128] data + cdata[0] = data.length() + 17; + cdata[1] = BLE_HS_ADV_TYPE_SVC_DATA_UUID128; // 0x21 + addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->u128.value, 16) + data); + break; + } + + default: + return; + } +} // setServiceData + + +/** + * @brief Set the short name. + * @param [in] name The short name of the device. + */ +void NimBLEExtAdvertisement::setShortName(const std::string &name) { + char cdata[2]; + cdata[0] = name.length() + 1; + cdata[1] = BLE_HS_ADV_TYPE_INCOMP_NAME; // 0x08 + addData(std::string(cdata, 2) + name); +} // setShortName + + +/** + * @brief Adds Tx power level to the advertisement data. + */ +void NimBLEExtAdvertisement::addTxPower() { + m_params.include_tx_power = 1; +} // addTxPower + + +/** + * @brief Set the preferred connection interval parameters. + * @param [in] min The minimum interval desired. + * @param [in] max The maximum interval desired. + */ +void NimBLEExtAdvertisement::setPreferredParams(uint16_t min, uint16_t max) { + uint8_t data[6]; + data[0] = BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN + 1; + data[1] = BLE_HS_ADV_TYPE_SLAVE_ITVL_RANGE; + data[2] = min; + data[3] = min >> 8; + data[4] = max; + data[5] = max >> 8; + addData(data, 6); +} // setPreferredParams + +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_BROADCASTER && CONFIG_BT_NIMBLE_EXT_ADV */ diff --git a/src/NimBLEExtAdvertising.h b/src/NimBLEExtAdvertising.h new file mode 100644 index 0000000..b1f21fc --- /dev/null +++ b/src/NimBLEExtAdvertising.h @@ -0,0 +1,152 @@ +/* + * NimBLEExtAdvertising.h + * + * Created: on February 6, 2022 + * Author H2zero + */ + +#ifndef MAIN_BLEEXTADVERTISING_H_ +#define MAIN_BLEEXTADVERTISING_H_ +#include "nimconfig.h" +#if defined(CONFIG_BT_ENABLED) && \ + defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) && \ + CONFIG_BT_NIMBLE_EXT_ADV + +# if defined(CONFIG_NIMBLE_CPP_IDF) +# include "host/ble_gap.h" +# else +# include "nimble/nimble/host/include/host/ble_gap.h" +# endif + +/**** FIX COMPILATION ****/ +#undef min +#undef max +/**************************/ + +#include "NimBLEAddress.h" +#include "NimBLEUUID.h" + +#include + +class NimBLEExtAdvertisingCallbacks; + + +/** + * @brief Extended advertisement data + */ +class NimBLEExtAdvertisement { +public: + NimBLEExtAdvertisement(uint8_t priPhy = BLE_HCI_LE_PHY_1M, + uint8_t secPhy = BLE_HCI_LE_PHY_1M); + void setAppearance(uint16_t appearance); + void setCompleteServices(const NimBLEUUID &uuid); + void setCompleteServices16(const std::vector &v_uuid); + void setCompleteServices32(const std::vector &v_uuid); + void setFlags(uint8_t flag); + void setManufacturerData(const std::string &data); + void setURI(const std::string &uri); + void setName(const std::string &name); + void setPartialServices(const NimBLEUUID &uuid); + void setPartialServices16(const std::vector &v_uuid); + void setPartialServices32(const std::vector &v_uuid); + void setServiceData(const NimBLEUUID &uuid, const std::string &data); + void setShortName(const std::string &name); + void setData(const uint8_t * data, size_t length); + void addData(const std::string &data); + void addData(const uint8_t * data, size_t length); + void addTxPower(); + void setPreferredParams(uint16_t min, uint16_t max); + void setLegacyAdvertising(bool val); + void setConnectable(bool val); + void setScannable(bool val); + void setMinInterval(uint32_t mininterval); + void setMaxInterval(uint32_t maxinterval); + void setPrimaryPhy(uint8_t phy); + void setSecondaryPhy(uint8_t phy); + void setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly); + void setDirectedPeer(const NimBLEAddress & addr); + void setDirected(bool val, bool high_duty = true); + void setAnonymous(bool val); + void setPrimaryChannels(bool ch37, bool ch38, bool ch39); + void setTxPower(int8_t dbm); + void setAddress(const NimBLEAddress & addr); + void enableScanRequestCallback(bool enable); + void clearData(); + size_t getDataSize(); + +private: + friend class NimBLEExtAdvertising; + + void setServices(const bool complete, const uint8_t size, + const std::vector &v_uuid); + + std::vector m_payload; + ble_gap_ext_adv_params m_params; + NimBLEAddress m_advAddress; +}; // NimBLEExtAdvertisement + + +/** + * @brief Extended advertising class. + */ +class NimBLEExtAdvertising { +public: + /** + * @brief Construct an extended advertising object. + */ + NimBLEExtAdvertising() :m_advStatus(CONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES + 1, false) {} + ~NimBLEExtAdvertising(); + bool start(uint8_t inst_id, int duration = 0, int max_events = 0); + bool setInstanceData(uint8_t inst_id, NimBLEExtAdvertisement& adv); + bool setScanResponseData(uint8_t inst_id, NimBLEExtAdvertisement & data); + bool removeInstance(uint8_t inst_id); + bool removeAll(); + bool stop(uint8_t inst_id); + bool stop(); + bool isActive(uint8_t inst_id); + bool isAdvertising(); + void setCallbacks(NimBLEExtAdvertisingCallbacks* callbacks, + bool deleteCallbacks = true); + +private: + friend class NimBLEDevice; + friend class NimBLEServer; + + void onHostSync(); + static int handleGapEvent(struct ble_gap_event *event, void *arg); + + bool m_scanResp; + bool m_deleteCallbacks; + NimBLEExtAdvertisingCallbacks* m_pCallbacks; + ble_gap_ext_adv_params m_advParams; + std::vector m_advStatus; +}; + + +/** + * @brief Callbacks associated with NimBLEExtAdvertising class. + */ +class NimBLEExtAdvertisingCallbacks { +public: + virtual ~NimBLEExtAdvertisingCallbacks() {}; + + /** + * @brief Handle an advertising stop event. + * @param [in] pAdv A convenience pointer to the extended advertising interface. + * @param [in] reason The reason code for stopping the advertising. + * @param [in] inst_id The instance ID of the advertisement that was stopped. + */ + virtual void onStopped(NimBLEExtAdvertising *pAdv, int reason, uint8_t inst_id); + + /** + * @brief Handle a scan response request. + * This is called when a scanning device requests a scan response. + * @param [in] pAdv A convenience pointer to the extended advertising interface. + * @param [in] inst_id The instance ID of the advertisement that the scan response request was made. + * @param [in] addr The address of the device making the request. + */ + virtual void onScanRequest(NimBLEExtAdvertising *pAdv, uint8_t inst_id, NimBLEAddress addr); +}; // NimBLEExtAdvertisingCallbacks + +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_BROADCASTER && CONFIG_BT_NIMBLE_EXT_ADV */ +#endif /* MAIN_BLEADVERTISING_H_ */ diff --git a/src/NimBLEScan.cpp b/src/NimBLEScan.cpp index a19db9d..eb87df8 100644 --- a/src/NimBLEScan.cpp +++ b/src/NimBLEScan.cpp @@ -56,8 +56,8 @@ NimBLEScan::~NimBLEScan() { * @param [in] param Parameter data for this event. */ /*STATIC*/int NimBLEScan::handleGapEvent(ble_gap_event* event, void* arg) { - - NimBLEScan* pScan = (NimBLEScan*)arg; + (void)arg; + NimBLEScan* pScan = NimBLEDevice::getScan(); switch(event->type) { @@ -67,16 +67,16 @@ NimBLEScan::~NimBLEScan() { NIMBLE_LOGI(LOG_TAG, "Scan op in progress - ignoring results"); return 0; } -#if MYNEWT_VAL(BLE_EXT_ADV) +#if CONFIG_BT_NIMBLE_EXT_ADV const auto& disc = event->ext_disc; - const auto event_type = disc.legacy_event_type; + const bool isLegacyAdv = disc.props & BLE_HCI_ADV_LEGACY_MASK; + const auto event_type = isLegacyAdv ? disc.legacy_event_type : disc.props; #else const auto& disc = event->disc; + const bool isLegacyAdv = true; const auto event_type = disc.event_type; #endif - NimBLEAddress advertisedAddress(disc.addr); - // Examine our list of ignored addresses and stop processing if we don't want to see it or are already connected if(NimBLEDevice::isIgnored(advertisedAddress)) { @@ -88,7 +88,12 @@ NimBLEScan::~NimBLEScan() { // If we've seen this device before get a pointer to it from the vector for(auto &it: pScan->m_scanResults.m_advertisedDevicesVector) { - if(it->getAddress() == advertisedAddress) { +#if CONFIG_BT_NIMBLE_EXT_ADV + // Same address but different set ID should create a new advertised device. + if (it->getAddress() == advertisedAddress && it->getSetId() == disc.sid) { +#else + if (it->getAddress() == advertisedAddress) { +#endif advertisedDevice = it; break; } @@ -96,20 +101,27 @@ NimBLEScan::~NimBLEScan() { // If we haven't seen this device before; create a new instance and insert it in the vector. // Otherwise just update the relevant parameters of the already known device. - if(advertisedDevice == nullptr && event_type != BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP){ + if (advertisedDevice == nullptr && + (!isLegacyAdv || event_type != BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP)) { // Check if we have reach the scan results limit, ignore this one if so. // We still need to store each device when maxResults is 0 to be able to append the scan results - if(pScan->m_maxResults > 0 && pScan->m_maxResults < 0xFF && - (pScan->m_scanResults.m_advertisedDevicesVector.size() >= pScan->m_maxResults)) - { + if (pScan->m_maxResults > 0 && pScan->m_maxResults < 0xFF && + (pScan->m_scanResults.m_advertisedDevicesVector.size() >= pScan->m_maxResults)) { return 0; } + advertisedDevice = new NimBLEAdvertisedDevice(); advertisedDevice->setAddress(advertisedAddress); - advertisedDevice->setAdvType(event_type); + advertisedDevice->setAdvType(event_type, isLegacyAdv); +#if CONFIG_BT_NIMBLE_EXT_ADV + advertisedDevice->setSetId(disc.sid); + advertisedDevice->setPrimaryPhy(disc.prim_phy); + advertisedDevice->setSecondaryPhy(disc.sec_phy); + advertisedDevice->setPeriodicInterval(disc.periodic_adv_itvl); +#endif pScan->m_scanResults.m_advertisedDevicesVector.push_back(advertisedDevice); NIMBLE_LOGI(LOG_TAG, "New advertiser: %s", advertisedAddress.toString().c_str()); - } else if(advertisedDevice != nullptr) { + } else if (advertisedDevice != nullptr) { NIMBLE_LOGI(LOG_TAG, "Updated advertiser: %s", advertisedAddress.toString().c_str()); } else { // Scan response from unknown device @@ -118,13 +130,13 @@ NimBLEScan::~NimBLEScan() { advertisedDevice->m_timestamp = time(nullptr); advertisedDevice->setRSSI(disc.rssi); - advertisedDevice->setPayload(disc.data, disc.length_data, - event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP); + advertisedDevice->setPayload(disc.data, disc.length_data, (isLegacyAdv && + event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP)); if (pScan->m_pAdvertisedDeviceCallbacks) { // If not active scanning or scan response is not available - // report the result to the callback now. - if(pScan->m_scan_params.passive || + // or extended advertisement scanning, report the result to the callback now. + if(pScan->m_scan_params.passive || !isLegacyAdv || (advertisedDevice->getAdvType() != BLE_HCI_ADV_TYPE_ADV_IND && advertisedDevice->getAdvType() != BLE_HCI_ADV_TYPE_ADV_SCAN_IND)) { @@ -132,7 +144,7 @@ NimBLEScan::~NimBLEScan() { pScan->m_pAdvertisedDeviceCallbacks->onResult(advertisedDevice); // Otherwise, wait for the scan response so we can report the complete data. - } else if (event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP) { + } else if (isLegacyAdv && event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP) { advertisedDevice->m_callbackSent = true; pScan->m_pAdvertisedDeviceCallbacks->onResult(advertisedDevice); } @@ -313,17 +325,27 @@ bool NimBLEScan::start(uint32_t duration, void (*scanCompleteCB)(NimBLEScanResul m_ignoreResults = true; } - -#if MYNEWT_VAL(BLE_EXT_ADV) - ble_gap_ext_disc_params scan_params; - scan_params.passive = m_scan_params.passive; - scan_params.itvl = m_scan_params.itvl; - scan_params.window = m_scan_params.window; - int rc = ble_gap_ext_disc(NimBLEDevice::m_own_addr_type, duration/10, 0, m_scan_params.filter_duplicates, m_scan_params.filter_policy, m_scan_params.limited, &scan_params, &scan_params, - NimBLEScan::handleGapEvent, this); +# if CONFIG_BT_NIMBLE_EXT_ADV + ble_gap_ext_disc_params scan_params; + scan_params.passive = m_scan_params.passive; + scan_params.itvl = m_scan_params.itvl; + scan_params.window = m_scan_params.window; + int rc = ble_gap_ext_disc(NimBLEDevice::m_own_addr_type, + duration/10, + 0, + m_scan_params.filter_duplicates, + m_scan_params.filter_policy, + m_scan_params.limited, + &scan_params, + &scan_params, + NimBLEScan::handleGapEvent, + NULL); #else - int rc = ble_gap_disc(NimBLEDevice::m_own_addr_type, duration, &m_scan_params, - NimBLEScan::handleGapEvent, this); + int rc = ble_gap_disc(NimBLEDevice::m_own_addr_type, + duration, + &m_scan_params, + NimBLEScan::handleGapEvent, + NULL); #endif switch(rc) { case 0: diff --git a/src/NimBLEServer.cpp b/src/NimBLEServer.cpp index 03ee785..f75dd62 100644 --- a/src/NimBLEServer.cpp +++ b/src/NimBLEServer.cpp @@ -42,7 +42,9 @@ NimBLEServer::NimBLEServer() { // m_svcChgChrHdl = 0xffff; // Future Use m_pServerCallbacks = &defaultCallbacks; m_gattsStarted = false; +#if !CONFIG_BT_NIMBLE_EXT_ADV m_advertiseOnDisconnect = true; +#endif m_svcChanged = false; m_deleteCallbacks = true; } // NimBLEServer @@ -139,15 +141,26 @@ NimBLEService *NimBLEServer::getServiceByHandle(uint16_t handle) { return nullptr; } + +#if CONFIG_BT_NIMBLE_EXT_ADV +/** + * @brief Retrieve the advertising object that can be used to advertise the existence of the server. + * @return An advertising object. + */ +NimBLEExtAdvertising* NimBLEServer::getAdvertising() { + return NimBLEDevice::getAdvertising(); +} // getAdvertising +#endif + +#if !CONFIG_BT_NIMBLE_EXT_ADV || defined(_DOXYGEN_) /** * @brief Retrieve the advertising object that can be used to advertise the existence of the server. - * * @return An advertising object. */ NimBLEAdvertising* NimBLEServer::getAdvertising() { return NimBLEDevice::getAdvertising(); } // getAdvertising - +#endif /** * @brief Sends a service changed notification and resets the GATT server. @@ -240,6 +253,7 @@ int NimBLEServer::disconnect(uint16_t connId, uint8_t reason) { } // disconnect +#if !CONFIG_BT_NIMBLE_EXT_ADV || defined(_DOXYGEN_) /** * @brief Set the server to automatically start advertising when a client disconnects. * @param [in] aod true == advertise, false == don't advertise. @@ -247,7 +261,7 @@ int NimBLEServer::disconnect(uint16_t connId, uint8_t reason) { void NimBLEServer::advertiseOnDisconnect(bool aod) { m_advertiseOnDisconnect = aod; } // advertiseOnDisconnect - +#endif /** * @brief Return the number of connected clients. @@ -325,7 +339,7 @@ NimBLEConnInfo NimBLEServer::getPeerIDInfo(uint16_t id) { */ /*STATIC*/ int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) { - NimBLEServer* server = (NimBLEServer*)arg; + NimBLEServer* server = NimBLEDevice::getServer(); NIMBLE_LOGD(LOG_TAG, ">> handleGapEvent: %s", NimBLEUtils::gapEventToString(event->type)); int rc = 0; @@ -337,7 +351,9 @@ int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) { if (event->connect.status != 0) { /* Connection failed; resume advertising */ NIMBLE_LOGE(LOG_TAG, "Connection failed"); +#if !CONFIG_BT_NIMBLE_EXT_ADV NimBLEDevice::startAdvertising(); +#endif } else { server->m_connectedPeersVec.push_back(event->connect.conn_handle); @@ -382,9 +398,11 @@ int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) { server->m_pServerCallbacks->onDisconnect(server); server->m_pServerCallbacks->onDisconnect(server, &event->disconnect.conn); +#if !CONFIG_BT_NIMBLE_EXT_ADV if(server->m_advertiseOnDisconnect) { server->startAdvertising(); } +#endif return 0; } // BLE_GAP_EVENT_DISCONNECT @@ -472,11 +490,15 @@ int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) { return 0; } // BLE_GAP_EVENT_NOTIFY_TX - case BLE_GAP_EVENT_ADV_COMPLETE: { - NIMBLE_LOGD(LOG_TAG, "Advertising Complete"); - NimBLEDevice::getAdvertising()->advCompleteCB(); - return 0; - } + + case BLE_GAP_EVENT_ADV_COMPLETE: +#if CONFIG_BT_NIMBLE_EXT_ADV + case BLE_GAP_EVENT_SCAN_REQ_RCVD: + return NimBLEExtAdvertising::handleGapEvent(event, arg); +#else + return NimBLEAdvertising::handleGapEvent(event, arg); +#endif + // BLE_GAP_EVENT_ADV_COMPLETE | BLE_GAP_EVENT_SCAN_REQ_RCVD case BLE_GAP_EVENT_CONN_UPDATE: { NIMBLE_LOGD(LOG_TAG, "Connection parameters updated."); @@ -653,7 +675,9 @@ void NimBLEServer::removeService(NimBLEService* service, bool deleteSvc) { service->m_removed = deleteSvc ? NIMBLE_ATT_REMOVE_DELETE : NIMBLE_ATT_REMOVE_HIDE; serviceChanged(); +#if !CONFIG_BT_NIMBLE_EXT_ADV NimBLEDevice::getAdvertising()->removeServiceUUID(service->getUUID()); +#endif } @@ -716,22 +740,52 @@ void NimBLEServer::resetGATT() { } +#if CONFIG_BT_NIMBLE_EXT_ADV /** * @brief Start advertising. - * - * Start the server advertising its existence. This is a convenience function and is equivalent to + * @param [in] inst_id The extended advertisement instance ID to start. + * @param [in] duration How long to advertise for in milliseconds, 0 = forever (default). + * @param [in] max_events Maximum number of advertisement events to send, 0 = no limit (default). + * @return True if advertising started successfully. + * @details Start the server advertising its existence. This is a convenience function and is equivalent to * retrieving the advertising object and invoking start upon it. */ -void NimBLEServer::startAdvertising() { - NimBLEDevice::startAdvertising(); +bool NimBLEServer::startAdvertising(uint8_t inst_id, + int duration, + int max_events) { + return getAdvertising()->start(inst_id, duration, max_events); } // startAdvertising /** - * @brief Stop advertising. + * @brief Convenience function to stop advertising a data set. + * @param [in] inst_id The extended advertisement instance ID to stop advertising. + * @return True if advertising stopped successfully. */ -void NimBLEServer::stopAdvertising() { - NimBLEDevice::stopAdvertising(); +bool NimBLEServer::stopAdvertising(uint8_t inst_id) { + return getAdvertising()->stop(inst_id); +} // stopAdvertising +#endif + +#if !CONFIG_BT_NIMBLE_EXT_ADV|| defined(_DOXYGEN_) +/** + * @brief Start advertising. + * @return True if advertising started successfully. + * @details Start the server advertising its existence. This is a convenience function and is equivalent to + * retrieving the advertising object and invoking start upon it. + */ +bool NimBLEServer::startAdvertising() { + return getAdvertising()->start(); +} // startAdvertising +#endif + + +/** + * @brief Stop advertising. + * @return True if advertising stopped successfully. + */ +bool NimBLEServer::stopAdvertising() { + return getAdvertising()->stop(); } // stopAdvertising diff --git a/src/NimBLEServer.h b/src/NimBLEServer.h index 605445a..5b946f0 100644 --- a/src/NimBLEServer.h +++ b/src/NimBLEServer.h @@ -25,7 +25,11 @@ #include "NimBLEUtils.h" #include "NimBLEAddress.h" +#if CONFIG_BT_NIMBLE_EXT_ADV +#include "NimBLEExtAdvertising.h" +#else #include "NimBLEAdvertising.h" +#endif #include "NimBLEService.h" #include "NimBLESecurity.h" #include "NimBLEConnInfo.h" @@ -46,11 +50,19 @@ public: NimBLEService* createService(const NimBLEUUID &uuid); void removeService(NimBLEService* service, bool deleteSvc = false); void addService(NimBLEService* service); - NimBLEAdvertising* getAdvertising(); void setCallbacks(NimBLEServerCallbacks* pCallbacks, bool deleteCallbacks = true); - void startAdvertising(); - void stopAdvertising(); +#if CONFIG_BT_NIMBLE_EXT_ADV + NimBLEExtAdvertising* getAdvertising(); + bool startAdvertising(uint8_t inst_id, + int duration = 0, + int max_events = 0); + bool stopAdvertising(uint8_t inst_id); +#else + NimBLEAdvertising* getAdvertising(); + bool startAdvertising(); +#endif + bool stopAdvertising(); void start(); NimBLEService* getServiceByUUID(const char* uuid, uint16_t instanceId = 0); NimBLEService* getServiceByUUID(const NimBLEUUID &uuid, uint16_t instanceId = 0); @@ -66,7 +78,9 @@ public: NimBLEConnInfo getPeerInfo(size_t index); NimBLEConnInfo getPeerInfo(const NimBLEAddress& address); NimBLEConnInfo getPeerIDInfo(uint16_t id); +#if !CONFIG_BT_NIMBLE_EXT_ADV || defined(_DOXYGEN_) void advertiseOnDisconnect(bool); +#endif private: NimBLEServer(); @@ -75,9 +89,15 @@ private: friend class NimBLEService; friend class NimBLEDevice; friend class NimBLEAdvertising; +#if CONFIG_BT_NIMBLE_EXT_ADV + friend class NimBLEExtAdvertising; + friend class NimBLEExtAdvertisementData; +#endif bool m_gattsStarted; +#if !CONFIG_BT_NIMBLE_EXT_ADV bool m_advertiseOnDisconnect; +#endif bool m_svcChanged; NimBLEServerCallbacks* m_pServerCallbacks; bool m_deleteCallbacks;