diff --git a/lib/libesp32/NimBLE-Arduino/CHANGELOG.md b/lib/libesp32/NimBLE-Arduino/CHANGELOG.md index 8dfc5a141..128d3c93d 100644 --- a/lib/libesp32/NimBLE-Arduino/CHANGELOG.md +++ b/lib/libesp32/NimBLE-Arduino/CHANGELOG.md @@ -2,6 +2,75 @@ All notable changes to this project will be documented in this file. +## [Unreleased] + +### Added +- `NimBLEDevice::setOwnAddrType` added to enable the use of random and random-resolvable addresses, by asukiaaa + +- New examples for securing and authenticating client/server connections, by mblasee. + +- `NimBLEAdvertiseing::SetMinPreferred` and `NimBLEAdvertiseing::SetMinPreferred` re-added. + +- Conditional checks added for command line config options in `nimconfig.h` to support custom configuration in platformio. + +- `NimBLEClient::setValue` Now takes an extra bool parameter `response` to enable the use of write with response (default = false). + +- `NimBLEClient::getCharacteristic(uint16_t handle)` Enabling the use of the characteristic handle to be used to find +the NimBLERemoteCharacteristic object. + +- `NimBLEHIDDevice` class added by wakwak-koba. + +- `NimBLEServerCallbacks::onDisconnect` overloaded callback added to provide a ble_gap_conn_desc parameter for the application +to obtain information about the disconnected client. + +- Conditional checks in `nimconfig.h` for command line defined macros to support platformio config settings. + +### Changed +- `NimBLEAdvertising::start` now returns a bool value to indicate success/failure. + +- Some asserts were removed in `NimBLEAdvertising::start` and replaced with better return code handling and logging. + +- If a host reset event occurs, scanning and advertising will now only be restarted if their previous duration was indefinite. + +- `NimBLERemoteCharacteristic::subscribe` and `NimBLERemoteCharacteristic::registerForNotify` will now set the callback +regardless of the existance of the CCCD and return true unless the descriptor write operation failed. + +- Advertising tx power level is now sent in the advertisement packet instead of scan response. + +- `NimBLEScan` When the scan ends the scan stopped flag is now set before calling the scan complete callback (if used) +this allows the starting of a new scan from the callback function. + +### Fixed +- Sometimes `NimBLEClient::connect` would hang on the task block if no event arrived to unblock. +A time limit has been added to timeout appropriately. + +- When getting descriptors for a characterisic the end handle of the service was used as a proxy for the characteristic end +handle. This would be rejected by some devices and has been changed to use the next characteristic handle as the end when possible. + +- An exception could occur when deleting a client instance if a notification arrived while the attribute vectors were being +deleted. A flag has been added to prevent this. + +- An exception could occur after a host reset event when the host re-synced if the tasks that were stopped during the event did +not finish processing. A yield has been added after re-syncing to allow tasks to finish before proceeding. + +- Occasionally the controller would fail to send a disconnected event causing the client to indicate it is connected +and would be unable to reconnect. A timer has been added to reset the host/controller if it expires. + +- Occasionally the call to start scanning would get stuck in a loop on BLE_HS_EBUSY, this loop has been removed. + +- 16bit and 32bit UUID's in some cases were not discovered or compared correctly if the device +advertised them as 16/32bit but resolved them to 128bits. Both are now checked. + +- `FreeRTOS` compile errors resolved in latest Ardruino core and IDF v3.3. + +- Multiple instances of `time()` called inside critical sections caused sporadic crashes, these have been moved out of critical regions. + +- Advertisement type now correctly set when using non-connectable (advertiser only) mode. + +- Advertising payload length correction, now accounts for appearance. + +- (Arduino) Ensure controller mode is set to BLE Only. + ## [1.0.2] - 2020-09-13 ### Changed diff --git a/lib/libesp32/NimBLE-Arduino/README.md b/lib/libesp32/NimBLE-Arduino/README.md index 120b0c782..ea28b8811 100644 --- a/lib/libesp32/NimBLE-Arduino/README.md +++ b/lib/libesp32/NimBLE-Arduino/README.md @@ -68,9 +68,9 @@ such as increasing max connections, default is 3, absolute maximum connections i
# Development Status -This Library is tracking the esp-nimble repo, nimble-1.2.0-idf master branch, currently [@95bd864.](https://github.com/espressif/esp-nimble) +This Library is tracking the esp-nimble repo, nimble-1.2.0-idf master branch, currently [@f4ae049.](https://github.com/espressif/esp-nimble) -Also tracking the NimBLE related changes in ESP-IDF, master branch, currently [@2ef4890.](https://github.com/espressif/esp-idf/tree/master/components/bt/host/nimble) +Also tracking the NimBLE related changes in ESP-IDF, master branch, currently [@3caa969.](https://github.com/espressif/esp-idf/tree/master/components/bt/host/nimble)
# Acknowledgments diff --git a/lib/libesp32/NimBLE-Arduino/docs/Command_line_config.md b/lib/libesp32/NimBLE-Arduino/docs/Command_line_config.md new file mode 100644 index 000000000..813156f76 --- /dev/null +++ b/lib/libesp32/NimBLE-Arduino/docs/Command_line_config.md @@ -0,0 +1,93 @@ +# Arduino command line and platformio config options + +`CONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED` + + If defined, NimBLE Client functions will not be included. +- Reduces flash size by approx. 7kB. +
+ +`CONFIG_BT_NIMBLE_ROLE_OBSERVER_DISABLED` + +If defined, NimBLE Scan functions will not be included. +- Reduces flash size by approx. 26kB. +
+ +`CONFIG_BT_NIMBLE_ROLE_PERIPHERAL_DISABLED` + +If defined NimBLE Server functions will not be included. +- Reduces flash size by approx. 16kB. +
+ +`CONFIG_BT_NIMBLE_ROLE_BROADCASTER_DISABLED` + +If defined, NimBLE Advertising functions will not be included. +- Reduces flash size by approx. 5kB. +
+ +`CONFIG_BT_NIMBLE_DEBUG` + +If defined, enables debug log messages from the NimBLE host +- Uses approx. 32kB of flash memory. +
+ +`CONFIG_NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT` + +If defined, NimBLE host return codes will be printed as text in debug log messages. +- Uses approx. 7kB of flash memory. +
+ +`CONFIG_NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT` + +If defined, GAP event codes will be printed as text in debug log messages. +- Uses approx. 1kB of flash memory. +
+ +`CONFIG_NIMBLE_CPP_ENABLE_ADVERTISMENT_TYPE_TEXT` + +If defined, advertisment types will be printed as text while scanning in debug log messages. +- Uses approx. 250 bytes of flash memory. +
+ +`CONFIG_BT_NIMBLE_PINNED_TO_CORE` + +Sets the core the NimBLE host stack will run on +- Options: 0 or 1 +
+ +`CONFIG_BT_NIMBLE_TASK_STACK_SIZE` + +Set the task stack size for the NimBLE core. +- Default is 4096 +
+ + +`CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL` + +Sets the NimBLE stack to use external PSRAM will be loaded +- Must be defined with a value of 1; Default is CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_INTERNAL 1 +
+ +`CONFIG_BT_NIMBLE_MAX_CONNECTIONS` + +Sets the number of simultaneous connections (esp controller max is 9) +- Default value is 3 +
+ +`CONFIG_BT_NIMBLE_MAX_BONDS` + +Sets the number of devices allowed to store/bond with +- Default value is 3 +
+ +`CONFIG_BT_NIMBLE_MAX_CCCDS` + +Sets the maximum number of CCCD subscriptions to store +- Default value is 8 +
+ +`CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME` + +Set the default device name +- Default value is "nimble" +
+ diff --git a/lib/libesp32/NimBLE-Arduino/examples/NimBLE_Secure_Client/NimBLE_Secure_Client.ino b/lib/libesp32/NimBLE-Arduino/examples/NimBLE_Secure_Client/NimBLE_Secure_Client.ino new file mode 100644 index 000000000..6f9af4f74 --- /dev/null +++ b/lib/libesp32/NimBLE-Arduino/examples/NimBLE_Secure_Client/NimBLE_Secure_Client.ino @@ -0,0 +1,91 @@ +/** NimBLE_Secure_Client Demo: + * + * This example demonstrates the secure passkey protected conenction and communication between an esp32 server and an esp32 client. + * Please note that esp32 stores auth info in nvs memory. After a successful connection it is possible that a passkey change will be ineffective. + * To avoid this clear the memory of the esp32's between security testings. esptool.py is capable of this, example: esptool.py --port /dev/ttyUSB0 erase_flash. + * + * Created: on Jan 08 2021 + * Author: mblasee + */ + +#include + +class ClientCallbacks : public NimBLEClientCallbacks +{ + uint32_t onPassKeyRequest() + { + Serial.println("Client Passkey Request"); + /** return the passkey to send to the server */ + /** Change this to be different from NimBLE_Secure_Server if you want to test what happens on key mismatch */ + return 123456; + }; +}; +static ClientCallbacks clientCB; + +void setup() +{ + Serial.begin(115200); + Serial.println("Starting NimBLE Client"); + + NimBLEDevice::init(""); + NimBLEDevice::setPower(ESP_PWR_LVL_P9); + NimBLEDevice::setSecurityAuth(true, true, true); + NimBLEDevice::setSecurityIOCap(BLE_HS_IO_KEYBOARD_ONLY); + NimBLEScan *pScan = NimBLEDevice::getScan(); + NimBLEScanResults results = pScan->start(5); + + NimBLEUUID serviceUuid("ABCD"); + + for (int i = 0; i < results.getCount(); i++) + { + NimBLEAdvertisedDevice device = results.getDevice(i); + Serial.println(device.getName().c_str()); + + if (device.isAdvertisingService(serviceUuid)) + { + NimBLEClient *pClient = NimBLEDevice::createClient(); + pClient->setClientCallbacks(&clientCB, false); + + if (pClient->connect(&device)) + { + pClient->secureConnection(); + NimBLERemoteService *pService = pClient->getService(serviceUuid); + if (pService != nullptr) + { + NimBLERemoteCharacteristic *pNonSecureCharacteristic = pService->getCharacteristic("1234"); + + if (pNonSecureCharacteristic != nullptr) + { + // Testing to read a non secured characteristic, you should be able to read this even if you have mismatching passkeys. + std::string value = pNonSecureCharacteristic->readValue(); + // print or do whatever you need with the value + Serial.println(value.c_str()); + } + + NimBLERemoteCharacteristic *pSecureCharacteristic = pService->getCharacteristic("1235"); + + if (pSecureCharacteristic != nullptr) + { + // Testing to read a secured characteristic, you should be able to read this only if you have matching passkeys, otherwise you should + // get an error like this. E NimBLERemoteCharacteristic: "<< readValue rc=261" + // This means you are trying to do something without the proper permissions. + std::string value = pSecureCharacteristic->readValue(); + // print or do whatever you need with the value + Serial.println(value.c_str()); + } + } + } + else + { + // failed to connect + Serial.println("failed to connect"); + } + + NimBLEDevice::deleteClient(pClient); + } + } +} + +void loop() +{ +} diff --git a/lib/libesp32/NimBLE-Arduino/examples/NimBLE_Secure_Server/NimBLE_Secure_Server.ino b/lib/libesp32/NimBLE-Arduino/examples/NimBLE_Secure_Server/NimBLE_Secure_Server.ino new file mode 100644 index 000000000..f63cbc162 --- /dev/null +++ b/lib/libesp32/NimBLE-Arduino/examples/NimBLE_Secure_Server/NimBLE_Secure_Server.ino @@ -0,0 +1,37 @@ +/** NimBLE_Secure_Server Demo: + * + * This example demonstrates the secure passkey protected conenction and communication between an esp32 server and an esp32 client. + * Please note that esp32 stores auth info in nvs memory. After a successful connection it is possible that a passkey change will be ineffective. + * To avoid this clear the memory of the esp32's between security testings. esptool.py is capable of this, example: esptool.py --port /dev/ttyUSB0 erase_flash. + * + * Created: on Jan 08 2021 + * Author: mblasee + */ + +#include + +void setup() { + Serial.begin(115200); + Serial.println("Starting NimBLE Server"); + NimBLEDevice::init("NimBLE"); + NimBLEDevice::setPower(ESP_PWR_LVL_P9); + + NimBLEDevice::setSecurityAuth(true, true, true); + NimBLEDevice::setSecurityPasskey(123456); + NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY); + NimBLEServer *pServer = NimBLEDevice::createServer(); + NimBLEService *pService = pServer->createService("ABCD"); + NimBLECharacteristic *pNonSecureCharacteristic = pService->createCharacteristic("1234", NIMBLE_PROPERTY::READ ); + NimBLECharacteristic *pSecureCharacteristic = pService->createCharacteristic("1235", NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::READ_AUTHEN); + + pService->start(); + pNonSecureCharacteristic->setValue("Hello Non Secure BLE"); + pSecureCharacteristic->setValue("Hello Secure BLE"); + + NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); + pAdvertising->addServiceUUID("ABCD"); + pAdvertising->start(); +} + +void loop() { +} diff --git a/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_notify/BLE_notify.ino b/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_notify/BLE_notify.ino index 83f129b83..cb0488819 100644 --- a/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_notify/BLE_notify.ino +++ b/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_notify/BLE_notify.ino @@ -116,9 +116,9 @@ void setup() { BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->addServiceUUID(SERVICE_UUID); pAdvertising->setScanResponse(false); - /**This method is removed as it was no longer useful and consumed advertising space - * pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter - */ + /** Note, this could be left out as that is the default value */ + pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter + BLEDevice::startAdvertising(); Serial.println("Waiting a client connection to notify..."); } diff --git a/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_server/BLE_server.ino b/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_server/BLE_server.ino index 652d77685..faa4d88ea 100644 --- a/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_server/BLE_server.ino +++ b/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_server/BLE_server.ino @@ -44,10 +44,9 @@ void setup() { BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->addServiceUUID(SERVICE_UUID); pAdvertising->setScanResponse(true); - /**These methods are removed as they are no longer useful and consumed advertising space - * pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue - * pAdvertising->setMinPreferred(0x12); - */ + pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue + pAdvertising->setMaxPreferred(0x12); + BLEDevice::startAdvertising(); Serial.println("Characteristic defined! Now you can read it in your phone!"); } diff --git a/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_server_multiconnect/BLE_server_multiconnect.ino b/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_server_multiconnect/BLE_server_multiconnect.ino index 2ec38c481..9ae3859c7 100644 --- a/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_server_multiconnect/BLE_server_multiconnect.ino +++ b/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_server_multiconnect/BLE_server_multiconnect.ino @@ -120,9 +120,9 @@ void setup() { BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->addServiceUUID(SERVICE_UUID); pAdvertising->setScanResponse(false); - /**This method is removed it was no longer useful and consumed advertising space - * pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter - */ + /** Note, this could be left out as that is the default value */ + pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter + BLEDevice::startAdvertising(); Serial.println("Waiting a client connection to notify..."); } diff --git a/lib/libesp32/NimBLE-Arduino/src/FreeRTOS.cpp b/lib/libesp32/NimBLE-Arduino/src/FreeRTOS.cpp index 4ebab3958..1c398cbf3 100644 --- a/lib/libesp32/NimBLE-Arduino/src/FreeRTOS.cpp +++ b/lib/libesp32/NimBLE-Arduino/src/FreeRTOS.cpp @@ -264,10 +264,14 @@ void FreeRTOS::Semaphore::setName(std::string name) { * @param [in] length The amount of storage to allocate for the ring buffer. * @param [in] type The type of buffer. One of RINGBUF_TYPE_NOSPLIT, RINGBUF_TYPE_ALLOWSPLIT, RINGBUF_TYPE_BYTEBUF. */ -#if defined(ESP_IDF_VERSION) && !defined(ESP_IDF_VERSION_VAL) //Quick hack to detect if using IDF version that replaced ringbuf_type_t, ESP_IDF_VERSION_VAL is for IDF>4.0.0 -Ringbuffer::Ringbuffer(size_t length, RingbufferType_t type) { +#ifdef ESP_IDF_VERSION //Quick hack to detect if using IDF version that replaced ringbuf_type_t +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) + Ringbuffer::Ringbuffer(size_t length, RingbufferType_t type) { #else -Ringbuffer::Ringbuffer(size_t length, ringbuf_type_t type) { + Ringbuffer::Ringbuffer(size_t length, ringbuf_type_t type) { +#endif +#else + Ringbuffer::Ringbuffer(size_t length, ringbuf_type_t type) { #endif m_handle = ::xRingbufferCreate(length, type); } // Ringbuffer diff --git a/lib/libesp32/NimBLE-Arduino/src/FreeRTOS.h b/lib/libesp32/NimBLE-Arduino/src/FreeRTOS.h index f93f0b1a0..fa33921fe 100644 --- a/lib/libesp32/NimBLE-Arduino/src/FreeRTOS.h +++ b/lib/libesp32/NimBLE-Arduino/src/FreeRTOS.h @@ -68,8 +68,12 @@ public: */ class Ringbuffer { public: -#if defined(ESP_IDF_VERSION) && !defined(ESP_IDF_VERSION_VAL) //Quick hack to detect if using IDF version that replaced ringbuf_type_t, ESP_IDF_VERSION_VAL is for IDF>4.0.0 +#ifdef ESP_IDF_VERSION //Quick hack to detect if using IDF version that replaced ringbuf_type_t +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) Ringbuffer(size_t length, RingbufferType_t type = RINGBUF_TYPE_NOSPLIT); +#else + Ringbuffer(size_t length, ringbuf_type_t type = RINGBUF_TYPE_NOSPLIT); +#endif #else Ringbuffer(size_t length, ringbuf_type_t type = RINGBUF_TYPE_NOSPLIT); #endif diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLE2904.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLE2904.cpp index d85cd87e4..80318b5b8 100644 --- a/lib/libesp32/NimBLE-Arduino/src/NimBLE2904.cpp +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLE2904.cpp @@ -37,7 +37,7 @@ NimBLE2904::NimBLE2904(NimBLECharacteristic* pCharacterisitic) m_data.m_unit = 0; m_data.m_description = 0; setValue((uint8_t*) &m_data, sizeof(m_data)); -} // BLE2902 +} // BLE2904 /** diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEAdvertising.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLEAdvertising.cpp index 36bdbf9e9..a10480410 100644 --- a/lib/libesp32/NimBLE-Arduino/src/NimBLEAdvertising.cpp +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEAdvertising.cpp @@ -32,7 +32,7 @@ static const char* LOG_TAG = "NimBLEAdvertising"; /** * @brief Construct a default advertising object. */ -NimBLEAdvertising::NimBLEAdvertising() { +NimBLEAdvertising::NimBLEAdvertising() : m_slaveItvl() { memset(&m_advData, 0, sizeof m_advData); memset(&m_scanData, 0, sizeof m_scanData); memset(&m_advParams, 0, sizeof m_advParams); @@ -41,15 +41,20 @@ NimBLEAdvertising::NimBLEAdvertising() { m_advData.name = (uint8_t *)name; m_advData.name_len = strlen(name); m_advData.name_is_complete = 1; - m_scanData.tx_pwr_lvl_is_present = 1; - m_scanData.tx_pwr_lvl = NimBLEDevice::getPower(); + m_advData.tx_pwr_lvl_is_present = 1; + m_advData.tx_pwr_lvl = NimBLEDevice::getPower(); m_advData.flags = (BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP); m_advData.appearance = 0; m_advData.appearance_is_present = 0; m_advData.mfg_data_len = 0; m_advData.mfg_data = nullptr; + m_advData.slave_itvl_range = nullptr; +#if !defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) + m_advParams.conn_mode = BLE_GAP_CONN_MODE_NON; +#else m_advParams.conn_mode = BLE_GAP_CONN_MODE_UND; +#endif m_advParams.disc_mode = BLE_GAP_DISC_MODE_GEN; m_advParams.itvl_min = 0; m_advParams.itvl_max = 0; @@ -58,6 +63,8 @@ NimBLEAdvertising::NimBLEAdvertising() { m_customScanResponseData = false; m_scanResp = true; m_advDataSet = false; + // Set this to non-zero to prevent auto start if host reset before started by app. + m_duration = BLE_HS_FOREVER; } // NimBLEAdvertising @@ -86,7 +93,6 @@ void NimBLEAdvertising::addServiceUUID(const char* serviceUUID) { * @param [in] serviceUUID The UUID of the service to expose. */ void NimBLEAdvertising::removeServiceUUID(const NimBLEUUID &serviceUUID) { - //m_serviceUUIDs.erase(std::remove_if(m_serviceUUIDs.begin(), m_serviceUUIDs.end(),[serviceUUID](const NimBLEUUID &s) {return serviceUUID == s;}), m_serviceUUIDs.end()); for(auto it = m_serviceUUIDs.begin(); it != m_serviceUUIDs.end(); ++it) { if((*it) == serviceUUID) { m_serviceUUIDs.erase(it); @@ -112,11 +118,9 @@ void NimBLEAdvertising::setAppearance(uint16_t appearance) { /** * @brief Set the type of advertisment to use. * @param [in] adv_type: - * * BLE_HCI_ADV_TYPE_ADV_IND (0) - indirect advertising - * * BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_HD (1) - direct advertisng - high duty cycle - * * BLE_HCI_ADV_TYPE_ADV_SCAN_IND (2) - indirect scan response - * * BLE_HCI_ADV_TYPE_ADV_NONCONN_IND (3) - indirect advertisng - not connectable - * * BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_LD (4) - direct advertising - low duty cycle + * * BLE_GAP_CONN_MODE_NON (0) - not connectable advertising + * * BLE_GAP_CONN_MODE_DIR (1) - directed connectable advertising + * * BLE_GAP_CONN_MODE_UND (2) - undirected connectable advertising */ void NimBLEAdvertising::setAdvertisementType(uint8_t adv_type){ m_advParams.conn_mode = adv_type; @@ -141,6 +145,64 @@ void NimBLEAdvertising::setMaxInterval(uint16_t maxinterval) { } // setMaxInterval +/** + * @brief Set the advertised min connection interval preferred by this device. + * @param [in] mininterval the max interval value. Range = 0x0006 to 0x0C80. + * @details Values not within the range will cancel advertising of this data.\n + * Consumes 6 bytes of advertising space (combined with max interval). + */ +void NimBLEAdvertising::setMinPreferred(uint16_t mininterval) { + // invalid paramters, set the slave interval to null + if(mininterval < 0x0006 || mininterval > 0x0C80) { + m_advData.slave_itvl_range = nullptr; + return; + } + + if(m_advData.slave_itvl_range == nullptr) { + m_advData.slave_itvl_range = m_slaveItvl; + } + + m_slaveItvl[0] = mininterval; + m_slaveItvl[1] = mininterval >> 8; + + uint16_t maxinterval = *(uint16_t*)(m_advData.slave_itvl_range+2); + + // If mininterval is higher than the maxinterval make them the same + if(mininterval > maxinterval) { + m_slaveItvl[2] = m_slaveItvl[0]; + m_slaveItvl[3] = m_slaveItvl[1]; + } +} // setMinPreferred + + +/** + * @brief Set the advertised max connection interval preferred by this device. + * @param [in] maxinterval the max interval value. Range = 0x0006 to 0x0C80. + * @details Values not within the range will cancel advertising of this data.\n + * Consumes 6 bytes of advertising space (combined with min interval). + */ +void NimBLEAdvertising::setMaxPreferred(uint16_t maxinterval) { + // invalid paramters, set the slave interval to null + if(maxinterval < 0x0006 || maxinterval > 0x0C80) { + m_advData.slave_itvl_range = nullptr; + return; + } + if(m_advData.slave_itvl_range == nullptr) { + m_advData.slave_itvl_range = m_slaveItvl; + } + m_slaveItvl[2] = maxinterval; + m_slaveItvl[3] = maxinterval >> 8; + + uint16_t mininterval = *(uint16_t*)(m_advData.slave_itvl_range); + + // If mininterval is higher than the maxinterval make them the same + if(mininterval > maxinterval) { + m_slaveItvl[0] = m_slaveItvl[2]; + m_slaveItvl[1] = m_slaveItvl[3]; + } +} // setMaxPreferred + + /** * @brief Set if scan response is available. * @param [in] set true = scan response available. @@ -156,7 +218,8 @@ void NimBLEAdvertising::setScanResponse(bool set) { * @param [in] connectWhitelistOnly If true, only allow connections from those on the white list. */ void NimBLEAdvertising::setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly) { - NIMBLE_LOGD(LOG_TAG, ">> setScanFilter: scanRequestWhitelistOnly: %d, connectWhitelistOnly: %d", scanRequestWhitelistOnly, connectWhitelistOnly); + NIMBLE_LOGD(LOG_TAG, ">> setScanFilter: scanRequestWhitelistOnly: %d, connectWhitelistOnly: %d", + scanRequestWhitelistOnly, connectWhitelistOnly); if (!scanRequestWhitelistOnly && !connectWhitelistOnly) { m_advParams.filter_policy = BLE_HCI_ADV_FILT_NONE; NIMBLE_LOGD(LOG_TAG, "<< setScanFilter"); @@ -194,7 +257,8 @@ void NimBLEAdvertising::setAdvertisementData(NimBLEAdvertisementData& advertisem (uint8_t*)advertisementData.getPayload().data(), advertisementData.getPayload().length()); if (rc != 0) { - NIMBLE_LOGE(LOG_TAG, "ble_gap_adv_set_data: %d %s", rc, NimBLEUtils::returnCodeToString(rc)); + NIMBLE_LOGE(LOG_TAG, "ble_gap_adv_set_data: %d %s", + rc, NimBLEUtils::returnCodeToString(rc)); } m_customAdvData = true; // Set the flag that indicates we are using custom advertising data. NIMBLE_LOGD(LOG_TAG, "<< setAdvertisementData"); @@ -213,7 +277,8 @@ void NimBLEAdvertising::setScanResponseData(NimBLEAdvertisementData& advertiseme (uint8_t*)advertisementData.getPayload().data(), advertisementData.getPayload().length()); if (rc != 0) { - NIMBLE_LOGE(LOG_TAG, "ble_gap_adv_rsp_set_data: %d %s", rc, NimBLEUtils::returnCodeToString(rc)); + NIMBLE_LOGE(LOG_TAG, "ble_gap_adv_rsp_set_data: %d %s", + rc, NimBLEUtils::returnCodeToString(rc)); } m_customScanResponseData = true; // Set the flag that indicates we are using custom scan response data. NIMBLE_LOGD(LOG_TAG, "<< setScanResponseData"); @@ -225,13 +290,14 @@ void NimBLEAdvertising::setScanResponseData(NimBLEAdvertisementData& advertiseme * @param [in] duration The duration, in seconds, to advertise, 0 == advertise forever. * @param [in] advCompleteCB A pointer to a callback to be invoked when advertising ends. */ -void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdvertising *pAdv)) { - NIMBLE_LOGD(LOG_TAG, ">> Advertising start: customAdvData: %d, customScanResponseData: %d", m_customAdvData, m_customScanResponseData); +bool NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdvertising *pAdv)) { + NIMBLE_LOGD(LOG_TAG, ">> Advertising start: customAdvData: %d, customScanResponseData: %d", + m_customAdvData, m_customScanResponseData); // If Host is not synced we cannot start advertising. if(!NimBLEDevice::m_synced) { NIMBLE_LOGE(LOG_TAG, "Host reset, wait for sync."); - return; + return false; } #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) @@ -240,17 +306,21 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv if(!pServer->m_gattsStarted){ pServer->start(); } else if(pServer->getConnectedCount() >= NIMBLE_MAX_CONNECTIONS) { - NIMBLE_LOGW(LOG_TAG, "Max connections reached - not advertising"); - return; + NIMBLE_LOGE(LOG_TAG, "Max connections reached - not advertising"); + return false; } } #endif // If already advertising just return if(ble_gap_adv_active()) { - return; + NIMBLE_LOGW(LOG_TAG, "Advertising already active"); + return false; } + // Save the duration incase of host reset so we can restart with the same params + m_duration = duration; + if(duration == 0){ duration = BLE_HS_FOREVER; } @@ -260,16 +330,31 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv m_advCompCB = advCompleteCB; + m_advParams.disc_mode = BLE_GAP_DISC_MODE_GEN; + m_advData.flags = (BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP); + if(m_advParams.conn_mode == BLE_GAP_CONN_MODE_NON) { + if(!m_scanResp) { + m_advParams.disc_mode = BLE_GAP_DISC_MODE_NON; + m_advData.flags = BLE_HS_ADV_F_BREDR_UNSUP; + } + } + int rc = 0; if (!m_customAdvData && !m_advDataSet) { //start with 3 bytes for the flags data - uint8_t payloadLen = 3; + uint8_t payloadLen = (2 + 1); + if(m_advData.appearance_is_present) + payloadLen += (2 + BLE_HS_ADV_APPEARANCE_LEN); + if(m_advData.tx_pwr_lvl_is_present) + payloadLen += (2 + 1); + if(m_advData.slave_itvl_range != nullptr) + payloadLen += (2 + 4); for(auto &it : m_serviceUUIDs) { if(it.getNative()->u.type == BLE_UUID_TYPE_16) { int add = (m_advData.num_uuids16 > 0) ? 2 : 4; - if((payloadLen + add) > 31){ + if((payloadLen + add) > BLE_HS_ADV_MAX_SZ){ m_advData.uuids16_is_complete = 0; continue; } @@ -278,7 +363,7 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv if(nullptr == (m_advData.uuids16 = (ble_uuid16_t*)realloc(m_advData.uuids16, (m_advData.num_uuids16 + 1) * sizeof(ble_uuid16_t)))) { - NIMBLE_LOGE(LOG_TAG, "Error, no mem"); + NIMBLE_LOGC(LOG_TAG, "Error, no mem"); abort(); } memcpy(&m_advData.uuids16[m_advData.num_uuids16].value, @@ -290,7 +375,7 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv } if(it.getNative()->u.type == BLE_UUID_TYPE_32) { int add = (m_advData.num_uuids32 > 0) ? 4 : 6; - if((payloadLen + add) > 31){ + if((payloadLen + add) > BLE_HS_ADV_MAX_SZ){ m_advData.uuids32_is_complete = 0; continue; } @@ -299,7 +384,7 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv if(nullptr == (m_advData.uuids32 = (ble_uuid32_t*)realloc(m_advData.uuids32, (m_advData.num_uuids32 + 1) * sizeof(ble_uuid32_t)))) { - NIMBLE_LOGE(LOG_TAG, "Error, no mem"); + NIMBLE_LOGC(LOG_TAG, "Error, no mem"); abort(); } memcpy(&m_advData.uuids32[m_advData.num_uuids32].value, @@ -311,7 +396,7 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv } if(it.getNative()->u.type == BLE_UUID_TYPE_128){ int add = (m_advData.num_uuids128 > 0) ? 16 : 18; - if((payloadLen + add) > 31){ + if((payloadLen + add) > BLE_HS_ADV_MAX_SZ){ m_advData.uuids128_is_complete = 0; continue; } @@ -320,7 +405,7 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv if(nullptr == (m_advData.uuids128 = (ble_uuid128_t*)realloc(m_advData.uuids128, (m_advData.num_uuids128 + 1) * sizeof(ble_uuid128_t)))) { - NIMBLE_LOGE(LOG_TAG, "Error, no mem"); + NIMBLE_LOGC(LOG_TAG, "Error, no mem"); abort(); } memcpy(&m_advData.uuids128[m_advData.num_uuids128].value, @@ -333,54 +418,74 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv } // check if there is room for the name, if not put it in scan data - if((payloadLen + m_advData.name_len) > 29) { + if((payloadLen + (2 + m_advData.name_len)) > BLE_HS_ADV_MAX_SZ) { if(m_scanResp){ m_scanData.name = m_advData.name; m_scanData.name_len = m_advData.name_len; - m_scanData.name_is_complete = m_advData.name_is_complete; + if(m_scanData.name_len > BLE_HS_ADV_MAX_SZ - 2) { + m_scanData.name_len = BLE_HS_ADV_MAX_SZ - 2; + m_scanData.name_is_complete = 0; + } else { + m_scanData.name_is_complete = 1; + } m_advData.name = nullptr; m_advData.name_len = 0; + m_advData.name_is_complete = 0; } else { + if(m_advData.tx_pwr_lvl_is_present) { + m_advData.tx_pwr_lvl = 0; + m_advData.tx_pwr_lvl_is_present = 0; + payloadLen -= (2 + 1); + } // if not using scan response just cut the name down // leaving 2 bytes for the data specifier. - m_advData.name_len = (29 - payloadLen); + if(m_advData.name_len > (BLE_HS_ADV_MAX_SZ - payloadLen - 2)) { + m_advData.name_len = (BLE_HS_ADV_MAX_SZ - payloadLen - 2); + m_advData.name_is_complete = 0; + } } - m_advData.name_is_complete = 0; - } - - if(m_advData.name_len > 0) { - payloadLen += (m_advData.name_len + 2); } if(m_scanResp) { - // name length + type byte + length byte + tx power type + length + data - if((m_scanData.name_len + 5) > 31) { - // prioritize name data over tx power - m_scanData.tx_pwr_lvl_is_present = 0; - m_scanData.tx_pwr_lvl = 0; - // limit name to 29 to leave room for the data specifiers - if(m_scanData.name_len > 29) { - m_scanData.name_len = 29; - m_scanData.name_is_complete = false; - } - } - rc = ble_gap_adv_rsp_set_fields(&m_scanData); - if (rc != 0) { - NIMBLE_LOGC(LOG_TAG, "error setting scan response data; rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc)); - abort(); + switch(rc) { + case 0: + break; + + case BLE_HS_EBUSY: + NIMBLE_LOGE(LOG_TAG, "Already advertising"); + break; + + case BLE_HS_EMSGSIZE: + NIMBLE_LOGE(LOG_TAG, "Scan data too long"); + break; + + default: + NIMBLE_LOGE(LOG_TAG, "Error setting scan response data; rc=%d, %s", + rc, NimBLEUtils::returnCodeToString(rc)); + break; } - // if not using scan response and there is room, - // put the tx power data into the advertisment - } else if (payloadLen < 29) { - m_advData.tx_pwr_lvl_is_present = 1; - m_advData.tx_pwr_lvl = NimBLEDevice::getPower(); } - rc = ble_gap_adv_set_fields(&m_advData); - if (rc != 0) { - NIMBLE_LOGC(LOG_TAG, "error setting advertisement data; rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc)); - abort(); + if(rc == 0) { + rc = ble_gap_adv_set_fields(&m_advData); + switch(rc) { + case 0: + break; + + case BLE_HS_EBUSY: + NIMBLE_LOGE(LOG_TAG, "Already advertising"); + break; + + case BLE_HS_EMSGSIZE: + NIMBLE_LOGE(LOG_TAG, "Advertisement data too long"); + break; + + default: + NIMBLE_LOGE(LOG_TAG, "Error setting advertisement data; rc=%d, %s", + rc, NimBLEUtils::returnCodeToString(rc)); + break; + } } if(m_advData.num_uuids128 > 0) { @@ -401,24 +506,54 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv m_advData.num_uuids16 = 0; } + if(rc !=0) { + return false; + } + m_advDataSet = true; } #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) - rc = ble_gap_adv_start(0, NULL, duration, + rc = ble_gap_adv_start(NimBLEDevice::m_own_addr_type, NULL, duration, &m_advParams, - (pServer != nullptr) ? NimBLEServer::handleGapEvent : NimBLEAdvertising::handleGapEvent, + (pServer != nullptr) ? NimBLEServer::handleGapEvent : + NimBLEAdvertising::handleGapEvent, (pServer != nullptr) ? (void*)pServer : (void*)this); #else - rc = ble_gap_adv_start(0, NULL, duration, + rc = ble_gap_adv_start(NimBLEDevice::m_own_addr_type, NULL, duration, &m_advParams, NimBLEAdvertising::handleGapEvent, this); #endif - if (rc != 0) { - NIMBLE_LOGC(LOG_TAG, "Error enabling advertising; rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc)); - abort(); + switch(rc) { + case 0: + break; + + case BLE_HS_EINVAL: + NIMBLE_LOGE(LOG_TAG, "Unable to advertise - Duration too long"); + break; + + case BLE_HS_EPREEMPTED: + NIMBLE_LOGE(LOG_TAG, "Unable to advertise - busy"); + 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; + } + + if(rc != 0) { + return false; } NIMBLE_LOGD(LOG_TAG, "<< Advertising start"); + return true; } // start @@ -427,9 +562,11 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv */ void 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)); + NIMBLE_LOGE(LOG_TAG, "ble_gap_adv_stop rc=%d %s", + rc, NimBLEUtils::returnCodeToString(rc)); return; } @@ -460,8 +597,17 @@ bool NimBLEAdvertising::isAdvertising() { * Host reset seems to clear advertising data, * we need clear the flag so it reloads it. */ -void NimBLEAdvertising::onHostReset() { +void NimBLEAdvertising::onHostSync() { + NIMBLE_LOGD(LOG_TAG, "Host re-synced"); + m_advDataSet = false; + // If we were advertising forever, restart it now + if(m_duration == 0) { + start(m_duration, m_advCompCB); + } else { + // Otherwise we should tell the app that advertising stopped. + advCompleteCB(); + } } @@ -475,6 +621,19 @@ int NimBLEAdvertising::handleGapEvent(struct ble_gap_event *event, void *arg) { NimBLEAdvertising *pAdv = (NimBLEAdvertising*)arg; if(event->type == 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->advCompleteCB(); } return 0; diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEAdvertising.h b/lib/libesp32/NimBLE-Arduino/src/NimBLEAdvertising.h index 2fab71004..0d97ecbbc 100644 --- a/lib/libesp32/NimBLE-Arduino/src/NimBLEAdvertising.h +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEAdvertising.h @@ -77,7 +77,7 @@ public: void addServiceUUID(const NimBLEUUID &serviceUUID); void addServiceUUID(const char* serviceUUID); void removeServiceUUID(const NimBLEUUID &serviceUUID); - void start(uint32_t duration = 0, void (*advCompleteCB)(NimBLEAdvertising *pAdv) = nullptr); + bool start(uint32_t duration = 0, void (*advCompleteCB)(NimBLEAdvertising *pAdv) = nullptr); void stop(); void setAppearance(uint16_t appearance); void setAdvertisementType(uint8_t adv_type); @@ -87,13 +87,15 @@ public: void setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly); void setScanResponseData(NimBLEAdvertisementData& advertisementData); void setScanResponse(bool); + void setMinPreferred(uint16_t); + void setMaxPreferred(uint16_t); void advCompleteCB(); bool isAdvertising(); private: friend class NimBLEDevice; - void onHostReset(); + void onHostSync(); static int handleGapEvent(struct ble_gap_event *event, void *arg); ble_hs_adv_fields m_advData; @@ -104,8 +106,9 @@ private: bool m_customScanResponseData; bool m_scanResp; bool m_advDataSet; - void (*m_advCompCB)(NimBLEAdvertising *pAdv); - + void (*m_advCompCB)(NimBLEAdvertising *pAdv); + uint8_t m_slaveItvl[4]; + uint32_t m_duration; }; #endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLECharacteristic.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLECharacteristic.cpp index 6f7d3d7e0..e9a5a49c9 100644 --- a/lib/libesp32/NimBLE-Arduino/src/NimBLECharacteristic.cpp +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLECharacteristic.cpp @@ -473,9 +473,10 @@ void NimBLECharacteristic::setValue(const uint8_t* data, size_t length) { return; } + time_t t = time(nullptr); portENTER_CRITICAL(&m_valMux); m_value = std::string((char*)data, length); - m_timestamp = time(nullptr); + m_timestamp = t; portEXIT_CRITICAL(&m_valMux); NIMBLE_LOGD(LOG_TAG, "<< setValue"); diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEClient.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLEClient.cpp index 539a7a016..ddd3abecc 100644 --- a/lib/libesp32/NimBLE-Arduino/src/NimBLEClient.cpp +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEClient.cpp @@ -24,6 +24,9 @@ #include #include +#include "nimble/nimble_port.h" + + static const char* LOG_TAG = "NimBLEClient"; static NimBLEClientCallbacks defaultCallbacks; @@ -56,11 +59,10 @@ static NimBLEClientCallbacks defaultCallbacks; NimBLEClient::NimBLEClient(const NimBLEAddress &peerAddress) : m_peerAddress(peerAddress) { m_pClientCallbacks = &defaultCallbacks; m_conn_id = BLE_HS_CONN_HANDLE_NONE; - m_isConnected = false; - m_waitingToConnect = false; m_connectTimeout = 30000; m_deleteCallbacks = false; m_pTaskData = nullptr; + m_connEstablished = false; 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) @@ -70,6 +72,9 @@ NimBLEClient::NimBLEClient(const NimBLEAddress &peerAddress) : m_peerAddress(pee m_pConnParams.supervision_timeout = BLE_GAP_INITIAL_SUPERVISION_TIMEOUT; // timeout = 400*10ms = 4000ms m_pConnParams.min_ce_len = BLE_GAP_INITIAL_CONN_MIN_CE_LEN; // Minimum length of connection event in 0.625ms units m_pConnParams.max_ce_len = BLE_GAP_INITIAL_CONN_MAX_CE_LEN; // Maximum length of connection event in 0.625ms units + + ble_npl_callout_init(&m_dcTimer, nimble_port_get_dflt_eventq(), + NimBLEClient::dcTimerCb, this); } // NimBLEClient @@ -89,6 +94,19 @@ NimBLEClient::~NimBLEClient() { } // ~NimBLEClient +/** + * @brief If we have asked to disconnect and the event does not + * occur within the supervision timeout + added delay, this will + * be called to reset the host in the case of a stalled controller. + */ +void NimBLEClient::dcTimerCb(ble_npl_event *event) { + NimBLEClient *pClient = (NimBLEClient*)event->arg; + NIMBLE_LOGC(LOG_TAG, "Timed out disconnecting from %s - resetting host", + std::string(pClient->getPeerAddress()).c_str()); + ble_hs_sched_reset(BLE_HS_ECONTROLLER); +} + + /** * @brief Delete all service objects created by this client and clear the vector. */ @@ -164,12 +182,9 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) { return false; } - if(ble_gap_conn_active()) { - NIMBLE_LOGE(LOG_TAG, "Connection in progress - must wait."); - return false; - } - - if(!NimBLEDevice::getScan()->stop()) { + if(isConnected() || m_pTaskData != nullptr) { + NIMBLE_LOGE(LOG_TAG, "Client busy, connected to %s, id=%d", + std::string(m_peerAddress).c_str(), getConnId()); return false; } @@ -180,54 +195,97 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) { m_peerAddress = address; } - ble_addr_t peerAddrt; - memcpy(&peerAddrt.val, m_peerAddress.getNative(),6); - peerAddrt.type = m_peerAddress.getType(); + ble_addr_t peerAddr_t; + memcpy(&peerAddr_t.val, m_peerAddress.getNative(),6); + peerAddr_t.type = m_peerAddress.getType(); + ble_task_data_t taskData = {this, xTaskGetCurrentTaskHandle(), 0, nullptr}; - m_pTaskData = &taskData; - int rc = 0; /* Try to connect the the advertiser. Allow 30 seconds (30000 ms) for * timeout (default value of m_connectTimeout). * Loop on BLE_HS_EBUSY if the scan hasn't stopped yet. */ - do{ - rc = ble_gap_connect(BLE_OWN_ADDR_PUBLIC, &peerAddrt, m_connectTimeout, &m_pConnParams, - NimBLEClient::handleGapEvent, this); - if(rc == BLE_HS_EBUSY) { - vTaskDelay(1 / portTICK_PERIOD_MS); + do { + rc = ble_gap_connect(NimBLEDevice::m_own_addr_type, &peerAddr_t, + m_connectTimeout, &m_pConnParams, + NimBLEClient::handleGapEvent, this); + switch (rc) { + case 0: + m_pTaskData = &taskData; + break; + + case BLE_HS_EBUSY: + // Scan was still running, stop it and try again + if (!NimBLEDevice::getScan()->stop()) { + return false; + } + break; + + case BLE_HS_EDONE: + // A connection to this device already exists, do not connect twice. + NIMBLE_LOGE(LOG_TAG, "Already connected to device; addr=%s", + std::string(m_peerAddress).c_str()); + return false; + + case BLE_HS_EALREADY: + // Already attemting to connect to this device, cancel the previous + // attempt and report failure here so we don't get 2 connections. + NIMBLE_LOGE(LOG_TAG, "Already attempting to connect to %s - cancelling", + std::string(m_peerAddress).c_str()); + ble_gap_conn_cancel(); + return false; + + default: + NIMBLE_LOGE(LOG_TAG, "Failed to connect to %s, rc=%d; %s", + std::string(m_peerAddress).c_str(), + rc, NimBLEUtils::returnCodeToString(rc)); + return false; } - }while(rc == BLE_HS_EBUSY); - if (rc != 0 && rc != BLE_HS_EDONE) { - NIMBLE_LOGE(LOG_TAG, "Error: Failed to connect to device; " - "addr=%s, rc=%d; %s", - std::string(m_peerAddress).c_str(), - rc, NimBLEUtils::returnCodeToString(rc)); + } while (rc == BLE_HS_EBUSY); + + // Wait for the connect timeout time +1 second for the connection to complete + if(ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(m_connectTimeout + 1000)) == pdFALSE) { m_pTaskData = nullptr; - m_waitingToConnect = false; + // If a connection was made but no response from MTU exchange; disconnect + if(isConnected()) { + NIMBLE_LOGE(LOG_TAG, "Connect timeout - no response"); + disconnect(); + } else { + // workaround; if the controller doesn't cancel the connection + // at the timeout cancel it here. + NIMBLE_LOGE(LOG_TAG, "Connect timeout - cancelling"); + ble_gap_conn_cancel(); + } + return false; - } - m_waitingToConnect = true; - - // Wait for the connection to complete. - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - - if(taskData.rc != 0){ + } else if(taskData.rc != 0){ + NIMBLE_LOGE(LOG_TAG, "Connection failed; status=%d %s", + taskData.rc, + NimBLEUtils::returnCodeToString(taskData.rc)); + // If the failure was not a result of a disconnection + // make sure we disconnect now to avoid dangling connections + if(isConnected()) { + ble_gap_terminate(m_conn_id, BLE_ERR_REM_USER_CONN_TERM); + } return false; + } else { + NIMBLE_LOGI(LOG_TAG, "Connection established"); } if(deleteAttibutes) { deleteServices(); } + m_connEstablished = true; m_pClientCallbacks->onConnect(this); NIMBLE_LOGD(LOG_TAG, "<< connect()"); - return true; + // Check if still connected before returning + return isConnected(); } // connect @@ -267,13 +325,31 @@ bool NimBLEClient::secureConnection() { */ int NimBLEClient::disconnect(uint8_t reason) { NIMBLE_LOGD(LOG_TAG, ">> disconnect()"); + int rc = 0; - if(m_isConnected){ + if(isConnected()){ rc = ble_gap_terminate(m_conn_id, reason); - if(rc != 0){ - NIMBLE_LOGE(LOG_TAG, "ble_gap_terminate failed: rc=%d %s", rc, - NimBLEUtils::returnCodeToString(rc)); + if (rc == 0) { + ble_addr_t peerAddr_t; + memcpy(&peerAddr_t.val, m_peerAddress.getNative(),6); + peerAddr_t.type = m_peerAddress.getType(); + + // Set the disconnect timeout to the supervison timeout time + 1 second + // In case the event triggers shortly after the supervision timeout. + // We don't want to prematurely reset the host. + ble_gap_conn_desc desc; + if(ble_gap_conn_find_by_addr(&peerAddr_t, &desc) == 0){ + ble_npl_time_t ticks; + ble_npl_time_ms_to_ticks((desc.supervision_timeout + 100) * 10, &ticks); + ble_npl_callout_reset(&m_dcTimer, ticks); + NIMBLE_LOGD(LOG_TAG, "DC TIMEOUT = %dms", (desc.supervision_timeout + 100) * 10); + } + } else if (rc != BLE_HS_EALREADY) { + NIMBLE_LOGE(LOG_TAG, "ble_gap_terminate failed: rc=%d %s", + rc, NimBLEUtils::returnCodeToString(rc)); } + } else { + NIMBLE_LOGD(LOG_TAG, "Not connected to any peers"); } NIMBLE_LOGD(LOG_TAG, "<< disconnect()"); @@ -454,6 +530,16 @@ NimBLERemoteService* NimBLEClient::getService(const NimBLEUUID &uuid) { if(m_servicesVector.size() > prev_size) { return m_servicesVector.back(); } + + // If the request was successful but 16/32 bit service not found + // try again with the 128 bit uuid. + if(uuid.bitSize() == BLE_UUID_TYPE_16 || + uuid.bitSize() == BLE_UUID_TYPE_32) + { + NimBLEUUID uuid128(uuid); + uuid128.to128(); + return getService(uuid128); + } } NIMBLE_LOGD(LOG_TAG, "<< getService: not found"); @@ -510,7 +596,7 @@ bool NimBLEClient::retrieveServices(const NimBLEUUID *uuid_filter) { NIMBLE_LOGD(LOG_TAG, ">> retrieveServices"); - if(!m_isConnected){ + if(!isConnected()){ NIMBLE_LOGE(LOG_TAG, "Disconnected, could not retrieve services -aborting"); return false; } @@ -618,10 +704,11 @@ std::string NimBLEClient::getValue(const NimBLEUUID &serviceUUID, const NimBLEUU * @param [in] serviceUUID The service that owns the characteristic. * @param [in] characteristicUUID The characteristic whose value we wish to write. * @param [in] value The value to write to the characteristic. + * @param [in] response If true, uses write with response operation. * @returns true if successful otherwise false */ bool NimBLEClient::setValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID, - const std::string &value) + const std::string &value, bool response) { NIMBLE_LOGD(LOG_TAG, ">> setValue: serviceUUID: %s, characteristicUUID: %s", serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); @@ -632,7 +719,7 @@ bool NimBLEClient::setValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &cha if(pService != nullptr) { NimBLERemoteCharacteristic* pChar = pService->getCharacteristic(characteristicUUID); if(pChar != nullptr) { - ret = pChar->writeValue(value); + ret = pChar->writeValue(value, response); } } @@ -641,6 +728,31 @@ bool NimBLEClient::setValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &cha } // setValue +/** + * @brief Get the remote characteristic with the specified handle. + * @param [in] handle The handle of the desired characteristic. + * @returns The matching remote characteristic, nullptr otherwise. + */ +NimBLERemoteCharacteristic* NimBLEClient::getCharacteristic(const uint16_t handle) +{ + NimBLERemoteService *pService = nullptr; + for(auto it = m_servicesVector.begin(); it != m_servicesVector.end(); ++it) { + if ((*it)->getStartHandle() <= handle && handle <= (*it)->getEndHandle()) { + pService = *it; + break; + } + } + + if (pService != nullptr) { + for (auto it = pService->begin(); it != pService->end(); ++it) { + if ((*it)->getHandle() == handle) { + return *it; + } + } + } + + return nullptr; +} /** * @brief Get the current mtu of this connection. @@ -656,7 +768,8 @@ uint16_t NimBLEClient::getMTU() { * @param [in] event The event structure sent by the NimBLE stack. * @param [in] arg A pointer to the client instance that registered for this callback. */ - /*STATIC*/ int NimBLEClient::handleGapEvent(struct ble_gap_event *event, void *arg) { + /*STATIC*/ + int NimBLEClient::handleGapEvent(struct ble_gap_event *event, void *arg) { NimBLEClient* client = (NimBLEClient*)arg; int rc; @@ -665,61 +778,66 @@ uint16_t NimBLEClient::getMTU() { switch(event->type) { case BLE_GAP_EVENT_DISCONNECT: { - if(!client->m_isConnected) - return 0; - - if(client->m_conn_id != event->disconnect.conn.conn_handle) - return 0; - - client->m_isConnected = false; - client->m_waitingToConnect=false; - // Remove the device from ignore list so we will scan it again - NimBLEDevice::removeIgnored(client->m_peerAddress); - - NIMBLE_LOGI(LOG_TAG, "disconnect; reason=%d, %s", event->disconnect.reason, - NimBLEUtils::returnCodeToString(event->disconnect.reason)); - + rc = event->disconnect.reason; // If Host reset tell the device now before returning to prevent // any errors caused by calling host functions before resyncing. - switch(event->disconnect.reason) { - case BLE_HS_ETIMEOUT_HCI: - case BLE_HS_EOS: + switch(rc) { case BLE_HS_ECONTROLLER: + case BLE_HS_ETIMEOUT_HCI: case BLE_HS_ENOTSYNCED: - NIMBLE_LOGC(LOG_TAG, "Disconnect - host reset, rc=%d", event->disconnect.reason); - NimBLEDevice::onReset(event->disconnect.reason); + case BLE_HS_EOS: + NIMBLE_LOGC(LOG_TAG, "Disconnect - host reset, rc=%d", rc); + NimBLEDevice::onReset(rc); break; default: + // Check that the event is for this client. + if(client->m_conn_id != event->disconnect.conn.conn_handle) { + return 0; + } break; } - //client->m_conn_id = BLE_HS_CONN_HANDLE_NONE; + client->m_conn_id = BLE_HS_CONN_HANDLE_NONE; + + // Stop the disconnect timer since we are now disconnected. + ble_npl_callout_stop(&client->m_dcTimer); + + // Remove the device from ignore list so we will scan it again + NimBLEDevice::removeIgnored(client->m_peerAddress); + + // If we received a connected event but did not get established (no PDU) + // then a disconnect event will be sent but we should not send it to the + // app for processing. Instead we will ensure the task is released + // and report the error. + if(!client->m_connEstablished) + break; + + NIMBLE_LOGI(LOG_TAG, "disconnect; reason=%d, %s", + rc, NimBLEUtils::returnCodeToString(rc)); + + client->m_connEstablished = false; client->m_pClientCallbacks->onDisconnect(client); - rc = event->disconnect.reason; break; } // BLE_GAP_EVENT_DISCONNECT case BLE_GAP_EVENT_CONNECT: { - - if(!client->m_waitingToConnect) + // If we aren't waiting for this connection response + // we should drop the connection immediately. + if(client->isConnected() || client->m_pTaskData == nullptr) { + ble_gap_terminate(event->connect.conn_handle, BLE_ERR_REM_USER_CONN_TERM); return 0; + } - //if(client->m_conn_id != BLE_HS_CONN_HANDLE_NONE) - // return 0; - - client->m_waitingToConnect=false; - - if (event->connect.status == 0) { - client->m_isConnected = true; - - NIMBLE_LOGD(LOG_TAG, "Connection established"); + rc = event->connect.status; + if (rc == 0) { + NIMBLE_LOGI(LOG_TAG, "Connected event"); client->m_conn_id = event->connect.conn_handle; rc = ble_gattc_exchange_mtu(client->m_conn_id, NULL,NULL); if(rc != 0) { - NIMBLE_LOGE(LOG_TAG, "ble_gattc_exchange_mtu: rc=%d %s",rc, - NimBLEUtils::returnCodeToString(rc)); + NIMBLE_LOGE(LOG_TAG, "MTU exchange error; rc=%d %s", + rc, NimBLEUtils::returnCodeToString(rc)); break; } @@ -727,14 +845,10 @@ uint16_t NimBLEClient::getMTU() { // scanning since we are already connected to it NimBLEDevice::addIgnored(client->m_peerAddress); } else { - NIMBLE_LOGE(LOG_TAG, "Error: Connection failed; status=%d %s", - event->connect.status, - NimBLEUtils::returnCodeToString(event->connect.status)); - - client->m_isConnected = false; - rc = event->connect.status; + client->m_conn_id = BLE_HS_CONN_HANDLE_NONE; break; } + return 0; } // BLE_GAP_EVENT_CONNECT @@ -742,7 +856,14 @@ uint16_t NimBLEClient::getMTU() { if(client->m_conn_id != event->notify_rx.conn_handle) return 0; - NIMBLE_LOGD(LOG_TAG, "Notify Recieved for handle: %d",event->notify_rx.attr_handle); + // If a notification comes before this flag is set we might + // access a vector while it is being cleared in connect() + if(!client->m_connEstablished) { + return 0; + } + + NIMBLE_LOGD(LOG_TAG, "Notify Recieved for handle: %d", + event->notify_rx.attr_handle); for(auto &it: client->m_servicesVector) { // Dont waste cycles searching services without this handle in its range @@ -752,8 +873,8 @@ uint16_t NimBLEClient::getMTU() { auto cVector = &it->m_characteristicVector; NIMBLE_LOGD(LOG_TAG, "checking service %s for handle: %d", - it->getUUID().toString().c_str(), - event->notify_rx.attr_handle); + it->getUUID().toString().c_str(), + event->notify_rx.attr_handle); auto characteristic = cVector->cbegin(); for(; characteristic != cVector->cend(); ++characteristic) { @@ -762,16 +883,19 @@ uint16_t NimBLEClient::getMTU() { } if(characteristic != cVector->cend()) { - NIMBLE_LOGD(LOG_TAG, "Got Notification for characteristic %s", (*characteristic)->toString().c_str()); + NIMBLE_LOGD(LOG_TAG, "Got Notification for characteristic %s", + (*characteristic)->toString().c_str()); + time_t t = time(nullptr); portENTER_CRITICAL(&(*characteristic)->m_valMux); - (*characteristic)->m_value = std::string((char *)event->notify_rx.om->om_data, event->notify_rx.om->om_len); - (*characteristic)->m_timestamp = time(nullptr); + (*characteristic)->m_value = std::string((char *)event->notify_rx.om->om_data, + event->notify_rx.om->om_len); + (*characteristic)->m_timestamp = t; portEXIT_CRITICAL(&(*characteristic)->m_valMux); if ((*characteristic)->m_notifyCallback != nullptr) { NIMBLE_LOGD(LOG_TAG, "Invoking callback for notification on characteristic %s", - (*characteristic)->toString().c_str()); + (*characteristic)->toString().c_str()); (*characteristic)->m_notifyCallback(*characteristic, event->notify_rx.om->om_data, event->notify_rx.om->om_len, !event->notify_rx.indication); @@ -790,10 +914,10 @@ uint16_t NimBLEClient::getMTU() { } NIMBLE_LOGD(LOG_TAG, "Peer requesting to update connection parameters"); NIMBLE_LOGD(LOG_TAG, "MinInterval: %d, MaxInterval: %d, Latency: %d, Timeout: %d", - event->conn_update_req.peer_params->itvl_min, - event->conn_update_req.peer_params->itvl_max, - event->conn_update_req.peer_params->latency, - event->conn_update_req.peer_params->supervision_timeout); + event->conn_update_req.peer_params->itvl_min, + event->conn_update_req.peer_params->itvl_max, + event->conn_update_req.peer_params->latency, + event->conn_update_req.peer_params->supervision_timeout); rc = client->m_pClientCallbacks->onConnParamsUpdateRequest(client, event->conn_update_req.peer_params) ? 0 : BLE_ERR_CONN_PARMS; @@ -827,7 +951,9 @@ uint16_t NimBLEClient::getMTU() { return 0; } - if(event->enc_change.status == 0 || event->enc_change.status == (BLE_HS_ERR_HCI_BASE + BLE_ERR_PINKEY_MISSING)) { + if(event->enc_change.status == 0 || + event->enc_change.status == (BLE_HS_ERR_HCI_BASE + BLE_ERR_PINKEY_MISSING)) + { struct ble_gap_conn_desc desc; rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc); assert(rc == 0); @@ -922,7 +1048,9 @@ uint16_t NimBLEClient::getMTU() { if(client->m_pTaskData != nullptr) { client->m_pTaskData->rc = rc; - xTaskNotifyGive(client->m_pTaskData->task); + if(client->m_pTaskData->task) { + xTaskNotifyGive(client->m_pTaskData->task); + } client->m_pTaskData = nullptr; } @@ -935,7 +1063,7 @@ uint16_t NimBLEClient::getMTU() { * @return True if we are connected and false if we are not connected. */ bool NimBLEClient::isConnected() { - return m_isConnected; + return m_conn_id != BLE_HS_CONN_HANDLE_NONE; } // isConnected diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEClient.h b/lib/libesp32/NimBLE-Arduino/src/NimBLEClient.h index ddeef3cc3..a4b847000 100644 --- a/lib/libesp32/NimBLE-Arduino/src/NimBLEClient.h +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEClient.h @@ -30,6 +30,7 @@ #include class NimBLERemoteService; +class NimBLERemoteCharacteristic; class NimBLEClientCallbacks; class NimBLEAdvertisedDevice; @@ -54,7 +55,8 @@ public: size_t deleteService(const NimBLEUUID &uuid); std::string getValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID); bool setValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID, - const std::string &value); + const std::string &value, bool response = false); + NimBLERemoteCharacteristic* getCharacteristic(const uint16_t handle); bool isConnected(); void setClientCallbacks(NimBLEClientCallbacks *pClientCallbacks, bool deleteCallbacks = true); @@ -82,16 +84,17 @@ private: const struct ble_gatt_error *error, const struct ble_gatt_svc *service, void *arg); + static void dcTimerCb(ble_npl_event *event); bool retrieveServices(const NimBLEUUID *uuid_filter = nullptr); NimBLEAddress m_peerAddress; uint16_t m_conn_id; - bool m_isConnected; - bool m_waitingToConnect; + bool m_connEstablished; bool m_deleteCallbacks; int32_t m_connectTimeout; NimBLEClientCallbacks* m_pClientCallbacks; - ble_task_data_t *m_pTaskData; + ble_task_data_t* m_pTaskData; + ble_npl_callout m_dcTimer; std::vector m_servicesVector; diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEDevice.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLEDevice.cpp index fb36e6e57..f34a522f7 100644 --- a/lib/libesp32/NimBLE-Arduino/src/NimBLEDevice.cpp +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEDevice.cpp @@ -25,6 +25,7 @@ #include "nimble/nimble_port.h" #include "nimble/nimble_port_freertos.h" #include "host/ble_hs.h" +#include "host/ble_hs_pvcy.h" #include "host/util/util.h" #include "services/gap/ble_svc_gap.h" #include "services/gatt/ble_svc_gatt.h" @@ -60,6 +61,7 @@ std::list NimBLEDevice::m_cList; #endif std::list NimBLEDevice::m_ignoreList; NimBLESecurityCallbacks* NimBLEDevice::m_securityCallbacks = nullptr; +uint8_t NimBLEDevice::m_own_addr_type = BLE_OWN_ADDR_PUBLIC; /** @@ -144,8 +146,8 @@ void NimBLEDevice::stopAdvertising() { #if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) /* STATIC */ NimBLEClient* NimBLEDevice::createClient(NimBLEAddress peerAddress) { if(m_cList.size() >= NIMBLE_MAX_CONNECTIONS) { - NIMBLE_LOGW("Number of clients exceeds Max connections. Max=(%d)", - NIMBLE_MAX_CONNECTIONS); + NIMBLE_LOGW(LOG_TAG,"Number of clients exceeds Max connections. Cur=%d Max=%d", + m_cList.size(), NIMBLE_MAX_CONNECTIONS); } NimBLEClient* pClient = new NimBLEClient(peerAddress); @@ -165,26 +167,31 @@ void NimBLEDevice::stopAdvertising() { return false; } + // Set the connection established flag to false to stop notifications + // from accessing the attribute vectors while they are being deleted. + pClient->m_connEstablished = false; int rc =0; - if(pClient->m_isConnected) { + if(pClient->isConnected()) { rc = pClient->disconnect(); if (rc != 0 && rc != BLE_HS_EALREADY && rc != BLE_HS_ENOTCONN) { return false; } - while(pClient->m_isConnected) { - vTaskDelay(10); + while(pClient->isConnected()) { + taskYIELD(); } - } + // Since we set the flag to false the app will not get a callback + // in the disconnect event so we call it here for good measure. + pClient->m_pClientCallbacks->onDisconnect(pClient); - if(pClient->m_waitingToConnect) { + } else if(pClient->m_pTaskData != nullptr) { rc = ble_gap_conn_cancel(); if (rc != 0 && rc != BLE_HS_EALREADY) { return false; } - while(pClient->m_waitingToConnect) { - vTaskDelay(10); + while(pClient->m_pTaskData != nullptr) { + taskYIELD(); } } @@ -405,30 +412,16 @@ void NimBLEDevice::stopAdvertising() { m_synced = false; -#if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER) - if(m_pScan != nullptr) { - m_pScan->onHostReset(); - } -#endif - -/* Not needed - if(m_pServer != nullptr) { - m_pServer->onHostReset(); - } - - for(auto it = m_cList.cbegin(); it != m_cList.cend(); ++it) { - (*it)->onHostReset(); - } -*/ - -#if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) - if(m_bleAdvertising != nullptr) { - m_bleAdvertising->onHostReset(); - } -#endif - NIMBLE_LOGC(LOG_TAG, "Resetting state; reason=%d, %s", reason, NimBLEUtils::returnCodeToString(reason)); + +#if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER) + if(initialized) { + if(m_pScan != nullptr) { + m_pScan->onHostReset(); + } + } +#endif } // onReset @@ -448,20 +441,22 @@ void NimBLEDevice::stopAdvertising() { int rc = ble_hs_util_ensure_addr(0); assert(rc == 0); + // Yield for houskeeping before returning to operations. + // Occasionally triggers exception without. + taskYIELD(); + m_synced = true; if(initialized) { #if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER) if(m_pScan != nullptr) { - // Restart scanning with the last values sent, allow to clear results. - m_pScan->start(m_pScan->m_duration, m_pScan->m_scanCompleteCB); + m_pScan->onHostSync(); } #endif #if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) if(m_bleAdvertising != nullptr) { - // Restart advertisng, parameters should already be set. - m_bleAdvertising->start(); + m_bleAdvertising->onHostSync(); } #endif } @@ -705,6 +700,35 @@ void NimBLEDevice::setSecurityCallbacks(NimBLESecurityCallbacks* callbacks) { } // setSecurityCallbacks +/** + * @brief Set the own address type. + * @param [in] own_addr_type Own Bluetooth Device address type.\n + * The available bits are defined as: + * * 0x00: BLE_OWN_ADDR_PUBLIC + * * 0x01: BLE_OWN_ADDR_RANDOM + * * 0x02: BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT + * * 0x03: BLE_OWN_ADDR_RPA_RANDOM_DEFAULT + * @param [in] useNRPA If true, and address type is random, uses a non-resolvable random address. + */ +void NimBLEDevice::setOwnAddrType(uint8_t own_addr_type, bool useNRPA) { + m_own_addr_type = own_addr_type; + switch (own_addr_type) { + case BLE_OWN_ADDR_PUBLIC: + ble_hs_pvcy_rpa_config(NIMBLE_HOST_DISABLE_PRIVACY); + break; + case BLE_OWN_ADDR_RANDOM: + setSecurityInitKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID); + ble_hs_pvcy_rpa_config(useNRPA ? NIMBLE_HOST_ENABLE_NRPA : NIMBLE_HOST_ENABLE_RPA); + break; + case BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT: + case BLE_OWN_ADDR_RPA_RANDOM_DEFAULT: + setSecurityInitKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID); + ble_hs_pvcy_rpa_config(NIMBLE_HOST_ENABLE_RPA); + break; + } +} // setOwnAddrType + + /** * @brief Start the connection securing and authorization for this connection. * @param conn_id The connection id of the peer device. diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEDevice.h b/lib/libesp32/NimBLE-Arduino/src/NimBLEDevice.h index 252c52afd..2d586bb42 100644 --- a/lib/libesp32/NimBLE-Arduino/src/NimBLEDevice.h +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEDevice.h @@ -116,6 +116,7 @@ public: static void setSecurityPasskey(uint32_t pin); static uint32_t getSecurityPasskey(); static void setSecurityCallbacks(NimBLESecurityCallbacks* pCallbacks); + static void setOwnAddrType(uint8_t own_addr_type, bool useNRPA=false); static int startSecurity(uint16_t conn_id); static int setMTU(uint16_t mtu); static uint16_t getMTU(); @@ -182,6 +183,7 @@ private: static uint32_t m_passkey; static ble_gap_event_listener m_listener; static gap_event_handler m_customGapHandler; + static uint8_t m_own_addr_type; }; diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEHIDDevice.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLEHIDDevice.cpp new file mode 100644 index 000000000..39f07dede --- /dev/null +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEHIDDevice.cpp @@ -0,0 +1,233 @@ +/* + * NimBLEHIDDevice.cpp + * + * Created: on Oct 06 2020 + * Author wakwak-koba + * + * Originally: + * + * BLEHIDDevice.cpp + * + * Created on: Jan 03, 2018 + * Author: chegewara + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include "nimconfig.h" +#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) + +#include "NimBLEHIDDevice.h" +#include "NimBLE2904.h" + +NimBLEHIDDevice::NimBLEHIDDevice(NimBLEServer* server) { + /* + * Here we create mandatory services described in bluetooth specification + */ + m_deviceInfoService = server->createService(NimBLEUUID((uint16_t) 0x180a)); + m_hidService = server->createService(NimBLEUUID((uint16_t) 0x1812), 40); + m_batteryService = server->createService(NimBLEUUID((uint16_t) 0x180f)); + + /* + * Mandatory characteristic for device info service + */ + m_pnpCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a50, NIMBLE_PROPERTY::READ); + + /* + * Mandatory characteristics for HID service + */ + m_hidInfoCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4a, NIMBLE_PROPERTY::READ); + m_reportMapCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4b, NIMBLE_PROPERTY::READ); + m_hidControlCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4c, NIMBLE_PROPERTY::WRITE_NR); + m_protocolModeCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4e, NIMBLE_PROPERTY::WRITE_NR | NIMBLE_PROPERTY::READ); + + /* + * Mandatory battery level characteristic with notification and presence descriptor + */ + m_batteryLevelCharacteristic = m_batteryService->createCharacteristic((uint16_t) 0x2a19, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY); + NimBLE2904* batteryLevelDescriptor = (NimBLE2904*)m_batteryLevelCharacteristic->createDescriptor((uint16_t) 0x2904); + batteryLevelDescriptor->setFormat(NimBLE2904::FORMAT_UINT8); + batteryLevelDescriptor->setNamespace(1); + batteryLevelDescriptor->setUnit(0x27ad); + + /* + * This value is setup here because its default value in most usage cases, its very rare to use boot mode + * and we want to simplify library using as much as possible + */ + const uint8_t pMode[] = { 0x01 }; + protocolMode()->setValue((uint8_t*) pMode, 1); +} + +NimBLEHIDDevice::~NimBLEHIDDevice() { +} + +/* + * @brief + */ +void NimBLEHIDDevice::reportMap(uint8_t* map, uint16_t size) { + m_reportMapCharacteristic->setValue(map, size); +} + +/* + * @brief This function suppose to be called at the end, when we have created all characteristics we need to build HID service + */ +void NimBLEHIDDevice::startServices() { + m_deviceInfoService->start(); + m_hidService->start(); + m_batteryService->start(); +} + +/* + * @brief Create manufacturer characteristic (this characteristic is optional) + */ +NimBLECharacteristic* NimBLEHIDDevice::manufacturer() { + m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a29, NIMBLE_PROPERTY::READ); + return m_manufacturerCharacteristic; +} + +/* + * @brief Set manufacturer name + * @param [in] name manufacturer name + */ +void NimBLEHIDDevice::manufacturer(std::string name) { + m_manufacturerCharacteristic->setValue(name); +} + +/* + * @brief + */ +void NimBLEHIDDevice::pnp(uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version) { + uint8_t pnp[] = { sig, (uint8_t) (vid >> 8), (uint8_t) vid, (uint8_t) (pid >> 8), (uint8_t) pid, (uint8_t) (version >> 8), (uint8_t) version }; + m_pnpCharacteristic->setValue(pnp, sizeof(pnp)); +} + +/* + * @brief + */ +void NimBLEHIDDevice::hidInfo(uint8_t country, uint8_t flags) { + uint8_t info[] = { 0x11, 0x1, country, flags }; + m_hidInfoCharacteristic->setValue(info, sizeof(info)); +} + +/* + * @brief Create input report characteristic that need to be saved as new characteristic object so can be further used + * @param [in] reportID input report ID, the same as in report map for input object related to created characteristic + * @return pointer to new input report characteristic + */ +NimBLECharacteristic* NimBLEHIDDevice::inputReport(uint8_t reportID) { + NimBLECharacteristic* inputReportCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4d, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY); + NimBLEDescriptor* inputReportDescriptor = inputReportCharacteristic->createDescriptor((uint16_t) 0x2908); + + uint8_t desc1_val[] = { reportID, 0x01 }; + inputReportDescriptor->setValue((uint8_t*) desc1_val, 2); + + return inputReportCharacteristic; +} + +/* + * @brief Create output report characteristic that need to be saved as new characteristic object so can be further used + * @param [in] reportID Output report ID, the same as in report map for output object related to created characteristic + * @return Pointer to new output report characteristic + */ +NimBLECharacteristic* NimBLEHIDDevice::outputReport(uint8_t reportID) { + NimBLECharacteristic* outputReportCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4d, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::WRITE_ENC); + NimBLEDescriptor* outputReportDescriptor = outputReportCharacteristic->createDescriptor((uint16_t) 0x2908); + + uint8_t desc1_val[] = { reportID, 0x02 }; + outputReportDescriptor->setValue((uint8_t*) desc1_val, 2); + + return outputReportCharacteristic; +} + +/* + * @brief Create feature report characteristic that need to be saved as new characteristic object so can be further used + * @param [in] reportID Feature report ID, the same as in report map for feature object related to created characteristic + * @return Pointer to new feature report characteristic + */ +NimBLECharacteristic* NimBLEHIDDevice::featureReport(uint8_t reportID) { + NimBLECharacteristic* featureReportCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4d, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::WRITE_ENC); + NimBLEDescriptor* featureReportDescriptor = featureReportCharacteristic->createDescriptor((uint16_t) 0x2908); + + uint8_t desc1_val[] = { reportID, 0x03 }; + featureReportDescriptor->setValue((uint8_t*) desc1_val, 2); + + return featureReportCharacteristic; +} + +/* + * @brief + */ +NimBLECharacteristic* NimBLEHIDDevice::bootInput() { + return m_hidService->createCharacteristic((uint16_t) 0x2a22, NIMBLE_PROPERTY::NOTIFY); +} + +/* + * @brief + */ +NimBLECharacteristic* NimBLEHIDDevice::bootOutput() { + return m_hidService->createCharacteristic((uint16_t) 0x2a32, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR); +} + +/* + * @brief + */ +NimBLECharacteristic* NimBLEHIDDevice::hidControl() { + return m_hidControlCharacteristic; +} + +/* + * @brief + */ +NimBLECharacteristic* NimBLEHIDDevice::protocolMode() { + return m_protocolModeCharacteristic; +} + +void NimBLEHIDDevice::setBatteryLevel(uint8_t level) { + m_batteryLevelCharacteristic->setValue(&level, 1); +} +/* + * @brief Returns battery level characteristic + * @ return battery level characteristic + *//* +BLECharacteristic* BLEHIDDevice::batteryLevel() { + return m_batteryLevelCharacteristic; +} + + + +BLECharacteristic* BLEHIDDevice::reportMap() { + return m_reportMapCharacteristic; +} + +BLECharacteristic* BLEHIDDevice::pnp() { + return m_pnpCharacteristic; +} + + +BLECharacteristic* BLEHIDDevice::hidInfo() { + return m_hidInfoCharacteristic; +} +*/ +/* + * @brief + */ +NimBLEService* NimBLEHIDDevice::deviceInfo() { + return m_deviceInfoService; +} + +/* + * @brief + */ +NimBLEService* NimBLEHIDDevice::hidService() { + return m_hidService; +} + +/* + * @brief + */ +NimBLEService* NimBLEHIDDevice::batteryService() { + return m_batteryService; +} + +#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) +#endif // #if defined(CONFIG_BT_ENABLED) diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEHIDDevice.h b/lib/libesp32/NimBLE-Arduino/src/NimBLEHIDDevice.h new file mode 100644 index 000000000..3e7a6f759 --- /dev/null +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEHIDDevice.h @@ -0,0 +1,85 @@ +/* + * NimBLEHIDDevice.h + * + * Created: on Oct 06 2020 + * Author wakwak-koba + * + * Originally: + * + * BLEHIDDevice.h + * + * Created on: Jan 03, 2018 + * Author: chegewara + */ + +#ifndef _BLEHIDDEVICE_H_ +#define _BLEHIDDEVICE_H_ + +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include "nimconfig.h" +#if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) + +#include "NimBLECharacteristic.h" +#include "NimBLEService.h" +#include "NimBLEDescriptor.h" +#include "HIDTypes.h" + +#define GENERIC_HID 0x03C0 +#define HID_KEYBOARD 0x03C1 +#define HID_MOUSE 0x03C2 +#define HID_JOYSTICK 0x03C3 +#define HID_GAMEPAD 0x03C4 +#define HID_TABLET 0x03C5 +#define HID_CARD_READER 0x03C6 +#define HID_DIGITAL_PEN 0x03C7 +#define HID_BARCODE 0x03C8 + +class NimBLEHIDDevice { +public: + NimBLEHIDDevice(NimBLEServer*); + virtual ~NimBLEHIDDevice(); + + void reportMap(uint8_t* map, uint16_t); + void startServices(); + + NimBLEService* deviceInfo(); + NimBLEService* hidService(); + NimBLEService* batteryService(); + + NimBLECharacteristic* manufacturer(); + void manufacturer(std::string name); + //NimBLECharacteristic* pnp(); + void pnp(uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version); + //NimBLECharacteristic* hidInfo(); + void hidInfo(uint8_t country, uint8_t flags); + //NimBLECharacteristic* batteryLevel(); + void setBatteryLevel(uint8_t level); + + + //NimBLECharacteristic* reportMap(); + NimBLECharacteristic* hidControl(); + NimBLECharacteristic* inputReport(uint8_t reportID); + NimBLECharacteristic* outputReport(uint8_t reportID); + NimBLECharacteristic* featureReport(uint8_t reportID); + NimBLECharacteristic* protocolMode(); + NimBLECharacteristic* bootInput(); + NimBLECharacteristic* bootOutput(); + +private: + NimBLEService* m_deviceInfoService; //0x180a + NimBLEService* m_hidService; //0x1812 + NimBLEService* m_batteryService = 0; //0x180f + + NimBLECharacteristic* m_manufacturerCharacteristic; //0x2a29 + NimBLECharacteristic* m_pnpCharacteristic; //0x2a50 + NimBLECharacteristic* m_hidInfoCharacteristic; //0x2a4a + NimBLECharacteristic* m_reportMapCharacteristic; //0x2a4b + NimBLECharacteristic* m_hidControlCharacteristic; //0x2a4c + NimBLECharacteristic* m_protocolModeCharacteristic; //0x2a4e + NimBLECharacteristic* m_batteryLevelCharacteristic; //0x2a19 +}; +#endif // CONFIG_BT_NIMBLE_ROLE_BROADCASTER +#endif // CONFIG_BT_ENABLED +#endif /* _BLEHIDDEVICE_H_ */ diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteCharacteristic.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteCharacteristic.cpp index 154206c73..8bc5090b1 100644 --- a/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteCharacteristic.cpp +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteCharacteristic.cpp @@ -38,7 +38,7 @@ static const char* LOG_TAG = "NimBLERemoteCharacteristic"; NimBLERemoteCharacteristic::NimBLERemoteCharacteristic(NimBLERemoteService *pRemoteService, const struct ble_gatt_chr *chr) { - + NIMBLE_LOGD(LOG_TAG, ">> NimBLERemoteCharacteristic()"); switch (chr->uuid.u.type) { case BLE_UUID_TYPE_16: m_uuid = NimBLEUUID(chr->uuid.u16.value); @@ -50,7 +50,6 @@ static const char* LOG_TAG = "NimBLERemoteCharacteristic"; m_uuid = NimBLEUUID(const_cast(&chr->uuid.u128)); break; default: - m_uuid = nullptr; break; } @@ -61,6 +60,8 @@ static const char* LOG_TAG = "NimBLERemoteCharacteristic"; m_notifyCallback = nullptr; m_timestamp = 0; m_valMux = portMUX_INITIALIZER_UNLOCKED; + + NIMBLE_LOGD(LOG_TAG, "<< NimBLERemoteCharacteristic(): %s", m_uuid.toString().c_str()); } // NimBLERemoteCharacteristic @@ -208,15 +209,21 @@ int NimBLERemoteCharacteristic::descriptorDiscCB(uint16_t conn_handle, bool NimBLERemoteCharacteristic::retrieveDescriptors(const NimBLEUUID *uuid_filter) { NIMBLE_LOGD(LOG_TAG, ">> retrieveDescriptors() for characteristic: %s", getUUID().toString().c_str()); + uint16_t endHandle = getRemoteService()->getEndHandle(this); + if(m_handle >= endHandle) { + return false; + } + int rc = 0; ble_task_data_t taskData = {this, xTaskGetCurrentTaskHandle(), 0, nullptr}; desc_filter_t filter = {uuid_filter, &taskData}; rc = ble_gattc_disc_all_dscs(getRemoteService()->getClient()->getConnId(), m_handle, - getRemoteService()->getEndHandle(), + endHandle, NimBLERemoteCharacteristic::descriptorDiscCB, &filter); + if (rc != 0) { NIMBLE_LOGE(LOG_TAG, "ble_gattc_disc_all_chrs: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc)); return false; @@ -225,12 +232,13 @@ bool NimBLERemoteCharacteristic::retrieveDescriptors(const NimBLEUUID *uuid_filt ulTaskNotifyTake(pdTRUE, portMAX_DELAY); if(taskData.rc != 0) { + NIMBLE_LOGE(LOG_TAG, "ble_gattc_disc_all_chrs: startHandle:%d endHandle:%d taskData.rc=%d %s", m_handle, endHandle, taskData.rc, NimBLEUtils::returnCodeToString(0x0100+taskData.rc)); return false; } return true; NIMBLE_LOGD(LOG_TAG, "<< retrieveDescriptors(): Found %d descriptors.", m_descriptorVector.size()); -} // getDescriptors +} // retrieveDescriptors /** @@ -243,7 +251,7 @@ NimBLERemoteDescriptor* NimBLERemoteCharacteristic::getDescriptor(const NimBLEUU for(auto &it: m_descriptorVector) { if(it->getUUID() == uuid) { - NIMBLE_LOGD(LOG_TAG, "<< getDescriptor: found"); + NIMBLE_LOGD(LOG_TAG, "<< getDescriptor: found the descriptor with uuid: %s", uuid.toString().c_str()); return it; } } @@ -253,7 +261,18 @@ NimBLERemoteDescriptor* NimBLERemoteCharacteristic::getDescriptor(const NimBLEUU if(m_descriptorVector.size() > prev_size) { return m_descriptorVector.back(); } + + // If the request was successful but 16/32 bit descriptor not found + // try again with the 128 bit uuid. + if(uuid.bitSize() == BLE_UUID_TYPE_16 || + uuid.bitSize() == BLE_UUID_TYPE_32) + { + NimBLEUUID uuid128(uuid); + uuid128.to128(); + return getDescriptor(uuid128); + } } + NIMBLE_LOGD(LOG_TAG, "<< getDescriptor: Not found"); return nullptr; } // getDescriptor @@ -447,9 +466,10 @@ std::string NimBLERemoteCharacteristic::readValue(time_t *timestamp) { } } while(rc != 0 && retryCount--); + time_t t = time(nullptr); portENTER_CRITICAL(&m_valMux); m_value = value; - m_timestamp = time(nullptr); + m_timestamp = t; if(timestamp != nullptr) { *timestamp = m_timestamp; } @@ -506,19 +526,19 @@ int NimBLERemoteCharacteristic::onReadCB(uint16_t conn_handle, * @param [in] notifyCallback A callback to be invoked for a notification. * @param [in] response If write response required set this to true. * If NULL is provided then no callback is performed. - * @return true if successful. + * @return false if writing to the descriptor failed. */ bool NimBLERemoteCharacteristic::setNotify(uint16_t val, notify_callback notifyCallback, bool response) { NIMBLE_LOGD(LOG_TAG, ">> setNotify(): %s, %02x", toString().c_str(), val); + m_notifyCallback = notifyCallback; + NimBLERemoteDescriptor* desc = getDescriptor(NimBLEUUID((uint16_t)0x2902)); if(desc == nullptr) { - NIMBLE_LOGE(LOG_TAG, "<< setNotify(): Could not get descriptor"); - return false; + NIMBLE_LOGW(LOG_TAG, "<< setNotify(): Callback set, CCCD not found"); + return true; } - m_notifyCallback = notifyCallback; - NIMBLE_LOGD(LOG_TAG, "<< setNotify()"); return desc->writeValue((uint8_t *)&val, 2, response); @@ -531,7 +551,7 @@ bool NimBLERemoteCharacteristic::setNotify(uint16_t val, notify_callback notifyC * @param [in] notifyCallback A callback to be invoked for a notification. * @param [in] response If true, require a write response from the descriptor write operation. * If NULL is provided then no callback is performed. - * @return true if successful. + * @return false if writing to the descriptor failed. */ bool NimBLERemoteCharacteristic::subscribe(bool notifications, notify_callback notifyCallback, bool response) { if(notifications) { @@ -545,7 +565,7 @@ bool NimBLERemoteCharacteristic::subscribe(bool notifications, notify_callback n /** * @brief Unsubscribe for notifications or indications. * @param [in] response bool if true, require a write response from the descriptor write operation. - * @return true if successful. + * @return false if writing to the descriptor failed. */ bool NimBLERemoteCharacteristic::unsubscribe(bool response) { return setNotify(0x00, nullptr, response); diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteDescriptor.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteDescriptor.cpp index 9281e7df7..fc0f06b67 100644 --- a/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteDescriptor.cpp +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteDescriptor.cpp @@ -31,6 +31,7 @@ static const char* LOG_TAG = "NimBLERemoteDescriptor"; NimBLERemoteDescriptor::NimBLERemoteDescriptor(NimBLERemoteCharacteristic* pRemoteCharacteristic, const struct ble_gatt_dsc *dsc) { + NIMBLE_LOGD(LOG_TAG, ">> NimBLERemoteDescriptor()"); switch (dsc->uuid.u.type) { case BLE_UUID_TYPE_16: m_uuid = NimBLEUUID(dsc->uuid.u16.value); @@ -42,12 +43,13 @@ NimBLERemoteDescriptor::NimBLERemoteDescriptor(NimBLERemoteCharacteristic* pRemo m_uuid = NimBLEUUID(const_cast(&dsc->uuid.u128)); break; default: - m_uuid = nullptr; break; } m_handle = dsc->handle; m_pRemoteCharacteristic = pRemoteCharacteristic; + + NIMBLE_LOGD(LOG_TAG, "<< NimBLERemoteDescriptor(): %s", m_uuid.toString().c_str()); } diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteService.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteService.cpp index 8901175dc..cb20e0b3f 100644 --- a/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteService.cpp +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteService.cpp @@ -44,12 +44,11 @@ NimBLERemoteService::NimBLERemoteService(NimBLEClient* pClient, const struct ble m_uuid = NimBLEUUID(const_cast(&service->uuid.u128)); break; default: - m_uuid = nullptr; break; } m_startHandle = service->start_handle; m_endHandle = service->end_handle; - NIMBLE_LOGD(LOG_TAG, "<< NimBLERemoteService()"); + NIMBLE_LOGD(LOG_TAG, "<< NimBLERemoteService(): %s", m_uuid.toString().c_str()); } @@ -95,8 +94,11 @@ NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const char* u * @return A pointer to the characteristic object, or nullptr if not found. */ NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const NimBLEUUID &uuid) { + NIMBLE_LOGD(LOG_TAG, ">> getCharacteristic: uuid: %s", uuid.toString().c_str()); + for(auto &it: m_characteristicVector) { if(it->getUUID() == uuid) { + NIMBLE_LOGD(LOG_TAG, "<< getCharacteristic: found the characteristic with uuid: %s", uuid.toString().c_str()); return it; } } @@ -106,8 +108,19 @@ NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const NimBLEU if(m_characteristicVector.size() > prev_size) { return m_characteristicVector.back(); } + + // If the request was successful but 16/32 bit characteristic not found + // try again with the 128 bit uuid. + if(uuid.bitSize() == BLE_UUID_TYPE_16 || + uuid.bitSize() == BLE_UUID_TYPE_32) + { + NimBLEUUID uuid128(uuid); + uuid128.to128(); + return getCharacteristic(uuid128); + } } + NIMBLE_LOGD(LOG_TAG, "<< getCharacteristic: not found"); return nullptr; } // getCharacteristic @@ -236,6 +249,23 @@ uint16_t NimBLERemoteService::getEndHandle() { return m_endHandle; } // getEndHandle +/** + * @brief Get the end handle of specified NimBLERemoteCharacteristic. + */ + +uint16_t NimBLERemoteService::getEndHandle(NimBLERemoteCharacteristic *pCharacteristic) { + uint16_t endHandle = m_endHandle; + + for(auto &it: m_characteristicVector) { + uint16_t defHandle = it->getDefHandle() - 1; + if(defHandle > pCharacteristic->getDefHandle() && endHandle > defHandle) { + endHandle = defHandle; + } + } + + return endHandle; +} // getEndHandle + /** * @brief Get the service start handle. diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteService.h b/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteService.h index 751c9effb..4920844e4 100644 --- a/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteService.h +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteService.h @@ -70,6 +70,7 @@ private: uint16_t getStartHandle(); uint16_t getEndHandle(); + uint16_t getEndHandle(NimBLERemoteCharacteristic *pCharacteristic); void releaseSemaphores(); // Properties diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEScan.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLEScan.cpp index dc82de549..122ff3332 100644 --- a/lib/libesp32/NimBLE-Arduino/src/NimBLEScan.cpp +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEScan.cpp @@ -30,7 +30,6 @@ static const char* LOG_TAG = "NimBLEScan"; * @brief Scan constuctor. */ NimBLEScan::NimBLEScan() { - m_own_addr_type = 0; m_scan_params.filter_policy = BLE_HCI_SCAN_FILT_NO_WL; m_scan_params.passive = 1; // If set, don’t send scan requests to advertisers (i.e., don’t request additional advertising data). m_scan_params.itvl = 0; // This is defined as the time interval from when the Controller started its last LE scan until it begins the subsequent LE scan. (units=0.625 msec) @@ -38,9 +37,10 @@ NimBLEScan::NimBLEScan() { m_scan_params.limited = 0; // If set, only discover devices in limited discoverable mode. m_scan_params.filter_duplicates = 0; // If set, the controller ignores all but the first advertisement from each device. m_pAdvertisedDeviceCallbacks = nullptr; - m_stopped = true; + m_ignoreResults = false; m_wantDuplicates = false; m_pTaskData = nullptr; + m_duration = BLE_HS_FOREVER; // make sure this is non-zero in the event of a host reset } @@ -63,8 +63,8 @@ NimBLEScan::~NimBLEScan() { switch(event->type) { case BLE_GAP_EVENT_DISC: { - if(pScan->m_stopped) { - NIMBLE_LOGE(LOG_TAG, "Scan stop called, ignoring results."); + if(pScan->m_ignoreResults) { + NIMBLE_LOGE(LOG_TAG, "Scan op in progress - ignoring results"); return 0; } @@ -129,7 +129,6 @@ NimBLEScan::~NimBLEScan() { pScan->m_scanCompleteCB(pScan->m_scanResults); } - pScan->m_stopped = true; if(pScan->m_pTaskData != nullptr) { pScan->m_pTaskData->rc = event->disc_complete.reason; xTaskNotifyGive(pScan->m_pTaskData->task); @@ -238,7 +237,7 @@ void NimBLEScan::setWindow(uint16_t windowMSecs) { * @return true if scanning or scan starting. */ bool NimBLEScan::isScanning() { - return !m_stopped; + return ble_gap_disc_active(); } @@ -252,25 +251,6 @@ bool NimBLEScan::isScanning() { bool NimBLEScan::start(uint32_t duration, void (*scanCompleteCB)(NimBLEScanResults), bool is_continue) { NIMBLE_LOGD(LOG_TAG, ">> start(duration=%d)", duration); - // If Host is not synced we cannot start scanning. - if(!NimBLEDevice::m_synced) { - NIMBLE_LOGC(LOG_TAG, "Host reset, wait for sync."); - return false; - } - - if(ble_gap_conn_active()) { - NIMBLE_LOGE(LOG_TAG, "Connection in progress - must wait."); - return false; - } - - // If we are already scanning don't start again or we will get stuck on the semaphore. - if(!m_stopped || ble_gap_disc_active()) { // double check - can cause host reset. - NIMBLE_LOGE(LOG_TAG, "Scan already in progress"); - return false; - } - - m_stopped = false; - // Save the callback to be invoked when the scan completes. m_scanCompleteCB = scanCompleteCB; // Save the duration in the case that the host is reset so we can reuse it. @@ -281,32 +261,51 @@ bool NimBLEScan::start(uint32_t duration, void (*scanCompleteCB)(NimBLEScanResul duration = BLE_HS_FOREVER; } else{ - duration = duration*1000; // convert duration to milliseconds + // convert duration to milliseconds + duration = duration * 1000; } - // if we are connecting to devices that are advertising even after being connected, multiconnecting peripherals - // then we should not clear vector or we will connect the same device few times + // Set the flag to ignore the results while we are deleting the vector if(!is_continue) { - clearResults(); + m_ignoreResults = true; } - int rc = 0; - do{ - rc = ble_gap_disc(m_own_addr_type, duration, &m_scan_params, - NimBLEScan::handleGapEvent, this); - if(rc == BLE_HS_EBUSY) { - vTaskDelay(1 / portTICK_PERIOD_MS); - } - } while(rc == BLE_HS_EBUSY); + int rc = ble_gap_disc(NimBLEDevice::m_own_addr_type, duration, &m_scan_params, + NimBLEScan::handleGapEvent, this); - if (rc != 0 && rc != BLE_HS_EDONE) { - NIMBLE_LOGE(LOG_TAG, "Error initiating GAP discovery procedure; rc=%d, %s", - rc, NimBLEUtils::returnCodeToString(rc)); - m_stopped = true; + switch(rc) { + case 0: + if(!is_continue) { + clearResults(); + } + break; + + case BLE_HS_EALREADY: + break; + + case BLE_HS_EBUSY: + NIMBLE_LOGE(LOG_TAG, "Unable to scan - connection in progress."); + break; + + case BLE_HS_ETIMEOUT_HCI: + case BLE_HS_EOS: + case BLE_HS_ECONTROLLER: + case BLE_HS_ENOTSYNCED: + NIMBLE_LOGC(LOG_TAG, "Unable to scan - Host Reset"); + break; + + default: + NIMBLE_LOGE(LOG_TAG, "Error initiating GAP discovery procedure; rc=%d, %s", + rc, NimBLEUtils::returnCodeToString(rc)); + break; + } + + m_ignoreResults = false; + NIMBLE_LOGD(LOG_TAG, "<< start()"); + + if(rc != 0 && rc != BLE_HS_EALREADY) { return false; } - - NIMBLE_LOGD(LOG_TAG, "<< start()"); return true; } // start @@ -347,8 +346,6 @@ bool NimBLEScan::stop() { return false; } - m_stopped = true; - if (rc != BLE_HS_EALREADY && m_scanCompleteCB != nullptr) { m_scanCompleteCB(m_scanResults); } @@ -381,13 +378,25 @@ void NimBLEScan::erase(const NimBLEAddress &address) { /** - * @brief If the host reset the scan will have stopped so we should set the flag as stopped. + * @brief Called when host reset, we set a flag to stop scanning until synced. */ void NimBLEScan::onHostReset() { - m_stopped = true; + m_ignoreResults = true; } +/** + * @brief If the host reset and re-synced this is called. + * If the application was scanning indefinitely with a callback, restart it. + */ +void NimBLEScan::onHostSync() { + m_ignoreResults = false; + + if(m_duration == 0 && m_pAdvertisedDeviceCallbacks != nullptr) { + start(m_duration, m_scanCompleteCB); + } +} + /** * @brief Get the results of the scan. * @return NimBLEScanResults object. diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEScan.h b/lib/libesp32/NimBLE-Arduino/src/NimBLEScan.h index 822629025..9007a7dd8 100644 --- a/lib/libesp32/NimBLE-Arduino/src/NimBLEScan.h +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEScan.h @@ -83,12 +83,12 @@ private: ~NimBLEScan(); static int handleGapEvent(ble_gap_event* event, void* arg); void onHostReset(); + void onHostSync(); NimBLEAdvertisedDeviceCallbacks* m_pAdvertisedDeviceCallbacks = nullptr; void (*m_scanCompleteCB)(NimBLEScanResults scanResults); ble_gap_disc_params m_scan_params; - uint8_t m_own_addr_type; - bool m_stopped; + bool m_ignoreResults; bool m_wantDuplicates; NimBLEScanResults m_scanResults; uint32_t m_duration; diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEServer.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLEServer.cpp index 8c75192a9..655511aea 100644 --- a/lib/libesp32/NimBLE-Arduino/src/NimBLEServer.cpp +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEServer.cpp @@ -296,6 +296,7 @@ size_t NimBLEServer::getConnectedCount() { } server->m_pServerCallbacks->onDisconnect(server); + server->m_pServerCallbacks->onDisconnect(server, &event->disconnect.conn); if(server->m_advertiseOnDisconnect) { server->startAdvertising(); @@ -658,6 +659,10 @@ void NimBLEServerCallbacks::onDisconnect(NimBLEServer* pServer) { NIMBLE_LOGD("NimBLEServerCallbacks", "onDisconnect(): Default"); } // onDisconnect +void NimBLEServerCallbacks::onDisconnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) { + NIMBLE_LOGD("NimBLEServerCallbacks", "onDisconnect(): Default"); +} // onDisconnect + uint32_t NimBLEServerCallbacks::onPassKeyRequest(){ NIMBLE_LOGD("NimBLEServerCallbacks", "onPassKeyRequest: default: 123456"); return 123456; diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEServer.h b/lib/libesp32/NimBLE-Arduino/src/NimBLEServer.h index 1fa24b23c..bedf9cf58 100644 --- a/lib/libesp32/NimBLE-Arduino/src/NimBLEServer.h +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEServer.h @@ -114,6 +114,15 @@ public: */ virtual void onDisconnect(NimBLEServer* pServer); + /** + * @brief Handle a client disconnection. + * This is called when a client discconnects. + * @param [in] pServer A pointer to the %BLE server that received the client disconnection. + * @param [in] desc A pointer to the connection description structure containig information + * about the connection. + */ + virtual void onDisconnect(NimBLEServer* pServer, ble_gap_conn_desc* desc); + /** * @brief Called when a client requests a passkey for pairing. * @return The passkey to be sent to the client. diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEUUID.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLEUUID.cpp index 1b00a3237..21ff27047 100644 --- a/lib/libesp32/NimBLE-Arduino/src/NimBLEUUID.cpp +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEUUID.cpp @@ -264,6 +264,37 @@ std::string NimBLEUUID::toString() const { */ bool NimBLEUUID::operator ==(const NimBLEUUID & rhs) const { if(m_valueSet && rhs.m_valueSet) { + NIMBLE_LOGD(LOG_TAG,"Comparing UUIDs; type %u to %u; UUID %s to %s", + m_uuid.u.type, rhs.m_uuid.u.type, + this->toString().c_str(), rhs.toString().c_str()); + + if(m_uuid.u.type != rhs.m_uuid.u.type) { + uint8_t uuidBase[16] = { + 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + if(m_uuid.u.type == BLE_UUID_TYPE_128){ + if(rhs.m_uuid.u.type == BLE_UUID_TYPE_16){ + memcpy(uuidBase+12, &rhs.m_uuid.u16.value, 2); + } else if (rhs.m_uuid.u.type == BLE_UUID_TYPE_32){ + memcpy(uuidBase+12, &rhs.m_uuid.u32.value, 4); + } + return memcmp(m_uuid.u128.value,uuidBase,16) == 0; + + } else if(rhs.m_uuid.u.type == BLE_UUID_TYPE_128) { + if(m_uuid.u.type == BLE_UUID_TYPE_16){ + memcpy(uuidBase+12, &m_uuid.u16.value, 2); + } else if (m_uuid.u.type == BLE_UUID_TYPE_32){ + memcpy(uuidBase+12, &m_uuid.u32.value, 4); + } + return memcmp(rhs.m_uuid.u128.value,uuidBase,16) == 0; + + } else { + return false; + } + } + return ble_uuid_cmp(&m_uuid.u, &rhs.m_uuid.u) == 0; } diff --git a/lib/libesp32/NimBLE-Arduino/src/esp-hci/src/esp_nimble_hci.c b/lib/libesp32/NimBLE-Arduino/src/esp-hci/src/esp_nimble_hci.c index 0ba15e9c3..98c133fa5 100644 --- a/lib/libesp32/NimBLE-Arduino/src/esp-hci/src/esp_nimble_hci.c +++ b/lib/libesp32/NimBLE-Arduino/src/esp-hci/src/esp_nimble_hci.c @@ -19,6 +19,13 @@ * under the License. */ +/* + * This file has been modified by Ryan Powell, aka h2zero. + * The modifications are for the purpose of improving performance and support + * for Esprssif versions used by the ardruino-esp32 core that are less current + * than the esp-idf releases. + */ + #include #include "sysinit/sysinit.h" #include "nimble/hci_common.h" @@ -30,8 +37,10 @@ #include "esp_bt.h" #include "freertos/semphr.h" #include "esp_compiler.h" +/* IPC is used to improve performance when calls come from a processor not running the NimBLE stack */ +/* but does not exist for solo */ #ifndef CONFIG_FREERTOS_UNICORE -#include "esp_ipc.h" + #include "esp_ipc.h" #endif #define NIMBLE_VHCI_TIMEOUT_MS 2000 @@ -81,31 +90,40 @@ void ble_hci_trans_cfg_hs(ble_hci_trans_rx_cmd_fn *cmd_cb, ble_hci_rx_acl_hs_arg = acl_arg; } -void ble_hci_trans_hs_cmd_tx_on_core_0(void *arg) +/* Added; Called from the core NimBLE is running on, not used for unicore */ +#ifndef CONFIG_FREERTOS_UNICORE +void ble_hci_trans_hs_cmd_tx_on_core(void *arg) { - uint8_t *cmd = arg; - uint16_t len = BLE_HCI_CMD_HDR_LEN + cmd[3] + 1; - esp_vhci_host_send_packet(cmd, len); + // Ugly but necessary as the arduino core does not provide enough IPC stack for variables. + esp_vhci_host_send_packet((uint8_t*)arg, *((uint8_t*)arg + 3) + 1 + BLE_HCI_CMD_HDR_LEN); } +#endif +/* Modified to use ipc calls in arduino to correct performance issues */ int ble_hci_trans_hs_cmd_tx(uint8_t *cmd) { + uint16_t len; uint8_t rc = 0; assert(cmd != NULL); *cmd = BLE_HCI_UART_H4_CMD; + len = BLE_HCI_CMD_HDR_LEN + cmd[3] + 1; if (!esp_vhci_host_check_send_available()) { ESP_LOGD(TAG, "Controller not ready to receive packets"); } if (xSemaphoreTake(vhci_send_sem, NIMBLE_VHCI_TIMEOUT_MS / portTICK_PERIOD_MS) == pdTRUE) { - if (xPortGetCoreID() != 0) { +/* esp_ipc_call_blocking does not exist for solo */ #ifndef CONFIG_FREERTOS_UNICORE - esp_ipc_call_blocking(0, ble_hci_trans_hs_cmd_tx_on_core_0, cmd); -#endif + if (xPortGetCoreID() != CONFIG_BT_NIMBLE_PINNED_TO_CORE) { + esp_ipc_call_blocking(CONFIG_BT_NIMBLE_PINNED_TO_CORE, + ble_hci_trans_hs_cmd_tx_on_core, cmd); } else { - ble_hci_trans_hs_cmd_tx_on_core_0(cmd); + esp_vhci_host_send_packet(cmd, len); } +#else /* Unicore */ + esp_vhci_host_send_packet(cmd, len); +#endif } else { rc = BLE_HS_ETIMEOUT_HCI; } @@ -124,21 +142,21 @@ int ble_hci_trans_ll_evt_tx(uint8_t *hci_ev) return rc; } -void ble_hci_trans_hs_acl_tx_on_core_0(void *arg) +/* Added; Called from the core NimBLE is running on, not used for unicore */ +#ifndef CONFIG_FREERTOS_UNICORE +void ble_hci_trans_hs_acl_tx_on_core(void *arg) { - uint8_t data[MYNEWT_VAL(BLE_ACL_BUF_SIZE) + 1]; - struct os_mbuf *om = arg; - uint16_t len = 1 + OS_MBUF_PKTLEN(om); - - data[0] = BLE_HCI_UART_H4_ACL; - os_mbuf_copydata(om, 0, OS_MBUF_PKTLEN(om), &data[1]); - - esp_vhci_host_send_packet(data, len); + // Ugly but necessary as the arduino core does not provide enough IPC stack for variables. + esp_vhci_host_send_packet((uint8_t*)arg + 2, *(uint16_t*)arg); } +#endif +/* Modified to use ipc calls in arduino to correct performance issues */ int ble_hci_trans_hs_acl_tx(struct os_mbuf *om) { - uint8_t rc = 0; + uint16_t len = 0; + uint8_t data[MYNEWT_VAL(BLE_ACL_BUF_SIZE) + 3], rc = 0; + bool tx_using_nimble_core = 0; /* If this packet is zero length, just free it */ if (OS_MBUF_PKTLEN(om) == 0) { os_mbuf_free_chain(om); @@ -149,14 +167,36 @@ int ble_hci_trans_hs_acl_tx(struct os_mbuf *om) ESP_LOGD(TAG, "Controller not ready to receive packets"); } - if (xSemaphoreTake(vhci_send_sem, NIMBLE_VHCI_TIMEOUT_MS / portTICK_PERIOD_MS) == pdTRUE) { - if (xPortGetCoreID() != 0) { + len = 1 + OS_MBUF_PKTLEN(om); +/* Don't check core ID if unicore */ #ifndef CONFIG_FREERTOS_UNICORE - esp_ipc_call_blocking(0, ble_hci_trans_hs_acl_tx_on_core_0, om); + tx_using_nimble_core = xPortGetCoreID() != CONFIG_BT_NIMBLE_PINNED_TO_CORE; + if (tx_using_nimble_core) { + data[0] = len; + data[1] = (len >> 8); + data[2] = BLE_HCI_UART_H4_ACL; + os_mbuf_copydata(om, 0, OS_MBUF_PKTLEN(om), &data[3]); + } else { + data[0] = BLE_HCI_UART_H4_ACL; + os_mbuf_copydata(om, 0, OS_MBUF_PKTLEN(om), &data[1]); + } +#else /* Unicore */ + data[0] = BLE_HCI_UART_H4_ACL; + os_mbuf_copydata(om, 0, OS_MBUF_PKTLEN(om), &data[1]); #endif + + if (xSemaphoreTake(vhci_send_sem, NIMBLE_VHCI_TIMEOUT_MS / portTICK_PERIOD_MS) == pdTRUE) { +/* esp_ipc_call_blocking does not exist for solo */ +#ifndef CONFIG_FREERTOS_UNICORE + if (tx_using_nimble_core) { + esp_ipc_call_blocking(CONFIG_BT_NIMBLE_PINNED_TO_CORE, + ble_hci_trans_hs_acl_tx_on_core, data); } else { - ble_hci_trans_hs_acl_tx_on_core_0(om); + esp_vhci_host_send_packet(data, len); } +#else /* Unicore */ + esp_vhci_host_send_packet(data, len); +#endif } else { rc = BLE_HS_ETIMEOUT_HCI; } @@ -367,6 +407,13 @@ static int host_rcv_pkt(uint8_t *data, uint16_t len) totlen = BLE_HCI_EVENT_HDR_LEN + data[2]; assert(totlen <= UINT8_MAX + BLE_HCI_EVENT_HDR_LEN); + if (totlen > MYNEWT_VAL(BLE_HCI_EVT_BUF_SIZE)) { + ESP_LOGE(TAG, "Received HCI data length at host (%d) exceeds maximum configured HCI event buffer size (%d).", + totlen, MYNEWT_VAL(BLE_HCI_EVT_BUF_SIZE)); + ble_hs_sched_reset(BLE_HS_ECONTROLLER); + return 0; + } + if (data[1] == BLE_HCI_EVCODE_HW_ERROR) { assert(0); } @@ -476,6 +523,9 @@ esp_err_t esp_nimble_hci_and_controller_init(void) esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + /* Added to ensure BLE only mode */ + bt_cfg.mode = ESP_BT_MODE_BLE; + /* Added to set max connections from nimconfig */ bt_cfg.ble_max_conn = CONFIG_BT_NIMBLE_MAX_CONNECTIONS; if ((ret = esp_bt_controller_init(&bt_cfg)) != ESP_OK) { @@ -485,6 +535,7 @@ esp_err_t esp_nimble_hci_and_controller_init(void) if ((ret = esp_bt_controller_enable(ESP_BT_MODE_BLE)) != ESP_OK) { return ret; } + return esp_nimble_hci_init(); } diff --git a/lib/libesp32/NimBLE-Arduino/src/esp_nimble_cfg.h b/lib/libesp32/NimBLE-Arduino/src/esp_nimble_cfg.h index 384ec4a56..bafbeac8f 100644 --- a/lib/libesp32/NimBLE-Arduino/src/esp_nimble_cfg.h +++ b/lib/libesp32/NimBLE-Arduino/src/esp_nimble_cfg.h @@ -483,6 +483,10 @@ #define MYNEWT_VAL_BLE_L2CAP_COC_MAX_NUM CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM #endif +#ifndef MYNEWT_VAL_BLE_L2CAP_COC_MPS +#define MYNEWT_VAL_BLE_L2CAP_COC_MPS (MYNEWT_VAL_MSYS_1_BLOCK_SIZE - 8) +#endif + #ifndef MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS #define MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS (1) #endif diff --git a/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_eddystone.c b/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_eddystone.c index 7d80d134d..eccb3e988 100644 --- a/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_eddystone.c +++ b/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_eddystone.c @@ -76,7 +76,7 @@ ble_eddystone_set_adv_data_gen(struct ble_hs_adv_fields *adv_fields, if (adv_fields->num_uuids16 > BLE_EDDYSTONE_MAX_UUIDS16) { return BLE_HS_EINVAL; } - if (svc_data_len > BLE_EDDYSTONE_MAX_SVC_DATA_LEN) { + if (svc_data_len > (BLE_EDDYSTONE_MAX_SVC_DATA_LEN - BLE_EDDYSTONE_SVC_DATA_BASE_SZ)) { return BLE_HS_EINVAL; } if (adv_fields->num_uuids16 > 0 && !adv_fields->uuids16_is_complete) { diff --git a/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_gap.c b/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_gap.c index 09ae27047..d77ff6a87 100644 --- a/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_gap.c +++ b/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_gap.c @@ -1017,7 +1017,7 @@ ble_gap_master_failed(int status) #endif default: - BLE_HS_DBG_ASSERT(0); + //BLE_HS_DBG_ASSERT(0); break; } } @@ -1458,8 +1458,8 @@ ble_gap_rx_periodic_adv_rpt(struct hci_le_subev_periodic_adv_rpt *evt) { struct ble_hs_periodic_sync *psync; struct ble_gap_event event; - ble_gap_event_fn *cb; - void *cb_arg; + ble_gap_event_fn *cb = NULL; + void *cb_arg = NULL; ble_hs_lock(); psync = ble_hs_periodic_sync_find_by_handle(evt->sync_handle); diff --git a/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_hs_conn.c b/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_hs_conn.c index eb65e3288..b45128e23 100644 --- a/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_hs_conn.c +++ b/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_hs_conn.c @@ -470,29 +470,52 @@ ble_hs_conn_timer(void) int32_t time_diff; uint16_t conn_handle; - conn_handle = BLE_HS_CONN_HANDLE_NONE; - next_exp_in = BLE_HS_FOREVER; - now = ble_npl_time_get(); + for (;;) { + conn_handle = BLE_HS_CONN_HANDLE_NONE; + next_exp_in = BLE_HS_FOREVER; + now = ble_npl_time_get(); - ble_hs_lock(); + ble_hs_lock(); - /* This loop performs one of two tasks: - * 1. Determine if any connections need to be terminated due to timeout. - * If so, break out of the loop and terminate the connection. This - * function will need to be executed again. - * 2. Otherwise, determine when the next timeout will occur. - */ - SLIST_FOREACH(conn, &ble_hs_conns, bhc_next) { - if (!(conn->bhc_flags & BLE_HS_CONN_F_TERMINATING)) { + /* This loop performs one of two tasks: + * 1. Determine if any connections need to be terminated due to timeout. + * If so, break out of the loop and terminate the connection. This + * function will need to be executed again. + * 2. Otherwise, determine when the next timeout will occur. + */ + SLIST_FOREACH(conn, &ble_hs_conns, bhc_next) { + if (!(conn->bhc_flags & BLE_HS_CONN_F_TERMINATING)) { #if MYNEWT_VAL(BLE_L2CAP_RX_FRAG_TIMEOUT) != 0 - /* Check each connection's rx fragment timer. If too much time - * passes after a partial packet is received, the connection is - * terminated. - */ - if (conn->bhc_rx_chan != NULL) { - time_diff = conn->bhc_rx_timeout - now; + /* Check each connection's rx fragment timer. If too much time + * passes after a partial packet is received, the connection is + * terminated. + */ + if (conn->bhc_rx_chan != NULL) { + time_diff = conn->bhc_rx_timeout - now; + if (time_diff <= 0) { + /* ACL reassembly has timed out. Remember the connection + * handle so it can be terminated after the mutex is + * unlocked. + */ + conn_handle = conn->bhc_handle; + break; + } + + /* Determine if this connection is the soonest to time out. */ + if (time_diff < next_exp_in) { + next_exp_in = time_diff; + } + } +#endif + +#if BLE_HS_ATT_SVR_QUEUED_WRITE_TMO + /* Check each connection's rx queued write timer. If too much + * time passes after a prep write is received, the queue is + * cleared. + */ + time_diff = ble_att_svr_ticks_until_tmo(&conn->bhc_att_svr, now); if (time_diff <= 0) { /* ACL reassembly has timed out. Remember the connection * handle so it can be terminated after the mutex is @@ -506,45 +529,22 @@ ble_hs_conn_timer(void) if (time_diff < next_exp_in) { next_exp_in = time_diff; } - } #endif - -#if BLE_HS_ATT_SVR_QUEUED_WRITE_TMO - /* Check each connection's rx queued write timer. If too much - * time passes after a prep write is received, the queue is - * cleared. - */ - time_diff = ble_att_svr_ticks_until_tmo(&conn->bhc_att_svr, now); - if (time_diff <= 0) { - /* ACL reassembly has timed out. Remember the connection - * handle so it can be terminated after the mutex is - * unlocked. - */ - conn_handle = conn->bhc_handle; - break; } - - /* Determine if this connection is the soonest to time out. */ - if (time_diff < next_exp_in) { - next_exp_in = time_diff; - } -#endif } + + ble_hs_unlock(); + + /* If a connection has timed out, terminate it. We need to repeatedly + * call this function again to determine when the next timeout is. + */ + if (conn_handle != BLE_HS_CONN_HANDLE_NONE) { + ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM); + continue; + } + + return next_exp_in; } - - ble_hs_unlock(); - - /* If a connection has timed out, terminate it. We need to recursively - * call this function again to determine when the next timeout is. This - * is a tail-recursive call, so it should be optimized to execute in the - * same stack frame. - */ - if (conn_handle != BLE_HS_CONN_HANDLE_NONE) { - ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM); - return ble_hs_conn_timer(); - } - - return next_exp_in; } int diff --git a/lib/libesp32/NimBLE-Arduino/src/nimble/host/store/config/src/ble_store_nvs.c b/lib/libesp32/NimBLE-Arduino/src/nimble/host/store/config/src/ble_store_nvs.c index a6fea44c7..13dae8d2b 100644 --- a/lib/libesp32/NimBLE-Arduino/src/nimble/host/store/config/src/ble_store_nvs.c +++ b/lib/libesp32/NimBLE-Arduino/src/nimble/host/store/config/src/ble_store_nvs.c @@ -180,7 +180,9 @@ static int get_nvs_db_attribute(int obj_type, bool empty, void *value, int num_value) { union ble_store_value cur = {0}; +#if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY) struct ble_hs_dev_records p_dev_rec = {0}; +#endif esp_err_t err; int i, count = 0, max_limit = 0; char key_string[NIMBLE_NVS_STR_NAME_MAX_LEN]; @@ -190,11 +192,15 @@ get_nvs_db_attribute(int obj_type, bool empty, void *value, int num_value) for (i = 1; i <= max_limit; i++) { get_nvs_key_string(obj_type, i, key_string); +#if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY) if (obj_type != BLE_STORE_OBJ_TYPE_PEER_DEV_REC) { +#endif err = get_nvs_db_value(obj_type, key_string, &cur); +#if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY) } else { err = get_nvs_peer_record(key_string, &p_dev_rec); } +#endif /* Check if the user is searching for empty index to write to */ if (err == ESP_ERR_NVS_NOT_FOUND) { if (empty) { @@ -206,10 +212,13 @@ get_nvs_db_attribute(int obj_type, bool empty, void *value, int num_value) /* If user has provided value, then the purpose is to find * non-matching entry from NVS */ if (value) { +#if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY) if (obj_type == BLE_STORE_OBJ_TYPE_PEER_DEV_REC) { err = get_nvs_matching_index(&p_dev_rec, value, num_value, sizeof(struct ble_hs_dev_records)); - } else { + } else +#endif + { if (obj_type != BLE_STORE_OBJ_TYPE_CCCD) { err = get_nvs_matching_index(&cur.sec, value, num_value, sizeof(struct ble_store_value_sec)); @@ -376,7 +385,9 @@ populate_db_from_nvs(int obj_type, void *dst, int *db_num) { uint8_t *db_item = (uint8_t *)dst; union ble_store_value cur = {0}; +#if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY) struct ble_hs_dev_records p_dev_rec = {0}; +#endif esp_err_t err; int i; @@ -385,8 +396,9 @@ populate_db_from_nvs(int obj_type, void *dst, int *db_num) for (i = 1; i <= get_nvs_max_obj_value(obj_type); i++) { get_nvs_key_string(obj_type, i, key_string); +#if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY) if (obj_type != BLE_STORE_OBJ_TYPE_PEER_DEV_REC) { - +#endif err = get_nvs_db_value(obj_type, key_string, &cur); if (err == ESP_ERR_NVS_NOT_FOUND) { continue; @@ -394,6 +406,7 @@ populate_db_from_nvs(int obj_type, void *dst, int *db_num) ESP_LOGE(TAG, "NVS read operation failed !!"); return -1; } +#if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY) } else { err = get_nvs_peer_record(key_string, &p_dev_rec); if (err == ESP_ERR_NVS_NOT_FOUND) { @@ -410,7 +423,9 @@ populate_db_from_nvs(int obj_type, void *dst, int *db_num) memcpy(db_item, &p_dev_rec, sizeof(struct ble_hs_dev_records)); db_item += sizeof(struct ble_hs_dev_records); (*db_num)++; - } else { + } else +#endif + { if (obj_type == BLE_STORE_OBJ_TYPE_CCCD) { ESP_LOGD(TAG, "CCCD in RAM is filled up from NVS index = %d", i); memcpy(db_item, &cur.cccd, sizeof(struct ble_store_value_cccd)); @@ -492,6 +507,11 @@ int ble_store_config_persist_cccds(void) union ble_store_value val; nvs_count = get_nvs_db_attribute(BLE_STORE_OBJ_TYPE_CCCD, 0, NULL, 0); + if (nvs_count == -1) { + ESP_LOGE(TAG, "NVS operation failed while persisting CCCD"); + return BLE_HS_ESTORE_FAIL; + } + if (nvs_count < ble_store_config_num_cccds) { /* NVS db count less than RAM count, write operation */ @@ -518,6 +538,11 @@ int ble_store_config_persist_peer_secs(void) union ble_store_value val; nvs_count = get_nvs_db_attribute(BLE_STORE_OBJ_TYPE_PEER_SEC, 0, NULL, 0); + if (nvs_count == -1) { + ESP_LOGE(TAG, "NVS operation failed while persisting peer sec"); + return BLE_HS_ESTORE_FAIL; + } + if (nvs_count < ble_store_config_num_peer_secs) { /* NVS db count less than RAM count, write operation */ @@ -544,6 +569,11 @@ int ble_store_config_persist_our_secs(void) union ble_store_value val; nvs_count = get_nvs_db_attribute(BLE_STORE_OBJ_TYPE_OUR_SEC, 0, NULL, 0); + if (nvs_count == -1) { + ESP_LOGE(TAG, "NVS operation failed while persisting our sec"); + return BLE_HS_ESTORE_FAIL; + } + if (nvs_count < ble_store_config_num_our_secs) { /* NVS db count less than RAM count, write operation */ @@ -573,7 +603,13 @@ int ble_store_persist_peer_records(void) struct ble_hs_dev_records *peer_dev_rec = ble_rpa_get_peer_dev_records(); nvs_count = get_nvs_db_attribute(BLE_STORE_OBJ_TYPE_PEER_DEV_REC, 0, NULL, 0); + if (nvs_count == -1) { + ESP_LOGE(TAG, "NVS operation failed while persisting peer_dev_rec"); + return BLE_HS_ESTORE_FAIL; + } + if (nvs_count < ble_store_num_peer_dev_rec) { + /* NVS db count less than RAM count, write operation */ ESP_LOGD(TAG, "Persisting peer dev record to NVS..."); peer_rec = peer_dev_rec[ble_store_num_peer_dev_rec - 1]; diff --git a/lib/libesp32/NimBLE-Arduino/src/nimconfig.h b/lib/libesp32/NimBLE-Arduino/src/nimconfig.h index 2f10fa2fc..d90921fa1 100644 --- a/lib/libesp32/NimBLE-Arduino/src/nimconfig.h +++ b/lib/libesp32/NimBLE-Arduino/src/nimconfig.h @@ -15,8 +15,13 @@ * This converts them to "CONFIG_BT_NIMBLE_" format used in the latest IDF. */ -/* Detect if using ESP-IDF or Arduino (Arduino won't have these defines in sdkconfig) */ -#if defined(CONFIG_BT_NIMBLE_TASK_STACK_SIZE) || defined(CONFIG_NIMBLE_TASK_STACK_SIZE) +/* Detect if using ESP-IDF or Arduino (Arduino won't have these defines in sdkconfig) + * + * Note: We do not use #ifdef CONFIG_BT_NIMBLE_ENABLED since we cannot enable NimBLE when using + * Arduino as a component and the esp-nimble-compnent, so we check if other config options are defined. + * We also need to use a config parameter that must be present and not likely defined in the command line. + */ +#if defined(CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN) || defined(CONFIG_NIMBLE_GAP_DEVICE_NAME_MAX_LEN) #if defined(CONFIG_NIMBLE_ENABLED) && !defined(CONFIG_BT_NIMBLE_ENABLED) #define CONFIG_BT_NIMBLE_ENABLED @@ -51,22 +56,30 @@ /** @brief Comment out if not using NimBLE Client functions \n * Reduces flash size by approx. 7kB. */ +#ifndef CONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED #define CONFIG_BT_NIMBLE_ROLE_CENTRAL +#endif /** @brief Comment out if not using NimBLE Scan functions \n * Reduces flash size by approx. 26kB. */ +#ifndef CONFIG_BT_NIMBLE_ROLE_OBSERVER_DISABLED #define CONFIG_BT_NIMBLE_ROLE_OBSERVER +#endif /** @brief Comment out if not using NimBLE Server functions \n * Reduces flash size by approx. 16kB. */ -// #define CONFIG_BT_NIMBLE_ROLE_PERIPHERAL +#ifndef CONFIG_BT_NIMBLE_ROLE_PERIPHERAL_DISABLED +#define CONFIG_BT_NIMBLE_ROLE_PERIPHERAL +#endif /** @brief Comment out if not using NimBLE Advertising functions \n * Reduces flash size by approx. 5kB. */ -// #define CONFIG_BT_NIMBLE_ROLE_BROADCASTER +#ifndef CONFIG_BT_NIMBLE_ROLE_BROADCASTER_DISABLED +#define CONFIG_BT_NIMBLE_ROLE_BROADCASTER +#endif /* Uncomment to see debug log messages from the NimBLE host * Uses approx. 32kB of flash memory. @@ -89,29 +102,46 @@ // #define CONFIG_NIMBLE_CPP_ENABLE_ADVERTISMENT_TYPE_TEXT /** @brief Sets the core NimBLE host runs on */ +#ifndef CONFIG_BT_NIMBLE_PINNED_TO_CORE #define CONFIG_BT_NIMBLE_PINNED_TO_CORE 0 +#endif /** @brief Sets the stack size for the NimBLE host task */ +#ifndef CONFIG_BT_NIMBLE_TASK_STACK_SIZE #define CONFIG_BT_NIMBLE_TASK_STACK_SIZE 4096 +#endif /** * @brief Sets the memory pool where NimBLE will be loaded * @details By default NimBLE is loaded in internal ram.\n * To use external PSRAM you must change this to `#define CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL 1` */ +#ifndef CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL #define CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_INTERNAL 1 +#endif /** @brief Sets the number of simultaneous connections (esp controller max is 9) */ +#ifndef CONFIG_BT_NIMBLE_MAX_CONNECTIONS #define CONFIG_BT_NIMBLE_MAX_CONNECTIONS 3 +#endif /** @brief Sets the number of devices allowed to store/bond with */ +#ifndef CONFIG_BT_NIMBLE_MAX_BONDS #define CONFIG_BT_NIMBLE_MAX_BONDS 3 +#endif /** @brief Sets the maximum number of CCCD subscriptions to store */ +#ifndef CONFIG_BT_NIMBLE_MAX_CCCDS #define CONFIG_BT_NIMBLE_MAX_CCCDS 8 +#endif + +/** @brief Default device name */ +#ifndef CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME +#define CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME "nimble" +#endif /** @brief Set if CCCD's and bond data should be stored in NVS */ -#define CONFIG_BT_NIMBLE_NVS_PERSIST 0 +#define CONFIG_BT_NIMBLE_NVS_PERSIST 1 /** @brief Allow legacy paring */ #define CONFIG_BT_NIMBLE_SM_LEGACY 1 @@ -119,9 +149,6 @@ /** @brief Allow BLE secure connections */ #define CONFIG_BT_NIMBLE_SM_SC 1 -/** @brief Default device name */ -#define CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME "nimble" - /** @brief Max device name length (bytes) */ #define CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN 31 @@ -154,7 +181,6 @@ */ #define CONFIG_BT_NIMBLE_MSYS1_BLOCK_COUNT 12 - /** @brief Random address refresh time in seconds */ #define CONFIG_BT_NIMBLE_RPA_TIMEOUT 900