Compare commits

...

119 Commits

Author SHA1 Message Date
ec488aae55 Set a time for scan responses so that callbacks are triggered. 2024-11-18 13:41:49 -07:00
a9a79233bd [BREAKING] - Refactor NimBLEScan
* General code cleanup
* `NimBLEScan::start` will no longer clear cache or results if scanning is already in progress.
* `NimBLEScan::clearResults` will now reset the vector capacity to 0.
* `NimBLEScan::stop` will no longer call the `onScanEnd` callback as the caller should know its been stopped when this is called.
* `NimBLEScan::clearDuplicateCache` has been removed as it was problematic and only for the esp32. Stop and start the scanner for the same effect.
* `NimBLEScan::start` takes a new bool parameter `restart`, default `true`, that will restart an already in progress scan and clear the duplicate filter so all devices will be discovered again.
* Scan response data that is received without advertisement first will now create the device and send a callback.
2024-11-18 13:40:43 -07:00
5f2730de02 Revert #724e1a7 and replace with stack checks.
Replaces `NimBLEDevice::setConnectionInProgress` and `NimBLEDevice::isConnectionInProgress()` with lower level checks to avoid potential incorrect state reporting.
`NimBLEClient::connect` will instead call `NimBLEScan::stop` if it stopped the scan to release any resources waiting, the call the callback if set.
2024-11-18 08:56:38 -07:00
fbbcfadc0c Refactor client connection establishment and client deletion.
* Removes the connection established flag as it should not be necessary.
* Deleting the client instance from the `onDisconnect` callback is now supported.
* `NimBLEDevice::deleteClient` no longer blocks tasks
* Adds a new `Config` struct to `NimBLEClient` to efficiently set single bit config settings.
* Adds `NimBLEClient::setSelfDelete` that takes the bool parameters `deleteOnDisconnect` and `deleteOnConnectFail`
  This will configure the client to delete itself when disconnected or the connection attempt fails.
* Adds `NimBLEClient::setConfig` and `NimBLEClient::getConfig` which takes or returns a `NimBLEClient::Config` object respectively.

* Reword `BLE_HS_EAPP` error string to be more accurate.
2024-11-18 08:56:38 -07:00
a6e75b3537 Ensure events are defined inNimBLEUtils::gapEventToString 2024-11-18 08:11:44 -07:00
7bd7b1dfc2 Workaround for bug in NimBLE stack when connect re-attempt enabled.
Connect reattempt does not pass the arg parameter correctly and the disconnect event will have no client pointer in the argument.
This finds the client from the connection handle to compensate.
2024-11-17 12:20:49 -07:00
a59e8ee9e1 Refactor NimBLEClient::connect and NimBLEClient::secureConnection.
This change ensures that only the function that sets `m_pTaskData` clears it, potentially preventing a segmentation fault.

* Client will no longer disconnect if task timeout occurs when waiting for MTU exchange response.
2024-11-14 10:45:24 -07:00
6a5d6ef5e3 Set task handle in constructor of NimBLETaskData.
* Create destructor for NimBLETaskData to delete semaphore if created.
2024-11-14 10:45:24 -07:00
5aa2fb1443 Refactor NimBLEUtils::taskWait to check notification value before blocking.
Instead of incrementing the notificatin value via `xTaskNotifyGive` this will now set a specific bit in the task notification value
which will be tested before blocking a task. This will prevent a task from blocking indefinitely if the event that calls `taskRelease`
occurs before entering the blocked state.

* Adds a config setting for the bit to set in the task notification value.
2024-11-14 10:45:24 -07:00
7d2ad92ad2 Bit shift length for resize 2024-11-14 10:30:58 -07:00
ab43135f82 Fix NimBLEUtils::dataToHexString returned string, must resize before modifying. 2024-11-14 10:30:58 -07:00
84f4d4f897 Improve NimBLEUtils::dataToHexString efficiency. 2024-11-13 09:40:52 -07:00
beac19cc92 [BREAKING] - Refactor NimBLEUtils
* Add functions `NimBLEUtils::taskWait` and `NimBLEUtils::taskRelease` to simplify task blocking/messaging.
* `NimBLEUtils::buildHexData` replaced with `NimBLEUtils::dataToHexString`
* Add missing GAP event strings.
* Add missing return code strings.
* `NimBLEUtils::dumpGapEvent` function removed.
* Event/error code strings optimized.
2024-11-12 16:56:59 -07:00
65e05e6c57 Add NimBLEClient::cancelConnect
Adds a function to cancel and in-progress connection.
2024-11-12 16:14:02 -07:00
38e764d157 Add asyncronous client connect and MTU exchange.
* Adds parameters `asyncConnect` and `exchangeMTU` to `NimBLEClient::connect`, default values work as the original connect method.
* * `asyncConnect`; if true, will send the connect command and return immediately with a true value for successfully sending the command, else false.
* * `exchangeMTU`; if true will send the exchange MTU command upon connection, otherwise not and the application can choose to do this later via the `exchangeMTU` method.
* Adds `onMTUChange` callback to `NimBLEClientCallbacks`
* Add `NimBLEDevice::getConnectedClients()` which returns a vector of pointers to the currently connected client instances.
* Calling `NimBLEClient::connect` will no longer cancel already in progress connections.
2024-11-10 13:02:11 -07:00
724e1a7083 Fix endless loop when calling scan start from scan end callback.
When attempting to connect and the scanner is running `NimBLEScan::stop` is called to stop it
which then calls the `onScanEnded` callback. If the app then starts the scan again in this
callback an endless loop will be created.

This change prevents the endless loop by setting a flag in `NimBLEDevice` that is checked before
starting a scan while a client is trying to connect.

* Adds `NimBLEDevice::setConnectionInProgress` and `NimBLEDevice::isConnectionInProgress` functions.
2024-11-10 10:36:51 -07:00
7d0636bc91 Fix NimBLEClient::secureConnection incorrectly failing with BLE_HS_EAGAIN. 2024-11-10 07:43:25 -07:00
020c61700d [BREAKING]- Refactor client
* General code cleanup and rename variables to use a consistent style.
* Removes the disconnect timer and will use the BLE_GAP_EVENT_TERM_FAILURE event to handle failed disconnects.
* `NimBLEClient::getConnId` has been renamed to `getConnHandle` to be consistent with bluetooth terminology.
* `NimBLEClient::disconnect` now returns a `bool = true on success` instead of an int to be consistent with the rest of the library.
* `NimBLEClient::setPeerAddress` now returns a bool, true on success.
* `NimBLEClientCallbacks::onConfirmPIN` renamed to `NimBLEClientCallbacks::onConfirmPasskey` to be consistent with bluetooth terminology.
* `NimBLEClient::setDataLen` now returns bool, true if successful.
* `NimBLEClient::updateConnParams` now returns bool, true if successful.
* `NimBLEClient::getServices` now returns a const reference to std::vector<NimBLERemoteService*> instead of a pointer to the internal vector.
2024-11-03 18:12:20 -07:00
68b82f5b85 Add NimBLEDevice::setOwnAddr and NimBLEUtils::generateAddr functions.
Adds a utility to generate random BLE addresses and set the address used.
2024-11-02 18:46:04 -06:00
a2fe5b4780 [BREAKING] Refactor NimBLEDevice
* General code cleanup
* `NimBLEDevice::getInitialized` renamed to `NimBLEDevice::isInitialized`.
* `NimBLEDevice::setPower` no longer takes the `esp_power_level_t` and `esp_ble_power_type_t`, instead only an integer value in dbm units is accepted.
* `NimBLEDevice::setPower` now returns a bool value, true = success.
* `NimBLEDevice::setMTU` now returns a bool value, true = success.
* `NimBLEDevice::injectConfirmPIN` renamed to `NimBLEDevice::injectConfirmPasskey` to use Bluetooth naming.
* Fixes crash if `NimBLEDevice::deinit` is called when the stack has not been initialized.
* Reverts 73f0277 as it would cause a crash when the NimBLEServer instance has a connection.
* `NimBLEDevice::getAddress` will now return the address currently in use.
* `NimBLEDevice::init` now returns a bool with `true` indicating success.
* `NimBLEDevice::deinit` now returns a bool with `true` inidicating success.
* `NimBLEDevice::setDeviceName` now returns a bool with `true` indicating success.
* `NimBLEDevice::setCustomGapHandler` now returns a bool with `true` indicating success.
* `NimBLEDevice::setOwnAddrType` now returns a bool with `true` indicating success.
* `NimBLEDevice::setOwnAddrType` will now correctly apply the provided address type for all devices.
* `NimBLEDevice::setOwnAddrType` no longer takes a `bool nrpa` parameter.
2024-11-02 18:46:04 -06:00
877a29a8b1 Update cmakelists.txt (#213) 2024-11-02 17:58:51 -06:00
a7ff1beacd doc: mention 0 is forever for NimBLESCan::start
Document that setting `duration` to zero means to scan forever.
2024-11-01 15:43:14 -06:00
ed2c59d5de Add initial esp32-P4 support (#208) 2024-10-31 19:06:57 -06:00
8ca58f5a89 implement connection parameters update callback (#210) 2024-10-24 21:21:43 -06:00
3820f57076 fix: #200 Enable use of data()/size() before trying c_str()/length() (#201)
* fix: #200 Use `data()`/`size()` instead of `c_str()`/`length()`

* Reduce duplication, only allow template function in characteristic and remote value attr if the type is not a pointer (otherwise sizeof is useless). add appropriate notes

* clean up AttValue::setValue to remove unnecessary length parameter enabling requirement of non-pointer type
2024-10-14 17:02:21 -06:00
987a69f544 Fix check for arduino component (#204) 2024-10-06 18:16:48 -06:00
b807e6671f Fix Advertised device initialization. 2024-10-06 15:03:11 -06:00
758c4d0471 Remove stray debug message. 2024-09-30 16:08:51 -06:00
5b24c8d681 Fix notifications not being sent.
The connection handle was not initialized correctly when recording the subscibe status causing the call to send notification/indications to fail with BLE_HS_ENOTCONN.
2024-09-30 16:07:00 -06:00
73f0277042 fix(NimBLEDevice): clear all before port_deinit to prevent crash 2024-09-24 16:03:49 -06:00
91210b8610 [BREAKING] Refactor attributes
Refactor attributes to reduce code duplication and improve maintainability.

* Add attribute base classes to provide common code.
* Add const where possible to functions and parameters.
* `NimBLECharacteristic::notify` no longer takes a `bool is_notification` parameter, instead `indicate()` should be called to send indications.
* `NimBLECharacteristic::indicate` now takes the same parameters as `notify`.
* `NimBLECharacteristicCallbacks` and `NimBLEDescriptorCallbacks` methods now take `const NimBLEConnInfo&` instead of non-const.
* `NimBLECharacteristic::onNotify` callback removed as unnecessary, the library does not call notify without app input.
* `NimBLERemoteCharacteristic::getRemoteService` now returns a `const NimBLERemoteService*` instead of non-const.
* Add NimBLEUUID constructor that takes a reference to `ble_uuid_any_t`.
* `NimBLERemoteService::getCharacteristics` now returns a `const std::vector<NimBLERemoteCharacteristic*>&` instead of non-const `std::vector<NimBLERemoteCharacteristic*>*`
* `NimBLERemoteService::getValue` now returns `NimBLEAttValue` instead of `std::string`
* `NimBLEService::getCharacteristics` now returns a `const std::vector<NimBLECharacteristic*>&` instead of a copy of std::vector<NimBLECharacteristic *>.
* Remove const requirement for NimBLEConnInfo parameter in callbacks.
  Const is unnecessary as the data can't be changed by application code.
* Change NimBLERemoteCharacteristic::getRemoteService to return const pointer.
2024-09-24 14:59:54 -06:00
b4b3b0c455 Fix NimBLEExtAdvertisement::setServiceData data length. 2024-09-15 10:28:49 -06:00
6f03b9a6ef fix: data was renamed to adv 2024-09-15 10:26:46 -06:00
cd115f1738 fix(NimBLEAdvertising): Ensure setServiceData() includes length of data 2024-08-29 17:12:21 -06:00
12074d1cc4 Fix crash when creating address from 17 char string. 2024-08-12 07:56:38 -06:00
d22db6ef8c Fix 16 and 32 bit UUID comparison. 2024-08-02 07:18:35 -06:00
209f70a083 Fix 128 bit uuid comparison. 2024-07-25 15:30:45 -06:00
10d589162b [Breaking] Refactor NimBLEUUID.
* msbFirst parameter has been removed from constructor as it was unnecessary,
caller should reverse the data first or call the new `reverseByteOrder` method after.
* `getNative` method replaced with `getBase` which returns a read-only pointer to the UUID size underlying.
* Added `reverseByteOrder` method, this will reverse the bytes of the UUID, which can be useful for advertising/logging.
* Added `getValue` method, which returns a read-only `uint8_t` pointer to the UUID value.
* Removed `m_valueSet` member variable, `bitSize()` can be used as a replacement.
* General code cleanup.
2024-07-23 18:14:09 -06:00
d1d1b49a26 Refactor NimBLEAttValue.
* Add length parameter to `setValue()` templates, with defaults for backward compatibility.
* Changed `setValue(const char*)` to add an optional length parameter so that a NULL character can be included, defaults to strlen.
* Moved non-inline functions to `NimBLEAttValue.cpp` file.
* Corrected includes to permit compilation as a stand-alone utility.
* Removed repeated code in `setValue()` by using `append()` after clearing the value.
* General code cleanup.
2024-07-21 16:36:00 -06:00
8c4832f711 Add clang-format. 2024-07-18 17:17:54 -06:00
efa858c4cc Only trigger build on master when pushing. 2024-07-18 17:17:30 -06:00
c2ab790e1d [BREAKING] Refactor NimBLEAdvertised device.
* Construct the device with the parameters from the advertisement in the initialization list.
* Removed no longer needed methods; setAddress, setAdvType, setRSSI, setSetId, setPrimaryPhy, setSecondaryPhy, setPeriodicInterval.
* Removed `hasRSSI()` method, the RSSI is always reported so this is redundant.
* Replace setPayload with new method; `update` which will update the device info when new advertisement data is received.
* getPayload now returns `const std::vector<uint8_t>` instead of a pointer to internal memory.
* Added `begin` and `end` read-only iterators for convienience and use in range loops.
* Timestamp removed, if needed then the app should track the time in the callback.
* Consolidate some functions to use getPayloadByType.
* Add optional index parameter to getPayloadByType.
* Change payload indexing to use 0 as the first item.
* Code cleanup and apply const correctness.
2024-07-17 20:44:49 -06:00
6279817143 [BREAKING] Refactor NimBLEAddress - use NimBLE core representation.
This simplifies the NimBLEAddress code by directly using the NimBLE core `ble_addr_t` type to hold the address
and allows using NimBLE core functions and macros to replace code in some methods.

* `getNative()` replaced with `getBase()` and now returns a pointer to `const ble_addr_t` instead of a pointer to the address value.
* Adds `isNrpa()` method to test if an address is random non-resolvable.
* Adds `isStatic()` method to test if an address is random static.
* Adds `isPublic()` method to test if an address is a public address.
* Adds `isNull()` methods to test if an address is NULL.
* Adds `getValue()` method which returns a read-only pointer to the address value.
* Adds `reverseByteOrder()` method which will reverse the byte order of the address value.
* `equals()` method and == operator will now also test if the address types are the same.
* Code cleanup.
2024-07-17 19:38:35 -06:00
21e1217e4c [BREAKING] Use an array to manage created client instances.
* Replaces the use of std::list with a fixed array to track and manage created client instances.
* Removes: NimBLEDevice::getClientList
* Replaces: NimBLEDevice::getClientListSize with NimBLEDevice::getCreatedClientCount
2024-07-10 07:05:47 -06:00
a92149ac74 [BREAKING] Change NIMBLE_LOGC macro to use printf.
Replaces all use of NIMBLE_LOGC with NIMBLE_LOGE and redefines NIMBLE_LOGC to use printf.
This allows NIMBLE_CPP_DEBUG_ASSERT messages to print event when log level filtering is set to none.
2024-07-04 19:05:35 -06:00
d9f5794b57 Replace NimBLEAttValue asserts with user defineable action.
* Adds the NIMBLE_CPP_DEBUG_ASSERT macro that calls a weak function to allow user defined action, defaults to critical error message and aborts.
* Adds a config option to disable asserts using the NIMBLE_CPP_DEBUG_ASSERT macro.
2024-07-04 18:47:28 -06:00
44977cdcce Remove NimBLEUtils::checkConnParams.
This function is not required as the stack will return an error code in the case of invalid params.
2024-07-03 15:01:44 -06:00
a5dd84b2f9 Remove descriptor asserts when the connection info is not found.
* Trigger onRead callbacks when the connection handle is NONE (internal).
2024-07-03 15:01:28 -06:00
b8d6e3d87f Remove assert for 0x2902, make a dummy descriptor instead.
Rather than asserting for a harmless situation, this will just create a dummy descriptor
object, marked as removed, and print a warning.
2024-07-03 15:01:14 -06:00
fae53b8d7f Remove asserts from NimBLEDevice. 2024-07-03 15:01:00 -06:00
358170847d Remove aborts in NimBLEAdvertising::start. 2024-07-03 14:27:59 -06:00
a1bd817875 Remove NimBLEServer::start abort. 2024-07-03 14:27:31 -06:00
fd698b0212 [NimBLEServer] Add getPeerName and get peer name on connect.
* Adds a new method, getPeerName to NimBLEServer to read the name from the peers device name characteristic.
* Adds a setting to automatically get the name of the peer when connected and provide it as an additional parameter in the onConnect callback.
* Adds callback with client name after authentication as it may change.
2024-06-30 16:44:49 -06:00
3c5a2fd4a9 add h2 2024-06-18 06:30:35 -06:00
0c797d5b41 add h2 2024-06-18 06:30:35 -06:00
cabd48aef3 Add documentation to NimBLEService::isStarted 2024-06-15 09:55:40 -06:00
a2a50957b1 Create package.json 2024-06-15 09:22:31 -06:00
8acea66c10 Reset advertisment vectors when changing their data.
* Add method to erase all service UUIDS.
2024-06-14 22:02:29 -06:00
bd29e738bd Add service isStarted method, remove abort in NimBLEServer::start. 2024-06-14 18:26:42 -06:00
792359b510 Remove asserts in NimBLECharacteristic read/write. 2024-06-14 10:01:50 -06:00
bc7bfe8278 Add clearData method to NimBLEAdvertisementData. 2024-06-14 10:01:41 -06:00
20349c64a4 Add conditional checks in NimBLEDevice init/deinit. 2024-06-14 10:01:34 -06:00
70c2d83b3b Remove broken links from docs. 2024-06-14 09:57:23 -06:00
63a3301696 Add missing const in NimBLEConnInfo. 2024-06-12 21:12:09 -06:00
c156b0202c Add isRpa method to NimBLEAddress.
This provides and easy way to check if the peer address is a Resolvable Private Address.
2024-06-12 21:12:09 -06:00
ef90a8aa95 Remove address from whitelist if the procedure fails.
This prevents a situation where the whitelist contains the address but the procedure errors and the address cannot be added again.
2024-06-12 21:12:09 -06:00
aeb4334e98 Add server and client onIdentity callback.
Adds a callback that is called when the identity address of the peer is resolved, this is useful for adding it to a whitelist.
2024-06-12 21:12:09 -06:00
44daa3c687 Fix failure to reconnect to device when bonded.
iOS devices specifically expect to receive the ID key of the bonded device even when RPA is not used.
This sets the default value of the key distribution config to provide the ID key as well as the encryption key.
2024-06-12 21:12:09 -06:00
f36929963d Add disconnect overload to take NimBLEConnInfo reference. (#162)
* NimBLEConnInfo - Note that the connection handle is the same as the connection id in the comment for getConnHandle()

* NimBLEServer - int disconnect(const NimBLEConnInfo&) helper method
2024-06-08 10:06:57 -06:00
2447af6a4d Fix Error in converting EddystoneTLM negative temperatures to float | resolves h2zero/NimBLE-Arduino#675 2024-06-08 10:02:47 -06:00
9c1f1adf7a [BREAKING] Asynchronous pin injections for Numeric Comparison and PassKey Input (#165)
* Make NimBLEConnInfo functions const.

* Update callback functions and update client to use new functions.

* Update examples.

* Update migration guide.

---------

Co-authored-by: Casey Smith <csmith@morningstarcorp.com>
2024-06-06 19:36:14 -06:00
6ca61bbd9c feat(NimBLEDevice): deleteAllBonds() Add return value (#158)
Add return value to `deleteAllBonds`.
2024-06-04 16:52:55 -06:00
32c213a8a3 feat(NimBLEClient): allow connection id / established flag to be set via public API. (#156)
* Adds NimBLEClient::setConnection  to allow servers to read the name of connected clients by passing their connection info to the Client class.
* Adds NimBLEClient::clearConnection to be able to reuse the client without deleting and recreating.
2024-06-04 10:37:24 -06:00
51bf1f3c7c feat(NimBLEAdvertising): support std::function for advertising complete callback (#157)
Updates the interface to use a typedef'd std::function for the advertise callback, which is backwards compatible but also allows std::bind and lambda functions.
2024-06-04 07:12:20 -06:00
7c0300c34e Change NimBLEHIDDevice formatting for consistency. 2024-06-02 17:02:02 -06:00
6fa3783206 Bug fix on PNP info
Fixed issue #492
2024-06-02 17:02:00 -06:00
226c67f729 Initialize ble_gatt_chr_def[]. Fixes #148 (#150)
IDF 5.2 introduced a new member, cpfd, to the
ble_gatt_chr_def struct. It needs to be initialized
to nullptr in order to avoid accessing uninitialized
memory. By initializing the whole struct, we get
everything initialized in a backward-compatible way.
2024-04-20 09:14:55 -06:00
bf4b5d4ffa ESP IDF 5.2 include fix (#144) 2024-03-01 12:29:18 -07:00
58f86cb7bd Fix inconsistent use of time units (#143)
Seconds to milliseconds
2024-02-29 17:34:53 -07:00
22fb1ab801 Add generic advertisement 'type' functions (#575) 2023-11-25 08:10:30 -07:00
4e65ce5d32 Support for C2 (#131) 2023-09-01 11:56:52 -06:00
b07cab5858 gptimer is used from driver 2023-09-01 08:22:37 -06:00
2ea0f26de0 Add support for esp32c6 2023-08-13 10:40:00 -06:00
1786d0ede3 Fix compilation when CONFIG_BTDM_BLE_SCAN_DUPL is not enabled.
*  fix warning: variable 'pkey' set but not used [-Wunused-but-set-variable]
---------

Co-authored-by: Franz Höpfinger <f.hoepfinger@hr-agrartechnik.de>
2023-05-29 09:08:14 -06:00
4ff9baf68a Fixed unhandled exception on handleGapEvent (#112)
Somehow, the conn_handle can have the value of `0xffff`. Not sure why or how, but anyway it should be managed before moving forward on NimBLECharacteristic::handleGapEvent. Otherwise, an unhandled exception will occur on `assert(rc == 0);`
2023-05-29 07:56:31 -06:00
c1e7a521b7 Add overloaded setManufacturerData to allow vector of uint8_t 2023-05-28 16:23:34 -06:00
fee3829a89 Fix build with platformio IDF+Arduino (#122) 2023-05-28 10:44:17 -06:00
091c1c014c Update doxygen config 2023-05-25 10:29:53 -06:00
3145319581 Build with idf v5.1 2023-05-25 09:35:11 -06:00
d0eaf6c1e0 Set service handle in getHandle function if not set already.
If a service has been created and started but not yet added to the gatt server then the call to getHandle will result in an invalid handle. This adds a call to set the handle value in the getHandle function.
2023-05-24 15:37:53 -06:00
46e7bb9302 Add onDiscovered scan callback.
This adds a callback that is called when a device is initially discovered.
It is always called wether scanning in passive or active mode. This allows
for active scanning to receive device detection information in the case where
the scan response is not sent/received.

This callback is optional, the application may decide to implement it or not.
2023-05-24 15:37:46 -06:00
559a26b74b Add index parameter for multiple manufacturer data sets.
Adds an index parameter to NimBLEAdvertisedDevice::getManufacturerData to allow for selecting from multiple instances in the advertisements.
Adds NimBLEAdvertisedDevice::getManufacturerDataCount to get the number of manufacturer data sets present in the advertised data.
2023-05-24 15:37:38 -06:00
4423e5fbb6 Expose advertisement flags in NimBLEAdvertisedDevice 2023-05-24 15:37:30 -06:00
0aa7e9510d Add directed peer address parameter to advertising start.
Adds a parameter to NimBLEAdvertising::start to take the peer address for use with directed advertising.
2023-05-24 15:37:22 -06:00
d83cd94d5b Revert 03b22e5 sub/unsub changes, app should specify response. 2023-05-24 15:37:17 -06:00
7d2d73d8fc Fix pairing when already in progress.
If pairing is requested before performing an action on a protected attribute pairing could fail due to already being in progress.
This fix will wait for the result of the pairing process before proceeding with the action.
2023-05-24 15:37:03 -06:00
05ac9deaea Remove deprecated API in case of ESP-IDF V5. (#102)
* Remove deprecated API in case of ESP-IDF V5.

0a93ee1337/docs/en/migration-guides/release-5.x/bluetooth-low-energy.rst (id17)

* Fix initialization in case of ESP-IDF V5
2022-11-03 20:47:04 -06:00
cad022650a Fix getPower return value
Power level -3 returned the wrong value.
2022-09-11 20:34:33 -06:00
e868f37135 Add conn_handle to NimBLECharacteristic::notify to send notification/indication to a specific client (#97)
Adds the ability to send notifications/indications to specific clients individually.
2022-09-11 20:18:55 -06:00
6fb26e3809 Change type of index to int (instead of uint8_t) to fix issue with adding more than 255 Characteristics (or Descriptors) (#101) 2022-09-11 19:54:15 -06:00
6a2f558ea5 [BREAKING] Replace advertised device callbacks with scan callbacks. (#389)
This replaces NimBLEAdvertisedDeviceCallbacks with NimBLEScanCallbacks and adds a onScanEnd callback.

The callback parameter for NimBLEScan::start has been removed and the blocking overload for NimBLEScan::start
has been replaced by an overload of NimBLEScan::getResults with the same parameters.
2022-08-27 14:04:50 -06:00
562a32eda6 Add docs build. 2022-08-27 13:02:29 -06:00
0f4da63fc8 Add CI build check. 2022-08-27 13:02:29 -06:00
ba79a1bf72 [Breaking] Update callbacks to use NimBLEConnInfo.
Change the callback functions that receive a ble_gap_conn_desc pointer to instead receive a NimBLEConnInfo reference.

* Add a reason parameter to the server disconnect callback.

* Remove connect and disconnect callback that do not receive info parameters.

* Remove onRead and onWrite Characteristic callbacks that do not receive info parameters.

* Add info parameter to Descriptor onWrite and onRead callbacks.

* Add details to migration guide.
2022-08-27 13:02:29 -06:00
ca8a7c56ac [Breaking] Remove extra notification status from onStatus callback. (#80)
Removes the unnecessary NimBLECharacteristicCallbacks::Status enum and callback parameter from NimBLECharacteristic::onStatus.

This was maintained for compatibility with the Arduino BLE library and is not necessary as the return code from the stack is also provided.

* Update examples/docs.
2022-08-26 20:40:21 -06:00
03b22e53a0 [Breaking] Remove deprecated functions/response parameter from un/sub (#76)
Removes the response parameter from remote characterisitc subscrible/unsubscribe functions
as a response is always required for this operation as per BLE specs.

Removes the deprecated functions from remote characteristic/desriptor.

* Update examples/docs.
2022-08-26 20:15:25 -06:00
2b758e05c7 Fix client example. 2022-08-26 19:54:43 -06:00
bfe68f4a91 [Breaking] Add disconnect reason to client callback. (#398) (#81)
Adds the reason code as a parameter to the client onDisconnect callback.

* Update examples/docs.
2022-08-26 19:51:19 -06:00
bb3dd5f114 [Breaking] Change all time input parameters to milliseconds. (#78)
Changes all functions that accept a time parameter to use milliseconds instead of seconds.

* Adds duration input to NimBLEDevice::startAdvertising and NimBLEServer::startAdvertising.
2022-08-26 19:32:01 -06:00
0b6337538c Remove NimBLESecurity class. (#75) 2022-08-21 21:33:39 -06:00
32e7059732 add NimBLEHIDDevice::batteryLevel() 2022-08-21 21:19:54 -06:00
cd3185fe43 Added the setDeviceName functionality while BLE is running 2022-08-21 21:19:30 -06:00
efa48c0d57 Release 1.4.0
* Fix typos

* Update docs
2022-07-31 11:34:56 -06:00
70ed6e293f Don't set advertisement flags if not connectable. 2022-07-10 07:15:46 -06:00
93de7ab8ed Bugfix/onRead callback not called for non-long read commands. 2022-06-26 17:03:21 -06:00
9285a9b31f Don't call scan result callback if it was already called. 2022-06-04 14:24:30 -06:00
38a1a2013b Update docs. 2022-05-29 20:08:23 -06:00
a36655c105 Add success/fail return value to disoverAttributes. 2022-04-16 20:32:01 -06:00
c285052f6d Use write with response when writing 0x2902 descriptors.
As per Bluetooth core specification we must write to 0x2902 descriptors with response request.
This change ensures the correct procedure is used.

Todo: Remove the "response" parameter from related functions.
2022-04-16 12:47:23 -06:00
95 changed files with 7173 additions and 7544 deletions

42
.clang-format Normal file
View File

@ -0,0 +1,42 @@
BasedOnStyle: Google
Language: Cpp
ColumnLimit: 120
IndentWidth: 4
TabWidth: 4
UseTab: Never
SortIncludes: Never
PPIndentWidth : 1
IndentPPDirectives: AfterHash
ReflowComments: true
SpacesBeforeTrailingComments: 1
AlignTrailingComments: true
AlignAfterOpenBracket: Align
AlignConsecutiveMacros:
Enabled: true
AcrossEmptyLines: true
AcrossComments: true
AlignConsecutiveAssignments:
Enabled: true
AcrossEmptyLines: false
AcrossComments: true
AlignCompound: true
PadOperators: true
AlignConsecutiveDeclarations:
Enabled: true
AcrossEmptyLines: false
AcrossComments: true
AlignEscapedNewlines: Left
AccessModifierOffset: -2
AlignArrayOfStructures: Right
AlignOperands: Align
AllowAllArgumentsOnNextLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortBlocksOnASingleLine: Empty
BreakBeforeBinaryOperators: None
BinPackArguments: false
BinPackParameters: false
DerivePointerAlignment: false
PenaltyBreakAssignment: 4
PenaltyExcessCharacter: 4
PenaltyBreakBeforeFirstCallParameter: 1
PointerAlignment: Left

70
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,70 @@
name: Build
on:
workflow_dispatch: # Start a workflow
pull_request:
push:
branches:
- master
jobs:
build-esp-idf-component:
name: Build with ESP-IDF ${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
runs-on: ubuntu-latest
strategy:
matrix:
# The version names here correspond to the versions of espressif/idf Docker image.
# See https://hub.docker.com/r/espressif/idf/tags and
# https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-docker-image.html
# for details.
idf_ver: ["release-v4.4", "release-v5.1"]
idf_target: ["esp32", "esp32s3", "esp32c2", "esp32c3", "esp32c6", "esp32h2"]
example:
- Advanced/NimBLE_Client
- Advanced/NimBLE_Server
- basic/BLE_client
- basic/BLE_notify
- basic/BLE_scan
- basic/BLE_server
- basic/BLE_uart
- Bluetooth_5/NimBLE_extended_client
- Bluetooth_5/NimBLE_extended_server
- Bluetooth_5/NimBLE_multi_advertiser
- NimBLE_server_get_client_name
exclude:
- idf_target: "esp32"
example: Bluetooth_5/NimBLE_extended_client
- idf_target: "esp32"
example: Bluetooth_5/NimBLE_extended_server
- idf_target: "esp32"
example: Bluetooth_5/NimBLE_multi_advertiser
- idf_ver: release-v4.4
idf_target: "esp32c2"
- idf_ver: release-v4.4
idf_target: "esp32c6"
- idf_ver: release-v4.4
idf_target: "esp32h2"
container: espressif/idf:${{ matrix.idf_ver }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
path: components/esp-nimble-cpp
- name: Build examples
env:
IDF_TARGET: ${{ matrix.idf_target }}
shell: bash
run: |
. ${IDF_PATH}/export.sh
cp -r components/esp-nimble-cpp/examples/* .
idf.py -C ${{ matrix.example }} -DEXTRA_COMPONENT_DIRS=$PWD/components build
build_docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Doxygen Action
uses: mattnotmitt/doxygen-action@v1.9.5
with:
working-directory: 'docs/'

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
docs/doxydocs

View File

@ -1,6 +1,35 @@
# Changelog
All notable changes to this project will be documented in this file.
## [Unreleased]
### Changed
- NimBLESecurity class removed.
### Added
- `NimBLEDevice::setDeviceName` to change the device name after initialization.
- `NimBLEHIDDevice::batteryLevel` returns the HID device battery level characteristic.
## [1.4.0] - 2022-07-31
### Fixed
- Fixed missing data from long notification values.
- Fixed NimbleCharacteristicCallbacks::onRead not being called when a non-long read command is received.
- Prevent a potential crash when retrieving characteristics from a service if the result was successful but no characteristics found.
- logs/typos.
### Changed
- AD flags are no longer set in the advertisements of non-connectable beacons, freeing up 3 bytes of advertisement room.
- Save resources when retrieving descriptors if the characteristic handle is the same as the end handle (no descriptors).
- Subscribing to characteristic notifications/indications will now always use write with response, as per BLE specifications.
- `NimBLEClient::discoverAttributes` now returns a bool value to indicate success/failure.
- Scan result callbacks are no longer called when the scan response data is updated in order to reduce duplicates.
### Added
- Preliminary support for non-esp devices, NRF51 and NRF52 devices supported with [n-able arduino core](https://github.com/h2zero/n-able-Arduino)
- Alias added for `NimBLEServerCallbacks::onMTUChange` to `onMtuChanged` in order to support porting code from original library.
- `NimBLEAttValue` Class added to reduce and control RAM footprint of characteristic/descriptor values and support conversions from Arduino Strings and many other data types.
- Bluetooth 5 extended advertising support for capable devices. CODED Phy, 2M Phy, extended advertising data, and multi-advertising are supported, periodic advertising will be implemented in the future.
## [1.3.3] - 2022-02-15

View File

@ -2,29 +2,42 @@
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
idf_build_get_property(__hack_component_targets __COMPONENT_TARGETS)
if("esp-nimble-component" IN_LIST BUILD_COMPONENTS OR "__esp-nimble-component" IN_LIST __hack_component_targets)
if(__COMPONENT_TARGETS MATCHES "___idf_esp-nimble-component")
list(APPEND ESP_NIMBLE_PRIV_REQUIRES
esp-nimble-component
)
elseif("nimble" IN_LIST BUILD_COMPONENTS OR "__nimble" IN_LIST __hack_component_targets)
elseif(__COMPONENT_TARGETS MATCHES "__idf_nimble")
list(APPEND ESP_NIMBLE_PRIV_REQUIRES
nimble
)
endif()
if("arduino" IN_LIST BUILD_COMPONENTS OR __hack_component_targets MATCHES "__idf_arduino")
# Arduino install using IDF component manager
if(__COMPONENT_TARGETS MATCHES "___idf_espressif__arduino-esp32")
list(APPEND ESP_NIMBLE_PRIV_REQUIRES
arduino-esp32
)
# Manual installation of Arduino framework
elseif(__COMPONENT_TARGETS MATCHES "__idf_arduino")
list(APPEND ESP_NIMBLE_PRIV_REQUIRES
arduino
)
# PlatformIO
elseif(__COMPONENT_TARGETS MATCHES "___idf_framework-arduinoespressif32")
list(APPEND ESP_NIMBLE_PRIV_REQUIRES
framework-arduinoespressif32
)
endif()
idf_component_register(
REQUIRED_IDF_TARGETS
"esp32"
"esp32s3"
"esp32c2"
"esp32c3"
"esp32c6"
"esp32h2"
"esp32p4"
INCLUDE_DIRS
"src"
SRCS
@ -32,6 +45,7 @@ idf_component_register(
"src/NimBLEAddress.cpp"
"src/NimBLEAdvertisedDevice.cpp"
"src/NimBLEAdvertising.cpp"
"src/NimBLEAttValue.cpp"
"src/NimBLEBeacon.cpp"
"src/NimBLECharacteristic.cpp"
"src/NimBLEClient.cpp"
@ -44,8 +58,8 @@ idf_component_register(
"src/NimBLERemoteCharacteristic.cpp"
"src/NimBLERemoteDescriptor.cpp"
"src/NimBLERemoteService.cpp"
"src/NimBLERemoteValueAttribute.cpp"
"src/NimBLEScan.cpp"
"src/NimBLESecurity.cpp"
"src/NimBLEServer.cpp"
"src/NimBLEService.cpp"
"src/NimBLEUtils.cpp"
@ -53,6 +67,7 @@ idf_component_register(
REQUIRES
bt
nvs_flash
driver
PRIV_REQUIRES
${ESP_NIMBLE_PRIV_REQUIRES}
)

67
Kconfig
View File

@ -33,7 +33,7 @@ config NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT
Enabling this option will display return code values as text
messages in the debug log. This will use approximately 8kB
of flash memory.
config NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT
bool "Show NimBLE gap events as text in debug log."
default "n"
@ -42,12 +42,12 @@ config NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT
messages in the debug log. This will use approximately 1kB
of flash memory.
config NIMBLE_CPP_ENABLE_ADVERTISMENT_TYPE_TEXT
config NIMBLE_CPP_ENABLE_ADVERTISEMENT_TYPE_TEXT
bool "Show advertisment types as text in debug log."
default "n"
help
Enabling this option will display advertisment types recieved
while scanning as text messages in the debug log.
while scanning as text messages in the debug log.
This will use approximately 250 bytes of flash memory.
config NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED
@ -68,5 +68,66 @@ config NIMBLE_CPP_ATT_VALUE_INIT_LENGTH
when the constructor is called. This is also the size used when a remote
characteristic or descriptor is constructed before a value is read/notifed.
Increasing this will reduce reallocations but increase memory footprint.
config NIMBLE_CPP_DEBUG_ASSERT_ENABLED
bool "Enable debug asserts."
default "n"
help
Enabling this option will add debug asserts to the NimBLE CPP library.
This will use approximately 1kB of flash memory.
config NIMBLE_CPP_FREERTOS_TASK_BLOCK_BIT
int "FreeRTOS task block bit"
default 31
help
Configure the bit to set in the task notification value when a task is blocked waiting for an event.
This should be set to a bit that is not used by other notifications in the system.
#
# BT config
#
config BT_ENABLED
bool "Bluetooth"
default "y"
help
Select this option to enable Bluetooth and show the submenu with Bluetooth configuration choices.
config BT_NIMBLE_ENABLED
bool "NimBLE - BLE only"
default "y"
help
This option is recommended for BLE only usecases to save on memory
if IDF_TARGET_ESP32P4
config BT_NIMBLE_TRANSPORT_UART
bool "Enable Uart Transport"
default "n"
#
# Enable ESP Hosted BT
# Used as VHCI transport between BT Host and Controller
#
config ESP_ENABLE_BT
bool "Enable Hosted Bluetooth support"
default "y"
help
Enable Bluetooth Support via Hosted
choice ESP_WIFI_REMOTE_LIBRARY
prompt "Choose WiFi-remote implementation"
default ESP_WIFI_REMOTE_LIBRARY_HOSTED
help
Select type of WiFi Remote implementation
ESP-HOSTED is the default and most versatile option.
It's also possible to use EPPP, which uses PPPoS link between micros and NAPT, so it's slower
and less universal.
config ESP_WIFI_REMOTE_LIBRARY_HOSTED
bool "ESP-HOSTED"
endchoice
endif
endmenu

View File

@ -7,12 +7,11 @@ Extended advertising allows for much more capability and flexibility.
* New PHY's (physical layers) that allow for faster data rate (2M PHY) or long range/slower data rates (CODED PHY) as well as the original 1M PHY.
* New periodic advertising, allowing the scanning device to sync with the advertisements of a beacon. This allows for the scanning device to sleep or perform other tasks before the next expected advertisement is sent, preserving cpu cycles and power (To be implemented).
<br>
* New periodic advertising, allowing the scanning device to sync with the advertisements of a beacon. This allows for the scanning device to sleep or perform other tasks before the next expected advertisement is sent, preserving cpu cycles and power (To be implemented).
<br/>
## Enabling extended advertising
Extended advertising is supported when enabled with the config option `CONFIG_BT_NIMBLE_EXT_ADV` set to a value of 1. This is done in menuconfig under `Component config > Bluetooth > NimBLE options >
Enable extended advertising`.
Extended advertising is supported when enabled with the config option `CONFIG_BT_NIMBLE_EXT_ADV` set to a value of 1. This is done in menuconfig under `Component config > Bluetooth > NimBLE options > Enable extended advertising`, or set in `nimconfig.h` for Arduino, or in `build_flags` in PlatformIO.
When enabled the following will occur:
* `NimBLEScan::start` method will scan on both the 1M PHY and the coded PHY standards automatically.

View File

@ -1,142 +0,0 @@
# Arduino command line and platformio config options
`CONFIG_BT_NIMBLE_MAX_CONNECTIONS`
Sets the number of simultaneous connections (esp controller max is 9)
- Default value is 3
<br/>
`CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED`
Enable/disable storing the timestamp when an attribute value is updated
This allows for checking the last update time using getTimeStamp() or getValue(time_t*)
If disabled, the timestamp returned from these functions will be 0.
Disabling timestamps will reduce the memory used for each value.
1 = Enabled, 0 = Disabled; Default = Disabled
<br/>
`CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH`
Set the default allocation size (bytes) for each attribute.
If not specified when the constructor is called. This is also the size used when a remote
characteristic or descriptor is constructed before a value is read/notifed.
Increasing this will reduce reallocations but increase memory footprint.
Default value is 20. Range: 1 : 512 (BLE_ATT_ATTR_MAX_LEN)
<br/>
`CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU`
Sets the default MTU size.
- Default value is 255
<br/>
`CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME`
Set the default device name
- Default value is "nimble"
<br/>
`CONFIG_BT_NIMBLE_DEBUG`
If defined, enables debug log messages from the NimBLE host
- Uses approx. 32kB of flash memory.
<br/>
`CONFIG_NIMBLE_CPP_LOG_LEVEL`
Define to set the debug log message level from the NimBLE CPP Wrapper.
If not defined it will use the same value as the Arduino core debug level.
Values: 0 = NONE, 1 = ERROR, 2 = WARNING, 3 = INFO, 4+ = DEBUG
<br/>
`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.
<br/>
`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.
<br/>
`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.
<br/>
`CONFIG_BT_NIMBLE_SVC_GAP_APPEARANCE`
Set the default appearance.
- Default value is 0x00
<br/>
`CONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED`
If defined, NimBLE Client functions will not be included.
- Reduces flash size by approx. 7kB.
<br/>
`CONFIG_BT_NIMBLE_ROLE_OBSERVER_DISABLED`
If defined, NimBLE Scan functions will not be included.
- Reduces flash size by approx. 26kB.
<br/>
`CONFIG_BT_NIMBLE_ROLE_PERIPHERAL_DISABLED`
If defined NimBLE Server functions will not be included.
- Reduces flash size by approx. 16kB.
<br/>
`CONFIG_BT_NIMBLE_ROLE_BROADCASTER_DISABLED`
If defined, NimBLE Advertising functions will not be included.
- Reduces flash size by approx. 5kB.
<br/>
`CONFIG_BT_NIMBLE_MAX_BONDS`
Sets the number of devices allowed to store/bond with
- Default value is 3
<br/>
`CONFIG_BT_NIMBLE_MAX_CCCDS`
Sets the maximum number of CCCD subscriptions to store
- Default value is 8
<br/>
`CONFIG_BT_NIMBLE_RPA_TIMEOUT`
Sets the random address refresh time in seconds.
- Default value is 900
<br/>
`CONFIG_BT_NIMBLE_MSYS1_BLOCK_COUNT`
Set the number of msys blocks For prepare write & prepare responses. This may need to be increased if
you are sending large blocks of data with a low MTU. E.g: 512 bytes with 23 MTU will fail.
- Default value is 12
<br/>
`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
<br/>
`CONFIG_BT_NIMBLE_PINNED_TO_CORE`
Sets the core the NimBLE host stack will run on
- Options: 0 or 1
<br/>
`CONFIG_BT_NIMBLE_TASK_STACK_SIZE`
Set the task stack size for the NimBLE core.
- Default is 4096
<br/>

View File

@ -1,4 +1,4 @@
# Doxyfile 1.9.1
# Doxyfile 1.9.5
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project.
@ -12,6 +12,16 @@
# For lists, items can also be appended using:
# TAG += value [value, ...]
# Values that contain spaces should be placed between quotes (\" \").
#
# Note:
#
# Use doxygen to compare the used configuration file with the template
# configuration file:
# doxygen -x [configFile]
# Use doxygen to compare the used configuration file with the template
# configuration file without replacing the environment variables or CMake type
# replacement variables:
# doxygen -x_noenv [configFile]
#---------------------------------------------------------------------------
# Project related configuration options
@ -38,7 +48,7 @@ PROJECT_NAME = esp-nimble-cpp
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 1.3.2
PROJECT_NUMBER = 1.4.1
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
@ -58,18 +68,30 @@ PROJECT_LOGO =
# entered, it will be relative to the location where doxygen was started. If
# left blank the current directory will be used.
OUTPUT_DIRECTORY = .
OUTPUT_DIRECTORY = doxydocs
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
# directories (in 2 levels) under the output directory of each output format and
# will distribute the generated files over these directories. Enabling this
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096
# sub-directories (in 2 levels) under the output directory of each output format
# and will distribute the generated files over these directories. Enabling this
# option can be useful when feeding doxygen a huge amount of source files, where
# putting all generated files in the same directory would otherwise causes
# performance problems for the file system.
# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to
# control the number of sub-directories.
# The default value is: NO.
CREATE_SUBDIRS = NO
# Controls the number of sub-directories that will be created when
# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every
# level increment doubles the number of directories, resulting in 4096
# directories at level 8 which is the default and also the maximum value. The
# sub-directories are organized in 2 levels, the first level always has a fixed
# numer of 16 directories.
# Minimum value: 0, maximum value: 8, default value: 8.
# This tag requires that the tag CREATE_SUBDIRS is set to YES.
CREATE_SUBDIRS_LEVEL = 8
# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
# characters to appear in the names of generated files. If set to NO, non-ASCII
# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
@ -81,26 +103,18 @@ ALLOW_UNICODE_NAMES = NO
# The OUTPUT_LANGUAGE tag is used to specify the language in which all
# documentation generated by doxygen is written. Doxygen will use this
# information to generate all constant output in the proper language.
# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
# Ukrainian and Vietnamese.
# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian,
# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English
# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek,
# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with
# English messages), Korean, Korean-en (Korean with English messages), Latvian,
# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese,
# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish,
# Swedish, Turkish, Ukrainian and Vietnamese.
# The default value is: English.
OUTPUT_LANGUAGE = English
# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all
# documentation generated by doxygen is written. Doxygen will use this
# information to generate all generated output in the proper direction.
# Possible values are: None, LTR, RTL and Context.
# The default value is: None.
OUTPUT_TEXT_DIRECTION = None
# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
# descriptions after the members that are listed in the file and class
# documentation (similar to Javadoc). Set to NO to disable this.
@ -258,16 +272,16 @@ TAB_SIZE = 4
# the documentation. An alias has the form:
# name=value
# For example adding
# "sideeffect=@par Side Effects:\n"
# "sideeffect=@par Side Effects:^^"
# will allow you to put the command \sideeffect (or @sideeffect) in the
# documentation, which will result in a user-defined paragraph with heading
# "Side Effects:". You can put \n's in the value part of an alias to insert
# newlines (in the resulting output). You can put ^^ in the value part of an
# alias to insert a newline as if a physical newline was in the original file.
# When you need a literal { or } or , in the value part of an alias you have to
# escape them by means of a backslash (\), this can lead to conflicts with the
# commands \{ and \} for these it is advised to use the version @{ and @} or use
# a double escape (\\{ and \\})
# "Side Effects:". Note that you cannot put \n's in the value part of an alias
# to insert newlines (in the resulting output). You can put ^^ in the value part
# of an alias to insert a newline as if a physical newline was in the original
# file. When you need a literal { or } or , in the value part of an alias you
# have to escape them by means of a backslash (\), this can lead to conflicts
# with the commands \{ and \} for these it is advised to use the version @{ and
# @} or use a double escape (\\{ and \\})
ALIASES =
@ -312,8 +326,8 @@ OPTIMIZE_OUTPUT_SLICE = NO
# extension. Doxygen has a built-in mapping, but you can override or extend it
# using this tag. The format is ext=language, where ext is a file extension, and
# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL,
# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice,
# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
# tries to guess whether the code is fixed or free formatted code, this is the
# default for Fortran type files). For instance to make doxygen treat .inc files
@ -460,13 +474,13 @@ TYPEDEF_HIDES_STRUCT = NO
LOOKUP_CACHE_SIZE = 0
# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use
# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use
# during processing. When set to 0 doxygen will based this on the number of
# cores available in the system. You can set it explicitly to a value larger
# than 0 to get more control over the balance between CPU load and processing
# speed. At this moment only the input processing can be done using multiple
# threads. Since this is still an experimental feature the default is set to 1,
# which efficively disables parallel processing. Please report any issues you
# which effectively disables parallel processing. Please report any issues you
# encounter. Generating dot graphs in parallel is controlled by the
# DOT_NUM_THREADS setting.
# Minimum value: 0, maximum value: 32, default value: 1.
@ -585,14 +599,15 @@ INTERNAL_DOCS = NO
# filesystem is case sensitive (i.e. it supports files in the same directory
# whose names only differ in casing), the option must be set to YES to properly
# deal with such files in case they appear in the input. For filesystems that
# are not case sensitive the option should be be set to NO to properly deal with
# are not case sensitive the option should be set to NO to properly deal with
# output files written for symbols that only differ in casing, such as for two
# classes, one named CLASS and the other named Class, and to also support
# references to files without having to specify the exact matching casing. On
# Windows (including Cygwin) and MacOS, users should typically set this option
# to NO, whereas on Linux or other Unix flavors it should typically be set to
# YES.
# The default value is: system dependent.
# Possible values are: SYSTEM, NO and YES.
# The default value is: SYSTEM.
CASE_SENSE_NAMES = NO
@ -610,6 +625,12 @@ HIDE_SCOPE_NAMES = NO
HIDE_COMPOUND_REFERENCE= NO
# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class
# will show which file needs to be included to use the class.
# The default value is: YES.
SHOW_HEADERFILE = YES
# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
# the files that are included by a file in the documentation of that file.
# The default value is: YES.
@ -767,7 +788,8 @@ FILE_VERSION_FILTER =
# output files in an output format independent way. To create the layout file
# that represents doxygen's defaults, run doxygen with the -l option. You can
# optionally specify a file name after the option, if omitted DoxygenLayout.xml
# will be used as the name of the layout file.
# will be used as the name of the layout file. See also section "Changing the
# layout of pages" for information.
#
# Note that if you run doxygen from a directory containing a file called
# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
@ -813,18 +835,26 @@ WARNINGS = YES
WARN_IF_UNDOCUMENTED = YES
# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
# potential errors in the documentation, such as not documenting some parameters
# in a documented function, or documenting parameters that don't exist or using
# markup commands wrongly.
# potential errors in the documentation, such as documenting some parameters in
# a documented function twice, or documenting parameters that don't exist or
# using markup commands wrongly.
# The default value is: YES.
WARN_IF_DOC_ERROR = YES
# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete
# function parameter documentation. If set to NO, doxygen will accept that some
# parameters have no documentation without warning.
# The default value is: YES.
WARN_IF_INCOMPLETE_DOC = YES
# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
# are documented, but have no documentation for their parameters or return
# value. If set to NO, doxygen will only warn about wrong or incomplete
# parameter documentation, but not about the absence of documentation. If
# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
# value. If set to NO, doxygen will only warn about wrong parameter
# documentation, but not about the absence of documentation. If EXTRACT_ALL is
# set to YES then this flag will automatically be disabled. See also
# WARN_IF_INCOMPLETE_DOC
# The default value is: NO.
WARN_NO_PARAMDOC = NO
@ -836,7 +866,7 @@ WARN_NO_PARAMDOC = NO
# Possible values are: NO, YES and FAIL_ON_WARNINGS.
# The default value is: NO.
WARN_AS_ERROR = NO
WARN_AS_ERROR = YES
# The WARN_FORMAT tag determines the format of the warning messages that doxygen
# can produce. The string should contain the $file, $line, and $text tags, which
@ -844,13 +874,27 @@ WARN_AS_ERROR = NO
# and the warning text. Optionally the format may contain $version, which will
# be replaced by the version of the file (if it could be obtained via
# FILE_VERSION_FILTER)
# See also: WARN_LINE_FORMAT
# The default value is: $file:$line: $text.
WARN_FORMAT = "$file:$line: $text"
# In the $text part of the WARN_FORMAT command it is possible that a reference
# to a more specific place is given. To make it easier to jump to this place
# (outside of doxygen) the user can define a custom "cut" / "paste" string.
# Example:
# WARN_LINE_FORMAT = "'vi $file +$line'"
# See also: WARN_FORMAT
# The default value is: at line $line of file $file.
WARN_LINE_FORMAT = "at line $line of file $file"
# The WARN_LOGFILE tag can be used to specify a file to which warning and error
# messages should be written. If left blank the output is written to standard
# error (stderr).
# error (stderr). In case the file specified cannot be opened for writing the
# warning and error messages are written to standard error. When as file - is
# specified the warning and error messages are written to standard output
# (stdout).
WARN_LOGFILE =
@ -873,10 +917,21 @@ INPUT = ../CHANGELOG.md \
# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
# documentation (see:
# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
# See also: INPUT_FILE_ENCODING
# The default value is: UTF-8.
INPUT_ENCODING = UTF-8
# This tag can be used to specify the character encoding of the source files
# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify
# character encoding on a per file pattern basis. Doxygen will compare the file
# name with each pattern and apply the encoding instead of the default
# INPUT_ENCODING) if there is a match. The character encodings are a list of the
# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding
# "INPUT_ENCODING" for further information on supported encodings.
INPUT_FILE_ENCODING =
# If the value of the INPUT tag contains directories, you can use the
# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
# *.h) to filter out the source-files in the directories.
@ -890,10 +945,10 @@ INPUT_ENCODING = UTF-8
#
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl,
# *.ucf, *.qsf and *.ice.
# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,
# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C
# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
# *.vhdl, *.ucf, *.qsf and *.ice.
FILE_PATTERNS = *.c \
*.cc \
@ -978,7 +1033,7 @@ EXCLUDE_PATTERNS =
# (namespaces, classes, functions, etc.) that should be excluded from the
# output. The symbol name can be a fully qualified name, a word, or if the
# wildcard * is used, a substring. Examples: ANamespace, AClass,
# AClass::ANamespace, ANamespace::*Test
# ANamespace::AClass, ANamespace::*Test
#
# Note that the wildcards are matched against the file with absolute path, so to
# exclude all test directories use the pattern */test/*
@ -1026,6 +1081,11 @@ IMAGE_PATH =
# code is scanned, but not when the output code is generated. If lines are added
# or removed, the anchors will not be placed correctly.
#
# Note that doxygen will use the data processed and written to standard output
# for further processing, therefore nothing else, like debug statements or used
# commands (so in case of a Windows batch file always use @echo OFF), should be
# written to standard output.
#
# Note that for custom extensions or not directly supported extensions you also
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
# properly processed by doxygen.
@ -1067,6 +1127,15 @@ FILTER_SOURCE_PATTERNS =
USE_MDFILE_AS_MAINPAGE = index.md
# The Fortran standard specifies that for fixed formatted Fortran code all
# characters from position 72 are to be considered as comment. A common
# extension is to allow longer lines before the automatic comment starts. The
# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can
# be processed before the automatic comment starts.
# Minimum value: 7, maximum value: 10000, default value: 72.
FORTRAN_COMMENT_AFTER = 72
#---------------------------------------------------------------------------
# Configuration options related to source browsing
#---------------------------------------------------------------------------
@ -1164,9 +1233,11 @@ VERBATIM_HEADERS = YES
CLANG_ASSISTED_PARSING = NO
# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to
# YES then doxygen will add the directory of each input to the include path.
# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS
# tag is set to YES then doxygen will add the directory of each input to the
# include path.
# The default value is: YES.
# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
CLANG_ADD_INC_PATHS = YES
@ -1299,9 +1370,26 @@ HTML_EXTRA_STYLESHEET =
HTML_EXTRA_FILES =
# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output
# should be rendered with a dark or light theme. Default setting AUTO_LIGHT
# enables light output unless the user preference is dark output. Other options
# are DARK to always use dark mode, LIGHT to always use light mode, AUTO_DARK to
# default to dark mode unless the user prefers light mode, and TOGGLE to let the
# user toggle between dark and light mode via a button.
# Possible values are: LIGHT Always generate light output., DARK Always generate
# dark output., AUTO_LIGHT Automatically set the mode according to the user
# preference, use light mode if no preference is set (the default)., AUTO_DARK
# Automatically set the mode according to the user preference, use dark mode if
# no preference is set. and TOGGLE Allow to user to switch between light and
# dark mode via a button..
# The default value is: AUTO_LIGHT.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_COLORSTYLE = AUTO_LIGHT
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
# will adjust the colors in the style sheet and background images according to
# this color. Hue is specified as an angle on a colorwheel, see
# this color. Hue is specified as an angle on a color-wheel, see
# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
# purple, and 360 is red again.
@ -1311,7 +1399,7 @@ HTML_EXTRA_FILES =
HTML_COLORSTYLE_HUE = 220
# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
# in the HTML output. For a value of 0 the output will use grayscales only. A
# in the HTML output. For a value of 0 the output will use gray-scales only. A
# value of 255 will produce the most vivid colors.
# Minimum value: 0, maximum value: 255, default value: 100.
# This tag requires that the tag GENERATE_HTML is set to YES.
@ -1393,6 +1481,13 @@ GENERATE_DOCSET = NO
DOCSET_FEEDNAME = "Doxygen generated docs"
# This tag determines the URL of the docset feed. A documentation feed provides
# an umbrella under which multiple documentation sets from a single provider
# (such as a company or product suite) can be grouped.
# This tag requires that the tag GENERATE_DOCSET is set to YES.
DOCSET_FEEDURL =
# This tag specifies a string that should uniquely identify the documentation
# set bundle. This should be a reverse domain-name style string, e.g.
# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
@ -1418,8 +1513,12 @@ DOCSET_PUBLISHER_NAME = Publisher
# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
# (see:
# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows.
# on Windows. In the beginning of 2021 Microsoft took the original page, with
# a.o. the download links, offline the HTML help workshop was already many years
# in maintenance mode). You can download the HTML help workshop from the web
# archives at Installation executable (see:
# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo
# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe).
#
# The HTML Help Workshop contains a compiler that can convert all HTML output
# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
@ -1578,16 +1677,28 @@ DISABLE_INDEX = NO
# to work a browser that supports JavaScript, DHTML, CSS and frames is required
# (i.e. any modern browser). Windows users are probably better off using the
# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
# further fine-tune the look of the index. As an example, the default style
# sheet generated by doxygen has an example that shows how to put an image at
# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
# the same information as the tab index, you could consider setting
# DISABLE_INDEX to YES when enabling this option.
# further fine tune the look of the index (see "Fine-tuning the output"). As an
# example, the default style sheet generated by doxygen has an example that
# shows how to put an image at the root of the tree instead of the PROJECT_NAME.
# Since the tree basically has the same information as the tab index, you could
# consider setting DISABLE_INDEX to YES when enabling this option.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
GENERATE_TREEVIEW = YES
# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the
# FULL_SIDEBAR option determines if the side bar is limited to only the treeview
# area (value NO) or if it should extend to the full height of the window (value
# YES). Setting this to YES gives a layout similar to
# https://docs.readthedocs.io with more room for contents, but less room for the
# project logo, title, and description. If either GENERATE_TREEVIEW or
# DISABLE_INDEX is set to NO, this option has no effect.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
FULL_SIDEBAR = NO
# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
# doxygen will group on one line in the generated HTML documentation.
#
@ -1612,6 +1723,13 @@ TREEVIEW_WIDTH = 250
EXT_LINKS_IN_WINDOW = NO
# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email
# addresses.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.
OBFUSCATE_EMAILS = YES
# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
@ -1632,17 +1750,6 @@ HTML_FORMULA_FORMAT = png
FORMULA_FONTSIZE = 10
# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
# generated for formulas are transparent PNGs. Transparent PNGs are not
# supported properly for IE 6.0, but are supported on all modern browsers.
#
# Note that when changing this option you need to delete any form_*.png files in
# the HTML output directory before the changes have effect.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.
FORMULA_TRANSPARENT = YES
# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
# to create new LaTeX commands to be used in formulas as building blocks. See
# the section "Including formulas" for details.
@ -1660,11 +1767,29 @@ FORMULA_MACROFILE =
USE_MATHJAX = NO
# With MATHJAX_VERSION it is possible to specify the MathJax version to be used.
# Note that the different versions of MathJax have different requirements with
# regards to the different settings, so it is possible that also other MathJax
# settings have to be changed when switching between the different MathJax
# versions.
# Possible values are: MathJax_2 and MathJax_3.
# The default value is: MathJax_2.
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_VERSION = MathJax_2
# When MathJax is enabled you can set the default output format to be used for
# the MathJax output. See the MathJax site (see:
# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details.
# the MathJax output. For more details about the output format see MathJax
# version 2 (see:
# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
# (see:
# http://docs.mathjax.org/en/latest/web/components/output.html).
# Possible values are: HTML-CSS (which is slower, but has the best
# compatibility), NativeMML (i.e. MathML) and SVG.
# compatibility. This is the name for Mathjax version 2, for MathJax version 3
# this will be translated into chtml), NativeMML (i.e. MathML. Only supported
# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This
# is the name for Mathjax version 3, for MathJax version 2 this will be
# translated into HTML-CSS) and SVG.
# The default value is: HTML-CSS.
# This tag requires that the tag USE_MATHJAX is set to YES.
@ -1677,15 +1802,21 @@ MATHJAX_FORMAT = HTML-CSS
# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
# Content Delivery Network so you can quickly see the result without installing
# MathJax. However, it is strongly recommended to install a local copy of
# MathJax from https://www.mathjax.org before deployment.
# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2.
# MathJax from https://www.mathjax.org before deployment. The default value is:
# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2
# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@2
# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
# extension names that should be enabled during MathJax rendering. For example
# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7-latest/tex.html
# #tex-and-latex-extensions):
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
# For example for MathJax version 3 (see
# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
# MATHJAX_EXTENSIONS = ams
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_EXTENSIONS =
@ -1865,29 +1996,31 @@ PAPER_TYPE = a4
EXTRA_PACKAGES =
# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
# generated LaTeX document. The header should contain everything until the first
# chapter. If it is left blank doxygen will generate a standard header. See
# section "Doxygen usage" for information on how to let doxygen write the
# default header to a separate file.
# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for
# the generated LaTeX document. The header should contain everything until the
# first chapter. If it is left blank doxygen will generate a standard header. It
# is highly recommended to start with a default header using
# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty
# and then modify the file new_header.tex. See also section "Doxygen usage" for
# information on how to generate the default header that doxygen normally uses.
#
# Note: Only use a user-defined header if you know what you are doing! The
# following commands have a special meaning inside the header: $title,
# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
# string, for the replacement values of the other commands the user is referred
# to HTML_HEADER.
# Note: Only use a user-defined header if you know what you are doing!
# Note: The header is subject to change so you typically have to regenerate the
# default header when upgrading to a newer version of doxygen. The following
# commands have a special meaning inside the header (and footer): For a
# description of the possible markers and block names see the documentation.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_HEADER =
# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
# generated LaTeX document. The footer should contain everything after the last
# chapter. If it is left blank doxygen will generate a standard footer. See
# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for
# the generated LaTeX document. The footer should contain everything after the
# last chapter. If it is left blank doxygen will generate a standard footer. See
# LATEX_HEADER for more information on how to generate a default footer and what
# special commands can be used inside the footer.
#
# Note: Only use a user-defined footer if you know what you are doing!
# special commands can be used inside the footer. See also section "Doxygen
# usage" for information on how to generate the default footer that doxygen
# normally uses. Note: Only use a user-defined footer if you know what you are
# doing!
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_FOOTER =
@ -1932,8 +2065,7 @@ USE_PDFLATEX = YES
# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
# command to the generated LaTeX files. This will instruct LaTeX to keep running
# if errors occur, instead of asking the user for help. This option is also used
# when generating formulas in HTML.
# if errors occur, instead of asking the user for help.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.
@ -1946,16 +2078,6 @@ LATEX_BATCHMODE = NO
LATEX_HIDE_INDICES = NO
# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
# code with syntax highlighting in the LaTeX output.
#
# Note that which sources are shown also depends on other settings such as
# SOURCE_BROWSER.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_SOURCE_CODE = NO
# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
# bibliography, e.g. plainnat, or ieeetr. See
# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
@ -2036,16 +2158,6 @@ RTF_STYLESHEET_FILE =
RTF_EXTENSIONS_FILE =
# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
# with syntax highlighting in the RTF output.
#
# Note that which sources are shown also depends on other settings such as
# SOURCE_BROWSER.
# The default value is: NO.
# This tag requires that the tag GENERATE_RTF is set to YES.
RTF_SOURCE_CODE = NO
#---------------------------------------------------------------------------
# Configuration options related to the man page output
#---------------------------------------------------------------------------
@ -2142,15 +2254,6 @@ GENERATE_DOCBOOK = NO
DOCBOOK_OUTPUT = docbook
# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
# program listings (including syntax highlighting and cross-referencing
# information) to the DOCBOOK output. Note that enabling this will significantly
# increase the size of the DOCBOOK output.
# The default value is: NO.
# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
DOCBOOK_PROGRAMLISTING = NO
#---------------------------------------------------------------------------
# Configuration options for the AutoGen Definitions output
#---------------------------------------------------------------------------
@ -2241,7 +2344,8 @@ SEARCH_INCLUDES = YES
# The INCLUDE_PATH tag can be used to specify one or more directories that
# contain include files that are not input files but should be processed by the
# preprocessor.
# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of
# RECURSIVE has no effect here.
# This tag requires that the tag SEARCH_INCLUDES is set to YES.
INCLUDE_PATH =
@ -2267,7 +2371,8 @@ PREDEFINED = _DOXYGEN_ \
CONFIG_BT_NIMBLE_ROLE_CENTRAL \
CONFIG_BT_NIMBLE_ROLE_OBSERVER \
CONFIG_BT_NIMBLE_ROLE_PERIPHERAL \
CONFIG_BT_NIMBLE_ROLE_BROADCASTER
CONFIG_BT_NIMBLE_ROLE_BROADCASTER \
CONFIG_BT_NIMBLE_EXT_ADV
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
# tag can be used to specify a list of macro names that should be expanded. The
@ -2338,15 +2443,6 @@ EXTERNAL_PAGES = YES
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
# NO turns the diagrams off. Note that this option also works with HAVE_DOT
# disabled, but it is recommended to install and use dot, since it yields more
# powerful graphs.
# The default value is: YES.
CLASS_DIAGRAMS = NO
# You can include diagrams made with dia in doxygen documentation. Doxygen will
# then run dia to produce the diagram and insert it in the documentation. The
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
@ -2379,37 +2475,52 @@ HAVE_DOT = NO
DOT_NUM_THREADS = 0
# When you want a differently looking font in the dot files that doxygen
# generates you can specify the font name using DOT_FONTNAME. You need to make
# sure dot is able to find the font, which can be done by putting it in a
# standard location or by setting the DOTFONTPATH environment variable or by
# setting DOT_FONTPATH to the directory containing the font.
# The default value is: Helvetica.
# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of
# subgraphs. When you want a differently looking font in the dot files that
# doxygen generates you can specify fontname, fontcolor and fontsize attributes.
# For details please see <a href=https://graphviz.org/doc/info/attrs.html>Node,
# Edge and Graph Attributes specification</a> You need to make sure dot is able
# to find the font, which can be done by putting it in a standard location or by
# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
# directory containing the font. Default graphviz fontsize is 14.
# The default value is: fontname=Helvetica,fontsize=10.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTNAME = Helvetica
DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10"
# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
# dot graphs.
# Minimum value: 4, maximum value: 24, default value: 10.
# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can
# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. <a
# href=https://graphviz.org/doc/info/arrows.html>Complete documentation about
# arrows shapes.</a>
# The default value is: labelfontname=Helvetica,labelfontsize=10.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTSIZE = 10
DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10"
# By default doxygen will tell dot to use the default font as specified with
# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
# the path where dot can find it using this tag.
# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes
# around nodes set 'shape=plain' or 'shape=plaintext' <a
# href=https://www.graphviz.org/doc/info/shapes.html>Shapes specification</a>
# The default value is: shape=box,height=0.2,width=0.4.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4"
# You can set the path where dot can find font specified with fontname in
# DOT_COMMON_ATTR and others dot attributes.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTPATH =
# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
# each documented class showing the direct and indirect inheritance relations.
# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a
# graph for each documented class showing the direct and indirect inheritance
# relations. In case HAVE_DOT is set as well dot will be used to draw the graph,
# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set
# to TEXT the direct and indirect inheritance relations will be shown as texts /
# links.
# Possible values are: NO, YES, TEXT and GRAPH.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
CLASS_GRAPH = YES
CLASS_GRAPH = TEXT
# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
# graph for each documented class showing the direct and indirect implementation
@ -2421,7 +2532,8 @@ CLASS_GRAPH = YES
COLLABORATION_GRAPH = YES
# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
# groups, showing the direct groups dependencies.
# groups, showing the direct groups dependencies. See also the chapter Grouping
# in the manual.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
@ -2536,6 +2648,13 @@ GRAPHICAL_HIERARCHY = YES
DIRECTORY_GRAPH = YES
# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels
# of child directories generated in directory dependency graphs by dot.
# Minimum value: 1, maximum value: 25, default value: 1.
# This tag requires that the tag DIRECTORY_GRAPH is set to YES.
DIR_GRAPH_MAX_DEPTH = 1
# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
# generated by dot. For an explanation of the image formats see the section
# output formats in the documentation of the dot tool (Graphviz (see:
@ -2589,10 +2708,10 @@ MSCFILE_DIRS =
DIAFILE_DIRS =
# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
# path where java can find the plantuml.jar file. If left blank, it is assumed
# PlantUML is not used or called during a preprocessing step. Doxygen will
# generate a warning when it encounters a \startuml command in this case and
# will not generate output for the diagram.
# path where java can find the plantuml.jar file or to the filename of jar file
# to be used. If left blank, it is assumed PlantUML is not used or called during
# a preprocessing step. Doxygen will generate a warning when it encounters a
# \startuml command in this case and will not generate output for the diagram.
PLANTUML_JAR_PATH =
@ -2630,18 +2749,6 @@ DOT_GRAPH_MAX_NODES = 50
MAX_DOT_GRAPH_DEPTH = 0
# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
# background. This is disabled by default, because dot on Windows does not seem
# to support this out of the box.
#
# Warning: Depending on the platform used, enabling this option may lead to
# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
# read).
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_TRANSPARENT = NO
# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
# files in one run (i.e. multiple -o and -T options on the command line). This
# makes dot run faster, but since only newer versions of dot (>1.8.10) support
@ -2654,6 +2761,8 @@ DOT_MULTI_TARGETS = NO
# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
# explaining the meaning of the various boxes and arrows in the dot generated
# graphs.
# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal
# graphical representation for inheritance and collaboration diagrams is used.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
@ -2662,8 +2771,8 @@ GENERATE_LEGEND = YES
# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate
# files that are used to generate the various graphs.
#
# Note: This setting is not only used for dot files but also for msc and
# plantuml temporary files.
# Note: This setting is not only used for dot files but also for msc temporary
# files.
# The default value is: YES.
DOT_CLEANUP = YES

View File

@ -1,7 +1,6 @@
# Improvements and updates
Many improvements have been made to this library vs the original, this is a brief overview of the most significant changes.
Refer to the [class documentation](https://h2zero.github.io/esp-nimble-cpp/annotated.html) for futher information on class specifics.
Many improvements have been made to this library vs the original, this is a brief overview of the most significant changes. Refer to the [class documentation](https://h2zero.github.io/esp-nimble-cpp/annotated.html) for further information on class specifics.
* [Server](#server)
* [Advertising](#advertising)
@ -10,23 +9,26 @@ Refer to the [class documentation](https://h2zero.github.io/esp-nimble-cpp/annot
<br/>
<a name="server"></a>
# Server
# Server
`NimBLECharacteristic::setValue(const T &s)`
`NimBLEDescriptor::setValue(const T &s)`
`NimBLEService::NimBLEService::createCharacteristic` takes a 3rd parameter to specify the maximum data size that can be stored by the characteristic. This allows for limiting the RAM use of the characteristic in cases where small amounts of data are expected.
<br/>
Now use a template to accomodate standard and custom types/values.
`NimBLECharacteristic::setValue(const T &s)`
`NimBLEDescriptor::setValue(const T &s)`
Now use the `NimbleAttValue` class and templates to accommodate standard and custom types/values.
**Example**
```
struct my_struct{
struct my_struct {
uint8_t one;
uint16_t two;
uint32_t four;
uint64_t eight;
float flt;
}myStruct;
} myStruct;
myStruct.one = 1;
myStruct.two = 2;
myStruct.four = 4;
@ -34,12 +36,14 @@ struct my_struct{
myStruct.flt = 1234.56;
pCharacteristic->setValue(myStruct);
```
This will send the struct to the recieving client when read or a notification sent.
`NimBLECharacteristic::getValue` now takes an optional timestamp parameter which will update it's value with
the time the last value was recieved. In addition an overloaded template has been added to retrieve the value
as a type specified by the user.
// Arduino String support
String myString = "Hello";
pCharacteristic->setValue(myString);
```
This will send the struct to the receiving client when read or a notification sent.
`NimBLECharacteristic::getValue` now takes an optional timestamp parameter which will update it's value with the time the last value was received. In addition an overloaded template has been added to retrieve the value as a type specified by the user.
**Example**
```
@ -48,38 +52,34 @@ as a type specified by the user.
```
<br/>
**Advertising will automatically start when a client disconnects.**
**Advertising will automatically start when a client disconnects.**
A new method `NimBLEServer::advertiseOnDisconnect(bool)` has been implemented to control this, true(default) = enabled.
<br/>
`NimBLEServer::removeService` takes an additional parameter `bool deleteSvc` that if true will delete the service
and all characteristics / descriptors belonging to it and invalidating any pointers to them.
`NimBLEServer::removeService` takes an additional parameter `bool deleteSvc` that if true will delete the service and all characteristics / descriptors belonging to it and invalidating any pointers to them.
If false the service is only removed from visibility by clients. The pointers to the service and
it's characteristics / descriptors will remain valid and the service can be re-added in the future
using `NimBLEServer::addService`.
If false the service is only removed from visibility by clients. The pointers to the service and it's characteristics / descriptors will remain valid and the service can be re-added in the future using `NimBLEServer::addService`.
<br/>
<a name="advertising"></a>
# Advertising
`NimBLEAdvertising::start`
Now takes 2 optional parameters, the first is the duration to advertise for (in seconds), the second is a callback
that is invoked when advertsing ends and takes a pointer to a `NimBLEAdvertising` object (similar to the `NimBLEScan::start` API).
Now takes 2 optional parameters, the first is the duration to advertise for (in milliseconds), the second is a callback that is invoked when advertising ends and takes a pointer to a `NimBLEAdvertising` object (similar to the `NimBLEScan::start` API).
This provides an opportunity to update the advertisment data if desired.
This provides an opportunity to update the advertisement data if desired.
Also now returns a bool value to indicate if advertising successfully started or not.
<br/>
Also now returns a bool value to indicate if advertising successfully started or not.
<br/>
<a name="client"></a>
# Client
# Client
`NimBLERemoteCharacteristic::readValue(time_t\*, bool)`
`NimBLERemoteDescriptor::readValue(bool)`
`NimBLERemoteCharacteristic::readValue(time_t\*, bool)`
`NimBLERemoteDescriptor::readValue(bool)`
Have been added as templates to allow reading the values as any specified type.
Have been added as templates to allow reading the values as any specified type.
**Example**
```
@ -93,56 +93,57 @@ struct my_struct{
time_t timestamp;
myStruct = pRemoteCharacteristic->readValue<myStruct>(&timestamp); // timestamp optional
```
```
<br/>
`NimBLERemoteCharacteristic::registerForNotify`
Has been **deprecated** as now the internally stored characteristic value is updated when notification/indication is recieved.
Has been removed.
`NimBLERemoteCharacteristic::subscribe` and `NimBLERemoteCharacteristic::unsubscribe` have been implemented to replace it.
A callback is no longer requred to get the most recent value unless timing is important. Instead, the application can call `NimBLERemoteCharacteristic::getValue` to
get the last updated value any time.
The internally stored characteristic value is now updated when notification/indication is recieved. Making a callback no longer required to get the most recent value unless timing is important. Instead, the application can call `NimBLERemoteCharacteristic::getValue` to get the most recent value any time.
<br/>
The `notifiy_callback` function is now defined as a `std::function` to take advantage of using `std::bind` to specifiy a class member function for the callback.
The `notify_callback` function is now defined as a `std::function` to take advantage of using `std::bind` to specify a class member function for the callback.
Example:
Example:
```
using namespace std::placeholders;
notify_callback callback = std::bind(&<ClassName>::<memberFunctionCallbackName>, this, _1, _2, _3, _4);
<remoteCharacteristicInstance>->subscribe(true, callback);
```
`NimBLERemoteCharacteristic::readValue` and `NimBLERemoteCharacteristic::getValue` take an optional timestamp parameter which will update it's value with
the time the last value was recieved.
`NimBLERemoteCharacteristic::readValue` and `NimBLERemoteCharacteristic::getValue` take an optional timestamp parameter which will update it's value with
the time the last value was received.
> NimBLEClient::getService
> NimBLERemoteService::getCharacteristic
> NimBLERemoteCharacteristic::getDescriptor
> NimBLERemoteCharacteristic::getDescriptor
These methods will now check the respective vectors for the attribute object and, if not found, will retrieve (only)
the specified attribute from the peripheral.
These methods will now check the respective vectors for the attribute object and, if not found, will retrieve (only)
the specified attribute from the peripheral.
These changes allow more control for the user to manage the resources used for the attributes.
These changes allow more control for the user to manage the resources used for the attributes.
<br/>
`NimBLEClient::connect()` can now be called without an address or advertised device parameter. This will connect to the
device with the address previously set when last connected or set with `NimBLEDevice::setPeerAddress()`.
`NimBLEClient::connect()` can now be called without an address or advertised device parameter. This will connect to the device with the address previously set when last connected or set with `NimBLEDevice::setPeerAddress()`.
<a name="general"></a>
# General
To reduce resource use all instances of `std::map` have been replaced with `std::vector`.
# General
To reduce resource use all instances of `std::map` have been replaced with `std::vector`.
Use of `FreeRTOS::Semaphore` has been removed as it was consuming too much ram, the related files have been left in place to accomodate application use.
Use of `FreeRTOS::Semaphore` has been removed as it was consuming too much ram, the related files have been left in place to accomodate application use.
Operators `==`, `!=` and `std::string` have been added to `NimBLEAddress` and `NimBLEUUID` for easier comparison and logging.
Operators `==`, `!=` and `std::string` have been added to `NimBLEAddress` and `NimBLEUUID` for easier comparison and logging.
New constructor for `NimBLEUUID(uint32_t, uint16_t, uint16_t, uint64_t)` added to lower memory use vs string construction. See: [#21](https://github.com/h2zero/NimBLE-Arduino/pull/21).
New constructor for `NimBLEUUID(uint32_t, uint16_t, uint16_t, uint64_t)` added to lower memory use vs string construction. See: [#21](https://github.com/h2zero/NimBLE-Arduino/pull/21).
Security/pairing operations are now handled in the respective `NimBLEClientCallbacks` and `NimBLEServerCallbacks` classes, `NimBLESecurity`(deprecated) remains for backward compatibility.
Security/pairing operations are now handled in the respective `NimBLEClientCallbacks` and `NimBLEServerCallbacks` classes, `NimBLESecurity`(deprecated) remains for backward compatibility.
Configuration options have been added to add or remove debugging information, when disabled (default) significatly reduces binary size.
Configuration options have been added to add or remove debugging information, when disabled (default) significantly reduces binary size.
In ESP-IDF the options are in menuconfig: `Main menu -> ESP-NimBLE-cpp configuration`.
For Arduino the options must be commented / uncommented in nimconfig.h.
<br/>
For Arduino the options must be commented / uncommented in nimconfig.h.
Characteristics and descriptors now use the `NimBLEAttValue` class to store their data. This is a polymorphic container class capable of converting to/from many different types efficiently. See: [#286](https://github.com/h2zero/NimBLE-Arduino/pull/286)

View File

@ -1,100 +1,121 @@
# Migrating from Bluedroid to NimBLE
This guide describes the required changes to existing projects migrating from the original bluedroid API to NimBLE.
This guide describes the required changes to existing projects migrating from the original bluedroid API to NimBLE.
**The changes listed here are only the required changes that must be made**, and a short overview of options for migrating existing applications.
**The changes listed here are only the required changes that must be made**, and a short overview of options for migrating existing applications.
For more information on the improvements and additions please refer to the [class documentation](https://h2zero.github.io/esp-nimble-cpp/annotated.html) and [Improvements and updates](Improvements_and_updates.md)
For more information on the improvements and additions please refer to the [class documentation](https://h2zero.github.io/NimBLE-Arduino/annotated.html) and [Improvements and updates](Improvements_and_updates.md)
* [General Changes](#general-information)
* [Server](#server-api)
* [Services](#services)
* [characteristics](#characteristics)
* [descriptors](#descriptors)
* [Characteristics](#characteristics)
* [Characteristic Callbacks](#characteristic-callbacks)
* [Descriptors](#descriptors)
* [Descriptor Callbacks](#descriptor-callbacks)
* [Security](#server-security)
* [Advertising](#advertising-api)
* [Client](#client-api)
* [Remote Services](#remote-services)
* [Remote characteristics](#remote-characteristics)
* [Client Callbacks](#client-callbacks)
* [Security](#client-security)
* [Scanning](#scan-api)
* [General Security](#security-api)
* [Configuration](#arduino-configuration)
<br/>
<br/>
<a name="general-information"></a>
## General Information
### Header Files
All classes are accessible by including `NimBLEDevice.h` in your application, no further headers need to be included.
All classes are accessible by including `NimBLEDevice.h` in your application, no further headers need to be included.
(Mainly for Arduino) You may choose to include `NimBLELog.h` in your appplication if you want to use the `NIMBLE_LOGx` macros for debugging.
These macros are used the same way as the `ESP_LOGx` macros.
(Mainly for Arduino) You may choose to include `NimBLELog.h` in your application if you want to use the `NIMBLE_LOGx` macros for debugging. These macros are used the same way as the `ESP_LOGx` macros.
<br/>
### Class Names
Class names remain the same as the original with the addition of a "Nim" prefix.
For example `BLEDevice` is now `NimBLEDevice` and `BLEServer` is now `NimBLEServer` etc.
Class names remain the same as the original with the addition of a "Nim" prefix.
For example `BLEDevice` is now `NimBLEDevice` and `BLEServer` is now `NimBLEServer` etc.
For convienience definitions have been added to allow applications to use either name for all classes
this means **no class names need to be changed in existing code** and makes migrating easier.
For convenience definitions have been added to allow applications to use either name for all classes this means **no class names need to be changed in existing code** and makes migrating easier.
<br/>
### BLE Addresses
`BLEAddress` (`NimBLEAddress`) When constructing an address the constructor now takes an *(optional)* `uint8_t type` paramameter
to specify the address type. Default is (0) Public static address.
`BLEAddress` (`NimBLEAddress`) When constructing an address the constructor now takes an *(optional)* `uint8_t type` parameter to specify the address type. Default is (0) Public static address.
For example `BLEAddress addr(11:22:33:44:55:66, 1)` will create the address object with an address type of: 1 (Random).
For example `BLEAddress addr(11:22:33:44:55:66, 1)` will create the address object with an address type of: 1 (Random).
As this paramameter is optional no changes to existing code are needed, it is mentioned here for information.
<br/>
`BLEAddress::getNative` (`NimBLEAddress::getNative`) returns a uint8_t pointer to the native address byte array.
In this library the address bytes are stored in reverse order from the original library. This is due to the way
the NimBLE stack expects addresses to be presented to it. All other functions such as `toString` are
not affected as the endian change is made within them.
As this parameter is optional no changes to existing code are needed, it is mentioned here for information.
`BLEAddress::getNative` (`NimBLEAddress::getNative`) returns a uint8_t pointer to the native address byte array. In this library the address bytes are stored in reverse order from the original library. This is due to the way the NimBLE stack expects addresses to be presented to it. All other functions such as `toString` are not affected as the endian change is made within them.
<br/>
<a name="server-api"></a>
## Server API
Creating a `BLEServer` instance is the same as original, no changes required.
For example `BLEDevice::createServer()` will work just as it did before.
For example `BLEDevice::createServer()` will work just as it did before.
`BLEServerCallbacks` (`NimBLEServerCallbacks`) has new methods for handling security operations.
<br/>
`BLEServerCallbacks::onConnect` (`NimBLEServerCallbacks::onConnect`) only has a single callback declaration which takes an additional (required) parameter `NimBLEConnInfo & connInfo`, which has methods to get information about the connected peer.
```
void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo)`
```
<br/>
`BLEServerCallbacks::onDisconnect` (`NimBLEServerCallbacks::onDisconnect`) only has a single callback declaration which takes 2 additional (required) parameters `NimBLEConnInfo & connInfo`, which provides information about the peer and `int reason`, which gives the reason code for disconnection.
```
void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason)`
```
<br/>
`BLEServerCallbacks::onMtuChanged` (`NimBLEServerCallbacks::onMtuChanged`) takes the parameter `NimBLEConnInfo & connInfo` instead of `esp_ble_gatts_cb_param_t`, which has methods to get information about the connected peer.
```
onMTUChange(uint16_t MTU, NimBLEConnInfo& connInfo)
```
**Note:** All callback methods have default implementations which allows the application to implement only the methods applicable.
<br/>
<a name="services"></a>
### Services
Creating a `BLEService` (`NimBLEService`) instance is the same as original, no changes required.
Creating a `BLEService` (`NimBLEService`) instance is the same as original, no changes required.
For example `BLEServer::createService(SERVICE_UUID)` will work just as it did before.
<br/>
<a name="characteristics"></a>
### Characteristics
`BLEService::createCharacteristic` (`NimBLEService::createCharacteristic`) is used the same way as originally except the properties parameter has changed.
`BLEService::createCharacteristic` (`NimBLEService::createCharacteristic`) is used the same way as originally except the properties parameter has changed.
When creating a characteristic the properties are now set with `NIMBLE_PROPERTY::XXXX` instead of `BLECharacteristic::XXXX`.
#### Originally
> BLECharacteristic::PROPERTY_READ |
> BLECharacteristic::PROPERTY_WRITE
BLECharacteristic::PROPERTY_WRITE
#### Is Now
> NIMBLE_PROPERTY::READ |
> NIMBLE_PROPERTY::WRITE
NIMBLE_PROPERTY::WRITE
<br/>
#### The full list of properties
> NIMBLE_PROPERTY::READ
> NIMBLE_PROPERTY::READ_ENC
> NIMBLE_PROPERTY::READ_AUTHEN
> NIMBLE_PROPERTY::READ_AUTHOR
> NIMBLE_PROPERTY::WRITE
> NIMBLE_PROPERTY::WRITE_NR
> NIMBLE_PROPERTY::WRITE_ENC
> NIMBLE_PROPERTY::WRITE_AUTHEN
> NIMBLE_PROPERTY::WRITE_AUTHOR
> NIMBLE_PROPERTY::BROADCAST
> NIMBLE_PROPERTY::NOTIFY
> NIMBLE_PROPERTY::INDICATE
NIMBLE_PROPERTY::READ_ENC
NIMBLE_PROPERTY::READ_AUTHEN
NIMBLE_PROPERTY::READ_AUTHOR
NIMBLE_PROPERTY::WRITE
NIMBLE_PROPERTY::WRITE_NR
NIMBLE_PROPERTY::WRITE_ENC
NIMBLE_PROPERTY::WRITE_AUTHEN
NIMBLE_PROPERTY::WRITE_AUTHOR
NIMBLE_PROPERTY::BROADCAST
NIMBLE_PROPERTY::NOTIFY
NIMBLE_PROPERTY::INDICATE
<br/>
**Example:**
@ -102,7 +123,7 @@ When creating a characteristic the properties are now set with `NIMBLE_PROPERTY:
BLECharacteristic *pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE
BLECharacteristic::PROPERTY_WRITE
);
```
@ -111,23 +132,30 @@ Needs to be changed to:
BLECharacteristic *pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE
NIMBLE_PROPERTY::WRITE
);
```
<br/>
`BLECharacteristicCallbacks` (`NimBLECharacteristicCallbacks`) has a new method `NimBLECharacteristicCallbacks::onSubscribe`
which is called when a client subscribes to notifications/indications.
<a name="characteristic-callbacks"></a>
#### Characteristic callbacks
`BLECharacteristicCallbacks` (`NimBLECharacteristicCallbacks`) has a new method `NimBLECharacteristicCallbacks::onSubscribe` which is called when a client subscribes to notifications/indications.
`BLECharacteristicCallbacks::onRead` (`NimBLECharacteristicCallbacks::onRead`) only has a single callback declaration, which takes an additional (required) parameter of `NimBLEConnInfo& connInfo`, which provides connection information about the peer.
`BLECharacteristicCallbacks::onWrite` (`NimBLECharacteristicCallbacks::onWrite`) only has a single callback declaration, which takes an additional (required) parameter of `NimBLEConnInfo& connInfo`, which provides connection information about the peer.
`BLECharacteristicCallbacks::onStatus` (`NimBLECharacteristicCallbacks::onStatus`) has had the status parameter removed as it was unnecessary since the status code from the BLE stack was also provided. The status code for success is 0 for notifications and BLE_HS_EDONE for indications, any other value is an error.
**Note:** All callback methods have default implementations which allows the application to implement only the methods applicable.
<br/>
> BLECharacteristic::getData
> BLECharacteristic::getData
**Has been removed from the API.**
Originally this returned a `uint8_t*` to the internal data, which is volatile.
To prevent possibly throwing exceptions this has been removed and `NimBLECharacteristic::getValue` should be used
to get a copy of the data first which can then safely be accessed via pointer.
**Has been removed from the API.**
Originally this returned a `uint8_t*` to the internal data, which is volatile.
To prevent possibly throwing exceptions this has been removed and `NimBLECharacteristic::getValue` should be used
to get a copy of the data first which can then safely be accessed via pointer.
**Example:**
```
@ -142,48 +170,47 @@ my_struct_t myStruct = pChr->getValue<my_struct_t>();
<a name="descriptors"></a>
### Descriptors
The previous method `BLECharacteristic::addDescriptor()` has been removed.
Descriptors are now created using the `NimBLECharacteristic::createDescriptor` method.
BLE2902 or NimBLE2902 class has been removed.
NimBLE automatically creates the 0x2902 descriptor if a characteristic has a notification or indication property assigned to it.
It was no longer useful to have a class for the 0x2902 descriptor as a new callback `NimBLECharacteristicCallbacks::onSubscribe` was added
to handle callback functionality and the client subscription status is handled internally.
BLE2902 or NimBLE2902 class has been removed.
NimBLE automatically creates the 0x2902 descriptor if a characteristic has a notification or indication property assigned to it.
**Note:** Attempting to create a 0x2902 descriptor will trigger an assert to notify the error,
It was no longer useful to have a class for the 0x2902 descriptor as a new callback `NimBLECharacteristicCallbacks::onSubscribe` was added
to handle callback functionality and the client subscription status is handled internally.
**Note:** Attempting to create a 0x2902 descriptor will trigger an assert to notify the error,
allowing the creation of it would cause a fault in the NimBLE stack.
All other descriptors are now created just as characteristics are by using the `NimBLECharacteristic::createDescriptor` method (except 0x2904, see below).
All other descriptors are now created just as characteristics are by using the `NimBLECharacteristic::createDescriptor` method (except 0x2904, see below).
Which are defined as:
```
NimBLEDescriptor* createDescriptor(const char* uuid,
uint32_t properties =
uint32_t properties =
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = 100);
NimBLEDescriptor* createDescriptor(NimBLEUUID uuid,
uint32_t properties =
uint32_t properties =
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = 100);
```
##### Example
```
pDescriptor = pCharacteristic->createDescriptor("ABCD",
NIMBLE_PROPERTY::READ |
pDescriptor = pCharacteristic->createDescriptor("ABCD",
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE |
NIMBLE_PROPERTY::WRITE_ENC,
25);
```
Would create a descriptor with the UUID 0xABCD, publicly readable but only writable if paired/bonded (encrypted) and has a max value length of 25 bytes.
Would create a descriptor with the UUID 0xABCD, publicly readable but only writable if paired/bonded (encrypted) and has a max value length of 25 bytes.
<br/>
For the 0x2904, there is a special class that is created when you call `createDescriptor("2904").
The pointer returned is of the base class `NimBLEDescriptor` but the call will create the derived class of `NimBLE2904` so you must cast the returned pointer to
The pointer returned is of the base class `NimBLEDescriptor` but the call will create the derived class of `NimBLE2904` so you must cast the returned pointer to
`NimBLE2904` to access the specific class methods.
##### Example
@ -192,130 +219,127 @@ p2904 = (NimBLE2904*)pCharacteristic->createDescriptor("2904");
```
<br/>
<a name="descriptor-callbacks"></a>
#### Descriptor callbacks
> `BLEDescriptorCallbacks::onRead` (`NimBLEDescriptorCallbacks::onRead`)
`BLEDescriptorCallbacks::onwrite` (`NimBLEDescriptorCallbacks::onwrite`)
The above descriptor callbacks take an additional (required) parameter `NimBLEConnInfo& connInfo`, which contains the connection information of the peer.
<br/>
<a name="server-security"></a>
### Server Security
Security is set on the characteristic or descriptor properties by applying one of the following:
> NIMBLE_PROPERTY::READ_ENC
> NIMBLE_PROPERTY::READ_AUTHEN
> NIMBLE_PROPERTY::READ_AUTHOR
> NIMBLE_PROPERTY::WRITE_ENC
> NIMBLE_PROPERTY::WRITE_AUTHEN
> NIMBLE_PROPERTY::WRITE_AUTHOR
NIMBLE_PROPERTY::READ_AUTHEN
NIMBLE_PROPERTY::READ_AUTHOR
NIMBLE_PROPERTY::WRITE_ENC
NIMBLE_PROPERTY::WRITE_AUTHEN
NIMBLE_PROPERTY::WRITE_AUTHOR
<br/>
When a peer wants to read or write a characteristic or descriptor with any of these properties applied it will trigger the pairing process. By default the "just-works" pairing will be performed automatically.
When a peer wants to read or write a characteristic or descriptor with any of these properties applied
it will trigger the pairing process. By default the "just-works" pairing will be performed automatically.
This can be changed to use passkey authentication or numeric comparison. See [Security API](#security-api) for details.
<br/>
<a name="advertising-api"></a>
## Advertising API
Advertising works the same as the original API except:
> BLEAdvertising::setMinPreferred
> BLEAdvertising::setMaxPreferred
Advertising works the same as the original API except:
These methods were found to not provide useful functionality and consumed valuable advertising space (6 bytes of 31) if used unknowingly.
If you wish to advertise these parameters you can still do so manually via `BLEAdvertisementData::addData` (`NimBLEAdvertisementData::addData`).
<br/>
Calling `NimBLEAdvertising::setAdvertisementData` will entirely replace any data set with `NimBLEAdvertising::addServiceUUID`, or
Calling `NimBLEAdvertising::setAdvertisementData` will entirely replace any data set with `NimBLEAdvertising::addServiceUUID`, or
`NimBLEAdvertising::setAppearance` or similar methods. You should set all the data you wish to advertise within the `NimBLEAdvertisementData` instead.
~~Calling `NimBLEAdvertising::setScanResponseData` without also calling `NimBLEAdvertising::setAdvertisementData` will have no effect.
When using custom scan response data you must also use custom advertisement data.~~
No longer true as of release 1.2.0 and above, custom scan response is now supported without custom advertisement data.
<br/>
> BLEAdvertising::start (NimBLEAdvertising::start)
Now takes 2 optional parameters, the first is the duration to advertise for (in seconds), the second is a callback
that is invoked when advertsing ends and takes a pointer to a `NimBLEAdvertising` object (similar to the `NimBLEScan::start` API).
This provides an opportunity to update the advertisment data if desired.
Now takes 2 optional parameters, the first is the duration to advertise for (in milliseconds), the second is a callback that is invoked when advertising ends and takes a pointer to a `NimBLEAdvertising` object (similar to the `NimBLEScan::start` API).
This provides an opportunity to update the advertisement data if desired.
<br/>
<a name="client-api"></a>
## Client API
Client instances are created just as before with `BLEDevice::createClient` (`NimBLEDevice::createClient`).
Client instances are created just as before with `BLEDevice::createClient` (`NimBLEDevice::createClient`).
Multiple client instances can be created, up to the maximum number of connections set in the config file (default: 3).
To delete a client instance you must use `NimBLEDevice::deleteClient`.
Multiple client instances can be created, up to the maximum number of connections set in the config file (default: 3). To delete a client instance you must use `NimBLEDevice::deleteClient`.
`BLEClient::connect`(`NimBLEClient::connect`) Has had it's parameters altered.
`BLEClient::connect`(`NimBLEClient::connect`) Has had it's parameters altered.
Defined as:
> NimBLEClient::connect(bool deleteServices = true);
> NimBLEClient::connect(NimBLEAdvertisedDevice\* device, bool deleteServices = true);
> NimBLEClient::connect(NimBLEAddress address, bool deleteServices = true);
> NimBLEClient::connect(bool deleteServices = true);
> NimBLEClient::connect(NimBLEAdvertisedDevice\* device, bool deleteServices = true);
> NimBLEClient::connect(NimBLEAddress address, bool deleteServices = true);
The type parameter has been removed and a new bool parameter has been added to indicate if the client should delete the attribute database previously retrieved (if applicable) for the peripheral, default value is true.
If set to false the client will use the attribute database it retrieved from the peripheral when previously connected.
The type parameter has been removed and a new bool parameter has been added to indicate if the client should
delete the attribute database previously retrieved (if applicable) for the peripheral, default value is true.
If set to false the client will use the attribute database it retrieved from the peripheral when previously connected.
This allows for faster connections and power saving if the devices dropped connection and are reconnecting.
<br/>
> `BLEClient::getServices` (`NimBLEClient::getServices`)
> `BLEClient::getServices` (`NimBLEClient::getServices`)
This method now takes an optional (bool) parameter to indicate if the services should be retrieved from the server (true) or
the currently known database returned (false : default).
This method now takes an optional (bool) parameter to indicate if the services should be retrieved from the server (true) or the currently known database returned (false : default).
Also now returns a pointer to `std::vector` instead of `std::map`.
<br/>
**Removed:** the automatic discovery of all peripheral attributes as they consumed time and resources for data
the user may not be interested in.
**Added:** `NimBLEClient::discoverAttributes` for the user to discover all the peripheral attributes
to replace the the removed automatic functionality.
**Removed:** the automatic discovery of all peripheral attributes as they consumed time and resources for data the user may not be interested in.
**Added:** `NimBLEClient::discoverAttributes` for the user to discover all the peripheral attributes to replace the the removed automatic functionality.
<br/>
<a name="remote-services"></a>
### Remote Services
`BLERemoteService` (`NimBLERemoteService`) Methods remain mostly unchanged with the exceptions of:
`BLERemoteService` (`NimBLERemoteService`) Methods remain mostly unchanged with the exceptions of:
> BLERemoteService::getCharacteristicsByHandle
This method has been removed.
<br/>
> `BLERemoteService::getCharacteristics` (`NimBLERemoteService::getCharacteristics`)
> `BLERemoteService::getCharacteristics` (`NimBLERemoteService::getCharacteristics`)
This method now takes an optional (bool) parameter to indicate if the characteristics should be retrieved from the server (true) or
the currently known database returned (false : default).
This method now takes an optional (bool) parameter to indicate if the characteristics should be retrieved from the server (true) or
the currently known database returned (false : default).
Also now returns a pointer to `std::vector` instead of `std::map`.
<br/>
<a name="remote-characteristics"></a>
### Remote Characteristics
`BLERemoteCharacteristic` (`NimBLERemoteCharacteristic`) There have been a few changes to the methods in this class:
`BLERemoteCharacteristic` (`NimBLERemoteCharacteristic`)
There have been a few changes to the methods in this class:
> `BLERemoteCharacteristic::writeValue` (`NimBLERemoteCharacteristic::writeValue`)
> `BLERemoteCharacteristic::registerForNotify` (`NimBLERemoteCharacteristic::registerForNotify`)
> `BLERemoteCharacteristic::writeValue` (`NimBLERemoteCharacteristic::writeValue`)
Now return true or false to indicate success or failure so you can choose to disconnect or try again.
Now returns true or false to indicate success or failure so you can choose to disconnect or try again.
<br/>
> `BLERemoteCharacteristic::registerForNotify` (`NimBLERemoteCharacteristic::registerForNotify`)
> `BLERemoteCharacteristic::registerForNotify`
Has been removed.
Is now **deprecated**.
> `NimBLERemoteCharacteristic::subscribe`
> `NimBLERemoteCharacteristic::unsubscribe`
Are the new methods added to replace it.
<br/>
> `BLERemoteCharacteristic::readUInt8` (`NimBLERemoteCharacteristic::readUInt8`)
> `BLERemoteCharacteristic::readUInt16` (`NimBLERemoteCharacteristic::readUInt16`)
> `BLERemoteCharacteristic::readUInt32` (`NimBLERemoteCharacteristic::readUInt32`)
> `BLERemoteCharacteristic::readFloat` (`NimBLERemoteCharacteristic::readFloat`)
> `BLERemoteCharacteristic::readUInt8` (`NimBLERemoteCharacteristic::readUInt8`)
> `BLERemoteCharacteristic::readUInt16` (`NimBLERemoteCharacteristic::readUInt16`)
> `BLERemoteCharacteristic::readUInt32` (`NimBLERemoteCharacteristic::readUInt32`)
> `BLERemoteCharacteristic::readFloat` (`NimBLERemoteCharacteristic::readFloat`)
Are **deprecated** a template: NimBLERemoteCharacteristic::readValue<type\>(time_t\*, bool) has been added to replace them.
Are **deprecated** a template: `NimBLERemoteCharacteristic::readValue<type\>(time_t\*, bool)` has been added to replace them.
<br/>
> `BLERemoteCharacteristic::readRawData`
> `BLERemoteCharacteristic::readRawData`
**Has been removed from the API**
Originally it stored an unnecessary copy of the data and was returning a `uint8_t` pointer to volatile internal data.
The user application should use `NimBLERemoteCharacteristic::readValue` or `NimBLERemoteCharacteristic::getValue`.
To obatain a copy of the data, then cast the returned std::string to the type required such as:
**Has been removed from the API**
Originally it stored an unnecessary copy of the data and was returning a `uint8_t` pointer to volatile internal data.
The user application should use `NimBLERemoteCharacteristic::readValue` or `NimBLERemoteCharacteristic::getValue`.
To obtain a copy of the data, then cast the returned std::string to the type required such as:
```
std::string value = pChr->readValue();
uint8_t *data = (uint8_t*)value.data();
@ -325,63 +349,79 @@ Alternatively use the `readValue` template:
my_struct_t myStruct = pChr->readValue<my_struct_t>();
```
<br/>
> `BLERemoteCharacteristic::getDescriptors` (`NimBLERemoteCharacteristic::getDescriptors`)
This method now takes an optional (bool) parameter to indicate if the descriptors should be retrieved from the server (true) or
the currently known database returned (false : default).
> `BLERemoteCharacteristic::getDescriptors` (`NimBLERemoteCharacteristic::getDescriptors`)
This method now takes an optional (bool) parameter to indicate if the descriptors should be retrieved from the server (true) or
the currently known database returned (false : default).
Also now returns a pointer to `std::vector` instead of `std::map`.
<br/>
<a name="client-callbacks"></a>
### Client callbacks
> `BLEClientCallbacks::onDisconnect` (`NimBLEClientCallbacks::onDisconnect`)
This now takes a second parameter `int reason` which provides the reason code for disconnection.
<br/>
<a name="client-security"></a>
### Client Security
The client will automatically initiate security when the peripheral responds that it's required.
The client will automatically initiate security when the peripheral responds that it's required.
The default configuration will use "just-works" pairing with no bonding, if you wish to enable bonding see below.
<br/>
<a name="scan-api"></a>
## BLE Scan
The scan API is mostly unchanged from the original except for `NimBLEScan::start`, in which the duration parameter is now in milliseconds instead of seconds.
<br/>
<a name="security-api"></a>
## Security API
Security operations have been moved to `BLEDevice` (`NimBLEDevice`).
Also security callback methods are now incorporated in the `NimBLEServerCallbacks` / `NimBLEClientCallbacks` classes.
However backward compatibility with the original `BLESecurity` (`NimBLESecurity`) class is retained to minimize application code changes.
Security operations have been moved to `BLEDevice` (`NimBLEDevice`).
The security callback methods are now incorporated in the `NimBLEServerCallbacks` / `NimBLEClientCallbacks` classes.
The callback methods are:
> `bool onConfirmPIN(uint32_t pin)`
> `bool onConfirmPasskey(NimBLEConnInfo& connInfo, uint32_t pin)`
Receives the pin when using numeric comparison authentication, `return true;` to accept.
Receives the pin when using numeric comparison authentication.
Call `NimBLEDevice::injectConfirmPIN(connInfo, true);` to accept or `NimBLEDevice::injectConfirmPIN(connInfo, false);` to reject.
<br/>
> `uint32_t onPassKeyRequest()`
> `void onPassKeyEntry(NimBLEConnInfo& connInfo)`
For server callback; return the passkey expected from the client.
For client callback; return the passkey to send to the server.
Client callback; client should respond with the passkey (pin) by calling `NimBLEDevice::injectPassKey(connInfo, 123456);`
<br/>
> `void onAuthenticationComplete(ble_gap_conn_desc\* desc)`
> `uint32_t onPassKeyDisplay()`
Authentication complete, success or failed information is in `desc`.
Server callback; should return the passkey (pin) expected from the client.
<br/>
> `void onAuthenticationComplete(NimBLEConnInfo& connInfo)`
Authentication complete, success or failed information is available from the `NimBLEConnInfo` methods.
<br/>
Security settings and IO capabilities are now set by the following methods of NimBLEDevice.
> `NimBLEDevice::setSecurityAuth(bool bonding, bool mitm, bool sc)`
> `NimBLEDevice::setSecurityAuth(uint8_t auth_req)`
> `NimBLEDevice::setSecurityAuth(bool bonding, bool mitm, bool sc)`
> `NimBLEDevice::setSecurityAuth(uint8_t auth_req)`
Sets the authorization mode for this device.
<br/>
> `NimBLEDevice::setSecurityIOCap(uint8_t iocap)`
> `NimBLEDevice::setSecurityIOCap(uint8_t iocap)`
Sets the Input/Output capabilities of this device.
<br/>
> `NimBLEDevice::setSecurityInitKey(uint8_t init_key)`
> `NimBLEDevice::setSecurityInitKey(uint8_t init_key)`
If we are the initiator of the security procedure this sets the keys we will distribute.
<br/>
> `NimBLEDevice::setSecurityRespKey(uint8_t resp_key)`
> `NimBLEDevice::setSecurityRespKey(uint8_t resp_key)`
Sets the keys we are willing to accept from the peer during pairing.
<br/>
@ -389,11 +429,9 @@ Sets the keys we are willing to accept from the peer during pairing.
<a name="arduino-configuration"></a>
## Arduino Configuration
Unlike the original library pre-packaged in the esp32-arduino, this library has all the configuration
options that are normally set in menuconfig available in the *src/nimconfig.h* file.
Unlike the original library pre-packaged in the esp32-arduino, this library has all the configuration options that are normally set in menuconfig available in the *src/nimconfig.h* file.
This allows Arduino users to fully customize the build, such as increasing max connections
or loading the BLE stack into external PSRAM.
This allows Arduino users to fully customize the build, such as increasing max connections or loading the BLE stack into external PSRAM.
For details on the options, they are fully commented in *nimconfig.h*
<br/>

View File

@ -23,7 +23,7 @@ This can be called any time you wish to use BLE functions and does not need to b
<a name="creating-a-server"></a>
## Creating a Server
BLE servers perform 2 tasks, they advertise their existance for clients to find them and they provide services which contain information for the connecting client.
BLE servers perform 2 tasks, they advertise their existence for clients to find them and they provide services which contain information for the connecting client.
After initializing the NimBLE stack we create a server by calling `NimBLEDevice::createServer()`, this will create a server instance and return a pointer to it.
@ -91,7 +91,7 @@ void app_main(void)
}
```
All that's left to do now is start the sevice, give the characteristic a value and start advertising for clients.
All that's left to do now is start the service, give the characteristic a value and start advertising for clients.
Fist we start the service by calling `NimBLEService::start()`.
@ -146,7 +146,7 @@ After initializing the NimBLE stack we create a scan instance by calling `NimBLE
Once we have created the scan we can start looking for advertising servers.
To do this we call `NimBLEScan::start(duration)`, the duration parameter is a uint32_t that specifies the number of seconds to scan for,
To do this we call `NimBLEScan::start(duration)`, the duration parameter is a uint32_t that specifies the number of milliseconds to scan for,
passing 0 will scan forever.
In this example we will scan for 10 seconds. This is a blocking function (a non blocking overload is also available).
@ -162,7 +162,7 @@ void app_main(void)
NimBLEDevice::init("");
NimBLEScan *pScan = NimBLEDevice::getScan();
NimBLEScanResults results = pScan->start(10);
NimBLEScanResults results = pScan->getResults(10 * 1000);
}
```
<br/>
@ -214,7 +214,7 @@ for(int i = 0; i < results.getCount(); i++) {
}
}
```
As shown, the call to `NimBLEClient::connect` should have it's eturn value tested to make sure it succeeded before proceeding to get data.
As shown, the call to `NimBLEClient::connect` should have it's return value tested to make sure it succeeded before proceeding to get data.
<br/>
Next we need to access the servers data by asking it for the service and the characteristic we are interested in, then read the characteristic value.
@ -222,7 +222,7 @@ Next we need to access the servers data by asking it for the service and the cha
To do this we call `NimBLEClient::getService`, which takes as a parameter the UUID of the service and returns
a pointer an instance to `NimBLERemoteService` or `nullptr` if the service was not found.
Next we will call `NimBLERemoteService::getCharateristic` which takes as a parameter the UUID of the service and returns
Next we will call `NimBLERemoteService::getCharacteristic` which takes as a parameter the UUID of the service and returns
a pointer to an instance of `NimBLERemoteCharacteristic` or `nullptr` if not found.
Finally we will read the characteristic value with `NimBLERemoteCharacteristic::readValue()`.
@ -302,7 +302,7 @@ void app_main(void)
NimBLEDevice::init("");
NimBLEScan *pScan = NimBLEDevice::getScan();
NimBLEScanResults results = pScan->start(10);
NimBLEScanResults results = pScan->start(10 * 1000);
NimBLEUUID serviceUuid("ABCD");

View File

@ -2,7 +2,7 @@
## Put BLE functions in a task running on the NimBLE stack core
When commands are sent to the stack from a differnt core they can experience delays in execution.
When commands are sent to the stack from a different core they can experience delays in execution.
This library detects this and invokes the esp32 IPC to reroute these commands through the correct core but this also increases overhead.
Therefore it is highly recommended to create tasks for BLE to run on the same core, the macro `CONFIG_BT_NIMBLE_PINNED_TO_CORE` can be used to set the core.
<br/>
@ -13,12 +13,12 @@ When a client instance has been created and has connected to a peer device and i
If you are periodically connecting to the same devices and you have deleted the client instance or the services when connecting again it will cause a retrieval of that information from the peer again.
This results in significant energy drain on the battery of the devices, fragments heap, and reduces connection performance.
Client instances in this library use approximately 20% of the original bluedroid library, deleteing them will provide much less gain than it did before.
Client instances in this library use approximately 20% of the original bluedroid library, deleting them will provide much less gain than it did before.
It is recommended to retain the client instance in cases where the time between connecting to the same device is less than 5 minutes.
<br/>
## Only retrieve the services and characteriscs needed
## Only retrieve the services and characteristics needed
As a client the use of `NimBLEClient::getServices` or `NimBLERemoteService::getCharacteristics` and using `true` for the parameter should be limited to devices that are not known.
Instead `NimBLEClient::getService(NimBLEUUID)` or `NimBLERemoteService::getCharacteristic(NimBLEUUID)` should be used to access certain attributes that are useful to the application.

View File

@ -13,18 +13,6 @@ NimBLE is a completely open source Bluetooth Low Energy stack produced by [Apach
It is more suited to resource constrained devices than bluedroid and has now been ported to the ESP32 by Espressif.
<br/>
# Arduino Installation
**Arduino Library manager:** Go to `sketch` -> `Include Library` -> `Manage Libraries` and search for NimBLE and install.
**Alternatively:** Download as .zip and extract to Arduino/libraries folder, or in Arduino IDE from Sketch menu -> Include library -> Add .Zip library.
`#include "NimBLEDevice.h"` at the beginning of your sketch.
Call `NimBLEDevice::init` in `setup`.
Tested and working with esp32-arduino in Arduino IDE and platform IO.
<br/>
# ESP-IDF Installation
### v4.0+
Download as .zip and extract or clone into the components folder in your esp-idf project.
@ -57,21 +45,6 @@ Also see [Improvements and updates](Improvements_and_updates.md) for information
For more advanced usage see [Usage tips](Usage_tips.md) for more performance and optimization.
<br/>
### Arduino specific
See the Refactored_original_examples in the examples folder for highlights of the differences with the original library.
More advanced examples highlighting many available features are in examples/NimBLE_Server, NimBLE_Client.
Beacon examples provided by [beegee-tokyo](https://github.com/beegee-tokyo) are in examples/BLE_Beacon_Scanner, BLE_EddystoneTLM_Beacon, BLE_EddystoneURL_Beacon.
Change the settings in the nimconfig.h file to customize NimBLE to your project, such as increasing max connections (default is 3).
<br/>
### Arduino command line and platformio
As an alternative to changing the configuration in nimconfig.h, Arduino command line and platformio.ini options are available.
See the command line configuration options available in [Command line config](Command_line_config.md).
<br/>
# Need help? Have a question or suggestion?
Come chat on [gitter](https://gitter.im/NimBLE-Arduino/community?utm_source=share-link&utm_medium=link&utm_campaign=share-link) or open an issue at [NimBLE-Arduino](https://github.com/h2zero/NimBLE-Arduino/issues) or [esp-nimble-cpp](https://github.com/h2zero/esp-nimble-cpp/issues)
<br/>

View File

@ -1,72 +1,72 @@
/** NimBLE_Server Demo:
/** NimBLE_Client Demo:
*
* Demonstrates many of the available features of the NimBLE client library.
*
*
* Created: on March 24 2020
* Author: H2zero
*
*
*/
#include <NimBLEDevice.h>
extern "C" {void app_main(void);}
void scanEndedCB(NimBLEScanResults results);
static NimBLEAdvertisedDevice* advDevice;
static bool doConnect = false;
static uint32_t scanTime = 0; /** 0 = scan forever */
static uint32_t scanTime = 0; /** scan time in milliseconds, 0 = scan forever */
/** None of these are required as they will be handled by the library with defaults. **
** Remove as you see fit for your needs */
** Remove as you see fit for your needs */
class ClientCallbacks : public NimBLEClientCallbacks {
void onConnect(NimBLEClient* pClient) {
printf("Connected\n");
/** After connection we should change the parameters if we don't need fast response times.
* These settings are 150ms interval, 0 latency, 450ms timout.
* These settings are 150ms interval, 0 latency, 450ms timout.
* Timeout should be a multiple of the interval, minimum is 100ms.
* I find a multiple of 3-5 * the interval works best for quick response/reconnect.
* Min interval: 120 * 1.25ms = 150, Max interval: 120 * 1.25ms = 150, 0 latency, 45 * 10ms = 450ms timeout
* Min interval: 120 * 1.25ms = 150, Max interval: 120 * 1.25ms = 150, 0 latency, 45 * 10ms = 450ms timeout
*/
pClient->updateConnParams(120,120,0,45);
};
}
void onDisconnect(NimBLEClient* pClient, int reason) {
printf("%s Disconnected, reason = %d - Starting scan\n",
pClient->getPeerAddress().toString().c_str(), reason);
NimBLEDevice::getScan()->start(scanTime);
}
void onDisconnect(NimBLEClient* pClient) {
printf("%s Disconnected - Starting scan\n", pClient->getPeerAddress().toString().c_str());
NimBLEDevice::getScan()->start(scanTime, scanEndedCB);
};
/********************* Security handled here **********************
****** Note: these are the same return values as defaults ********/
uint32_t onPassKeyRequest(){
printf("Client Passkey Request\n");
/** return the passkey to send to the server */
return 123456;
void onPassKeyEntry(NimBLEConnInfo& connInfo){
printf("Server Passkey Entry\n");
/** This should prompt the user to enter the passkey displayed
* on the peer device.
*/
NimBLEDevice::injectPassKey(connInfo, 123456);
};
bool onConfirmPIN(uint32_t pass_key){
printf("The passkey YES/NO number: %d\n", pass_key);
/** Return false if passkeys don't match. */
return true;
void onConfirmPasskey(NimBLEConnInfo& connInfo, uint32_t pass_key){
printf("The passkey YES/NO number: %" PRIu32 "\n", pass_key);
/** Inject false if passkeys don't match. */
NimBLEDevice::injectConfirmPasskey(connInfo, true);
};
/** Pairing process complete, we can check the results in ble_gap_conn_desc */
void onAuthenticationComplete(ble_gap_conn_desc* desc){
if(!desc->sec_state.encrypted) {
/** Pairing process complete, we can check the results in connInfo */
void onAuthenticationComplete(NimBLEConnInfo& connInfo){
if(!connInfo.isEncrypted()) {
printf("Encrypt connection failed - disconnecting\n");
/** Find the client with the connection handle provided in desc */
NimBLEDevice::getClientByID(desc->conn_handle)->disconnect();
NimBLEDevice::getClientByHandle(connInfo.getConnHandle())->disconnect();
return;
}
};
}
};
/** Define a class to handle the callbacks when advertisments are received */
class AdvertisedDeviceCallbacks: public NimBLEAdvertisedDeviceCallbacks {
class scanCallbacks: public NimBLEScanCallbacks {
void onResult(NimBLEAdvertisedDevice* advertisedDevice) {
printf("Advertised Device found: %s\n", advertisedDevice->toString().c_str());
if(advertisedDevice->isAdvertisingService(NimBLEUUID("DEAD")))
@ -74,31 +74,31 @@ class AdvertisedDeviceCallbacks: public NimBLEAdvertisedDeviceCallbacks {
printf("Found Our Service\n");
/** stop scan before connecting */
NimBLEDevice::getScan()->stop();
/** Save the device reference in a global for the client to use*/
/** Save the device reference in a global for the client to use*/
advDevice = advertisedDevice;
/** Ready to connect now */
/** Ready to connect now */
doConnect = true;
}
};
}
/** Callback to process the results of the completed scan or restart it */
void onScanEnd(NimBLEScanResults results) {
printf("Scan Ended\n");
}
};
/** Notification / Indication receiving handler callback */
void notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify){
std::string str = (isNotify == true) ? "Notification" : "Indication";
std::string str = (isNotify == true) ? "Notification" : "Indication";
str += " from ";
str += pRemoteCharacteristic->getRemoteService()->getClient()->getPeerAddress().toString();
str += pRemoteCharacteristic->getClient()->getPeerAddress().toString();
str += ": Service = " + pRemoteCharacteristic->getRemoteService()->getUUID().toString();
str += ", Characteristic = " + pRemoteCharacteristic->getUUID().toString();
str += ", Value = " + std::string((char*)pData, length);
printf("%s\n", str.c_str());
}
/** Callback to process the results of the last scan or restart it */
void scanEndedCB(NimBLEScanResults results){
printf("Scan Ended\n");
}
/** Create a single global instance of the callback class to be used by all clients */
static ClientCallbacks clientCB;
@ -107,10 +107,10 @@ static ClientCallbacks clientCB;
/** Handles the provisioning of clients and connects / interfaces with the server */
bool connectToServer() {
NimBLEClient* pClient = nullptr;
/** Check if we have a client we should reuse first **/
if(NimBLEDevice::getClientListSize()) {
/** Special case when we already know this device, we send false as the
if(NimBLEDevice::getCreatedClientCount()) {
/** Special case when we already know this device, we send false as the
* second argument in connect() to prevent refreshing the service database.
* This saves considerable time and power.
*/
@ -121,7 +121,7 @@ bool connectToServer() {
return false;
}
printf("Reconnected client\n");
}
}
/** We don't already have a client that knows this device,
* we will check for a client that is disconnected that we can use.
*/
@ -129,28 +129,28 @@ bool connectToServer() {
pClient = NimBLEDevice::getDisconnectedClient();
}
}
/** No client to reuse? Create a new one. */
if(!pClient) {
if(NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) {
if(NimBLEDevice::getCreatedClientCount() >= NIMBLE_MAX_CONNECTIONS) {
printf("Max clients reached - no more connections available\n");
return false;
}
pClient = NimBLEDevice::createClient();
printf("New client created\n");
pClient->setClientCallbacks(&clientCB, false);
/** Set initial connection parameters: These settings are 15ms interval, 0 latency, 120ms timout.
* These settings are safe for 3 clients to connect reliably, can go faster if you have less
/** Set initial connection parameters: These settings are 15ms interval, 0 latency, 120ms timout.
* These settings are safe for 3 clients to connect reliably, can go faster if you have less
* connections. Timeout should be a multiple of the interval, minimum is 100ms.
* Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 12 * 10ms = 120ms timeout
* Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 12 * 10ms = 120ms timeout
*/
pClient->setConnectionParams(6,6,0,15);
/** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */
pClient->setConnectTimeout(5);
/** Set how long we are willing to wait for the connection to complete (milliseconds), default is 30000. */
pClient->setConnectTimeout(5 * 1000);
if (!pClient->connect(advDevice)) {
/** Created a client but failed to connect, don't need to keep it as it has no data */
@ -158,24 +158,24 @@ bool connectToServer() {
printf("Failed to connect, deleted client\n");
return false;
}
}
}
if(!pClient->isConnected()) {
if (!pClient->connect(advDevice)) {
printf("Failed to connect\n");
return false;
}
}
printf("Connected to: %s RSSI: %d\n",
printf("Connected to: %s RSSI: %d\n",
pClient->getPeerAddress().toString().c_str(),
pClient->getRssi());
/** Now we can read/write/subscribe the charateristics of the services we are interested in */
NimBLERemoteService* pSvc = nullptr;
NimBLERemoteCharacteristic* pChr = nullptr;
NimBLERemoteDescriptor* pDsc = nullptr;
pSvc = pClient->getService("DEAD");
if(pSvc) { /** make sure it's not null */
pChr = pSvc->getCharacteristic("BEEF");
@ -187,32 +187,32 @@ bool connectToServer() {
pChr->getUUID().toString().c_str(),
pChr->readValue().c_str());
}
if(pChr->canWrite()) {
if(pChr->writeValue("Tasty")) {
printf("Wrote new value to: %s\n", pChr->getUUID().toString().c_str());
}
else {
/** Disconnect if write failed */
/** Disconnect if write failed */
pClient->disconnect();
return false;
}
if(pChr->canRead()) {
printf("The value of: %s is now: %s\n",
pChr->getUUID().toString().c_str(),
pChr->readValue().c_str());
}
}
/** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe().
* Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false.
* Unsubscribe parameter defaults are: response=false.
/** registerForNotify() has been removed and replaced with subscribe() / unsubscribe().
* Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=true.
* Unsubscribe parameter defaults are: response=true.
*/
if(pChr->canNotify()) {
//if(!pChr->registerForNotify(notifyCB)) {
if(!pChr->subscribe(true, notifyCB)) {
/** Disconnect if subscribe failed */
/** Disconnect if subscribe failed */
pClient->disconnect();
return false;
}
@ -221,17 +221,17 @@ bool connectToServer() {
/** Send false as first argument to subscribe to indications instead of notifications */
//if(!pChr->registerForNotify(notifyCB, false)) {
if(!pChr->subscribe(false, notifyCB)) {
/** Disconnect if subscribe failed */
/** Disconnect if subscribe failed */
pClient->disconnect();
return false;
}
}
}
else{
printf("DEAD service not found.\n");
}
pSvc = pClient->getService("BAAD");
if(pSvc) { /** make sure it's not null */
pChr = pSvc->getCharacteristic("F00D");
@ -243,39 +243,39 @@ bool connectToServer() {
pChr->getUUID().toString().c_str(),
pChr->readValue().c_str());
}
pDsc = pChr->getDescriptor(NimBLEUUID("C01D"));
if(pDsc) { /** make sure it's not null */
printf("Descriptor: %s Value: %s\n",
pDsc->getUUID().toString().c_str(),
pDsc->readValue().c_str());
}
if(pChr->canWrite()) {
if(pChr->writeValue("No tip!")) {
printf("Wrote new value to: %s\n", pChr->getUUID().toString().c_str());
}
else {
/** Disconnect if write failed */
/** Disconnect if write failed */
pClient->disconnect();
return false;
}
if(pChr->canRead()) {
printf("The value of: %s is now: %s\n",
pChr->getUUID().toString().c_str(),
pChr->readValue().c_str());
}
}
/** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe().
* Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false.
* Unsubscribe parameter defaults are: response=false.
* Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=true.
* Unsubscribe parameter defaults are: response=true.
*/
if(pChr->canNotify()) {
//if(!pChr->registerForNotify(notifyCB)) {
if(!pChr->subscribe(true, notifyCB)) {
/** Disconnect if subscribe failed */
/** Disconnect if subscribe failed */
pClient->disconnect();
return false;
}
@ -284,17 +284,17 @@ bool connectToServer() {
/** Send false as first argument to subscribe to indications instead of notifications */
//if(!pChr->registerForNotify(notifyCB, false)) {
if(!pChr->subscribe(false, notifyCB)) {
/** Disconnect if subscribe failed */
/** Disconnect if subscribe failed */
pClient->disconnect();
return false;
}
}
}
}
}
else{
printf("BAAD service not found.\n");
}
printf("Done with this device!\n");
return true;
}
@ -310,12 +310,12 @@ void connectTask (void * parameter){
} else {
printf("Failed to connect, starting scan\n");
}
NimBLEDevice::getScan()->start(scanTime,scanEndedCB);
NimBLEDevice::getScan()->start(scanTime);
}
vTaskDelay(10/portTICK_PERIOD_MS);
}
vTaskDelete(NULL);
}
@ -323,7 +323,7 @@ void app_main (void){
printf("Starting NimBLE Client\n");
/** Initialize NimBLE, no device name spcified as we are not advertising */
NimBLEDevice::init("");
/** Set the IO capabilities of the device, each option will trigger a different pairing method.
* BLE_HS_IO_KEYBOARD_ONLY - Passkey pairing
* BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing
@ -331,42 +331,42 @@ void app_main (void){
*/
//NimBLEDevice::setSecurityIOCap(BLE_HS_IO_KEYBOARD_ONLY); // use passkey
//NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_YESNO); //use numeric comparison
/** 2 different ways to set security - both calls achieve the same result.
* no bonding, no man in the middle protection, secure connections.
*
* These are the default values, only shown here for demonstration.
*/
//NimBLEDevice::setSecurityAuth(false, false, true);
*
* These are the default values, only shown here for demonstration.
*/
//NimBLEDevice::setSecurityAuth(false, false, true);
NimBLEDevice::setSecurityAuth(/*BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM |*/ BLE_SM_PAIR_AUTHREQ_SC);
/** Optional: set the transmit power, default is -3db */
NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** 12db */
/** Optional: set any devices you don't want to get advertisments from */
// NimBLEDevice::addIgnored(NimBLEAddress ("aa:bb:cc:dd:ee:ff"));
/** create new scan */
NimBLEScan* pScan = NimBLEDevice::getScan();
// NimBLEDevice::addIgnored(NimBLEAddress ("aa:bb:cc:dd:ee:ff"));
/** create new scan */
NimBLEScan* pScan = NimBLEDevice::getScan();
/** create a callback that gets called when advertisers are found */
pScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks());
pScan->setScanCallbacks (new scanCallbacks());
/** Set scan interval (how often) and window (how long) in milliseconds */
pScan->setInterval(400);
pScan->setWindow(100);
/** Active scan will gather scan response data from advertisers
* but will use more energy from both devices
*/
pScan->setActiveScan(true);
/** Start scanning for advertisers for the scan time specified (in seconds) 0 = forever
* Optional callback for when scanning stops.
/** Start scanning for advertisers for the scan time specified (in milliseconds) 0 = forever
* Optional callback for when scanning stops.
*/
pScan->start(scanTime, scanEndedCB);
pScan->start(scanTime);
printf("Scanning for peripherals\n");
xTaskCreate(connectTask, "connectTask", 5000, NULL, 1, NULL);
}

View File

@ -0,0 +1,12 @@
# Override some defaults so BT stack is enabled
# in this example
#
# BT config
#
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y

View File

@ -2,10 +2,10 @@
/** NimBLE_Server Demo:
*
* Demonstrates many of the available features of the NimBLE server library.
*
*
* Created: on March 22 2020
* Author: H2zero
*
*
*/
#include "NimBLEDevice.h"
#include "NimBLELog.h"
@ -17,55 +17,51 @@ extern "C" {void app_main(void);}
static NimBLEServer* pServer;
/** None of these are required as they will be handled by the library with defaults. **
** Remove as you see fit for your needs */
** Remove as you see fit for your needs */
class ServerCallbacks: public NimBLEServerCallbacks {
void onConnect(NimBLEServer* pServer) {
printf("Client connected\n");
NimBLEDevice::startAdvertising();
};
/** Alternative onConnect() method to extract details of the connection.
* See: src/ble_gap.h for the details of the ble_gap_conn_desc struct.
*/
void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) {
printf("Client address: %s\n", NimBLEAddress(desc->peer_ota_addr).toString().c_str());
void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) {
printf("Client address: %s\n", connInfo.getAddress().toString().c_str());
/** We can use the connection handle here to ask for different connection parameters.
* Args: connection handle, min connection interval, max connection interval
* latency, supervision timeout.
* Units; Min/Max Intervals: 1.25 millisecond increments.
* Latency: number of intervals allowed to skip.
* Timeout: 10 millisecond increments, try for 3x interval time for best results.
* Timeout: 10 millisecond increments, try for 3x interval time for best results.
*/
pServer->updateConnParams(desc->conn_handle, 24, 48, 0, 18);
pServer->updateConnParams(connInfo.getConnHandle(), 24, 48, 0, 18);
};
void onDisconnect(NimBLEServer* pServer) {
void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) {
printf("Client disconnected - start advertising\n");
NimBLEDevice::startAdvertising();
};
void onMTUChange(uint16_t MTU, ble_gap_conn_desc* desc) {
printf("MTU updated: %u for connection ID: %u\n", MTU, desc->conn_handle);
void onMTUChange(uint16_t MTU, NimBLEConnInfo& connInfo) {
printf("MTU updated: %u for connection ID: %u\n", MTU, connInfo.getConnHandle());
pServer->updateConnParams(connInfo.getConnHandle(), 24, 48, 0, 60);
};
/********************* Security handled here **********************
****** Note: these are the same return values as defaults ********/
uint32_t onPassKeyRequest(){
printf("Server Passkey Request\n");
/** This should return a random 6 digit number for security
uint32_t onPassKeyDisplay(){
printf("Server Passkey Display\n");
/** This should return a random 6 digit number for security
* or make your own static passkey as done here.
*/
return 123456;
return 123456;
};
bool onConfirmPIN(uint32_t pass_key){
printf("The passkey YES/NO number: %d\n", pass_key);
/** Return false if passkeys don't match. */
return true;
void onConfirmasskeyN(NimBLEConnInfo& connInfo, uint32_t pass_key){
printf("The passkey YES/NO number: %" PRIu32 "\n", pass_key);
/** Inject false if passkeys don't match. */
NimBLEDevice::injectConfirmPasskey(connInfo, true);
};
void onAuthenticationComplete(ble_gap_conn_desc* desc){
/** Check that encryption was successful, if not we disconnect the client */
if(!desc->sec_state.encrypted) {
/** NOTE: createServer returns the current server reference unless one is not already created */
NimBLEDevice::createServer()->disconnect(desc->conn_handle);
void onAuthenticationComplete(NimBLEConnInfo& connInfo){
/** Check that encryption was successful, if not we disconnect the client */
if(!connInfo.isEncrypted()) {
NimBLEDevice::getServer()->disconnect(connInfo.getConnHandle());
printf("Encrypt connection failed - disconnecting client\n");
return;
}
@ -75,50 +71,67 @@ class ServerCallbacks: public NimBLEServerCallbacks {
/** Handler class for characteristic actions */
class CharacteristicCallbacks: public NimBLECharacteristicCallbacks {
void onRead(NimBLECharacteristic* pCharacteristic){
printf("%s : onRead(), value: %s\n",
pCharacteristic->getUUID().toString().c_str(),
pCharacteristic->getValue().c_str());
};
void onRead(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) {
printf("%s : onRead(), value: %s\n",
pCharacteristic->getUUID().toString().c_str(),
pCharacteristic->getValue().c_str());
}
void onWrite(NimBLECharacteristic* pCharacteristic) {
printf("%s : onWrite(), value: %s\n",
pCharacteristic->getUUID().toString().c_str(),
pCharacteristic->getValue().c_str());
};
/** Called before notification or indication is sent,
void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) {
printf("%s : onWrite(), value: %s\n",
pCharacteristic->getUUID().toString().c_str(),
pCharacteristic->getValue().c_str());
}
/** Called before notification or indication is sent,
* the value can be changed here before sending if desired.
*/
void onNotify(NimBLECharacteristic* pCharacteristic) {
printf("Sending notification to clients\n");
};
}
/** The status returned in status is defined in NimBLECharacteristic.h.
/**
* The value returned in code is the NimBLE host return code.
*/
void onStatus(NimBLECharacteristic* pCharacteristic, Status status, int code) {
printf("Notification/Indication status code: %d , return code: %d, %s\n",
status,
code,
NimBLEUtils::returnCodeToString(code));
};
void onStatus(NimBLECharacteristic* pCharacteristic, int code) {
printf("Notification/Indication return code: %d, %s\n",
code, NimBLEUtils::returnCodeToString(code));
}
void onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue) {
std::string str = "Client ID: ";
str += connInfo.getConnHandle();
str += " Address: ";
str += connInfo.getAddress().toString();
if(subValue == 0) {
str += " Unsubscribed to ";
}else if(subValue == 1) {
str += " Subscribed to notfications for ";
} else if(subValue == 2) {
str += " Subscribed to indications for ";
} else if(subValue == 3) {
str += " Subscribed to notifications and indications for ";
}
str += std::string(pCharacteristic->getUUID());
printf("%s\n", str.c_str());
}
};
/** Handler class for descriptor actions */
/** Handler class for descriptor actions */
class DescriptorCallbacks : public NimBLEDescriptorCallbacks {
void onWrite(NimBLEDescriptor* pDescriptor) {
std::string dscVal((char*)pDescriptor->getValue(), pDescriptor->getLength());
void onWrite(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo) {
std::string dscVal = pDescriptor->getValue();
printf("Descriptor witten value: %s\n", dscVal.c_str());
};
void onRead(NimBLEDescriptor* pDescriptor) {
void onRead(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo) {
printf("%s Descriptor read\n", pDescriptor->getUUID().toString().c_str());
};;
};
/** Define callback instances globally to use for multiple Charateristics \ Descriptors */
/** Define callback instances globally to use for multiple Charateristics \ Descriptors */
static DescriptorCallbacks dscCallbacks;
static CharacteristicCallbacks chrCallbacks;
@ -129,13 +142,13 @@ void notifyTask(void * parameter){
if(pSvc) {
NimBLECharacteristic* pChr = pSvc->getCharacteristic("F00D");
if(pChr) {
pChr->notify(true);
pChr->notify();
}
}
}
vTaskDelay(2000/portTICK_PERIOD_MS);
}
vTaskDelete(NULL);
}
@ -155,10 +168,10 @@ void app_main(void) {
/** 2 different ways to set security - both calls achieve the same result.
* no bonding, no man in the middle protection, secure connections.
*
* These are the default values, only shown here for demonstration.
*/
//NimBLEDevice::setSecurityAuth(false, false, true);
*
* These are the default values, only shown here for demonstration.
*/
//NimBLEDevice::setSecurityAuth(false, false, true);
NimBLEDevice::setSecurityAuth(/*BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM |*/ BLE_SM_PAIR_AUTHREQ_SC);
pServer = NimBLEDevice::createServer();
@ -173,7 +186,7 @@ void app_main(void) {
NIMBLE_PROPERTY::READ_ENC | // only allow reading if paired / encrypted
NIMBLE_PROPERTY::WRITE_ENC // only allow writing if paired / encrypted
);
pBeefCharacteristic->setValue("Burger");
pBeefCharacteristic->setCallbacks(&chrCallbacks);
@ -182,10 +195,10 @@ void app_main(void) {
* and sizes. However we must cast the returned reference to the correct type as the method
* only returns a pointer to the base NimBLEDescriptor class.
*/
NimBLE2904* pBeef2904 = (NimBLE2904*)pBeefCharacteristic->createDescriptor("2904");
NimBLE2904* pBeef2904 = (NimBLE2904*)pBeefCharacteristic->createDescriptor("2904");
pBeef2904->setFormat(NimBLE2904::FORMAT_UTF8);
pBeef2904->setCallbacks(&dscCallbacks);
NimBLEService* pBaadService = pServer->createService("BAAD");
NimBLECharacteristic* pFoodCharacteristic = pBaadService->createCharacteristic(
@ -201,7 +214,7 @@ void app_main(void) {
/** Custom descriptor: Arguments are UUID, Properties, max length in bytes of the value */
NimBLEDescriptor* pC01Ddsc = pFoodCharacteristic->createDescriptor(
"C01D",
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE|
NIMBLE_PROPERTY::WRITE_ENC, // only allow writing if paired / encrypted
20
@ -209,7 +222,7 @@ void app_main(void) {
pC01Ddsc->setValue("Send it back!");
pC01Ddsc->setCallbacks(&dscCallbacks);
/** Start the services when finished creating all Characteristics and Descriptors */
/** Start the services when finished creating all Characteristics and Descriptors */
pDeadService->start();
pBaadService->start();
@ -224,6 +237,6 @@ void app_main(void) {
pAdvertising->start();
printf("Advertising Started\n");
xTaskCreate(notifyTask, "notifyTask", 5000, NULL, 1, NULL);
}

View File

@ -0,0 +1,12 @@
# Override some defaults so BT stack is enabled
# in this example
#
# BT config
#
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y

View File

@ -11,14 +11,12 @@
extern "C" void app_main(void);
void scanEndedCB(NimBLEScanResults results);
#define SERVICE_UUID "ABCD"
#define CHARACTERISTIC_UUID "1234"
static NimBLEAdvertisedDevice* advDevice;
static bool doConnect = false;
static uint32_t scanTime = 10; /* 0 = scan forever */
static uint32_t scanTime = 10 * 1000; // In milliseconds, 0 = scan forever
/* Define the PHY's to use when connecting to peer devices, can be 1, 2, or all 3 (default).*/
static uint8_t connectPhys = BLE_GAP_LE_PHY_CODED_MASK | BLE_GAP_LE_PHY_1M_MASK /*| BLE_GAP_LE_PHY_2M_MASK */ ;
@ -29,16 +27,16 @@ class ClientCallbacks : public NimBLEClientCallbacks {
printf("Connected\n");
};
void onDisconnect(NimBLEClient* pClient) {
printf("%s Disconnected - Starting scan\n", pClient->getPeerAddress().toString().c_str());
NimBLEDevice::getScan()->start(scanTime, scanEndedCB);
void onDisconnect(NimBLEClient* pClient, int reason) {
printf("%s Disconnected, reason = %d - Starting scan\n",
pClient->getPeerAddress().toString().c_str(), reason);
NimBLEDevice::getScan()->start(scanTime);
};
};
/* Define a class to handle the callbacks when advertisements are received */
class AdvertisedDeviceCallbacks: public NimBLEAdvertisedDeviceCallbacks {
class scanCallbacks: public NimBLEScanCallbacks {
void onResult(NimBLEAdvertisedDevice* advertisedDevice) {
printf("Advertised Device found: %s\n", advertisedDevice->toString().c_str());
if(advertisedDevice->isAdvertisingService(NimBLEUUID("ABCD")))
@ -51,17 +49,13 @@ class AdvertisedDeviceCallbacks: public NimBLEAdvertisedDeviceCallbacks {
/* stop scan before connecting */
NimBLEDevice::getScan()->stop();
}
};
};
/* Callback to process the results of the last scan or restart it */
void scanEndedCB(NimBLEScanResults results){
printf("Scan Ended\n");
if (!doConnect) { /* Don't start the scan while connecting */
NimBLEDevice::getScan()->start(scanTime, scanEndedCB);
}
}
/** Callback to process the results of the completed scan or restart it */
void onScanEnd(NimBLEScanResults results) {
printf("Scan Ended\n");
}
};
/* Handles the provisioning of clients and connects / interfaces with the server */
@ -79,8 +73,8 @@ bool connectToServer() {
*/
pClient->setConnectPhy(connectPhys);
/** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */
pClient->setConnectTimeout(10);
/** Set how long we are willing to wait for the connection to complete (milliseconds), default is 30000. */
pClient->setConnectTimeout(10 * 1000);
if (!pClient->connect(advDevice)) {
/* Created a client but failed to connect, don't need to keep it as it has no data */
@ -131,7 +125,7 @@ void connectTask (void * parameter){
}
doConnect = false;
NimBLEDevice::getScan()->start(scanTime, scanEndedCB);
NimBLEDevice::getScan()->start(scanTime);
}
vTaskDelay(pdMS_TO_TICKS(10));
}
@ -149,7 +143,7 @@ void app_main (void) {
NimBLEScan* pScan = NimBLEDevice::getScan();
/* create a callback that gets called when advertisers are found */
pScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks());
pScan->setScanCallbacks(new scanCallbacks());
/* Set scan interval (how often) and window (how long) in milliseconds */
pScan->setInterval(97);
@ -160,10 +154,10 @@ void app_main (void) {
*/
pScan->setActiveScan(true);
/* Start scanning for advertisers for the scan time specified (in seconds) 0 = forever
/* Start scanning for advertisers for the scan time specified (in milliseconds) 0 = forever
* Optional callback for when scanning stops.
*/
pScan->start(scanTime, scanEndedCB);
pScan->start(scanTime);
printf("Scanning for peripherals\n");
}

View File

@ -0,0 +1,13 @@
# Override some defaults so BT stack is enabled
# in this example
#
# BT config
#
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_EXT_ADV=y

View File

@ -37,12 +37,12 @@ static uint8_t secondaryPhy = BLE_HCI_LE_PHY_1M;
/* Handler class for server events */
class ServerCallbacks: public NimBLEServerCallbacks {
void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) {
printf("Client connected: %s\n", NimBLEAddress(desc->peer_ota_addr).toString().c_str());
void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) {
printf("Client connected:: %s\n", connInfo.getAddress().toString().c_str());
};
void onDisconnect(NimBLEServer* pServer) {
printf("Client disconnected - sleeping for %u seconds\n", sleepSeconds);
void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) {
printf("Client disconnected - sleeping for %" PRIu32" seconds\n", sleepSeconds);
esp_deep_sleep_start();
};
};
@ -57,7 +57,7 @@ class advertisingCallbacks: public NimBLEExtAdvertisingCallbacks {
printf("Client connecting\n");
return;
case BLE_HS_ETIMEOUT:
printf("Time expired - sleeping for %u seconds\n", sleepSeconds);
printf("Time expired - sleeping for %" PRIu32" seconds\n", sleepSeconds);
break;
default:
break;

View File

@ -0,0 +1,13 @@
# Override some defaults so BT stack is enabled
# in this example
#
# BT config
#
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_EXT_ADV=y

View File

@ -37,15 +37,15 @@ static uint8_t secondaryPhy = BLE_HCI_LE_PHY_1M;
/* Handler class for server events */
class ServerCallbacks: public NimBLEServerCallbacks {
void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) {
printf("Client connected: %s\n", NimBLEAddress(desc->peer_ota_addr).toString().c_str());
void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) {
printf("Client connected: %s\n", connInfo.getAddress().toString().c_str());
};
void onDisconnect(NimBLEServer* pServer) {
void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) {
printf("Client disconnected\n");
// if still advertising we won't sleep yet.
if (!pServer->getAdvertising()->isAdvertising()) {
printf("Sleeping for %u seconds\n", sleepTime);
printf("Sleeping for %" PRIu32" seconds\n", sleepTime);
esp_deep_sleep_start();
}
};
@ -61,7 +61,7 @@ class advCallbacks: public NimBLEExtAdvertisingCallbacks {
printf(" client connecting\n");
return;
case BLE_HS_ETIMEOUT:
printf("Time expired - sleeping for %u seconds\n", sleepTime);
printf("Time expired - sleeping for %" PRIu32" seconds\n", sleepTime);
break;
default:
break;

View File

@ -0,0 +1,13 @@
# Override some defaults so BT stack is enabled
# in this example
#
# BT config
#
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_EXT_ADV=y

View File

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(NimBLE_Async_Client)

View File

@ -0,0 +1,3 @@
PROJECT_NAME := NimBLE_Async_Client
include $(IDF_PATH)/make/project.mk

View File

@ -0,0 +1,4 @@
set(COMPONENT_SRCS "main.cpp")
set(COMPONENT_ADD_INCLUDEDIRS ".")
register_component()

View File

@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@ -0,0 +1,83 @@
/**
* NimBLE_Async_client Demo:
*
* Demonstrates asynchronous client operations.
*
* Created: on November 4, 2024
* Author: H2zero
*
*/
#include <NimBLEDevice.h>
static constexpr uint32_t scanTimeMs = 5 * 1000;
class ClientCallbacks : public NimBLEClientCallbacks {
void onConnect(NimBLEClient* pClient) {
printf("Connected to: %s\n", pClient->getPeerAddress().toString().c_str());
}
void onDisconnect(NimBLEClient* pClient, int reason) {
printf("%s Disconnected, reason = %d - Starting scan\n", pClient->getPeerAddress().toString().c_str(), reason);
NimBLEDevice::getScan()->start(scanTimeMs);
}
} clientCB;
class scanCallbacks : public NimBLEScanCallbacks {
void onResult(NimBLEAdvertisedDevice* advertisedDevice) {
printf("Advertised Device found: %s\n", advertisedDevice->toString().c_str());
if (advertisedDevice->haveName() && advertisedDevice->getName() == "NimBLE-Server") {
printf("Found Our Device\n");
auto pClient = NimBLEDevice::getDisconnectedClient();
if (!pClient) {
pClient = NimBLEDevice::createClient(advertisedDevice->getAddress());
if (!pClient) {
printf("Failed to create client\n");
return;
}
}
pClient->setClientCallbacks(&clientCB, false);
if (!pClient->connect(true, true, false)) { // delete attributes, async connect, no MTU exchange
NimBLEDevice::deleteClient(pClient);
printf("Failed to connect\n");
return;
}
}
}
void onScanEnd(NimBLEScanResults results) {
printf("Scan Ended\n");
NimBLEDevice::getScan()->start(scanTimeMs);
}
};
extern "C" void app_main(void) {
printf("Starting NimBLE Async Client\n");
NimBLEDevice::init("");
NimBLEDevice::setPower(3); /** +3db */
NimBLEScan* pScan = NimBLEDevice::getScan();
pScan->setScanCallbacks(new scanCallbacks());
pScan->setInterval(45);
pScan->setWindow(15);
pScan->setActiveScan(true);
pScan->start(scanTimeMs);
for (;;) {
vTaskDelay(pdMS_TO_TICKS(1000));
auto pClients = NimBLEDevice::getConnectedClients();
if (!pClients.size()) {
continue;
}
for (auto& pClient : pClients) {
printf("%s\n", pClient->toString().c_str());
NimBLEDevice::deleteClient(pClient);
}
NimBLEDevice::getScan()->start(scanTimeMs);
}
}

View File

@ -0,0 +1,12 @@
# Override some defaults so BT stack is enabled
# in this example
#
# BT config
#
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y

View File

@ -0,0 +1,7 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(SUPPORTED_TARGETS esp32)
project(NimBLE_server_get_client_name)

View File

@ -0,0 +1,4 @@
set(COMPONENT_SRCS "main.cpp")
set(COMPONENT_ADD_INCLUDEDIRS ".")
register_component()

View File

@ -0,0 +1,83 @@
/** NimBLE_server_get_client_name
*
* Demonstrates 2 ways for the server to read the device name from the connected client.
*
* Created: on June 24 2024
* Author: H2zero
*
*/
#include <NimBLEDevice.h>
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
#define ENC_CHARACTERISTIC_UUID "9551f35b-8d91-42e4-8f7e-1358dfe272dc"
NimBLEServer* pServer;
class ServerCallbacks : public NimBLEServerCallbacks {
// Same as before but now includes the name parameter
void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, std::string& name) override {
printf("Client address: %s Name: %s\n", connInfo.getAddress().toString().c_str(), name.c_str());
}
// Same as before but now includes the name parameter
void onAuthenticationComplete(NimBLEConnInfo& connInfo, const std::string& name) override {
if (!connInfo.isEncrypted()) {
NimBLEDevice::getServer()->disconnect(connInfo.getConnHandle());
printf("Encrypt connection failed - disconnecting client\n");
return;
}
printf("Encrypted Client address: %s Name: %s\n", connInfo.getAddress().toString().c_str(), name.c_str());
}
};
extern "C" void app_main(void) {
printf("Starting BLE Server!\n");
NimBLEDevice::init("Connect to me!");
NimBLEDevice::setSecurityAuth(true, false, true); // Enable bonding to see full name on phones.
pServer = NimBLEDevice::createServer();
NimBLEService* pService = pServer->createService(SERVICE_UUID);
NimBLECharacteristic* pCharacteristic =
pService->createCharacteristic(CHARACTERISTIC_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE);
pCharacteristic->setValue("Hello World says NimBLE!");
NimBLECharacteristic* pEncCharacteristic = pService->createCharacteristic(
ENC_CHARACTERISTIC_UUID,
(NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::WRITE_ENC));
pEncCharacteristic->setValue("Hello World says NimBLE Encrypted");
pService->start();
pServer->setCallbacks(new ServerCallbacks());
pServer->getPeerNameOnConnect(true); // Setting this will enable the onConnect callback that provides the name.
BLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->start();
printf("Advertising started, connect with your phone.\n");
while (true) {
auto clientCount = pServer->getConnectedCount();
if (clientCount) {
printf("Connected clients:\n");
for (auto i = 0; i < clientCount; ++i) {
NimBLEConnInfo peerInfo = pServer->getPeerInfo(i);
printf("Client address: %s Name: %s\n", peerInfo.getAddress().toString().c_str(),
// This function blocks until the name is retrieved, so cannot be used in callback functions.
pServer->getPeerName(peerInfo).c_str());
}
}
vTaskDelay(pdMS_TO_TICKS(10000));
}
}

View File

@ -0,0 +1,12 @@
# Override some defaults so BT stack is enabled
# in this example
#
# BT config
#
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y

View File

@ -5,7 +5,7 @@
* updated by chegewara
* updated for NimBLE by H2zero
*/
/** NimBLE differences highlighted in comment blocks **/
/*******original********
@ -14,7 +14,7 @@
#include "NimBLEDevice.h"
extern "C"{void app_main(void);}
// The remote service we wish to connect to.
static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
// The characteristic of the remote service we are interested in.
@ -38,28 +38,41 @@ static void notifyCallback(
}
/** None of these are required as they will be handled by the library with defaults. **
** Remove as you see fit for your needs */
** Remove as you see fit for your needs */
class MyClientCallback : public BLEClientCallbacks {
void onConnect(BLEClient* pclient) {
}
void onDisconnect(BLEClient* pclient) {
/** onDisconnect now takes a reason parameter to indicate the reason for disconnection
void onDisconnect(BLEClient* pclient) { */
void onDisconnect(BLEClient* pclient, int reason) {
connected = false;
printf("onDisconnect");
}
/***************** New - Security handled here ********************
****** Note: these are the same return values as defaults ********/
uint32_t onPassKeyRequest(){
printf("Client PassKeyRequest\n");
return 123456;
}
bool onConfirmPIN(uint32_t pass_key){
printf("The passkey YES/NO number: %d\n", pass_key);
return true;
}
void onPassKeyEntry(NimBLEConnInfo& connInfo){
printf("Server Passkey Entry\n");
/** This should prompt the user to enter the passkey displayed
* on the peer device.
*/
NimBLEDevice::injectPassKey(connInfo, 123456);
};
void onAuthenticationComplete(ble_gap_conn_desc desc){
printf("Starting BLE work!\n");
void onConfirmPasskey(NimBLEConnInfo& connInfo, uint32_t pass_key){
printf("The passkey YES/NO number: %" PRIu32 "\n", pass_key);
/** Inject false if passkeys don't match. */
NimBLEDevice::injectConfirmPasskey(connInfo, true);
};
/** Pairing process complete, we can check the results in connInfo */
void onAuthenticationComplete(NimBLEConnInfo& connInfo){
if(!connInfo.isEncrypted()) {
printf("Encrypt connection failed - disconnecting\n");
/** Find the client with the connection handle provided in desc */
NimBLEDevice::getClientByHandle(connInfo.getConnHandle())->disconnect();
return;
}
}
/*******************************************************************/
};
@ -99,10 +112,11 @@ bool connectToServer() {
if(pRemoteCharacteristic->canRead()) {
std::string value = pRemoteCharacteristic->readValue();
printf("The characteristic value was: %s\n", value.c_str());
}
/** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe().
* Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false.
* Unsubscribe parameter defaults are: response=false.
}
/** registerForNotify() has been removed and replaced with subscribe() / unsubscribe().
* Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=true.
* Unsubscribe parameter defaults are: response=true.
*/
if(pRemoteCharacteristic->canNotify()) {
//pRemoteCharacteristic->registerForNotify(notifyCallback);
@ -120,9 +134,9 @@ class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
/**
* Called for each advertising BLE server.
*/
/*** Only a reference to the advertised device is passed now
void onResult(BLEAdvertisedDevice advertisedDevice) { **/
void onResult(BLEAdvertisedDevice advertisedDevice) { **/
void onResult(BLEAdvertisedDevice* advertisedDevice) {
printf("BLE Advertised Device found: %s\n", advertisedDevice->toString().c_str());
@ -149,7 +163,7 @@ class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
void connectTask (void * parameter){
for(;;) {
// If the flag "doConnect" is true then we have scanned for and found the desired
// BLE Server with which we wish to connect. Now we connect to it. Once we are
// BLE Server with which we wish to connect. Now we connect to it. Once we are
// connected we set the connected flag to be true.
if (doConnect == true) {
if (connectToServer()) {
@ -166,17 +180,17 @@ void connectTask (void * parameter){
char buf[256];
snprintf(buf, 256, "Time since boot: %lu", (unsigned long)(esp_timer_get_time() / 1000000ULL));
printf("Setting new characteristic value to %s\n", buf);
// Set the characteristic's value to be the array of bytes that is actually a string.
/*** Note: write value now returns true if successful, false otherwise - try again or disconnect ***/
pRemoteCharacteristic->writeValue((uint8_t*)buf, strlen(buf), false);
}else if(doScan){
BLEDevice::getScan()->start(0); // this is just eample to start scan after disconnect, most likely there is better way to do it in arduino
BLEDevice::getScan()->start(0); // this is just eample to start scan after disconnect, most likely there is better way to do it
}
vTaskDelay(1000/portTICK_PERIOD_MS); // Delay a second between loops.
}
vTaskDelete(NULL);
} // End of loop
@ -189,12 +203,12 @@ void app_main(void) {
// have detected a new device. Specify that we want active scanning and start the
// scan to run for 5 seconds.
BLEScan* pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setScanCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setInterval(1349);
pBLEScan->setWindow(449);
pBLEScan->setActiveScan(true);
xTaskCreate(connectTask, "connectTask", 5000, NULL, 1, NULL);
pBLEScan->start(5, false);
pBLEScan->start(5 * 1000, false);
} // End of setup.

View File

@ -0,0 +1,12 @@
# Override some defaults so BT stack is enabled
# in this example
#
# BT config
#
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y

View File

@ -46,30 +46,40 @@ uint32_t value = 0;
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
/** None of these are required as they will be handled by the library with defaults. **
** Remove as you see fit for your needs */
** Remove as you see fit for your needs */
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
void onConnect(BLEServer* pServer, BLEConnInfo& connInfo) {
deviceConnected = true;
};
void onDisconnect(BLEServer* pServer) {
void onDisconnect(BLEServer* pServer, BLEConnInfo& connInfo, int reason) {
deviceConnected = false;
}
/***************** New - Security handled here ********************
****** Note: these are the same return values as defaults ********/
uint32_t onPassKeyRequest(){
printf("Server PassKeyRequest\n");
return 123456;
}
uint32_t onPassKeyDisplay(){
printf("Server Passkey Display\n");
/** This should return a random 6 digit number for security
* or make your own static passkey as done here.
*/
return 123456;
};
bool onConfirmPIN(uint32_t pass_key){
printf("The passkey YES/NO number: %d\n", pass_key);
return true;
}
void onConfirmPasskey(NimBLEConnInfo& connInfo, uint32_t pass_key){
printf("The passkey YES/NO number: %" PRIu32 "\n", pass_key);
/** Inject false if passkeys don't match. */
NimBLEDevice::injectConfirmPasskey(connInfo, true);
};
void onAuthenticationComplete(ble_gap_conn_desc desc){
printf("Starting BLE work!\n");
}
void onAuthenticationComplete(NimBLEConnInfo& connInfo){
/** Check that encryption was successful, if not we disconnect the client */
if(!connInfo.isEncrypted()) {
NimBLEDevice::getServer()->disconnect(connInfo.getConnHandle());
printf("Encrypt connection failed - disconnecting client\n");
return;
}
printf("Starting BLE work!");
};
/*******************************************************************/
};
@ -94,13 +104,13 @@ void connectedTask (void * parameter){
// do stuff here on connecting
oldDeviceConnected = deviceConnected;
}
vTaskDelay(10/portTICK_PERIOD_MS); // Delay between loops to reset watchdog timer
}
vTaskDelete(NULL);
}
void app_main(void) {
// Create the BLE Device
BLEDevice::init("ESP32");
@ -115,26 +125,25 @@ void app_main(void) {
// Create a BLE Characteristic
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
/******* Enum Type NIMBLE_PROPERTY now *******
/******* Enum Type NIMBLE_PROPERTY now *******
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_NOTIFY |
BLECharacteristic::PROPERTY_INDICATE
);
**********************************************/
**********************************************/
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE |
NIMBLE_PROPERTY::NOTIFY |
NIMBLE_PROPERTY::INDICATE
);
// https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
// Create a BLE Descriptor
/***************************************************
NOTE: DO NOT create a 2902 descriptor.
it will be created automatically if notifications
/***************************************************
NOTE: DO NOT create a 2902 descriptor.
it will be created automatically if notifications
or indications are enabled on a characteristic.
pCharacteristic->addDescriptor(new BLE2902());
****************************************************/
// Start the service
@ -147,9 +156,9 @@ void app_main(void) {
/** This method had been removed **
pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
**/
xTaskCreate(connectedTask, "connectedTask", 5000, NULL, 1, NULL);
BLEDevice::startAdvertising();
printf("Waiting a client connection to notify...\n");
}

View File

@ -0,0 +1,12 @@
# Override some defaults so BT stack is enabled
# in this example
#
# BT config
#
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y

View File

@ -17,7 +17,7 @@
extern "C"{void app_main(void);}
int scanTime = 5; //In seconds
int scanTime = 5 * 1000; // In milliseconds, 0 = scan forever
BLEScan* pBLEScan;
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
@ -29,7 +29,7 @@ class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
void scanTask (void * parameter){
for(;;) {
// put your main code here, to run repeatedly:
BLEScanResults foundDevices = pBLEScan->start(scanTime, false);
BLEScanResults foundDevices = pBLEScan->getResults(scanTime, false);
printf("Devices found: %d\n", foundDevices.getCount());
printf("Scan done!\n");
pBLEScan->clearResults(); // delete results fromBLEScan buffer to release memory
@ -44,7 +44,7 @@ void app_main(void) {
BLEDevice::init("");
pBLEScan = BLEDevice::getScan(); //create new scan
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setScanCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster
pBLEScan->setInterval(100);
pBLEScan->setWindow(99); // less or equal setInterval value

View File

@ -0,0 +1,12 @@
# Override some defaults so BT stack is enabled
# in this example
#
# BT config
#
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y

View File

@ -0,0 +1,12 @@
# Override some defaults so BT stack is enabled
# in this example
#
# BT config
#
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y

View File

@ -6,7 +6,7 @@
Create a BLE server that, once we receive a connection, will send periodic notifications.
The service advertises itself as: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
Has a characteristic of: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E - used for receiving data with "WRITE"
Has a characteristic of: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E - used for receiving data with "WRITE"
Has a characteristic of: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E - used to send data with "NOTIFY"
The design of creating the BLE server is:
@ -18,7 +18,7 @@
6. Start advertising.
In this example rxValue is the data received (only accessible inside that function).
And txValue is the data to be sent, in this example just a byte incremented every second.
And txValue is the data to be sent, in this example just a byte incremented every second.
*/
/** NimBLE differences highlighted in comment blocks **/
@ -48,35 +48,45 @@ uint8_t txValue = 0;
/** None of these are required as they will be handled by the library with defaults. **
** Remove as you see fit for your needs */
** Remove as you see fit for your needs */
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
void onConnect(BLEServer* pServer, BLEConnInfo& connInfo) {
deviceConnected = true;
};
void onDisconnect(BLEServer* pServer) {
void onDisconnect(BLEServer* pServer, BLEConnInfo& connInfo, int reason) {
deviceConnected = false;
}
/***************** New - Security handled here ********************
****** Note: these are the same return values as defaults ********/
uint32_t onPassKeyRequest(){
printf("Server PassKeyRequest\n");
return 123456;
}
uint32_t onPassKeyDisplay(){
printf("Server Passkey Display\n");
/** This should return a random 6 digit number for security
* or make your own static passkey as done here.
*/
return 123456;
};
bool onConfirmPIN(uint32_t pass_key){
printf("The passkey YES/NO number: %d\n", pass_key);
return true;
}
void onConfirmPasskey(NimBLEConnInfo& connInfo, uint32_t pass_key){
printf("The passkey YES/NO number: %" PRIu32 "\n", pass_key);
/** Inject false if passkeys don't match. */
NimBLEDevice::injectConfirmPasskey(connInfo, true);
};
void onAuthenticationComplete(ble_gap_conn_desc desc){
printf("Starting BLE work!\n");
}
void onAuthenticationComplete(NimBLEConnInfo& connInfo){
/** Check that encryption was successful, if not we disconnect the client */
if(!connInfo.isEncrypted()) {
NimBLEDevice::getServer()->disconnect(connInfo.getConnHandle());
printf("Encrypt connection failed - disconnecting client\n");
return;
}
printf("Starting BLE work!");
};
/*******************************************************************/
};
class MyCallbacks: public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
void onWrite(BLECharacteristic *pCharacteristic, BLEConnInfo& connInfo) {
std::string rxValue = pCharacteristic->getValue();
if (rxValue.length() > 0) {
@ -84,7 +94,7 @@ class MyCallbacks: public BLECharacteristicCallbacks {
printf("Received Value: ");
for (int i = 0; i < rxValue.length(); i++)
printf("%d", rxValue[i]);
printf("\n*********\n");
}
}
@ -109,10 +119,10 @@ void connectedTask (void * parameter){
// do stuff here on connecting
oldDeviceConnected = deviceConnected;
}
vTaskDelay(10/portTICK_PERIOD_MS); // Delay between loops to reset watchdog timer
}
vTaskDelete(NULL);
}
@ -130,27 +140,27 @@ void app_main(void) {
// Create a BLE Characteristic
pTxCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID_TX,
/******* Enum Type NIMBLE_PROPERTY now *******
/******* Enum Type NIMBLE_PROPERTY now *******
BLECharacteristic::PROPERTY_NOTIFY
);
**********************************************/
**********************************************/
NIMBLE_PROPERTY::NOTIFY
);
/***************************************************
NOTE: DO NOT create a 2902 descriptor
it will be created automatically if notifications
/***************************************************
NOTE: DO NOT create a 2902 descriptor
it will be created automatically if notifications
or indications are enabled on a characteristic.
pCharacteristic->addDescriptor(new BLE2902());
****************************************************/
****************************************************/
BLECharacteristic * pRxCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID_RX,
/******* Enum Type NIMBLE_PROPERTY now *******
/******* Enum Type NIMBLE_PROPERTY now *******
BLECharacteristic::PROPERTY_WRITE
);
*********************************************/
*********************************************/
NIMBLE_PROPERTY::WRITE
);
@ -160,7 +170,7 @@ void app_main(void) {
pService->start();
xTaskCreate(connectedTask, "connectedTask", 5000, NULL, 1, NULL);
// Start advertising
pServer->getAdvertising()->start();
printf("Waiting a client connection to notify...\n");

View File

@ -0,0 +1,12 @@
# Override some defaults so BT stack is enabled
# in this example
#
# BT config
#
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y

16
idf_component.yml Normal file
View File

@ -0,0 +1,16 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_hosted:
version: "*"
rules:
- if: "target in [esp32p4]"
espressif/esp_wifi_remote:
version: "*"
rules:
- if: "target in [esp32p4]"
## Required IDF version
idf:
version: ">=5.3.0"
rules:
- if: "target in [esp32p4]"

17
package.json Normal file
View File

@ -0,0 +1,17 @@
{
"name": "esp-nimble-cpp",
"version": "1.5.0",
"description": "NimBLE, BLE stack for the Espressif ESP32, ESP32-S and ESP32-C series of SoCs",
"keywords": [
"BLE",
"espidf",
"arduino",
"espressif",
"esp32"
],
"license": "LGPL-2.1-or-later",
"repository": {
"type": "git",
"url": "https://github.com/h2zero/esp-nimble-cpp"
}
}

View File

@ -12,21 +12,17 @@
* Author: kolban
*/
/*
* See also:
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml
*/
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#include "NimBLE2904.h"
NimBLE2904::NimBLE2904(NimBLECharacteristic* pCharacterisitic)
NimBLE2904::NimBLE2904(NimBLECharacteristic* pCharacteristic)
: NimBLEDescriptor(NimBLEUUID((uint16_t) 0x2904),
BLE_GATT_CHR_F_READ,
sizeof(BLE2904_Data),
pCharacterisitic)
pCharacteristic)
{
m_data.m_format = 0;
m_data.m_exponent = 0;

View File

@ -33,9 +33,6 @@ struct BLE2904_Data {
* @brief Descriptor for Characteristic Presentation Format.
*
* This is a convenience descriptor for the Characteristic Presentation Format which has a UUID of 0x2904.
*
* See also:
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml
*/
class NimBLE2904: public NimBLEDescriptor {
public:

View File

@ -14,139 +14,151 @@
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <algorithm>
# include "NimBLEAddress.h"
# include "NimBLELog.h"
#include "NimBLEAddress.h"
#include "NimBLEUtils.h"
#include "NimBLELog.h"
# include <algorithm>
static const char* LOG_TAG = "NimBLEAddress";
/*************************************************
* NOTE: NimBLE address bytes are in INVERSE ORDER!
* We will accomodate that fact in these methods.
*************************************************/
* We will accommodate that fact in these methods.
*************************************************/
/**
* @brief Create an address from the native NimBLE representation.
* @param [in] address The native NimBLE address.
*/
NimBLEAddress::NimBLEAddress(ble_addr_t address) {
memcpy(m_address, address.val, 6);
m_addrType = address.type;
} // NimBLEAddress
NimBLEAddress::NimBLEAddress(ble_addr_t address) : ble_addr_t{address} {}
/**
* @brief Create a blank address, i.e. 00:00:00:00:00:00, type 0.
*/
NimBLEAddress::NimBLEAddress() {
NimBLEAddress("");
} // NimBLEAddress
/**
* @brief Create an address from a hex string
* @brief Create an address from a hex string.
*
* A hex string is of the format:
* ```
* 00:00:00:00:00:00
* ```
* which is 17 characters in length.
*
* @param [in] stringAddress The hex string representation of the address.
* @param [in] addr The hex string representation of the address.
* @param [in] type The type of the address.
*/
NimBLEAddress::NimBLEAddress(const std::string &stringAddress, uint8_t type) {
m_addrType = type;
NimBLEAddress::NimBLEAddress(const std::string& addr, uint8_t type) {
this->type = type;
if (stringAddress.length() == 0) {
memset(m_address, 0, 6);
if (addr.length() == BLE_DEV_ADDR_LEN) {
std::reverse_copy(addr.data(), addr.data() + BLE_DEV_ADDR_LEN, this->val);
return;
}
if (stringAddress.length() == 6) {
std::reverse_copy(stringAddress.data(), stringAddress.data() + 6, m_address);
if (addr.length() == 17) {
std::string mac{addr};
mac.erase(std::remove(mac.begin(), mac.end(), ':'), mac.end());
uint64_t address = std::stoull(mac, nullptr, 16);
memcpy(this->val, &address, sizeof this->val);
return;
}
if (stringAddress.length() != 17) {
memset(m_address, 0, sizeof m_address); // "00:00:00:00:00:00" represents an invalid address
NIMBLE_LOGD(LOG_TAG, "Invalid address '%s'", stringAddress.c_str());
return;
}
int data[6];
if(sscanf(stringAddress.c_str(), "%x:%x:%x:%x:%x:%x", &data[5], &data[4], &data[3], &data[2], &data[1], &data[0]) != 6) {
memset(m_address, 0, sizeof m_address); // "00:00:00:00:00:00" represents an invalid address
NIMBLE_LOGD(LOG_TAG, "Invalid address '%s'", stringAddress.c_str());
}
for(size_t index = 0; index < sizeof m_address; index++) {
m_address[index] = data[index];
}
*this = NimBLEAddress{};
NIMBLE_LOGE(LOG_TAG, "Invalid address '%s'", addr.c_str());
} // NimBLEAddress
/**
* @brief Constructor for compatibility with bluedroid esp library using native ESP representation.
* @param [in] address A uint8_t[6] or esp_bd_addr_t containing the address.
* @param [in] type The type of the address.
*/
NimBLEAddress::NimBLEAddress(uint8_t address[6], uint8_t type) {
std::reverse_copy(address, address + sizeof m_address, m_address);
m_addrType = type;
NimBLEAddress::NimBLEAddress(const uint8_t address[BLE_DEV_ADDR_LEN], uint8_t type) {
std::reverse_copy(address, address + BLE_DEV_ADDR_LEN, this->val);
this->type = type;
} // NimBLEAddress
/**
* @brief Constructor for address using a hex value.\n
* Use the same byte order, so use 0xa4c1385def16 for "a4:c1:38:5d:ef:16"
* @param [in] address uint64_t containing the address.
* @param [in] type The type of the address.
*/
NimBLEAddress::NimBLEAddress(const uint64_t &address, uint8_t type) {
memcpy(m_address, &address, sizeof m_address);
m_addrType = type;
NimBLEAddress::NimBLEAddress(const uint64_t& address, uint8_t type) {
memcpy(this->val, &address, sizeof this->val);
this->type = type;
} // NimBLEAddress
/**
* @brief Determine if this address equals another.
* @param [in] otherAddress The other address to compare against.
* @return True if the addresses are equal.
*/
bool NimBLEAddress::equals(const NimBLEAddress &otherAddress) const {
bool NimBLEAddress::equals(const NimBLEAddress& otherAddress) const {
return *this == otherAddress;
} // equals
/**
* @brief Get the native representation of the address.
* @return a pointer to the uint8_t[6] array of the address.
* @brief Get the NimBLE base struct of the address.
* @return A read only reference to the NimBLE base struct of the address.
*/
const uint8_t *NimBLEAddress::getNative() const {
return m_address;
} // getNative
const ble_addr_t* NimBLEAddress::getBase() const {
return reinterpret_cast<const ble_addr_t*>(this);
} // getBase
/**
* @brief Get the address type.
* @return The address type.
*/
uint8_t NimBLEAddress::getType() const {
return m_addrType;
return this->type;
} // getType
/**
* @brief Get the address value.
* @return A read only reference to the address value.
*/
const uint8_t* NimBLEAddress::getVal() const {
return this->val;
} // getVal
/**
* @brief Determine if this address is a Resolvable Private Address.
* @return True if the address is a RPA.
*/
bool NimBLEAddress::isRpa() const {
return BLE_ADDR_IS_RPA(this);
} // isRpa
/**
* @brief Determine if this address is a Non-Resolvable Private Address.
* @return True if the address is a NRPA.
*/
bool NimBLEAddress::isNrpa() const {
return BLE_ADDR_IS_NRPA(this);
} // isNrpa
/**
* @brief Determine if this address is a Static Address.
* @return True if the address is a Static Address.
*/
bool NimBLEAddress::isStatic() const {
return BLE_ADDR_IS_STATIC(this);
} // isStatic
/**
* @brief Determine if this address is a Public Address.
* @return True if the address is a Public Address.
*/
bool NimBLEAddress::isPublic() const {
return this->type == BLE_ADDR_PUBLIC;
} // isPublic
/**
* @brief Determine if this address is a NULL Address.
* @return True if the address is a NULL Address.
*/
bool NimBLEAddress::isNull() const {
return *this == NimBLEAddress{};
} // isNull
/**
* @brief Convert a BLE address to a string.
*
* A string representation of an address is in the format:
*
* ```
* xx:xx:xx:xx:xx:xx
* ```
*
* @return The string representation of the address.
* @deprecated Use std::string() operator instead.
*/
@ -154,43 +166,57 @@ std::string NimBLEAddress::toString() const {
return std::string(*this);
} // toString
/**
* @brief Reverse the byte order of the address.
* @return A reference to this address.
*/
const NimBLEAddress& NimBLEAddress::reverseByteOrder() {
std::reverse(this->val, this->val + BLE_DEV_ADDR_LEN);
return *this;
} // reverseByteOrder
/**
* @brief Convienience operator to check if this address is equal to another.
* @brief Convenience operator to check if this address is equal to another.
*/
bool NimBLEAddress::operator ==(const NimBLEAddress & rhs) const {
return memcmp(rhs.m_address, m_address, sizeof m_address) == 0;
bool NimBLEAddress::operator==(const NimBLEAddress& rhs) const {
if (this->type != rhs.type) {
return false;
}
return memcmp(rhs.val, this->val, sizeof this->val) == 0;
} // operator ==
/**
* @brief Convienience operator to check if this address is not equal to another.
* @brief Convenience operator to check if this address is not equal to another.
*/
bool NimBLEAddress::operator !=(const NimBLEAddress & rhs) const {
bool NimBLEAddress::operator!=(const NimBLEAddress& rhs) const {
return !this->operator==(rhs);
} // operator !=
/**
* @brief Convienience operator to convert this address to string representation.
* @details This allows passing NimBLEAddress to functions
* that accept std::string and/or or it's methods as a parameter.
* @brief Convenience operator to convert this address to string representation.
* @details This allows passing NimBLEAddress to functions that accept std::string and/or it's methods as a parameter.
*/
NimBLEAddress::operator std::string() const {
char buffer[18];
snprintf(buffer, sizeof(buffer), "%02x:%02x:%02x:%02x:%02x:%02x",
m_address[5], m_address[4], m_address[3],
m_address[2], m_address[1], m_address[0]);
return std::string(buffer);
snprintf(buffer,
sizeof(buffer),
"%02x:%02x:%02x:%02x:%02x:%02x",
this->val[5],
this->val[4],
this->val[3],
this->val[2],
this->val[1],
this->val[0]);
return std::string{buffer};
} // operator std::string
/**
* @brief Convienience operator to convert the native address representation to uint_64.
* @brief Convenience operator to convert the native address representation to uint_64.
*/
NimBLEAddress::operator uint64_t() const {
uint64_t address = 0;
memcpy(&address, m_address, sizeof m_address);
memcpy(&address, this->val, sizeof this->val);
return address;
} // operator uint64_t

View File

@ -12,51 +12,56 @@
* Author: kolban
*/
#ifndef COMPONENTS_NIMBLEADDRESS_H_
#define COMPONENTS_NIMBLEADDRESS_H_
#ifndef NIMBLE_CPP_ADDRESS_H_
#define NIMBLE_CPP_ADDRESS_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
#if defined(CONFIG_NIMBLE_CPP_IDF)
#include "nimble/ble.h"
#else
#include "nimble/nimble/include/nimble/ble.h"
#endif
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "nimble/ble.h"
# else
# include "nimble/nimble/include/nimble/ble.h"
# endif
/**** FIX COMPILATION ****/
#undef min
#undef max
# undef min
# undef max
/**************************/
#include <string>
#include <algorithm>
# include <string>
/**
* @brief A %BLE device address.
*
* Every %BLE device has a unique address which can be used to identify it and form connections.
*/
class NimBLEAddress {
public:
NimBLEAddress();
NimBLEAddress(ble_addr_t address);
NimBLEAddress(uint8_t address[6], uint8_t type = BLE_ADDR_PUBLIC);
NimBLEAddress(const std::string &stringAddress, uint8_t type = BLE_ADDR_PUBLIC);
NimBLEAddress(const uint64_t &address, uint8_t type = BLE_ADDR_PUBLIC);
bool equals(const NimBLEAddress &otherAddress) const;
const uint8_t* getNative() const;
std::string toString() const;
uint8_t getType() const;
class NimBLEAddress : private ble_addr_t {
public:
/**
* @brief Create a blank address, i.e. 00:00:00:00:00:00, type 0.
*/
NimBLEAddress() = default;
NimBLEAddress(const ble_addr_t address);
NimBLEAddress(const uint8_t address[BLE_DEV_ADDR_LEN], uint8_t type = BLE_ADDR_PUBLIC);
NimBLEAddress(const std::string& stringAddress, uint8_t type = BLE_ADDR_PUBLIC);
NimBLEAddress(const uint64_t& address, uint8_t type = BLE_ADDR_PUBLIC);
bool operator ==(const NimBLEAddress & rhs) const;
bool operator !=(const NimBLEAddress & rhs) const;
operator std::string() const;
operator uint64_t() const;
private:
uint8_t m_address[6];
uint8_t m_addrType;
bool isRpa() const;
bool isNrpa() const;
bool isStatic() const;
bool isPublic() const;
bool isNull() const;
bool equals(const NimBLEAddress& otherAddress) const;
const ble_addr_t* getBase() const;
std::string toString() const;
uint8_t getType() const;
const uint8_t* getVal() const;
const NimBLEAddress& reverseByteOrder();
bool operator==(const NimBLEAddress& rhs) const;
bool operator!=(const NimBLEAddress& rhs) const;
operator std::string() const;
operator uint64_t() const;
};
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_NIMBLEADDRESS_H_ */
#endif /* NIMBLE_CPP_ADDRESS_H_ */

File diff suppressed because it is too large Load Diff

View File

@ -12,25 +12,25 @@
* Author: kolban
*/
#ifndef COMPONENTS_NIMBLEADVERTISEDDEVICE_H_
#define COMPONENTS_NIMBLEADVERTISEDDEVICE_H_
#ifndef NIMBLE_CPP_ADVERTISED_DEVICE_H_
#define NIMBLE_CPP_ADVERTISED_DEVICE_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
#include "NimBLEAddress.h"
#include "NimBLEScan.h"
#include "NimBLEUUID.h"
# include "NimBLEAddress.h"
# include "NimBLEScan.h"
# include "NimBLEUUID.h"
#if defined(CONFIG_NIMBLE_CPP_IDF)
#include "host/ble_hs_adv.h"
#else
#include "nimble/nimble/host/include/host/ble_hs_adv.h"
#endif
#include <map>
#include <vector>
#include <time.h>
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_hs_adv.h"
# include "host/ble_gap.h"
# else
# include "nimble/nimble/host/include/host/ble_hs_adv.h"
# include "nimble/nimble/host/include/host/ble_gap.h"
# endif
# include <vector>
class NimBLEScan;
/**
@ -40,17 +40,59 @@ class NimBLEScan;
* class provides a model of a detected device.
*/
class NimBLEAdvertisedDevice {
public:
NimBLEAdvertisedDevice();
public:
NimBLEAdvertisedDevice() = default;
NimBLEAddress getAddress();
uint8_t getAdvType();
uint16_t getAppearance();
uint16_t getAdvInterval();
uint16_t getMinInterval();
uint16_t getMaxInterval();
std::string getManufacturerData();
std::string getURI();
uint8_t getAdvType() const;
uint8_t getAdvFlags() const;
uint16_t getAppearance() const;
uint16_t getAdvInterval() const;
uint16_t getMinInterval() const;
uint16_t getMaxInterval() const;
uint8_t getManufacturerDataCount() const;
const NimBLEAddress& getAddress() const;
std::string getManufacturerData(uint8_t index = 0) const;
std::string getURI() const;
std::string getPayloadByType(uint16_t type, uint8_t index = 0) const;
std::string getName() const;
int8_t getRSSI() const;
NimBLEScan* getScan() const;
uint8_t getServiceDataCount() const;
std::string getServiceData(uint8_t index = 0) const;
std::string getServiceData(const NimBLEUUID& uuid) const;
NimBLEUUID getServiceDataUUID(uint8_t index = 0) const;
NimBLEUUID getServiceUUID(uint8_t index = 0) const;
uint8_t getServiceUUIDCount() const;
NimBLEAddress getTargetAddress(uint8_t index = 0) const;
uint8_t getTargetAddressCount() const;
int8_t getTXPower() const;
uint8_t getAdvLength() const;
uint8_t getAddressType() const;
bool isAdvertisingService(const NimBLEUUID& uuid) const;
bool haveAppearance() const;
bool haveManufacturerData() const;
bool haveName() const;
bool haveServiceData() const;
bool haveServiceUUID() const;
bool haveTXPower() const;
bool haveConnParams() const;
bool haveAdvInterval() const;
bool haveTargetAddress() const;
bool haveURI() const;
bool haveType(uint16_t type) const;
std::string toString() const;
bool isConnectable() const;
bool isLegacyAdvertisement() const;
# if CONFIG_BT_NIMBLE_EXT_ADV
uint8_t getSetId() const;
uint8_t getPrimaryPhy() const;
uint8_t getSecondaryPhy() const;
uint16_t getPeriodicInterval() const;
# endif
const std::vector<uint8_t>& getPayload() const;
const std::vector<uint8_t>::const_iterator begin() const;
const std::vector<uint8_t>::const_iterator end() const;
/**
* @brief A template to convert the service data to <type\>.
@ -60,21 +102,14 @@ public:
* less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>getManufacturerData<type>(skipSizeCheck);</tt>
*/
template<typename T>
T getManufacturerData(bool skipSizeCheck = false) {
template <typename T>
T getManufacturerData(bool skipSizeCheck = false) const {
std::string data = getManufacturerData();
if(!skipSizeCheck && data.size() < sizeof(T)) return T();
const char *pData = data.data();
return *((T *)pData);
if (!skipSizeCheck && data.size() < sizeof(T)) return T();
const char* pData = data.data();
return *((T*)pData);
}
std::string getName();
int getRSSI();
NimBLEScan* getScan();
uint8_t getServiceDataCount();
std::string getServiceData(uint8_t index = 0);
std::string getServiceData(const NimBLEUUID &uuid);
/**
* @brief A template to convert the service data to <tt><type\></tt>.
* @tparam T The type to convert the data to.
@ -84,12 +119,12 @@ public:
* less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>getServiceData<type>(skipSizeCheck);</tt>
*/
template<typename T>
T getServiceData(uint8_t index = 0, bool skipSizeCheck = false) {
template <typename T>
T getServiceData(uint8_t index = 0, bool skipSizeCheck = false) const {
std::string data = getServiceData(index);
if(!skipSizeCheck && data.size() < sizeof(T)) return T();
const char *pData = data.data();
return *((T *)pData);
if (!skipSizeCheck && data.size() < sizeof(T)) return T();
const char* pData = data.data();
return *((T*)pData);
}
/**
@ -101,98 +136,39 @@ public:
* less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>getServiceData<type>(skipSizeCheck);</tt>
*/
template<typename T>
T getServiceData(const NimBLEUUID &uuid, bool skipSizeCheck = false) {
template <typename T>
T getServiceData(const NimBLEUUID& uuid, bool skipSizeCheck = false) const {
std::string data = getServiceData(uuid);
if(!skipSizeCheck && data.size() < sizeof(T)) return T();
const char *pData = data.data();
return *((T *)pData);
if (!skipSizeCheck && data.size() < sizeof(T)) return T();
const char* pData = data.data();
return *((T*)pData);
}
NimBLEUUID getServiceDataUUID(uint8_t index = 0);
NimBLEUUID getServiceUUID(uint8_t index = 0);
uint8_t getServiceUUIDCount();
NimBLEAddress getTargetAddress(uint8_t index = 0);
uint8_t getTargetAddressCount();
int8_t getTXPower();
uint8_t* getPayload();
uint8_t getAdvLength();
size_t getPayloadLength();
uint8_t getAddressType();
time_t getTimestamp();
bool isAdvertisingService(const NimBLEUUID &uuid);
bool haveAppearance();
bool haveManufacturerData();
bool haveName();
bool haveRSSI();
bool haveServiceData();
bool haveServiceUUID();
bool haveTXPower();
bool haveConnParams();
bool haveAdvInterval();
bool haveTargetAddress();
bool haveURI();
std::string toString();
bool isConnectable();
bool isLegacyAdvertisement();
#if CONFIG_BT_NIMBLE_EXT_ADV
uint8_t getSetId();
uint8_t getPrimaryPhy();
uint8_t getSecondaryPhy();
uint16_t getPeriodicInterval();
#endif
private:
private:
friend class NimBLEScan;
void setAddress(NimBLEAddress address);
void setAdvType(uint8_t advType, bool isLegacyAdv);
void setPayload(const uint8_t *payload, uint8_t length, bool append);
void setRSSI(int rssi);
#if CONFIG_BT_NIMBLE_EXT_ADV
void setSetId(uint8_t sid) { m_sid = sid; }
void setPrimaryPhy(uint8_t phy) { m_primPhy = phy; }
void setSecondaryPhy(uint8_t phy) { m_secPhy = phy; }
void setPeriodicInterval(uint16_t itvl) { m_periodicItvl = itvl; }
#endif
uint8_t findAdvField(uint8_t type, uint8_t index = 0, size_t * data_loc = nullptr);
size_t findServiceData(uint8_t index, uint8_t* bytes);
NimBLEAdvertisedDevice(const ble_gap_event* event, uint8_t eventType);
void update(const ble_gap_event* event, uint8_t eventType);
uint8_t findAdvField(uint8_t type, uint8_t index = 0, size_t* data_loc = nullptr) const;
size_t findServiceData(uint8_t index, uint8_t* bytes) const;
NimBLEAddress m_address = NimBLEAddress("");
uint8_t m_advType;
int m_rssi;
time_t m_timestamp;
bool m_callbackSent;
uint8_t m_advLength;
#if CONFIG_BT_NIMBLE_EXT_ADV
bool m_isLegacyAdv;
uint8_t m_sid;
uint8_t m_primPhy;
uint8_t m_secPhy;
uint16_t m_periodicItvl;
#endif
NimBLEAddress m_address{};
uint8_t m_advType{};
int8_t m_rssi{};
uint8_t m_callbackSent{};
uint8_t m_advLength{};
uint32_t m_srTimeout{};
std::vector<uint8_t> m_payload;
};
# if CONFIG_BT_NIMBLE_EXT_ADV
bool m_isLegacyAdv{};
uint8_t m_sid{};
uint8_t m_primPhy{};
uint8_t m_secPhy{};
uint16_t m_periodicItvl{};
# endif
/**
* @brief A callback handler for callbacks associated device scanning.
*
* When we are performing a scan as a %BLE client, we may wish to know when a new device that is advertising
* has been found. This class can be sub-classed and registered such that when a scan is performed and
* a new advertised device has been found, we will be called back to be notified.
*/
class NimBLEAdvertisedDeviceCallbacks {
public:
virtual ~NimBLEAdvertisedDeviceCallbacks() {}
/**
* @brief Called when a new scan result is detected.
*
* As we are scanning, we will find new devices. When found, this call back is invoked with a reference to the
* device that was found. During any individual scan, a device will only be detected one time.
*/
virtual void onResult(NimBLEAdvertisedDevice* advertisedDevice) = 0;
std::vector<uint8_t> m_payload;
};
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_OBSERVER */
#endif /* COMPONENTS_NIMBLEADVERTISEDDEVICE_H_ */
#endif /* NIMBLE_CPP_ADVERTISED_DEVICE_H_ */

View File

@ -44,7 +44,7 @@ NimBLEAdvertising::NimBLEAdvertising() {
* @brief Stops the current advertising and resets the advertising data to the default values.
*/
void NimBLEAdvertising::reset() {
if(NimBLEDevice::getInitialized() && isAdvertising()) {
if(NimBLEDevice::isInitialized() && isAdvertising()) {
stop();
}
memset(&m_advData, 0, sizeof m_advData);
@ -56,7 +56,11 @@ void NimBLEAdvertising::reset() {
m_advData.name = (uint8_t *)name;
m_advData.name_len = strlen(name);
m_advData.name_is_complete = 1;
#ifndef CONFIG_IDF_TARGET_ESP32P4
m_advData.tx_pwr_lvl = NimBLEDevice::getPower();
#else
m_advData.tx_pwr_lvl = 0;
#endif
m_advData.flags = (BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP);
#if !defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
@ -96,8 +100,8 @@ void NimBLEAdvertising::addServiceUUID(const char* serviceUUID) {
/**
* @brief Add a service uuid to exposed list of services.
* @param [in] serviceUUID The UUID of the service to expose.
* @brief Remove a service UUID from the advertisment.
* @param [in] serviceUUID The UUID of the service to remove.
*/
void NimBLEAdvertising::removeServiceUUID(const NimBLEUUID &serviceUUID) {
for(auto it = m_serviceUUIDs.begin(); it != m_serviceUUIDs.end(); ++it) {
@ -110,10 +114,17 @@ void NimBLEAdvertising::removeServiceUUID(const NimBLEUUID &serviceUUID) {
} // addServiceUUID
/**
* @brief Remove all service UUIDs from the advertisment.
*/
void NimBLEAdvertising::removeServices() {
std::vector<NimBLEUUID>().swap(m_serviceUUIDs);
m_advDataSet = false;
} // removeServices
/**
* @brief Set the device appearance in the advertising data.
* The codes for distinct appearances can be found here:\n
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.gap.appearance.xml.
* @param [in] appearance The appearance of the device in the advertising data.
*/
void NimBLEAdvertising::setAppearance(uint16_t appearance) {
@ -137,7 +148,7 @@ void NimBLEAdvertising::addTxPower() {
* @param [in] name The name to advertise.
*/
void NimBLEAdvertising::setName(const std::string &name) {
m_name.assign(name.begin(), name.end());
std::vector<uint8_t>(name.begin(), name.end()).swap(m_name);
m_advData.name = &m_name[0];
m_advData.name_len = m_name.size();
m_advDataSet = false;
@ -149,7 +160,19 @@ void NimBLEAdvertising::setName(const std::string &name) {
* @param [in] data The data to advertise.
*/
void NimBLEAdvertising::setManufacturerData(const std::string &data) {
m_mfgData.assign(data.begin(), data.end());
std::vector<uint8_t>(data.begin(), data.end()).swap(m_mfgData);
m_advData.mfg_data = &m_mfgData[0];
m_advData.mfg_data_len = m_mfgData.size();
m_advDataSet = false;
} // setManufacturerData
/**
* @brief Set the advertised manufacturer data.
* @param [in] data The data to advertise.
*/
void NimBLEAdvertising::setManufacturerData(const std::vector<uint8_t> &data) {
m_mfgData = data;
m_advData.mfg_data = &m_mfgData[0];
m_advData.mfg_data_len = m_mfgData.size();
m_advDataSet = false;
@ -161,7 +184,7 @@ void NimBLEAdvertising::setManufacturerData(const std::string &data) {
* @param [in] uri The URI to advertise.
*/
void NimBLEAdvertising::setURI(const std::string &uri) {
m_uri.assign(uri.begin(), uri.end());
std::vector<uint8_t>(uri.begin(), uri.end()).swap(m_uri);
m_advData.uri = &m_uri[0];
m_advData.uri_len = m_uri.size();
m_advDataSet = false;
@ -177,7 +200,7 @@ void NimBLEAdvertising::setURI(const std::string &uri) {
void NimBLEAdvertising::setServiceData(const NimBLEUUID &uuid, const std::string &data) {
switch (uuid.bitSize()) {
case 16: {
m_svcData16.assign((uint8_t*)&uuid.getNative()->u16.value, (uint8_t*)&uuid.getNative()->u16.value + 2);
std::vector<uint8_t>(uuid.getValue(), uuid.getValue() + 2).swap(m_svcData16);
m_svcData16.insert(m_svcData16.end(), data.begin(), data.end());
m_advData.svc_data_uuid16 = (uint8_t*)&m_svcData16[0];
m_advData.svc_data_uuid16_len = (data.length() > 0) ? m_svcData16.size() : 0;
@ -185,7 +208,7 @@ void NimBLEAdvertising::setServiceData(const NimBLEUUID &uuid, const std::string
}
case 32: {
m_svcData32.assign((uint8_t*)&uuid.getNative()->u32.value, (uint8_t*)&uuid.getNative()->u32.value + 4);
std::vector<uint8_t>(uuid.getValue(), uuid.getValue() + 4).swap(m_svcData32);
m_svcData32.insert(m_svcData32.end(), data.begin(), data.end());
m_advData.svc_data_uuid32 = (uint8_t*)&m_svcData32[0];
m_advData.svc_data_uuid32_len = (data.length() > 0) ? m_svcData32.size() : 0;
@ -193,7 +216,7 @@ void NimBLEAdvertising::setServiceData(const NimBLEUUID &uuid, const std::string
}
case 128: {
m_svcData128.assign(uuid.getNative()->u128.value, uuid.getNative()->u128.value + 16);
std::vector<uint8_t>(uuid.getValue(), uuid.getValue() + 16).swap(m_svcData128);
m_svcData128.insert(m_svcData128.end(), data.begin(), data.end());
m_advData.svc_data_uuid128 = (uint8_t*)&m_svcData128[0];
m_advData.svc_data_uuid128_len = (data.length() > 0) ? m_svcData128.size() : 0;
@ -385,11 +408,12 @@ void NimBLEAdvertising::setScanResponseData(NimBLEAdvertisementData& advertiseme
/**
* @brief Start advertising.
* @param [in] duration The duration, in seconds, to advertise, 0 == advertise forever.
* @param [in] duration The duration, in milliseconds, to advertise, 0 == advertise forever.
* @param [in] advCompleteCB A pointer to a callback to be invoked when advertising ends.
* @param [in] dirAddr The address of a peer to directly advertise to.
* @return True if advertising started successfully.
*/
bool NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdvertising *pAdv)) {
bool NimBLEAdvertising::start(uint32_t duration, advCompleteCB_t advCompleteCB, NimBLEAddress* dirAddr) {
NIMBLE_LOGD(LOG_TAG, ">> Advertising start: customAdvData: %d, customScanResponseData: %d",
m_customAdvData, m_customScanResponseData);
@ -423,9 +447,6 @@ bool NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
if(duration == 0){
duration = BLE_HS_FOREVER;
}
else{
duration = duration*1000; // convert duration to milliseconds
}
m_advCompCB = advCompleteCB;
@ -434,15 +455,16 @@ bool NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
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;
// non-connectable advertising does not require AD flags.
m_advData.flags = 0;
}
}
int rc = 0;
if (!m_customAdvData && !m_advDataSet) {
//start with 3 bytes for the flags data
uint8_t payloadLen = (2 + 1);
//start with 3 bytes for the flags data if required
uint8_t payloadLen = (m_advData.flags > 0) ? (2 + 1) : 0;
if(m_advData.mfg_data_len > 0)
payloadLen += (2 + m_advData.mfg_data_len);
@ -468,7 +490,7 @@ bool NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
payloadLen += (2 + BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN);
for(auto &it : m_serviceUUIDs) {
if(it.getNative()->u.type == BLE_UUID_TYPE_16) {
if(it.bitSize() == BLE_UUID_TYPE_16) {
int add = (m_advData.num_uuids16 > 0) ? 2 : 4;
if((payloadLen + add) > BLE_HS_ADV_MAX_SZ){
m_advData.uuids16_is_complete = 0;
@ -476,18 +498,17 @@ bool NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
}
payloadLen += add;
if(nullptr == (m_advData.uuids16 = (ble_uuid16_t*)realloc((void*)m_advData.uuids16,
(m_advData.num_uuids16 + 1) * sizeof(ble_uuid16_t))))
if(nullptr == (m_advData.uuids16 = reinterpret_cast<ble_uuid16_t*>(realloc((void*)m_advData.uuids16,
(m_advData.num_uuids16 + 1) * sizeof(ble_uuid16_t)))))
{
NIMBLE_LOGC(LOG_TAG, "Error, no mem");
abort();
NIMBLE_LOGE(LOG_TAG, "Error, no mem");
return false;
}
memcpy((void*)&m_advData.uuids16[m_advData.num_uuids16],
&it.getNative()->u16, sizeof(ble_uuid16_t));
reinterpret_cast<ble_uuid16_t*>(&it), sizeof(ble_uuid16_t));
m_advData.uuids16_is_complete = 1;
m_advData.num_uuids16++;
}
if(it.getNative()->u.type == BLE_UUID_TYPE_32) {
} else if(it.bitSize() == BLE_UUID_TYPE_32) {
int add = (m_advData.num_uuids32 > 0) ? 4 : 6;
if((payloadLen + add) > BLE_HS_ADV_MAX_SZ){
m_advData.uuids32_is_complete = 0;
@ -495,18 +516,17 @@ bool NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
}
payloadLen += add;
if(nullptr == (m_advData.uuids32 = (ble_uuid32_t*)realloc((void*)m_advData.uuids32,
(m_advData.num_uuids32 + 1) * sizeof(ble_uuid32_t))))
if(nullptr == (m_advData.uuids32 = reinterpret_cast<ble_uuid32_t*>(realloc((void*)m_advData.uuids32,
(m_advData.num_uuids32 + 1) * sizeof(ble_uuid32_t)))))
{
NIMBLE_LOGC(LOG_TAG, "Error, no mem");
abort();
NIMBLE_LOGE(LOG_TAG, "Error, no mem");
return false;
}
memcpy((void*)&m_advData.uuids32[m_advData.num_uuids32],
&it.getNative()->u32, sizeof(ble_uuid32_t));
reinterpret_cast<ble_uuid32_t*>(&it), sizeof(ble_uuid32_t));
m_advData.uuids32_is_complete = 1;
m_advData.num_uuids32++;
}
if(it.getNative()->u.type == BLE_UUID_TYPE_128){
} else if(it.bitSize() == BLE_UUID_TYPE_128){
int add = (m_advData.num_uuids128 > 0) ? 16 : 18;
if((payloadLen + add) > BLE_HS_ADV_MAX_SZ){
m_advData.uuids128_is_complete = 0;
@ -514,14 +534,14 @@ bool NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
}
payloadLen += add;
if(nullptr == (m_advData.uuids128 = (ble_uuid128_t*)realloc((void*)m_advData.uuids128,
(m_advData.num_uuids128 + 1) * sizeof(ble_uuid128_t))))
if(nullptr == (m_advData.uuids128 = reinterpret_cast<ble_uuid128_t*>(realloc((void*)m_advData.uuids128,
(m_advData.num_uuids128 + 1) * sizeof(ble_uuid128_t)))))
{
NIMBLE_LOGC(LOG_TAG, "Error, no mem");
abort();
NIMBLE_LOGE(LOG_TAG, "Error, no mem");
return false;
}
memcpy((void*)&m_advData.uuids128[m_advData.num_uuids128],
&it.getNative()->u128, sizeof(ble_uuid128_t));
reinterpret_cast<ble_uuid128_t*>(&it), sizeof(ble_uuid128_t));
m_advData.uuids128_is_complete = 1;
m_advData.num_uuids128++;
}
@ -623,14 +643,20 @@ bool NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
}
#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
rc = ble_gap_adv_start(NimBLEDevice::m_own_addr_type, NULL, duration,
rc = ble_gap_adv_start(NimBLEDevice::m_ownAddrType,
(dirAddr != nullptr) ? dirAddr->getBase() : NULL,
duration,
&m_advParams,
(pServer != nullptr) ? NimBLEServer::handleGapEvent :
NimBLEAdvertising::handleGapEvent,
(void*)this);
#else
rc = ble_gap_adv_start(NimBLEDevice::m_own_addr_type, NULL, duration,
&m_advParams, NimBLEAdvertising::handleGapEvent, this);
rc = ble_gap_adv_start(NimBLEDevice::m_ownAddrType,
(dirAddr != nullptr) ? &peerAddr : NULL,
duration,
&m_advParams,
NimBLEAdvertising::handleGapEvent,
(void*)this);
#endif
switch(rc) {
case 0:
@ -739,7 +765,7 @@ int NimBLEAdvertising::handleGapEvent(struct ble_gap_event *event, void *arg) {
case BLE_HS_EOS:
case BLE_HS_ECONTROLLER:
case BLE_HS_ENOTSYNCED:
NIMBLE_LOGC(LOG_TAG, "host reset, rc=%d", event->adv_complete.reason);
NIMBLE_LOGE(LOG_TAG, "host reset, rc=%d", event->adv_complete.reason);
NimBLEDevice::onReset(event->adv_complete.reason);
return 0;
default:
@ -757,7 +783,7 @@ int NimBLEAdvertising::handleGapEvent(struct ble_gap_event *event, void *arg) {
*/
void NimBLEAdvertisementData::addData(const std::string &data) {
if ((m_payload.length() + data.length()) > BLE_HS_ADV_MAX_SZ) {
NIMBLE_LOGE(LOG_TAG, "Advertisement data length exceded");
NIMBLE_LOGE(LOG_TAG, "Advertisement data length exceeded");
return;
}
m_payload.append(data);
@ -777,9 +803,6 @@ void NimBLEAdvertisementData::addData(char * data, size_t length) {
/**
* @brief Set the appearance.
* @param [in] appearance The appearance code value.
*
* See also:
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.gap.appearance.xml
*/
void NimBLEAdvertisementData::setAppearance(uint16_t appearance) {
char cdata[2];
@ -817,6 +840,18 @@ void NimBLEAdvertisementData::setManufacturerData(const std::string &data) {
} // setManufacturerData
/**
* @brief Set manufacturer specific data.
* @param [in] data The manufacturer data to advertise.
*/
void NimBLEAdvertisementData::setManufacturerData(const std::vector<uint8_t> &data) {
char cdata[2];
cdata[0] = data.size() + 1;
cdata[1] = BLE_HS_ADV_TYPE_MFG_DATA ; // 0xff
addData(std::string(cdata, 2) + std::string((char*)&data[0], data.size()));
} // setManufacturerData
/**
* @brief Set the URI to advertise.
* @param [in] uri The uri to advertise.
@ -925,21 +960,9 @@ void NimBLEAdvertisementData::setServices(const bool complete, const uint8_t siz
for(auto &it : v_uuid){
if(it.bitSize() != size) {
NIMBLE_LOGE(LOG_TAG, "Service UUID(%d) invalid", size);
return;
continue;
} else {
switch(size) {
case 16:
uuids += std::string((char*)&it.getNative()->u16.value, 2);
break;
case 32:
uuids += std::string((char*)&it.getNative()->u32.value, 4);
break;
case 128:
uuids += std::string((char*)&it.getNative()->u128.value, 16);
break;
default:
return;
}
uuids += std::string(reinterpret_cast<const char*>(it.getValue()), size / 8);
}
}
@ -953,35 +976,30 @@ void NimBLEAdvertisementData::setServices(const bool complete, const uint8_t siz
* @param [in] data The data to be associated with the service data advertised.
*/
void NimBLEAdvertisementData::setServiceData(const NimBLEUUID &uuid, const std::string &data) {
char cdata[2];
switch (uuid.bitSize()) {
case 16: {
uint8_t size = uuid.bitSize() / 8;
char cdata[2] = {static_cast<char>(1 + size + data.length()), BLE_HS_ADV_TYPE_SVC_DATA_UUID16};
switch (size) {
case 2: {
// [Len] [0x16] [UUID16] data
cdata[0] = data.length() + 3;
cdata[1] = BLE_HS_ADV_TYPE_SVC_DATA_UUID16; // 0x16
addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->u16.value, 2) + data);
break;
}
case 32: {
// [Len] [0x20] [UUID32] data
cdata[0] = data.length() + 5;
cdata[1] = BLE_HS_ADV_TYPE_SVC_DATA_UUID32; // 0x20
addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->u32.value, 4) + data);
break;
}
case 128: {
case 16: {
// [Len] [0x21] [UUID128] data
cdata[0] = data.length() + 17;
cdata[1] = BLE_HS_ADV_TYPE_SVC_DATA_UUID128; // 0x21
addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->u128.value, 16) + data);
break;
}
case 4: {
// [Len] [0x20] [UUID32] data
cdata[1] = BLE_HS_ADV_TYPE_SVC_DATA_UUID32; // 0x20
break;
}
default:
return;
}
addData(std::string(cdata, 2) + std::string(reinterpret_cast<const char*>(uuid.getValue()), size) + data);
} // setServiceData
@ -1004,7 +1022,11 @@ void NimBLEAdvertisementData::addTxPower() {
char cdata[3];
cdata[0] = BLE_HS_ADV_TX_PWR_LVL_LEN + 1;
cdata[1] = BLE_HS_ADV_TYPE_TX_PWR_LVL;
#ifndef CONFIG_IDF_TARGET_ESP32P4
cdata[2] = NimBLEDevice::getPower();
#else
cdata[2] = 0;
#endif
addData(cdata, 3);
} // addTxPower
@ -1034,4 +1056,12 @@ std::string NimBLEAdvertisementData::getPayload() {
return m_payload;
} // getPayload
/**
* @brief Clear the advertisement data for reuse.
*/
void NimBLEAdvertisementData::clearData() {
m_payload.clear();
}
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_BROADCASTER && !CONFIG_BT_NIMBLE_EXT_ADV */

View File

@ -31,7 +31,9 @@
/**************************/
#include "NimBLEUUID.h"
#include "NimBLEAddress.h"
#include <functional>
#include <vector>
/* COMPATIBILITY - DO NOT USE */
@ -43,6 +45,9 @@
#define ESP_BLE_ADV_FLAG_NON_LIMIT_DISC (0x00 )
/* ************************* */
class NimBLEAdvertising;
typedef std::function<void(NimBLEAdvertising*)> advCompleteCB_t;
/**
* @brief Advertisement data set by the programmer to be published by the %BLE server.
@ -58,6 +63,7 @@ public:
void setCompleteServices32(const std::vector<NimBLEUUID> &v_uuid);
void setFlags(uint8_t);
void setManufacturerData(const std::string &data);
void setManufacturerData(const std::vector<uint8_t> &data);
void setURI(const std::string &uri);
void setName(const std::string &name);
void setPartialServices(const NimBLEUUID &uuid);
@ -70,6 +76,7 @@ public:
void addTxPower();
void setPreferredParams(uint16_t min, uint16_t max);
std::string getPayload(); // Retrieve the current advert payload.
void clearData(); // Clear the advertisement data.
private:
friend class NimBLEAdvertising;
@ -90,11 +97,13 @@ public:
void addServiceUUID(const NimBLEUUID &serviceUUID);
void addServiceUUID(const char* serviceUUID);
void removeServiceUUID(const NimBLEUUID &serviceUUID);
bool start(uint32_t duration = 0, void (*advCompleteCB)(NimBLEAdvertising *pAdv) = nullptr);
bool start(uint32_t duration = 0, advCompleteCB_t advCompleteCB = nullptr, NimBLEAddress* dirAddr = nullptr);
void removeServices();
bool stop();
void setAppearance(uint16_t appearance);
void setName(const std::string &name);
void setManufacturerData(const std::string &data);
void setManufacturerData(const std::vector<uint8_t> &data);
void setURI(const std::string &uri);
void setServiceData(const NimBLEUUID &uuid, const std::string &data);
void setAdvertisementType(uint8_t adv_type);
@ -126,7 +135,7 @@ private:
bool m_customScanResponseData;
bool m_scanResp;
bool m_advDataSet;
void (*m_advCompCB)(NimBLEAdvertising *pAdv);
advCompleteCB_t m_advCompCB{nullptr};
uint8_t m_slaveItvl[4];
uint32_t m_duration;
std::vector<uint8_t> m_svcData16;

130
src/NimBLEAttValue.cpp Normal file
View File

@ -0,0 +1,130 @@
/*
* NimBLEAttValue.cpp
*
* Created: on July 17, 2024
* Author H2zero
*
*/
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "nimble/nimble_npl.h"
# else
# include "nimble/nimble/include/nimble/nimble_npl.h"
# endif
# include "NimBLEAttValue.h"
// Default constructor implementation.
NimBLEAttValue::NimBLEAttValue(uint16_t init_len, uint16_t max_len)
: m_attr_value{static_cast<uint8_t*>(calloc(init_len + 1, 1))},
m_attr_max_len{std::min<uint16_t>(BLE_ATT_ATTR_MAX_LEN, max_len)},
m_attr_len{},
m_capacity{init_len}
# if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED
,
m_timestamp{}
# endif
{
NIMBLE_CPP_DEBUG_ASSERT(m_attr_value);
}
// Value constructor implementation.
NimBLEAttValue::NimBLEAttValue(const uint8_t *value, uint16_t len, uint16_t max_len)
: NimBLEAttValue(len, max_len) {
memcpy(m_attr_value, value, len);
m_attr_value[len] = '\0';
m_attr_len = len;
}
// Destructor implementation.
NimBLEAttValue::~NimBLEAttValue() {
if (m_attr_value != nullptr) {
free(m_attr_value);
}
}
// Move assignment operator implementation.
NimBLEAttValue& NimBLEAttValue::operator=(NimBLEAttValue&& source) {
if (this != &source) {
free(m_attr_value);
m_attr_value = source.m_attr_value;
m_attr_max_len = source.m_attr_max_len;
m_attr_len = source.m_attr_len;
m_capacity = source.m_capacity;
setTimeStamp(source.getTimeStamp());
source.m_attr_value = nullptr;
}
return *this;
}
// Copy assignment implementation.
NimBLEAttValue& NimBLEAttValue::operator=(const NimBLEAttValue& source) {
if (this != &source) {
deepCopy(source);
}
return *this;
}
// Copy all the data from the source object to this object, including allocated space.
void NimBLEAttValue::deepCopy(const NimBLEAttValue& source) {
uint8_t* res = static_cast<uint8_t*>(realloc(m_attr_value, source.m_capacity + 1));
NIMBLE_CPP_DEBUG_ASSERT(res);
ble_npl_hw_enter_critical();
m_attr_value = res;
m_attr_max_len = source.m_attr_max_len;
m_attr_len = source.m_attr_len;
m_capacity = source.m_capacity;
setTimeStamp(source.getTimeStamp());
memcpy(m_attr_value, source.m_attr_value, m_attr_len + 1);
ble_npl_hw_exit_critical(0);
}
// Set the value of the attribute.
bool NimBLEAttValue::setValue(const uint8_t* value, uint16_t len) {
m_attr_len = 0; // Just set the value length to 0 and append instead of repeating code.
append(value, len);
return memcmp(m_attr_value, value, len) == 0 && m_attr_len == len;
}
// Append the new data, allocate as necessary.
NimBLEAttValue& NimBLEAttValue::append(const uint8_t* value, uint16_t len) {
if (len == 0) {
return *this;
}
if ((m_attr_len + len) > m_attr_max_len) {
NIMBLE_LOGE("NimBLEAttValue", "val > max, len=%u, max=%u", len, m_attr_max_len);
return *this;
}
uint8_t* res = m_attr_value;
uint16_t new_len = m_attr_len + len;
if (new_len > m_capacity) {
res = static_cast<uint8_t*>(realloc(m_attr_value, (new_len + 1)));
m_capacity = new_len;
}
NIMBLE_CPP_DEBUG_ASSERT(res);
# if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED
time_t t = time(nullptr);
# else
time_t t = 0;
# endif
ble_npl_hw_enter_critical();
memcpy(res + m_attr_len, value, len);
m_attr_value = res;
m_attr_len = new_len;
m_attr_value[m_attr_len] = '\0';
setTimeStamp(t);
ble_npl_hw_exit_critical(0);
return *this;
}
#endif // CONFIG_BT_ENABLED

View File

@ -6,76 +6,77 @@
*
*/
#ifndef MAIN_NIMBLEATTVALUE_H_
#define MAIN_NIMBLEATTVALUE_H_
#ifndef NIMBLE_CPP_ATTVALUE_H
#define NIMBLE_CPP_ATTVALUE_H
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
#ifdef NIMBLE_CPP_ARDUINO_STRING_AVAILABLE
#include <Arduino.h>
#endif
# ifdef NIMBLE_CPP_ARDUINO_STRING_AVAILABLE
# include <Arduino.h>
# endif
#include "NimBLELog.h"
# include "NimBLELog.h"
# include <string>
# include <vector>
# include <ctime>
# include <cstring>
# include <cstdint>
/**** FIX COMPILATION ****/
#undef min
#undef max
/**************************/
# ifndef CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED
# define CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED 0
# endif
#include <string>
#include <vector>
# ifndef BLE_ATT_ATTR_MAX_LEN
# define BLE_ATT_ATTR_MAX_LEN 512
# endif
#ifndef CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED
# define CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED 0
#endif
# if !defined(CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH)
# define CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH 20
# elif CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH > BLE_ATT_ATTR_MAX_LEN
# error CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH cannot be larger than 512 (BLE_ATT_ATTR_MAX_LEN)
# elif CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH < 1
# error CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH cannot be less than 1; Range = 1 : 512
# endif
#if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED
# include <time.h>
#endif
#if !defined(CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH)
# define CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH 20
#elif CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH > BLE_ATT_ATTR_MAX_LEN
# error CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH cannot be larger than 512 (BLE_ATT_ATTR_MAX_LEN)
#elif CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH < 1
# error CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH cannot be less than 1; Range = 1 : 512
#endif
/* Used to determine if the type passed to a template has a data() and size() method. */
template <typename T, typename = void, typename = void>
struct Has_data_size : std::false_type {};
template <typename T>
struct Has_data_size<T, decltype(void(std::declval<T&>().data())), decltype(void(std::declval<T&>().size()))>
: std::true_type {};
/* Used to determine if the type passed to a template has a c_str() and length() method. */
template <typename T, typename = void, typename = void>
struct Has_c_str_len : std::false_type {};
struct Has_c_str_length : std::false_type {};
template <typename T>
struct Has_c_str_len<T, decltype(void(std::declval<T &>().c_str())),
decltype(void(std::declval<T &>().length()))> : std::true_type {};
struct Has_c_str_length<T, decltype(void(std::declval<T&>().c_str())), decltype(void(std::declval<T&>().length()))>
: std::true_type {};
/**
* @brief A specialized container class to hold BLE attribute values.
* @details This class is designed to be more memory efficient than using\n
* standard container types for value storage, while being convertable to\n
* standard container types for value storage, while being convertible to\n
* many different container classes.
*/
class NimBLEAttValue
{
uint8_t* m_attr_value = nullptr;
uint16_t m_attr_max_len = 0;
uint16_t m_attr_len = 0;
uint16_t m_capacity = 0;
#if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED
time_t m_timestamp = 0;
#endif
void deepCopy(const NimBLEAttValue & source);
class NimBLEAttValue {
uint8_t* m_attr_value{};
uint16_t m_attr_max_len{};
uint16_t m_attr_len{};
uint16_t m_capacity{};
# if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED
time_t m_timestamp{};
# endif
void deepCopy(const NimBLEAttValue& source);
public:
public:
/**
* @brief Default constructor.
* @param[in] init_len The initial size in bytes.
* @param[in] max_len The max size in bytes that the value can be.
*/
NimBLEAttValue(uint16_t init_len = CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);
NimBLEAttValue(uint16_t init_len = CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);
/**
* @brief Construct with an initial value from a buffer.
@ -86,15 +87,6 @@ public:
NimBLEAttValue(const uint8_t *value, uint16_t len,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);
/**
* @brief Construct with an initializer list.
* @param list An initializer list containing the initial value to set.
* @param[in] max_len The max size in bytes that the value can be.
*/
NimBLEAttValue(std::initializer_list<uint8_t> list,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN)
:NimBLEAttValue(list.begin(), (uint16_t)list.size(), max_len){}
/**
* @brief Construct with an initial value from a const char string.
* @param value A pointer to the initial value to set.
@ -103,13 +95,21 @@ public:
NimBLEAttValue(const char *value, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN)
:NimBLEAttValue((uint8_t*)value, (uint16_t)strlen(value), max_len){}
/**
* @brief Construct with an initializer list.
* @param list An initializer list containing the initial value to set.
* @param[in] max_len The max size in bytes that the value can be.
*/
NimBLEAttValue(std::initializer_list<uint8_t> list, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN)
: NimBLEAttValue(list.begin(), list.size(), max_len) {}
/**
* @brief Construct with an initial value from a std::string.
* @param str A std::string containing to the initial value to set.
* @param[in] max_len The max size in bytes that the value can be.
*/
NimBLEAttValue(const std::string str, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN)
:NimBLEAttValue((uint8_t*)str.data(), (uint16_t)str.length(), max_len){}
: NimBLEAttValue(reinterpret_cast<const uint8_t*>(&str[0]), str.length(), max_len) {}
/**
* @brief Construct with an initial value from a std::vector<uint8_t>.
@ -117,90 +117,99 @@ public:
* @param[in] max_len The max size in bytes that the value can be.
*/
NimBLEAttValue(const std::vector<uint8_t> vec, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN)
:NimBLEAttValue(&vec[0], (uint16_t)vec.size(), max_len){}
: NimBLEAttValue(&vec[0], vec.size(), max_len) {}
#ifdef NIMBLE_CPP_ARDUINO_STRING_AVAILABLE
# ifdef NIMBLE_CPP_ARDUINO_STRING_AVAILABLE
/**
* @brief Construct with an initial value from an Arduino String.
* @param str An Arduino String containing to the initial value to set.
* @param[in] max_len The max size in bytes that the value can be.
*/
NimBLEAttValue(const String str, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN)
:NimBLEAttValue((uint8_t*)str.c_str(), str.length(), max_len){}
#endif
: NimBLEAttValue(reinterpret_cast<const uint8_t*>(str.c_str()), str.length(), max_len) {}
# endif
/** @brief Copy constructor */
NimBLEAttValue(const NimBLEAttValue & source) { deepCopy(source); }
NimBLEAttValue(const NimBLEAttValue& source) { deepCopy(source); }
/** @brief Move constructor */
NimBLEAttValue(NimBLEAttValue && source) { *this = std::move(source); }
NimBLEAttValue(NimBLEAttValue&& source) { *this = std::move(source); }
/** @brief Destructor */
~NimBLEAttValue();
/** @brief Returns the max size in bytes */
uint16_t max_size() const { return m_attr_max_len; }
uint16_t max_size() const { return m_attr_max_len; }
/** @brief Returns the currently allocated capacity in bytes */
uint16_t capacity() const { return m_capacity; }
uint16_t capacity() const { return m_capacity; }
/** @brief Returns the current length of the value in bytes */
uint16_t length() const { return m_attr_len; }
uint16_t length() const { return m_attr_len; }
/** @brief Returns the current size of the value in bytes */
uint16_t size() const { return m_attr_len; }
uint16_t size() const { return m_attr_len; }
/** @brief Returns a pointer to the internal buffer of the value */
const uint8_t* data() const { return m_attr_value; }
const uint8_t* data() const { return m_attr_value; }
/** @brief Returns a pointer to the internal buffer of the value as a const char* */
const char* c_str() const { return (const char*)m_attr_value; }
const char* c_str() const { return reinterpret_cast<const char*>(m_attr_value); }
/** @brief Iterator begin */
const uint8_t* begin() const { return m_attr_value; }
const uint8_t* begin() const { return m_attr_value; }
/** @brief Iterator end */
const uint8_t* end() const { return m_attr_value + m_attr_len; }
const uint8_t* end() const { return m_attr_value + m_attr_len; }
#if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED
# if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED
/** @brief Returns a timestamp of when the value was last updated */
time_t getTimeStamp() const { return m_timestamp; }
time_t getTimeStamp() const { return m_timestamp; }
/** @brief Set the timestamp to the current time */
void setTimeStamp() { m_timestamp = time(nullptr); }
void setTimeStamp() { m_timestamp = time(nullptr); }
/**
* @brief Set the timestamp to the specified time
* @param[in] t The timestamp value to set
*/
void setTimeStamp(time_t t) { m_timestamp = t; }
#else
time_t getTimeStamp() const { return 0; }
void setTimeStamp() { }
void setTimeStamp(time_t t) { }
#endif
void setTimeStamp(time_t t) { m_timestamp = t; }
# else
time_t getTimeStamp() const { return 0; }
void setTimeStamp() {}
void setTimeStamp(time_t t) {}
# endif
/**
* @brief Set the value from a buffer
* @param[in] value A ponter to a buffer containing the value.
* @param[in] value A pointer to a buffer containing the value.
* @param[in] len The length of the value in bytes.
* @returns True if successful.
*/
bool setValue(const uint8_t *value, uint16_t len);
bool setValue(const uint8_t* value, uint16_t len);
/**
* @brief Set value to the value of const char*.
* @param [in] s A ponter to a const char value to set.
* @param [in] s A pointer to a const char value to set.
* @param [in] len The length of the value in bytes, defaults to strlen(s).
*/
bool setValue(const char* s) {
return setValue((uint8_t*)s, (uint16_t)strlen(s)); }
bool setValue(const char* s, uint16_t len = 0) {
if (len == 0) {
len = strlen(s);
}
return setValue(reinterpret_cast<const uint8_t*>(s), len);
}
/**
* @brief Get a pointer to the value buffer with timestamp.
* @param[in] timestamp A ponter to a time_t variable to store the timestamp.
* @returns A pointer to the internal value buffer.
*/
const uint8_t* getValue(time_t *timestamp);
const NimBLEAttValue& getValue(time_t* timestamp = nullptr) const {
if (timestamp != nullptr) {
# if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED
*timestamp = m_timestamp;
# else
*timestamp = 0;
# endif
}
return *this;
}
/**
* @brief Append data to the value.
@ -208,39 +217,25 @@ public:
* @param[in] len The length of the value to append in bytes.
* @returns A reference to the appended NimBLEAttValue.
*/
NimBLEAttValue& append(const uint8_t *value, uint16_t len);
NimBLEAttValue& append(const uint8_t* value, uint16_t len);
/*********************** Template Functions ************************/
/**
* @brief Template to set value to the value of <type\>val.
* @param [in] s The <type\>value to set.
* @details Only used for types without a `c_str()` method.
* @note This function is only availabe if the type T is not a pointer.
*/
template<typename T>
#ifdef _DOXYGEN_
bool
#else
typename std::enable_if<!Has_c_str_len<T>::value, bool>::type
#endif
setValue(const T &s) {
return setValue((uint8_t*)&s, sizeof(T));
}
/**
* @brief Template to set value to the value of <type\>val.
* @param [in] s The <type\>value to set.
* @details Only used if the <type\> has a `c_str()` method.
*/
template<typename T>
#ifdef _DOXYGEN_
bool
#else
typename std::enable_if<Has_c_str_len<T>::value, bool>::type
#endif
setValue(const T & s) {
return setValue((uint8_t*)s.c_str(), (uint16_t)s.length());
template <typename T>
typename std::enable_if<!std::is_pointer<T>::value, bool>::type
setValue(const T& s) {
if constexpr (Has_data_size<T>::value) {
return setValue(reinterpret_cast<const uint8_t*>(s.data()), s.size());
} else if constexpr (Has_c_str_length<T>::value) {
return setValue(reinterpret_cast<const uint8_t*>(s.c_str()), s.length());
} else {
return setValue(reinterpret_cast<const uint8_t*>(&s), sizeof(s));
}
}
/**
@ -253,195 +248,67 @@ public:
* less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>getValue<type>(&timestamp, skipSizeCheck);</tt>
*/
template<typename T>
T getValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) {
if(!skipSizeCheck && size() < sizeof(T)) {
return T();
}
return *((T *)getValue(timestamp));
}
template <typename T>
T getValue(time_t* timestamp = nullptr, bool skipSizeCheck = false) const {
if (!skipSizeCheck && size() < sizeof(T)) {
return T();
}
if (timestamp != nullptr) {
# if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED
*timestamp = m_timestamp;
# else
*timestamp = 0;
# endif
}
return *(reinterpret_cast<const T*>(m_attr_value));
}
/*********************** Operators ************************/
/** @brief Subscript operator */
uint8_t operator [](int pos) const {
assert(pos < m_attr_len && "out of range"); return m_attr_value[pos]; }
uint8_t operator[](int pos) const {
NIMBLE_CPP_DEBUG_ASSERT(pos < m_attr_len);
return m_attr_value[pos];
}
/** @brief Operator; Get the value as a std::vector<uint8_t>. */
operator std::vector<uint8_t>() const {
return std::vector<uint8_t>(m_attr_value, m_attr_value + m_attr_len); }
operator std::vector<uint8_t>() const { return std::vector<uint8_t>(m_attr_value, m_attr_value + m_attr_len); }
/** @brief Operator; Get the value as a std::string. */
operator std::string() const {
return std::string((char*)m_attr_value, m_attr_len); }
operator std::string() const { return std::string(reinterpret_cast<char*>(m_attr_value), m_attr_len); }
/** @brief Operator; Get the value as a const uint8_t*. */
operator const uint8_t*() const { return m_attr_value; }
/** @brief Operator; Append another NimBLEAttValue. */
NimBLEAttValue& operator +=(const NimBLEAttValue & source) {
return append(source.data(), source.size()); }
NimBLEAttValue& operator+=(const NimBLEAttValue& source) { return append(source.data(), source.size()); }
/** @brief Operator; Set the value from a std::string source. */
NimBLEAttValue& operator =(const std::string & source) {
setValue((uint8_t*)source.data(), (uint16_t)source.size()); return *this; }
NimBLEAttValue& operator=(const std::string& source) {
setValue(reinterpret_cast<const uint8_t*>(&source[0]), source.size());
return *this;
}
/** @brief Move assignment operator */
NimBLEAttValue& operator =(NimBLEAttValue && source);
NimBLEAttValue& operator=(NimBLEAttValue&& source);
/** @brief Copy assignment operator */
NimBLEAttValue& operator =(const NimBLEAttValue & source);
NimBLEAttValue& operator=(const NimBLEAttValue& source);
/** @brief Equality operator */
bool operator ==(const NimBLEAttValue & source) {
return (m_attr_len == source.size()) ?
memcmp(m_attr_value, source.data(), m_attr_len) == 0 : false; }
bool operator==(const NimBLEAttValue& source) const {
return (m_attr_len == source.size()) ? memcmp(m_attr_value, source.data(), m_attr_len) == 0 : false;
}
/** @brief Inequality operator */
bool operator !=(const NimBLEAttValue & source){ return !(*this == source); }
bool operator!=(const NimBLEAttValue& source) const { return !(*this == source); }
#ifdef NIMBLE_CPP_ARDUINO_STRING_AVAILABLE
# ifdef NIMBLE_CPP_ARDUINO_STRING_AVAILABLE
/** @brief Operator; Get the value as an Arduino String value. */
operator String() const { return String((char*)m_attr_value); }
#endif
operator String() const { return String(reinterpret_cast<char*>(m_attr_value)); }
# endif
};
inline NimBLEAttValue::NimBLEAttValue(uint16_t init_len, uint16_t max_len) {
m_attr_value = (uint8_t*)calloc(init_len + 1, 1);
assert(m_attr_value && "No Mem");
m_attr_max_len = std::min(BLE_ATT_ATTR_MAX_LEN, (int)max_len);
m_attr_len = 0;
m_capacity = init_len;
setTimeStamp(0);
}
inline NimBLEAttValue::NimBLEAttValue(const uint8_t *value, uint16_t len, uint16_t max_len)
: NimBLEAttValue(len, max_len) {
memcpy(m_attr_value, value, len);
m_attr_value[len] = '\0';
m_attr_len = len;
}
inline NimBLEAttValue::~NimBLEAttValue() {
if(m_attr_value != nullptr) {
free(m_attr_value);
}
}
inline NimBLEAttValue& NimBLEAttValue::operator =(NimBLEAttValue && source) {
if (this != &source){
free(m_attr_value);
m_attr_value = source.m_attr_value;
m_attr_max_len = source.m_attr_max_len;
m_attr_len = source.m_attr_len;
m_capacity = source.m_capacity;
setTimeStamp(source.getTimeStamp());
source.m_attr_value = nullptr;
}
return *this;
}
inline NimBLEAttValue& NimBLEAttValue::operator =(const NimBLEAttValue & source) {
if (this != &source) {
deepCopy(source);
}
return *this;
}
inline void NimBLEAttValue::deepCopy(const NimBLEAttValue & source) {
uint8_t* res = (uint8_t*)realloc( m_attr_value, source.m_capacity + 1);
assert(res && "deepCopy: realloc failed");
ble_npl_hw_enter_critical();
m_attr_value = res;
m_attr_max_len = source.m_attr_max_len;
m_attr_len = source.m_attr_len;
m_capacity = source.m_capacity;
setTimeStamp(source.getTimeStamp());
memcpy(m_attr_value, source.m_attr_value, m_attr_len + 1);
ble_npl_hw_exit_critical(0);
}
inline const uint8_t* NimBLEAttValue::getValue(time_t *timestamp) {
if(timestamp != nullptr) {
#if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED
*timestamp = m_timestamp;
#else
*timestamp = 0;
#endif
}
return m_attr_value;
}
inline bool NimBLEAttValue::setValue(const uint8_t *value, uint16_t len) {
if (len > m_attr_max_len) {
NIMBLE_LOGE("NimBLEAttValue", "value exceeds max, len=%u, max=%u",
len, m_attr_max_len);
return false;
}
uint8_t *res = m_attr_value;
if (len > m_capacity) {
res = (uint8_t*)realloc(m_attr_value, (len + 1));
m_capacity = len;
}
assert(res && "setValue: realloc failed");
#if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED
time_t t = time(nullptr);
#else
time_t t = 0;
#endif
ble_npl_hw_enter_critical();
m_attr_value = res;
memcpy(m_attr_value, value, len);
m_attr_value[len] = '\0';
m_attr_len = len;
setTimeStamp(t);
ble_npl_hw_exit_critical(0);
return true;
}
inline NimBLEAttValue& NimBLEAttValue::append(const uint8_t *value, uint16_t len) {
if (len < 1) {
return *this;
}
if ((m_attr_len + len) > m_attr_max_len) {
NIMBLE_LOGE("NimBLEAttValue", "val > max, len=%u, max=%u",
len, m_attr_max_len);
return *this;
}
uint8_t* res = m_attr_value;
uint16_t new_len = m_attr_len + len;
if (new_len > m_capacity) {
res = (uint8_t*)realloc(m_attr_value, (new_len + 1));
m_capacity = new_len;
}
assert(res && "append: realloc failed");
#if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED
time_t t = time(nullptr);
#else
time_t t = 0;
#endif
ble_npl_hw_enter_critical();
m_attr_value = res;
memcpy(m_attr_value + m_attr_len, value, len);
m_attr_len = new_len;
m_attr_value[m_attr_len] = '\0';
setTimeStamp(t);
ble_npl_hw_exit_critical(0);
return *this;
}
#endif /*(CONFIG_BT_ENABLED) */
#endif /* MAIN_NIMBLEATTVALUE_H_ */
#endif /* NIMBLE_CPP_ATTVALUE_H_ */

50
src/NimBLEAttribute.h Normal file
View File

@ -0,0 +1,50 @@
/*
* NimBLEAttribute.h
*
* Created: on July 28 2024
* Author H2zero
*/
#ifndef NIMBLE_CPP_ATTRIBUTE_H_
#define NIMBLE_CPP_ATTRIBUTE_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && (defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) || defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL))
# include "NimBLEUUID.h"
/**
* @brief A base class for BLE attributes.
*/
class NimBLEAttribute {
public:
/**
* @brief Get the UUID of the attribute.
* @return The UUID.
*/
const NimBLEUUID& getUUID() const { return m_uuid; }
/**
* @brief Get the handle of the attribute.
*/
uint16_t getHandle() const { return m_handle; };
protected:
/**
* @brief Construct a new NimBLEAttribute object.
* @param [in] handle The handle of the attribute.
* @param [in] uuid The UUID of the attribute.
*/
NimBLEAttribute(const NimBLEUUID& uuid, uint16_t handle) : m_uuid{uuid}, m_handle{handle} {}
/**
* @brief Destroy the NimBLEAttribute object.
*/
~NimBLEAttribute() = default;
const NimBLEUUID m_uuid{};
uint16_t m_handle{0};
};
#endif // CONFIG_BT_ENABLED && (CONFIG_BT_NIMBLE_ROLE_PERIPHERAL || CONFIG_BT_NIMBLE_ROLE_CENTRAL)
#endif // NIMBLE_CPP_ATTRIBUTE_H_

View File

@ -79,7 +79,7 @@ uint16_t NimBLEBeacon::getMinor() {
* @return The UUID advertised.
*/
NimBLEUUID NimBLEBeacon::getProximityUUID() {
return NimBLEUUID(m_beaconData.proximityUUID, 16, true);
return NimBLEUUID(m_beaconData.proximityUUID, 16).reverseByteOrder();
}
@ -130,7 +130,7 @@ void NimBLEBeacon::setManufacturerId(uint16_t manufacturerId) {
*/
void NimBLEBeacon::setMinor(uint16_t minor) {
m_beaconData.minor = ENDIAN_CHANGE_U16(minor);
} // setMinior
} // setMinor
/**
@ -140,9 +140,7 @@ void NimBLEBeacon::setMinor(uint16_t minor) {
void NimBLEBeacon::setProximityUUID(const NimBLEUUID &uuid) {
NimBLEUUID temp_uuid = uuid;
temp_uuid.to128();
std::reverse_copy(temp_uuid.getNative()->u128.value,
temp_uuid.getNative()->u128.value + 16,
m_beaconData.proximityUUID);
std::reverse_copy(temp_uuid.getValue(), temp_uuid.getValue() + 16, m_beaconData.proximityUUID);
} // setProximityUUID

View File

@ -13,18 +13,16 @@
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#include "NimBLECharacteristic.h"
#include "NimBLE2904.h"
#include "NimBLEDevice.h"
#include "NimBLELog.h"
# include "NimBLECharacteristic.h"
# include "NimBLE2904.h"
# include "NimBLEDevice.h"
# include "NimBLELog.h"
#define NULL_HANDLE (0xffff)
#define NIMBLE_SUB_NOTIFY 0x0001
#define NIMBLE_SUB_INDICATE 0x0002
# define NIMBLE_SUB_NOTIFY 0x0001
# define NIMBLE_SUB_INDICATE 0x0002
static NimBLECharacteristicCallbacks defaultCallback;
static const char* LOG_TAG = "NimBLECharacteristic";
static const char* LOG_TAG = "NimBLECharacteristic";
/**
* @brief Construct a characteristic
@ -33,10 +31,8 @@ static const char* LOG_TAG = "NimBLECharacteristic";
* @param [in] max_len - The maximum length in bytes that the characteristic value can hold. (Default: 512 bytes for esp32, 20 for all others).
* @param [in] pService - pointer to the service instance this characteristic belongs to.
*/
NimBLECharacteristic::NimBLECharacteristic(const char* uuid, uint16_t properties,
uint16_t max_len, NimBLEService* pService)
: NimBLECharacteristic(NimBLEUUID(uuid), properties, max_len, pService) {
}
NimBLECharacteristic::NimBLECharacteristic(const char* uuid, uint16_t properties, uint16_t max_len, NimBLEService* pService)
: NimBLECharacteristic(NimBLEUUID(uuid), properties, max_len, pService) {}
/**
* @brief Construct a characteristic
@ -45,27 +41,20 @@ NimBLECharacteristic::NimBLECharacteristic(const char* uuid, uint16_t properties
* @param [in] max_len - The maximum length in bytes that the characteristic value can hold. (Default: 512 bytes for esp32, 20 for all others).
* @param [in] pService - pointer to the service instance this characteristic belongs to.
*/
NimBLECharacteristic::NimBLECharacteristic(const NimBLEUUID &uuid, uint16_t properties,
uint16_t max_len, NimBLEService* pService)
: m_value(std::min(CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH , (int)max_len), max_len) {
m_uuid = uuid;
m_handle = NULL_HANDLE;
m_properties = properties;
m_pCallbacks = &defaultCallback;
m_pService = pService;
m_removed = 0;
NimBLECharacteristic::NimBLECharacteristic(const NimBLEUUID& uuid, uint16_t properties, uint16_t max_len, NimBLEService* pService)
: NimBLELocalValueAttribute{uuid, 0, max_len}, m_pCallbacks{&defaultCallback}, m_pService{pService} {
setProperties(properties);
} // NimBLECharacteristic
/**
* @brief Destructor.
*/
NimBLECharacteristic::~NimBLECharacteristic() {
for(auto &it : m_dscVec) {
delete it;
for (const auto& dsc : m_vDescriptors) {
delete dsc;
}
} // ~NimBLECharacteristic
/**
* @brief Create a new BLE Descriptor associated with this characteristic.
* @param [in] uuid - The UUID of the descriptor.
@ -77,7 +66,6 @@ NimBLEDescriptor* NimBLECharacteristic::createDescriptor(const char* uuid, uint3
return createDescriptor(NimBLEUUID(uuid), properties, max_len);
}
/**
* @brief Create a new BLE Descriptor associated with this characteristic.
* @param [in] uuid - The UUID of the descriptor.
@ -85,11 +73,9 @@ NimBLEDescriptor* NimBLECharacteristic::createDescriptor(const char* uuid, uint3
* @param [in] max_len - The max length in bytes of the descriptor value.
* @return The new BLE descriptor.
*/
NimBLEDescriptor* NimBLECharacteristic::createDescriptor(const NimBLEUUID &uuid, uint32_t properties, uint16_t max_len) {
NimBLEDescriptor* NimBLECharacteristic::createDescriptor(const NimBLEUUID& uuid, uint32_t properties, uint16_t max_len) {
NimBLEDescriptor* pDescriptor = nullptr;
if(uuid == NimBLEUUID(uint16_t(0x2902))) {
assert(0 && "0x2902 descriptors cannot be manually created");
} else if (uuid == NimBLEUUID(uint16_t(0x2904))) {
if (uuid == NimBLEUUID(uint16_t(0x2904))) {
pDescriptor = new NimBLE2904(this);
} else {
pDescriptor = new NimBLEDescriptor(uuid, properties, max_len, this);
@ -99,47 +85,52 @@ NimBLEDescriptor* NimBLECharacteristic::createDescriptor(const NimBLEUUID &uuid,
return pDescriptor;
} // createDescriptor
/**
* @brief Add a descriptor to the characteristic.
* @param [in] pDescriptor A pointer to the descriptor to add.
*/
void NimBLECharacteristic::addDescriptor(NimBLEDescriptor *pDescriptor) {
void NimBLECharacteristic::addDescriptor(NimBLEDescriptor* pDescriptor) {
bool foundRemoved = false;
if(pDescriptor->m_removed > 0) {
for(auto& it : m_dscVec) {
if(it == pDescriptor) {
if (pDescriptor->getRemoved() > 0) {
for (const auto& dsc : m_vDescriptors) {
if (dsc == pDescriptor) {
foundRemoved = true;
pDescriptor->m_removed = 0;
pDescriptor->setRemoved(0);
}
}
}
if(!foundRemoved) {
m_dscVec.push_back(pDescriptor);
// Check if the descriptor is already in the vector and if so, return.
for (const auto& dsc : m_vDescriptors) {
if (dsc == pDescriptor) {
pDescriptor->setCharacteristic(this); // Update the characteristic pointer in the descriptor.
return;
}
}
if (!foundRemoved) {
m_vDescriptors.push_back(pDescriptor);
}
pDescriptor->setCharacteristic(this);
NimBLEDevice::getServer()->serviceChanged();
}
/**
* @brief Remove a descriptor from the characterisitc.
* @param[in] pDescriptor A pointer to the descriptor instance to remove from the characterisitc.
* @brief Remove a descriptor from the characteristic.
* @param[in] pDescriptor A pointer to the descriptor instance to remove from the characteristic.
* @param[in] deleteDsc If true it will delete the descriptor instance and free it's resources.
*/
void NimBLECharacteristic::removeDescriptor(NimBLEDescriptor *pDescriptor, bool deleteDsc) {
void NimBLECharacteristic::removeDescriptor(NimBLEDescriptor* pDescriptor, bool deleteDsc) {
// Check if the descriptor was already removed and if so, check if this
// is being called to delete the object and do so if requested.
// Otherwise, ignore the call and return.
if(pDescriptor->m_removed > 0) {
if(deleteDsc) {
for(auto it = m_dscVec.begin(); it != m_dscVec.end(); ++it) {
if (pDescriptor->getRemoved() > 0) {
if (deleteDsc) {
for (auto it = m_vDescriptors.begin(); it != m_vDescriptors.end(); ++it) {
if ((*it) == pDescriptor) {
delete *it;
m_dscVec.erase(it);
delete (*it);
m_vDescriptors.erase(it);
break;
}
}
@ -148,30 +139,28 @@ void NimBLECharacteristic::removeDescriptor(NimBLEDescriptor *pDescriptor, bool
return;
}
pDescriptor->m_removed = deleteDsc ? NIMBLE_ATT_REMOVE_DELETE : NIMBLE_ATT_REMOVE_HIDE;
pDescriptor->setRemoved(deleteDsc ? NIMBLE_ATT_REMOVE_DELETE : NIMBLE_ATT_REMOVE_HIDE);
NimBLEDevice::getServer()->serviceChanged();
} // removeDescriptor
/**
* @brief Return the BLE Descriptor for the given UUID.
* @param [in] uuid The UUID of the descriptor.
* @return A pointer to the descriptor object or nullptr if not found.
*/
NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const char* uuid) {
NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const char* uuid) const {
return getDescriptorByUUID(NimBLEUUID(uuid));
} // getDescriptorByUUID
/**
* @brief Return the BLE Descriptor for the given UUID.
* @param [in] uuid The UUID of the descriptor.
* @return A pointer to the descriptor object or nullptr if not found.
*/
NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const NimBLEUUID &uuid) {
for (auto &it : m_dscVec) {
if (it->getUUID() == uuid) {
return it;
NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const NimBLEUUID& uuid) const {
for (const auto& dsc : m_vDescriptors) {
if (dsc->getUUID() == uuid) {
return dsc;
}
}
return nullptr;
@ -182,337 +171,210 @@ NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const NimBLEUUID &uu
* @param [in] handle The handle of the descriptor.
* @return A pointer to the descriptor object or nullptr if not found.
*/
NimBLEDescriptor *NimBLECharacteristic::getDescriptorByHandle(uint16_t handle) {
for (auto &it : m_dscVec) {
if (it->getHandle() == handle) {
return it;
NimBLEDescriptor* NimBLECharacteristic::getDescriptorByHandle(uint16_t handle) const {
for (const auto& dsc : m_vDescriptors) {
if (dsc->getHandle() == handle) {
return dsc;
}
}
return nullptr;
}
/**
* @brief Get the handle of the characteristic.
* @return The handle of the characteristic.
*/
uint16_t NimBLECharacteristic::getHandle() {
return m_handle;
} // getHandle
} // getDescriptorByHandle
/**
* @brief Get the properties of the characteristic.
* @return The properties of the characteristic.
*/
uint16_t NimBLECharacteristic::getProperties() {
uint16_t NimBLECharacteristic::getProperties() const {
return m_properties;
} // getProperties
/**
* @brief Get the service associated with this characteristic.
* @brief Get the service that owns this characteristic.
*/
NimBLEService* NimBLECharacteristic::getService() {
NimBLEService* NimBLECharacteristic::getService() const {
return m_pService;
} // getService
void NimBLECharacteristic::setService(NimBLEService *pService) {
void NimBLECharacteristic::setService(NimBLEService* pService) {
m_pService = pService;
}
/**
* @brief Get the UUID of the characteristic.
* @return The UUID of the characteristic.
*/
NimBLEUUID NimBLECharacteristic::getUUID() {
return m_uuid;
} // getUUID
/**
* @brief Retrieve the current value of the characteristic.
* @return The NimBLEAttValue containing the current characteristic value.
*/
NimBLEAttValue NimBLECharacteristic::getValue(time_t *timestamp) {
if(timestamp != nullptr) {
m_value.getValue(timestamp);
}
return m_value;
} // getValue
/**
* @brief Retrieve the the current data length of the characteristic.
* @return The length of the current characteristic data.
*/
size_t NimBLECharacteristic::getDataLength() {
return m_value.size();
}
/**
* @brief STATIC callback to handle events from the NimBLE stack.
*/
int NimBLECharacteristic::handleGapEvent(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt,
void *arg)
{
const ble_uuid_t *uuid;
int rc;
struct ble_gap_conn_desc desc;
NimBLECharacteristic* pCharacteristic = (NimBLECharacteristic*)arg;
NIMBLE_LOGD(LOG_TAG, "Characteristic %s %s event", pCharacteristic->getUUID().toString().c_str(),
ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR ? "Read" : "Write");
uuid = ctxt->chr->uuid;
if(ble_uuid_cmp(uuid, &pCharacteristic->getUUID().getNative()->u) == 0){
switch(ctxt->op) {
case BLE_GATT_ACCESS_OP_READ_CHR: {
// If the packet header is only 8 bytes this is a follow up of a long read
// so we don't want to call the onRead() callback again.
if(ctxt->om->om_pkthdr_len > 8) {
rc = ble_gap_conn_find(conn_handle, &desc);
assert(rc == 0);
pCharacteristic->m_pCallbacks->onRead(pCharacteristic);
pCharacteristic->m_pCallbacks->onRead(pCharacteristic, &desc);
}
ble_npl_hw_enter_critical();
rc = os_mbuf_append(ctxt->om, pCharacteristic->m_value.data(), pCharacteristic->m_value.size());
ble_npl_hw_exit_critical(0);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
case BLE_GATT_ACCESS_OP_WRITE_CHR: {
uint16_t att_max_len = pCharacteristic->m_value.max_size();
if (ctxt->om->om_len > att_max_len) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
uint8_t buf[att_max_len];
size_t len = ctxt->om->om_len;
memcpy(buf, ctxt->om->om_data,len);
os_mbuf *next;
next = SLIST_NEXT(ctxt->om, om_next);
while(next != NULL){
if((len + next->om_len) > att_max_len) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
memcpy(&buf[len], next->om_data, next->om_len);
len += next->om_len;
next = SLIST_NEXT(next, om_next);
}
rc = ble_gap_conn_find(conn_handle, &desc);
assert(rc == 0);
pCharacteristic->setValue(buf, len);
pCharacteristic->m_pCallbacks->onWrite(pCharacteristic);
pCharacteristic->m_pCallbacks->onWrite(pCharacteristic, &desc);
return 0;
}
default:
break;
}
}
return BLE_ATT_ERR_UNLIKELY;
}
} // setService
/**
* @brief Get the number of clients subscribed to the characteristic.
* @returns Number of clients subscribed to notifications / indications.
*/
size_t NimBLECharacteristic::getSubscribedCount() {
size_t NimBLECharacteristic::getSubscribedCount() const {
return m_subscribedVec.size();
}
/**
* @brief Set the subscribe status for this characteristic.\n
* This will maintain a vector of subscribed clients and their indicate/notify status.
*/
void NimBLECharacteristic::setSubscribe(struct ble_gap_event *event) {
ble_gap_conn_desc desc;
if(ble_gap_conn_find(event->subscribe.conn_handle, &desc) != 0) {
return;
}
void NimBLECharacteristic::setSubscribe(const ble_gap_event* event, NimBLEConnInfo& connInfo) {
uint16_t subVal = 0;
if(event->subscribe.cur_notify > 0 && (m_properties & NIMBLE_PROPERTY::NOTIFY)) {
if (event->subscribe.cur_notify > 0 && (m_properties & NIMBLE_PROPERTY::NOTIFY)) {
subVal |= NIMBLE_SUB_NOTIFY;
}
if(event->subscribe.cur_indicate && (m_properties & NIMBLE_PROPERTY::INDICATE)) {
if (event->subscribe.cur_indicate && (m_properties & NIMBLE_PROPERTY::INDICATE)) {
subVal |= NIMBLE_SUB_INDICATE;
}
NIMBLE_LOGI(LOG_TAG, "New subscribe value for conn: %d val: %d",
event->subscribe.conn_handle, subVal);
NIMBLE_LOGI(LOG_TAG, "New subscribe value for conn: %d val: %d", connInfo.getConnHandle(), subVal);
if(!event->subscribe.cur_indicate && event->subscribe.prev_indicate) {
NimBLEDevice::getServer()->clearIndicateWait(event->subscribe.conn_handle);
if (!event->subscribe.cur_indicate && event->subscribe.prev_indicate) {
NimBLEDevice::getServer()->clearIndicateWait(connInfo.getConnHandle());
}
auto it = m_subscribedVec.begin();
for(;it != m_subscribedVec.end(); ++it) {
if((*it).first == event->subscribe.conn_handle) {
for (; it != m_subscribedVec.end(); ++it) {
if ((*it).first == connInfo.getConnHandle()) {
break;
}
}
if(subVal > 0) {
if(it == m_subscribedVec.end()) {
m_subscribedVec.push_back({event->subscribe.conn_handle, subVal});
if (subVal > 0) {
if (it == m_subscribedVec.end()) {
m_subscribedVec.push_back({connInfo.getConnHandle(), subVal});
} else {
(*it).second = subVal;
}
} else if(it != m_subscribedVec.end()) {
} else if (it != m_subscribedVec.end()) {
m_subscribedVec.erase(it);
}
m_pCallbacks->onSubscribe(this, &desc, subVal);
m_pCallbacks->onSubscribe(this, connInfo, subVal);
}
/**
* @brief Send an indication.
* @param[in] conn_handle Connection handle to send an individual indication, or BLE_HS_CONN_HANDLE_NONE to send
* the indication to all subscribed clients.
*/
void NimBLECharacteristic::indicate() {
notify(false);
void NimBLECharacteristic::indicate(uint16_t conn_handle) const {
sendValue(m_value.data(), m_value.size(), false, conn_handle);
} // indicate
/**
* @brief Send an indication.
* @param[in] value A pointer to the data to send.
* @param[in] length The length of the data to send.
* @param[in] conn_handle Connection handle to send an individual indication, or BLE_HS_CONN_HANDLE_NONE to send
* the indication to all subscribed clients.
*/
void NimBLECharacteristic::indicate(const uint8_t* value, size_t length) {
notify(value, length, false);
void NimBLECharacteristic::indicate(const uint8_t* value, size_t length, uint16_t conn_handle) const {
sendValue(value, length, false, conn_handle);
} // indicate
/**
* @brief Send a notification.
* @param[in] conn_handle Connection handle to send an individual notification, or BLE_HS_CONN_HANDLE_NONE to send
* the notification to all subscribed clients.
*/
void NimBLECharacteristic::notify(uint16_t conn_handle) const {
sendValue(m_value.data(), m_value.size(), true, conn_handle);
} // notify
/**
* @brief Send an indication.
* @param[in] value A std::vector<uint8_t> containing the value to send as the notification value.
* @brief Send a notification.
* @param[in] value A pointer to the data to send.
* @param[in] length The length of the data to send.
* @param[in] conn_handle Connection handle to send an individual notification, or BLE_HS_CONN_HANDLE_NONE to send
* the notification to all subscribed clients.
*/
void NimBLECharacteristic::indicate(const std::vector<uint8_t>& value) {
notify(value.data(), value.size(), false);
void NimBLECharacteristic::notify(const uint8_t* value, size_t length, uint16_t conn_handle) const {
sendValue(value, length, true, conn_handle);
} // indicate
/**
* @brief Send a notification or indication.
* @param[in] is_notification if true sends a notification, false sends an indication.
*/
void NimBLECharacteristic::notify(bool is_notification) {
notify(m_value.data(), m_value.length(), is_notification);
} // notify
/**
* @brief Send a notification or indication.
* @param[in] value A std::vector<uint8_t> containing the value to send as the notification value.
* @param[in] is_notification if true sends a notification, false sends an indication.
*/
void NimBLECharacteristic::notify(const std::vector<uint8_t>& value, bool is_notification) {
notify(value.data(), value.size(), is_notification);
} // notify
/**
* @brief Send a notification or indication.
* @brief Sends a notification or indication.
* @param[in] value A pointer to the data to send.
* @param[in] length The length of the data to send.
* @param[in] is_notification if true sends a notification, false sends an indication.
* @param[in] conn_handle Connection handle to send to a specific peer, or BLE_HS_CONN_HANDLE_NONE to send
* to all subscribed clients.
*/
void NimBLECharacteristic::notify(const uint8_t* value, size_t length, bool is_notification) {
NIMBLE_LOGD(LOG_TAG, ">> notify: length: %d", length);
void NimBLECharacteristic::sendValue(const uint8_t* value, size_t length, bool is_notification, uint16_t conn_handle) const {
NIMBLE_LOGD(LOG_TAG, ">> sendValue");
if(!(m_properties & NIMBLE_PROPERTY::NOTIFY) &&
!(m_properties & NIMBLE_PROPERTY::INDICATE))
{
NIMBLE_LOGE(LOG_TAG,
"<< notify-Error; Notify/indicate not enabled for characterisitc: %s",
std::string(getUUID()).c_str());
}
if (m_subscribedVec.size() == 0) {
NIMBLE_LOGD(LOG_TAG, "<< notify: No clients subscribed.");
if (is_notification && !(getProperties() & NIMBLE_PROPERTY::NOTIFY)) {
NIMBLE_LOGE(LOG_TAG, "<< sendValue: notification not enabled for characteristic");
return;
}
m_pCallbacks->onNotify(this);
if (!is_notification && !(getProperties() & NIMBLE_PROPERTY::INDICATE)) {
NIMBLE_LOGE(LOG_TAG, "<< sendValue: indication not enabled for characteristic");
return;
}
bool reqSec = (m_properties & BLE_GATT_CHR_F_READ_AUTHEN) ||
(m_properties & BLE_GATT_CHR_F_READ_AUTHOR) ||
(m_properties & BLE_GATT_CHR_F_READ_ENC);
int rc = 0;
for (auto &it : m_subscribedVec) {
uint16_t _mtu = getService()->getServer()->getPeerMTU(it.first) - 3;
if (!m_subscribedVec.size()) {
NIMBLE_LOGD(LOG_TAG, "<< sendValue: No clients subscribed.");
return;
}
for (const auto& it : m_subscribedVec) {
// check if connected and subscribed
if(_mtu == 0 || it.second == 0) {
if (!it.second) {
continue;
}
// sending to a specific client?
if ((conn_handle <= BLE_HCI_LE_CONN_HANDLE_MAX) && (it.first != conn_handle)) {
continue;
}
if (is_notification && !(it.second & NIMBLE_SUB_NOTIFY)) {
continue;
}
if (!is_notification && !(it.second & NIMBLE_SUB_INDICATE)) {
continue;
}
// check if security requirements are satisfied
if(reqSec) {
struct ble_gap_conn_desc desc;
rc = ble_gap_conn_find(it.first, &desc);
if(rc != 0 || !desc.sec_state.encrypted) {
if ((getProperties() & BLE_GATT_CHR_F_READ_AUTHEN) || (getProperties() & BLE_GATT_CHR_F_READ_AUTHOR) ||
(getProperties() & BLE_GATT_CHR_F_READ_ENC)) {
ble_gap_conn_desc desc;
if (ble_gap_conn_find(it.first, &desc) != 0 || !desc.sec_state.encrypted) {
continue;
}
}
if (length > _mtu) {
NIMBLE_LOGW(LOG_TAG, "- Truncating to %d bytes (maximum notify size)", _mtu);
}
if(is_notification && (!(it.second & NIMBLE_SUB_NOTIFY))) {
NIMBLE_LOGW(LOG_TAG,
"Sending notification to client subscribed to indications, sending indication instead");
is_notification = false;
}
if(!is_notification && (!(it.second & NIMBLE_SUB_INDICATE))) {
NIMBLE_LOGW(LOG_TAG,
"Sending indication to client subscribed to notification, sending notification instead");
is_notification = true;
}
// don't create the m_buf until we are sure to send the data or else
// we could be allocating a buffer that doesn't get released.
// We also must create it in each loop iteration because it is consumed with each host call.
os_mbuf *om = ble_hs_mbuf_from_flat(value, length);
os_mbuf* om = ble_hs_mbuf_from_flat(value, length);
if (!om) {
NIMBLE_LOGE(LOG_TAG, "<< sendValue: failed to allocate mbuf");
return;
}
if(!is_notification && (m_properties & NIMBLE_PROPERTY::INDICATE)) {
if(!NimBLEDevice::getServer()->setIndicateWait(it.first)) {
NIMBLE_LOGE(LOG_TAG, "prior Indication in progress");
os_mbuf_free_chain(om);
return;
if (is_notification) {
ble_gattc_notify_custom(it.first, getHandle(), om);
} else {
if (!NimBLEDevice::getServer()->setIndicateWait(it.first)) {
NIMBLE_LOGE(LOG_TAG, "<< sendValue: waiting for previous indicate");
os_mbuf_free_chain(om);
return;
}
rc = ble_gattc_indicate_custom(it.first, m_handle, om);
if(rc != 0){
if (ble_gattc_indicate_custom(it.first, getHandle(), om) != 0) {
NimBLEDevice::getServer()->clearIndicateWait(it.first);
}
} else {
ble_gattc_notify_custom(it.first, m_handle, om);
}
}
NIMBLE_LOGD(LOG_TAG, "<< notify");
} // Notify
NIMBLE_LOGD(LOG_TAG, "<< sendValue");
} // sendValue
void NimBLECharacteristic::readEvent(NimBLEConnInfo& connInfo) {
m_pCallbacks->onRead(this, connInfo);
}
void NimBLECharacteristic::writeEvent(const uint8_t* val, uint16_t len, NimBLEConnInfo& connInfo) {
setValue(val, len);
m_pCallbacks->onWrite(this, connInfo);
}
/**
* @brief Set the callback handlers for this characteristic.
@ -520,7 +382,7 @@ void NimBLECharacteristic::notify(const uint8_t* value, size_t length, bool is_n
* used to define any callbacks for the characteristic.
*/
void NimBLECharacteristic::setCallbacks(NimBLECharacteristicCallbacks* pCallbacks) {
if (pCallbacks != nullptr){
if (pCallbacks != nullptr) {
m_pCallbacks = pCallbacks;
} else {
m_pCallbacks = &defaultCallback;
@ -530,49 +392,21 @@ void NimBLECharacteristic::setCallbacks(NimBLECharacteristicCallbacks* pCallback
/**
* @brief Get the callback handlers for this characteristic.
*/
NimBLECharacteristicCallbacks* NimBLECharacteristic::getCallbacks() {
NimBLECharacteristicCallbacks* NimBLECharacteristic::getCallbacks() const {
return m_pCallbacks;
} //getCallbacks
/**
* @brief Set the value of the characteristic from a data buffer .
* @param [in] data The data buffer to set for the characteristic.
* @param [in] length The number of bytes in the data buffer.
*/
void NimBLECharacteristic::setValue(const uint8_t* data, size_t length) {
#if CONFIG_NIMBLE_CPP_LOG_LEVEL >= 4
char* pHex = NimBLEUtils::buildHexData(nullptr, data, length);
NIMBLE_LOGD(LOG_TAG, ">> setValue: length=%d, data=%s, characteristic UUID=%s",
length, pHex, getUUID().toString().c_str());
free(pHex);
#endif
m_value.setValue(data, length);
NIMBLE_LOGD(LOG_TAG, "<< setValue");
} // setValue
/**
* @brief Set the value of the characteristic from a `std::vector<uint8_t>`.\n
* @param [in] vec The std::vector<uint8_t> reference to set the characteristic value from.
*/
void NimBLECharacteristic::setValue(const std::vector<uint8_t>& vec) {
return setValue((uint8_t*)&vec[0], vec.size());
}// setValue
} // getCallbacks
/**
* @brief Return a string representation of the characteristic.
* @return A string representation of the characteristic.
*/
std::string NimBLECharacteristic::toString() {
std::string NimBLECharacteristic::toString() const {
std::string res = "UUID: " + m_uuid.toString() + ", handle : 0x";
char hex[5];
snprintf(hex, sizeof(hex), "%04x", m_handle);
char hex[5];
snprintf(hex, sizeof(hex), "%04x", getHandle());
res += hex;
res += " ";
if (m_properties & BLE_GATT_CHR_PROP_READ ) res += "Read ";
if (m_properties & BLE_GATT_CHR_PROP_READ) res += "Read ";
if (m_properties & BLE_GATT_CHR_PROP_WRITE) res += "Write ";
if (m_properties & BLE_GATT_CHR_PROP_WRITE_NO_RSP) res += "WriteNoResponse ";
if (m_properties & BLE_GATT_CHR_PROP_BROADCAST) res += "Broadcast ";
@ -581,68 +415,39 @@ std::string NimBLECharacteristic::toString() {
return res;
} // toString
NimBLECharacteristicCallbacks::~NimBLECharacteristicCallbacks() {}
/**
* @brief Callback function to support a read request.
* @param [in] pCharacteristic The characteristic that is the source of the event.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
*/
void NimBLECharacteristicCallbacks::onRead(NimBLECharacteristic* pCharacteristic) {
NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onRead: default");
} // onRead
/**
* @brief Callback function to support a read request.
* @param [in] pCharacteristic The characteristic that is the source of the event.
* @param [in] desc The connection description struct that is associated with the peer that performed the read.
*/
void NimBLECharacteristicCallbacks::onRead(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc) {
void NimBLECharacteristicCallbacks::onRead(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) {
NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onRead: default");
} // onRead
/**
* @brief Callback function to support a write request.
* @param [in] pCharacteristic The characteristic that is the source of the event.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
*/
void NimBLECharacteristicCallbacks::onWrite(NimBLECharacteristic* pCharacteristic) {
void NimBLECharacteristicCallbacks::onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) {
NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onWrite: default");
} // onWrite
/**
* @brief Callback function to support a write request.
* @param [in] pCharacteristic The characteristic that is the source of the event.
* @param [in] desc The connection description struct that is associated with the peer that performed the write.
*/
void NimBLECharacteristicCallbacks::onWrite(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc) {
NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onWrite: default");
} // onWrite
/**
* @brief Callback function to support a Notify request.
* @param [in] pCharacteristic The characteristic that is the source of the event.
*/
void NimBLECharacteristicCallbacks::onNotify(NimBLECharacteristic* pCharacteristic) {
NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onNotify: default");
} // onNotify
/**
* @brief Callback function to support a Notify/Indicate Status report.
* @param [in] pCharacteristic The characteristic that is the source of the event.
* @param [in] s Status of the notification/indication.
* @param [in] code Additional return code from the NimBLE stack.
* @param [in] code Status return code from the NimBLE stack.
* @details The status code for success is 0 for notifications and BLE_HS_EDONE for indications,
* any other value is an error.
*/
void NimBLECharacteristicCallbacks::onStatus(NimBLECharacteristic* pCharacteristic, Status s, int code) {
void NimBLECharacteristicCallbacks::onStatus(NimBLECharacteristic* pCharacteristic, int code) {
NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onStatus: default");
} // onStatus
/**
* @brief Callback function called when a client changes subscription status.
* @param [in] pCharacteristic The characteristic that is the source of the event.
* @param [in] desc The connection description struct that is associated with the client.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
* @param [in] subValue The subscription status:
* * 0 = Un-Subscribed
* * 1 = Notifications
@ -650,9 +455,8 @@ void NimBLECharacteristicCallbacks::onStatus(NimBLECharacteristic* pCharacterist
* * 3 = Notifications and Indications
*/
void NimBLECharacteristicCallbacks::onSubscribe(NimBLECharacteristic* pCharacteristic,
ble_gap_conn_desc* desc,
uint16_t subValue)
{
NimBLEConnInfo& connInfo,
uint16_t subValue) {
NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onSubscribe: default");
}

View File

@ -11,186 +11,132 @@
* Author: kolban
*/
#ifndef MAIN_NIMBLECHARACTERISTIC_H_
#define MAIN_NIMBLECHARACTERISTIC_H_
#ifndef NIMBLE_CPP_CHARACTERISTIC_H_
#define NIMBLE_CPP_CHARACTERISTIC_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#if defined(CONFIG_NIMBLE_CPP_IDF)
#include "host/ble_hs.h"
#else
#include "nimble/nimble/host/include/host/ble_hs.h"
#endif
/**** FIX COMPILATION ****/
#undef min
#undef max
/**************************/
typedef enum {
READ = BLE_GATT_CHR_F_READ,
READ_ENC = BLE_GATT_CHR_F_READ_ENC,
READ_AUTHEN = BLE_GATT_CHR_F_READ_AUTHEN,
READ_AUTHOR = BLE_GATT_CHR_F_READ_AUTHOR,
WRITE = BLE_GATT_CHR_F_WRITE,
WRITE_NR = BLE_GATT_CHR_F_WRITE_NO_RSP,
WRITE_ENC = BLE_GATT_CHR_F_WRITE_ENC,
WRITE_AUTHEN = BLE_GATT_CHR_F_WRITE_AUTHEN,
WRITE_AUTHOR = BLE_GATT_CHR_F_WRITE_AUTHOR,
BROADCAST = BLE_GATT_CHR_F_BROADCAST,
NOTIFY = BLE_GATT_CHR_F_NOTIFY,
INDICATE = BLE_GATT_CHR_F_INDICATE
} NIMBLE_PROPERTY;
#include "NimBLEService.h"
#include "NimBLEDescriptor.h"
#include "NimBLEAttValue.h"
#include <string>
#include <vector>
class NimBLEService;
class NimBLEDescriptor;
class NimBLECharacteristicCallbacks;
class NimBLECharacteristic;
# include "NimBLELocalValueAttribute.h"
# include "NimBLEServer.h"
# include "NimBLEService.h"
# include "NimBLEDescriptor.h"
# include "NimBLEAttValue.h"
# include "NimBLEConnInfo.h"
# include <string>
# include <vector>
/**
* @brief The model of a %BLE Characteristic.
* @brief The model of a BLE Characteristic.
*
* A BLE Characteristic is an identified value container that manages a value. It is exposed by a BLE server and
* can be read and written to by a %BLE client.
* A BLE Characteristic is an identified value container that manages a value. It is exposed by a BLE service and
* can be read and written to by a BLE client.
*/
class NimBLECharacteristic {
public:
NimBLECharacteristic(const char* uuid,
uint16_t properties =
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN,
NimBLEService* pService = nullptr);
NimBLECharacteristic(const NimBLEUUID &uuid,
uint16_t properties =
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN,
NimBLEService* pService = nullptr);
class NimBLECharacteristic : public NimBLELocalValueAttribute {
public:
NimBLECharacteristic(const char* uuid,
uint16_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN,
NimBLEService* pService = nullptr);
NimBLECharacteristic(const NimBLEUUID& uuid,
uint16_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN,
NimBLEService* pService = nullptr);
~NimBLECharacteristic();
uint16_t getHandle();
NimBLEUUID getUUID();
std::string toString();
void indicate();
void indicate(const uint8_t* value, size_t length);
void indicate(const std::vector<uint8_t>& value);
void notify(bool is_notification = true);
void notify(const uint8_t* value, size_t length, bool is_notification = true);
void notify(const std::vector<uint8_t>& value, bool is_notification = true);
size_t getSubscribedCount();
void addDescriptor(NimBLEDescriptor *pDescriptor);
NimBLEDescriptor* getDescriptorByUUID(const char* uuid);
NimBLEDescriptor* getDescriptorByUUID(const NimBLEUUID &uuid);
NimBLEDescriptor* getDescriptorByHandle(uint16_t handle);
void removeDescriptor(NimBLEDescriptor *pDescriptor, bool deleteDsc = false);
NimBLEService* getService();
uint16_t getProperties();
NimBLEAttValue getValue(time_t *timestamp = nullptr);
size_t getDataLength();
void setValue(const uint8_t* data, size_t size);
void setValue(const std::vector<uint8_t>& vec);
void setCallbacks(NimBLECharacteristicCallbacks* pCallbacks);
std::string toString() const;
size_t getSubscribedCount() const;
void addDescriptor(NimBLEDescriptor* pDescriptor);
void removeDescriptor(NimBLEDescriptor* pDescriptor, bool deleteDsc = false);
uint16_t getProperties() const;
void setCallbacks(NimBLECharacteristicCallbacks* pCallbacks);
void indicate(uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const;
void indicate(const uint8_t* value, size_t length, uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const;
void notify(uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const;
void notify(const uint8_t* value, size_t length, uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const;
NimBLEDescriptor* createDescriptor(const char* uuid,
uint32_t properties =
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);;
NimBLEDescriptor* createDescriptor(const NimBLEUUID &uuid,
uint32_t properties =
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);
NimBLECharacteristicCallbacks* getCallbacks();
uint32_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);
NimBLEDescriptor* createDescriptor(const NimBLEUUID& uuid,
uint32_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);
NimBLEDescriptor* getDescriptorByUUID(const char* uuid) const;
NimBLEDescriptor* getDescriptorByUUID(const NimBLEUUID& uuid) const;
NimBLEDescriptor* getDescriptorByHandle(uint16_t handle) const;
NimBLEService* getService() const;
NimBLECharacteristicCallbacks* getCallbacks() const;
/*********************** Template Functions ************************/
/**
* @brief Template to set the characteristic value to <type\>val.
* @param [in] s The value to set.
*/
template<typename T>
void setValue(const T &s) { m_value.setValue<T>(s); }
/**
* @brief Template to convert the characteristic data to <type\>.
* @tparam T The type to convert the data to.
* @param [in] timestamp (Optional) A pointer to a time_t struct to store the time the value was read.
* @param [in] skipSizeCheck (Optional) If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>getValue<type>(&timestamp, skipSizeCheck);</tt>
*/
template<typename T>
T getValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) {
return m_value.getValue<T>(timestamp, skipSizeCheck);
}
/**
* @brief Template to send a notification from a class type that has a c_str() and length() method.
* @brief Template to send a notification for classes which may have
* data()/size() or c_str()/length() methods. Falls back to sending
* the data by casting the first element of the array to a uint8_t
* pointer and getting the length of the array using sizeof.
* @tparam T The a reference to a class containing the data to send.
* @param[in] value The <type\>value to set.
* @param[in] is_notification if true sends a notification, false sends an indication.
* @details Only used if the <type\> has a `c_str()` method.
* @param[in] conn_handle The connection handle to send the notification to.
* @note This function is only available if the type T is not a pointer.
*/
template<typename T>
#ifdef _DOXYGEN_
void
#else
typename std::enable_if<Has_c_str_len<T>::value, void>::type
#endif
notify(const T& value, bool is_notification = true) {
notify((uint8_t*)value.c_str(), value.length(), is_notification);
template <typename T>
typename std::enable_if<!std::is_pointer<T>::value, void>::type
notify(const T& value, uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const {
if constexpr (Has_data_size<T>::value) {
notify(reinterpret_cast<const uint8_t*>(value.data()), value.size(), conn_handle);
} else if constexpr (Has_c_str_length<T>::value) {
notify(reinterpret_cast<const uint8_t*>(value.c_str()), value.length(), conn_handle);
} else {
notify(reinterpret_cast<const uint8_t*>(&value), sizeof(value), conn_handle);
}
}
/**
* @brief Template to send an indication from a class type that has a c_str() and length() method.
* @brief Template to send an indication for classes which may have
* data()/size() or c_str()/length() methods. Falls back to sending
* the data by casting the first element of the array to a uint8_t
* pointer and getting the length of the array using sizeof.
* @tparam T The a reference to a class containing the data to send.
* @param[in] value The <type\>value to set.
* @details Only used if the <type\> has a `c_str()` method.
* @param[in] conn_handle The connection handle to send the indication to.
* @note This function is only available if the type T is not a pointer.
*/
template<typename T>
#ifdef _DOXYGEN_
void
#else
typename std::enable_if<Has_c_str_len<T>::value, void>::type
#endif
indicate(const T& value) {
indicate((uint8_t*)value.c_str(), value.length());
template <typename T>
typename std::enable_if<!std::is_pointer<T>::value, void>::type
indicate(const T& value, uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const {
if constexpr (Has_data_size<T>::value) {
indicate(reinterpret_cast<const uint8_t*>(value.data()), value.size(), conn_handle);
} else if constexpr (Has_c_str_length<T>::value) {
indicate(reinterpret_cast<const uint8_t*>(value.c_str()), value.length(), conn_handle);
} else {
indicate(reinterpret_cast<const uint8_t*>(&value), sizeof(value), conn_handle);
}
}
private:
private:
friend class NimBLEServer;
friend class NimBLEService;
friend class NimBLEServer;
friend class NimBLEService;
void setService(NimBLEService* pService);
void setSubscribe(const ble_gap_event* event, NimBLEConnInfo& connInfo);
void readEvent(NimBLEConnInfo& connInfo) override;
void writeEvent(const uint8_t* val, uint16_t len, NimBLEConnInfo& connInfo) override;
void sendValue(const uint8_t* value,
size_t length,
bool is_notification = true,
uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const;
void setService(NimBLEService *pService);
void setSubscribe(struct ble_gap_event *event);
static int handleGapEvent(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg);
NimBLEUUID m_uuid;
uint16_t m_handle;
uint16_t m_properties;
NimBLECharacteristicCallbacks* m_pCallbacks;
NimBLEService* m_pService;
NimBLEAttValue m_value;
std::vector<NimBLEDescriptor*> m_dscVec;
uint8_t m_removed;
std::vector<std::pair<uint16_t, uint16_t>> m_subscribedVec;
NimBLECharacteristicCallbacks* m_pCallbacks{nullptr};
NimBLEService* m_pService{nullptr};
std::vector<NimBLEDescriptor*> m_vDescriptors{};
std::vector<std::pair<uint16_t, uint16_t>> m_subscribedVec{};
}; // NimBLECharacteristic
/**
* @brief Callbacks that can be associated with a %BLE characteristic to inform of events.
*
@ -199,33 +145,13 @@ private:
* sub-classed instance of this class and will be notified when such an event happens.
*/
class NimBLECharacteristicCallbacks {
public:
/**
* @brief An enum to provide the callback the status of the
* notification/indication, implemented for backward compatibility.
* @deprecated To be removed in the future as the NimBLE stack return code is also provided.
*/
typedef enum {
SUCCESS_INDICATE,
SUCCESS_NOTIFY,
ERROR_INDICATE_DISABLED,
ERROR_NOTIFY_DISABLED,
ERROR_GATT,
ERROR_NO_CLIENT,
ERROR_INDICATE_TIMEOUT,
ERROR_INDICATE_FAILURE
}Status;
virtual ~NimBLECharacteristicCallbacks();
virtual void onRead(NimBLECharacteristic* pCharacteristic);
virtual void onRead(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc);
virtual void onWrite(NimBLECharacteristic* pCharacteristic);
virtual void onWrite(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc);
virtual void onNotify(NimBLECharacteristic* pCharacteristic);
virtual void onStatus(NimBLECharacteristic* pCharacteristic, Status s, int code);
virtual void onSubscribe(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc, uint16_t subValue);
public:
virtual ~NimBLECharacteristicCallbacks() {}
virtual void onRead(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo);
virtual void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo);
virtual void onStatus(NimBLECharacteristic* pCharacteristic, int code);
virtual void onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue);
};
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */
#endif /*MAIN_NIMBLECHARACTERISTIC_H_*/
#endif /*NIMBLE_CPP_CHARACTERISTIC_H_*/

File diff suppressed because it is too large Load Diff

View File

@ -11,114 +11,139 @@
* Author: kolban
*/
#ifndef MAIN_NIMBLECLIENT_H_
#define MAIN_NIMBLECLIENT_H_
#ifndef NIMBLE_CPP_CLIENT_H_
#define NIMBLE_CPP_CLIENT_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
#include "NimBLEAddress.h"
#include "NimBLEUUID.h"
#include "NimBLEUtils.h"
#include "NimBLEConnInfo.h"
#include "NimBLEAttValue.h"
#include "NimBLEAdvertisedDevice.h"
#include "NimBLERemoteService.h"
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_gap.h"
# else
# include "nimble/nimble/host/include/host/ble_gap.h"
# endif
#include <vector>
#include <string>
# include "NimBLEAddress.h"
# include <stdint.h>
# include <vector>
# include <string>
class NimBLEAddress;
class NimBLEUUID;
class NimBLERemoteService;
class NimBLERemoteCharacteristic;
class NimBLEClientCallbacks;
class NimBLEAdvertisedDevice;
class NimBLEAttValue;
class NimBLEClientCallbacks;
class NimBLEConnInfo;
struct NimBLETaskData;
/**
* @brief A model of a %BLE client.
* @brief A model of a BLE client.
*/
class NimBLEClient {
public:
bool connect(NimBLEAdvertisedDevice* device, bool deleteAttibutes = true);
bool connect(const NimBLEAddress &address, bool deleteAttibutes = true);
bool connect(bool deleteAttibutes = true);
int disconnect(uint8_t reason = BLE_ERR_REM_USER_CONN_TERM);
NimBLEAddress getPeerAddress();
void setPeerAddress(const NimBLEAddress &address);
int getRssi();
std::vector<NimBLERemoteService*>* getServices(bool refresh = false);
public:
bool connect(NimBLEAdvertisedDevice* device,
bool deleteAttributes = true,
bool asyncConnect = false,
bool exchangeMTU = true);
bool connect(const NimBLEAddress& address, bool deleteAttributes = true, bool asyncConnect = false, bool exchangeMTU = true);
bool connect(bool deleteAttributes = true, bool asyncConnect = false, bool exchangeMTU = true);
bool disconnect(uint8_t reason = BLE_ERR_REM_USER_CONN_TERM);
bool cancelConnect() const;
void setSelfDelete(bool deleteOnDisconnect, bool deleteOnConnectFail);
NimBLEAddress getPeerAddress() const;
bool setPeerAddress(const NimBLEAddress& address);
int getRssi() const;
bool isConnected() const;
void setClientCallbacks(NimBLEClientCallbacks* pClientCallbacks, bool deleteCallbacks = true);
std::string toString() const;
uint16_t getConnHandle() const;
void clearConnection();
bool setConnection(const NimBLEConnInfo& connInfo);
bool setConnection(uint16_t connHandle);
uint16_t getMTU() const;
bool exchangeMTU();
bool secureConnection() const;
void setConnectTimeout(uint32_t timeout);
bool setDataLen(uint16_t txOctets);
bool discoverAttributes();
NimBLEConnInfo getConnInfo() const;
int getLastError() const;
bool updateConnParams(uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout);
void setConnectionParams(uint16_t minInterval,
uint16_t maxInterval,
uint16_t latency,
uint16_t timeout,
uint16_t scanInterval = 16,
uint16_t scanWindow = 16);
const std::vector<NimBLERemoteService*>& getServices(bool refresh = false);
std::vector<NimBLERemoteService*>::iterator begin();
std::vector<NimBLERemoteService*>::iterator end();
NimBLERemoteCharacteristic* getCharacteristic(uint16_t handle);
NimBLERemoteService* getService(const char* uuid);
NimBLERemoteService* getService(const NimBLEUUID &uuid);
NimBLERemoteService* getService(const NimBLEUUID& uuid);
void deleteServices();
size_t deleteService(const NimBLEUUID &uuid);
NimBLEAttValue getValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID);
bool setValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID,
const NimBLEAttValue &value, bool response = false);
NimBLERemoteCharacteristic* getCharacteristic(const uint16_t handle);
bool isConnected();
void setClientCallbacks(NimBLEClientCallbacks *pClientCallbacks,
bool deleteCallbacks = true);
std::string toString();
uint16_t getConnId();
uint16_t getMTU();
bool secureConnection();
void setConnectTimeout(uint8_t timeout);
void setConnectionParams(uint16_t minInterval, uint16_t maxInterval,
uint16_t latency, uint16_t timeout,
uint16_t scanInterval=16, uint16_t scanWindow=16);
void updateConnParams(uint16_t minInterval, uint16_t maxInterval,
uint16_t latency, uint16_t timeout);
void setDataLen(uint16_t tx_octets);
void discoverAttributes();
NimBLEConnInfo getConnInfo();
int getLastError();
#if CONFIG_BT_NIMBLE_EXT_ADV
void setConnectPhy(uint8_t mask);
#endif
size_t deleteService(const NimBLEUUID& uuid);
NimBLEAttValue getValue(const NimBLEUUID& serviceUUID, const NimBLEUUID& characteristicUUID);
bool setValue(const NimBLEUUID& serviceUUID,
const NimBLEUUID& characteristicUUID,
const NimBLEAttValue& value,
bool response = false);
private:
NimBLEClient(const NimBLEAddress &peerAddress);
# if CONFIG_BT_NIMBLE_EXT_ADV
void setConnectPhy(uint8_t mask);
# endif
struct Config {
uint8_t deleteCallbacks : 1; // Delete the callback object when the client is deleted.
uint8_t deleteOnDisconnect : 1; // Delete the client when disconnected.
uint8_t deleteOnConnectFail : 1; // Delete the client when a connection attempt fails.
uint8_t asyncConnect : 1; // Connect asynchronously.
uint8_t exchangeMTU : 1; // Exchange MTU after connection.
};
Config getConfig() const;
void setConfig(Config config);
private:
NimBLEClient(const NimBLEAddress& peerAddress);
~NimBLEClient();
NimBLEClient(const NimBLEClient&) = delete;
NimBLEClient& operator=(const NimBLEClient&) = delete;
friend class NimBLEDevice;
friend class NimBLERemoteService;
bool retrieveServices(const NimBLEUUID* uuidFilter = nullptr);
static int handleGapEvent(struct ble_gap_event* event, void* arg);
static int exchangeMTUCb(uint16_t conn_handle, const ble_gatt_error* error, uint16_t mtu, void* arg);
static int serviceDiscoveredCB(uint16_t connHandle,
const struct ble_gatt_error* error,
const struct ble_gatt_svc* service,
void* arg);
static int handleGapEvent(struct ble_gap_event *event, void *arg);
static int serviceDiscoveredCB(uint16_t conn_handle,
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;
mutable int m_lastErr;
int32_t m_connectTimeout;
mutable NimBLETaskData* m_pTaskData;
std::vector<NimBLERemoteService*> m_svcVec;
NimBLEClientCallbacks* m_pClientCallbacks;
uint16_t m_connHandle;
uint8_t m_terminateFailCount;
Config m_config;
NimBLEAddress m_peerAddress;
int m_lastErr;
uint16_t m_conn_id;
bool m_connEstablished;
bool m_deleteCallbacks;
int32_t m_connectTimeout;
NimBLEClientCallbacks* m_pClientCallbacks;
ble_task_data_t* m_pTaskData;
ble_npl_callout m_dcTimer;
#if CONFIG_BT_NIMBLE_EXT_ADV
uint8_t m_phyMask;
#endif
std::vector<NimBLERemoteService*> m_servicesVector;
private:
friend class NimBLEClientCallbacks;
ble_gap_conn_params m_pConnParams;
# if CONFIG_BT_NIMBLE_EXT_ADV
uint8_t m_phyMask;
# endif
ble_gap_conn_params m_connParams;
friend class NimBLEDevice;
}; // class NimBLEClient
/**
* @brief Callbacks associated with a %BLE client.
*/
class NimBLEClientCallbacks {
public:
public:
virtual ~NimBLEClientCallbacks() {};
/**
@ -130,40 +155,52 @@ public:
/**
* @brief Called when disconnected from the server.
* @param [in] pClient A pointer to the calling client object.
* @param [in] reason Contains the reason code for the disconnection.
*/
virtual void onDisconnect(NimBLEClient* pClient);
virtual void onDisconnect(NimBLEClient* pClient, int reason);
/**
* @brief Called when server requests to update the connection parameters.
* @param [in] pClient A pointer to the calling client object.
* @param [in] params A pointer to the struct containing the connection parameters requested.
* @return True to accept the parmeters.
* @return True to accept the parameters.
*/
virtual bool onConnParamsUpdateRequest(NimBLEClient* pClient, const ble_gap_upd_params* params);
/**
* @brief Called when server requests a passkey for pairing.
* @return The passkey to be sent to the server.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
*/
virtual uint32_t onPassKeyRequest();
/*virtual void onPassKeyNotify(uint32_t pass_key);
virtual bool onSecurityRequest();*/
virtual void onPassKeyEntry(NimBLEConnInfo& connInfo);
/**
* @brief Called when the pairing procedure is complete.
* @param [in] desc A pointer to the struct containing the connection information.\n
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.\n
* This can be used to check the status of the connection encryption/pairing.
*/
virtual void onAuthenticationComplete(ble_gap_conn_desc* desc);
virtual void onAuthenticationComplete(NimBLEConnInfo& connInfo);
/**
* @brief Called when using numeric comparision for pairing.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
* @param [in] pin The pin to compare with the server.
* @return True to accept the pin.
*/
virtual bool onConfirmPIN(uint32_t pin);
virtual void onConfirmPasskey(NimBLEConnInfo& connInfo, uint32_t pin);
/**
* @brief Called when the peer identity address is resolved.
* @param [in] connInfo A reference to a NimBLEConnInfo instance with information
*/
virtual void onIdentity(NimBLEConnInfo& connInfo);
/**
* @brief Called when the connection MTU changes.
* @param [in] pClient A pointer to the client that the MTU change is associated with.
* @param [in] MTU The new MTU value.
* about the peer connection parameters.
*/
virtual void onMTUChange(NimBLEClient* pClient, uint16_t MTU);
};
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */
#endif /* MAIN_NIMBLECLIENT_H_ */
#endif /* NIMBLE_CPP_CLIENT_H_ */

View File

@ -1,55 +1,66 @@
#ifndef NIMBLECONNINFO_H_
#define NIMBLECONNINFO_H_
#if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_gap.h"
#else
# include "nimble/nimble/host/include/host/ble_gap.h"
#endif
#include "NimBLEAddress.h"
/**
* @brief Connection information.
*/
class NimBLEConnInfo {
friend class NimBLEServer;
friend class NimBLEClient;
ble_gap_conn_desc m_desc;
NimBLEConnInfo() { m_desc = {}; }
NimBLEConnInfo(ble_gap_conn_desc desc) { m_desc = desc; }
public:
public:
/** @brief Gets the over-the-air address of the connected peer */
NimBLEAddress getAddress() { return NimBLEAddress(m_desc.peer_ota_addr); }
NimBLEAddress getAddress() const { return NimBLEAddress(m_desc.peer_ota_addr); }
/** @brief Gets the ID address of the connected peer */
NimBLEAddress getIdAddress() { return NimBLEAddress(m_desc.peer_id_addr); }
NimBLEAddress getIdAddress() const { return NimBLEAddress(m_desc.peer_id_addr); }
/** @brief Gets the connection handle of the connected peer */
uint16_t getConnHandle() { return m_desc.conn_handle; }
/** @brief Gets the connection handle (also known as the connection id) of the connected peer */
uint16_t getConnHandle() const { return m_desc.conn_handle; }
/** @brief Gets the connection interval for this connection (in 1.25ms units) */
uint16_t getConnInterval() { return m_desc.conn_itvl; }
uint16_t getConnInterval() const { return m_desc.conn_itvl; }
/** @brief Gets the supervision timeout for this connection (in 10ms units) */
uint16_t getConnTimeout() { return m_desc.supervision_timeout; }
uint16_t getConnTimeout() const { return m_desc.supervision_timeout; }
/** @brief Gets the allowable latency for this connection (unit = number of intervals) */
uint16_t getConnLatency() { return m_desc.conn_latency; }
uint16_t getConnLatency() const { return m_desc.conn_latency; }
/** @brief Gets the maximum transmission unit size for this connection (in bytes) */
uint16_t getMTU() { return ble_att_mtu(m_desc.conn_handle); }
uint16_t getMTU() const { return ble_att_mtu(m_desc.conn_handle); }
/** @brief Check if we are in the master role in this connection */
bool isMaster() { return (m_desc.role == BLE_GAP_ROLE_MASTER); }
bool isMaster() const { return (m_desc.role == BLE_GAP_ROLE_MASTER); }
/** @brief Check if we are in the slave role in this connection */
bool isSlave() { return (m_desc.role == BLE_GAP_ROLE_SLAVE); }
bool isSlave() const { return (m_desc.role == BLE_GAP_ROLE_SLAVE); }
/** @brief Check if we are connected to a bonded peer */
bool isBonded() { return (m_desc.sec_state.bonded == 1); }
bool isBonded() const { return (m_desc.sec_state.bonded == 1); }
/** @brief Check if the connection in encrypted */
bool isEncrypted() { return (m_desc.sec_state.encrypted == 1); }
bool isEncrypted() const { return (m_desc.sec_state.encrypted == 1); }
/** @brief Check if the the connection has been authenticated */
bool isAuthenticated() { return (m_desc.sec_state.authenticated == 1); }
bool isAuthenticated() const { return (m_desc.sec_state.authenticated == 1); }
/** @brief Gets the key size used to encrypt the connection */
uint8_t getSecKeySize() { return m_desc.sec_state.key_size; }
uint8_t getSecKeySize() const { return m_desc.sec_state.key_size; }
private:
friend class NimBLEServer;
friend class NimBLEClient;
friend class NimBLECharacteristic;
friend class NimBLEDescriptor;
ble_gap_conn_desc m_desc{};
NimBLEConnInfo(){};
NimBLEConnInfo(ble_gap_conn_desc desc) { m_desc = desc; }
};
#endif

View File

@ -15,17 +15,24 @@
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#include "NimBLEService.h"
#include "NimBLEDescriptor.h"
#include "NimBLELog.h"
# include "NimBLEService.h"
# include "NimBLEDescriptor.h"
# include "NimBLELog.h"
#include <string>
# include <string>
#define NULL_HANDLE (0xffff)
static const char* LOG_TAG = "NimBLEDescriptor";
static const char* LOG_TAG = "NimBLEDescriptor";
static NimBLEDescriptorCallbacks defaultCallbacks;
/**
* @brief Construct a descriptor
* @param [in] uuid - UUID (const char*) for the descriptor.
* @param [in] properties - Properties for the descriptor.
* @param [in] max_len - The maximum length in bytes that the descriptor value can hold. (Default: 512 bytes for esp32, 20 for all others).
* @param [in] pCharacteristic - pointer to the characteristic instance this descriptor belongs to.
*/
NimBLEDescriptor::NimBLEDescriptor(const char* uuid, uint16_t properties, uint16_t max_len, NimBLECharacteristic* pCharacteristic)
: NimBLEDescriptor(NimBLEUUID(uuid), properties, max_len, pCharacteristic) {}
/**
* @brief Construct a descriptor
@ -34,265 +41,107 @@ static NimBLEDescriptorCallbacks defaultCallbacks;
* @param [in] max_len - The maximum length in bytes that the descriptor value can hold. (Default: 512 bytes for esp32, 20 for all others).
* @param [in] pCharacteristic - pointer to the characteristic instance this descriptor belongs to.
*/
NimBLEDescriptor::NimBLEDescriptor(const char* uuid, uint16_t properties, uint16_t max_len,
NimBLECharacteristic* pCharacteristic)
: NimBLEDescriptor(NimBLEUUID(uuid), properties, max_len, pCharacteristic) {
}
/**
* @brief Construct a descriptor
* @param [in] uuid - UUID (const char*) for the descriptor.
* @param [in] properties - Properties for the descriptor.
* @param [in] max_len - The maximum length in bytes that the descriptor value can hold. (Default: 512 bytes for esp32, 20 for all others).
* @param [in] pCharacteristic - pointer to the characteristic instance this descriptor belongs to.
*/
NimBLEDescriptor::NimBLEDescriptor(NimBLEUUID uuid, uint16_t properties, uint16_t max_len,
NimBLECharacteristic* pCharacteristic)
: m_value(std::min(CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH , (int)max_len), max_len) {
m_uuid = uuid;
m_handle = NULL_HANDLE; // Handle is initially unknown.
m_pCharacteristic = pCharacteristic;
m_pCallbacks = &defaultCallbacks; // No initial callback.
m_properties = 0;
m_removed = 0;
if (properties & BLE_GATT_CHR_F_READ) { // convert uint16_t properties to uint8_t
m_properties |= BLE_ATT_F_READ;
}
if (properties & (BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_WRITE)) {
m_properties |= BLE_ATT_F_WRITE;
}
if (properties & BLE_GATT_CHR_F_READ_ENC) {
m_properties |= BLE_ATT_F_READ_ENC;
}
if (properties & BLE_GATT_CHR_F_READ_AUTHEN) {
m_properties |= BLE_ATT_F_READ_AUTHEN;
}
if (properties & BLE_GATT_CHR_F_READ_AUTHOR) {
m_properties |= BLE_ATT_F_READ_AUTHOR;
}
if (properties & BLE_GATT_CHR_F_WRITE_ENC) {
m_properties |= BLE_ATT_F_WRITE_ENC;
}
if (properties & BLE_GATT_CHR_F_WRITE_AUTHEN) {
m_properties |= BLE_ATT_F_WRITE_AUTHEN;
}
if (properties & BLE_GATT_CHR_F_WRITE_AUTHOR) {
m_properties |= BLE_ATT_F_WRITE_AUTHOR;
NimBLEDescriptor::NimBLEDescriptor(const NimBLEUUID& uuid, uint16_t properties, uint16_t max_len, NimBLECharacteristic* pCharacteristic)
: NimBLELocalValueAttribute{uuid, 0, max_len}, m_pCallbacks{&defaultCallbacks}, m_pCharacteristic{pCharacteristic} {
// Check if this is the client configuration descriptor and set to removed if true.
if (uuid == NimBLEUUID((uint16_t)0x2902)) {
NIMBLE_LOGW(LOG_TAG, "Manually created 2902 descriptor has no functionality; please remove.");
setRemoved(NIMBLE_ATT_REMOVE_HIDE);
}
// convert uint16_t properties to uint8_t for descriptor properties
uint8_t descProperties = 0;
if (properties & NIMBLE_PROPERTY::READ) {
descProperties |= BLE_ATT_F_READ;
}
if (properties & (NIMBLE_PROPERTY::WRITE_NR | NIMBLE_PROPERTY::WRITE)) {
descProperties |= BLE_ATT_F_WRITE;
}
if (properties & NIMBLE_PROPERTY::READ_ENC) {
descProperties |= BLE_ATT_F_READ_ENC;
}
if (properties & NIMBLE_PROPERTY::READ_AUTHEN) {
descProperties |= BLE_ATT_F_READ_AUTHEN;
}
if (properties & NIMBLE_PROPERTY::READ_AUTHOR) {
descProperties |= BLE_ATT_F_READ_AUTHOR;
}
if (properties & NIMBLE_PROPERTY::WRITE_ENC) {
descProperties |= BLE_ATT_F_WRITE_ENC;
}
if (properties & NIMBLE_PROPERTY::WRITE_AUTHEN) {
descProperties |= BLE_ATT_F_WRITE_AUTHEN;
}
if (properties & NIMBLE_PROPERTY::WRITE_AUTHOR) {
descProperties |= BLE_ATT_F_WRITE_AUTHOR;
}
setProperties(descProperties);
} // NimBLEDescriptor
/**
* @brief NimBLEDescriptor destructor.
*/
NimBLEDescriptor::~NimBLEDescriptor() {
} // ~NimBLEDescriptor
/**
* @brief Get the BLE handle for this descriptor.
* @return The handle for this descriptor.
*/
uint16_t NimBLEDescriptor::getHandle() {
return m_handle;
} // getHandle
/**
* @brief Get the length of the value of this descriptor.
* @return The length (in bytes) of the value of this descriptor.
*/
size_t NimBLEDescriptor::getLength() {
return m_value.size();
} // getLength
/**
* @brief Get the UUID of the descriptor.
*/
NimBLEUUID NimBLEDescriptor::getUUID() {
return m_uuid;
} // getUUID
/**
* @brief Get the value of this descriptor.
* @return The NimBLEAttValue of this descriptor.
*/
NimBLEAttValue NimBLEDescriptor::getValue(time_t *timestamp) {
if (timestamp != nullptr) {
m_value.getValue(timestamp);
}
return m_value;
} // getValue
/**
* @brief Get the value of this descriptor as a string.
* @return A std::string instance containing a copy of the descriptor's value.
*/
std::string NimBLEDescriptor::getStringValue() {
return std::string(m_value);
}
/**
* @brief Get the characteristic this descriptor belongs to.
* @return A pointer to the characteristic this descriptor belongs to.
*/
NimBLECharacteristic* NimBLEDescriptor::getCharacteristic() {
NimBLECharacteristic* NimBLEDescriptor::getCharacteristic() const {
return m_pCharacteristic;
} // getCharacteristic
int NimBLEDescriptor::handleGapEvent(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg) {
(void)conn_handle;
(void)attr_handle;
const ble_uuid_t *uuid;
int rc;
NimBLEDescriptor* pDescriptor = (NimBLEDescriptor*)arg;
NIMBLE_LOGD(LOG_TAG, "Descriptor %s %s event", pDescriptor->getUUID().toString().c_str(),
ctxt->op == BLE_GATT_ACCESS_OP_READ_DSC ? "Read" : "Write");
uuid = ctxt->chr->uuid;
if(ble_uuid_cmp(uuid, &pDescriptor->getUUID().getNative()->u) == 0){
switch(ctxt->op) {
case BLE_GATT_ACCESS_OP_READ_DSC: {
// If the packet header is only 8 bytes this is a follow up of a long read
// so we don't want to call the onRead() callback again.
if(ctxt->om->om_pkthdr_len > 8) {
pDescriptor->m_pCallbacks->onRead(pDescriptor);
}
ble_npl_hw_enter_critical();
rc = os_mbuf_append(ctxt->om, pDescriptor->m_value.data(), pDescriptor->m_value.size());
ble_npl_hw_exit_critical(0);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
case BLE_GATT_ACCESS_OP_WRITE_DSC: {
uint16_t att_max_len = pDescriptor->m_value.max_size();
if (ctxt->om->om_len > att_max_len) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
uint8_t buf[att_max_len];
size_t len = ctxt->om->om_len;
memcpy(buf, ctxt->om->om_data,len);
os_mbuf *next;
next = SLIST_NEXT(ctxt->om, om_next);
while(next != NULL){
if((len + next->om_len) > att_max_len) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
memcpy(&buf[len], next->om_data, next->om_len);
len += next->om_len;
next = SLIST_NEXT(next, om_next);
}
pDescriptor->setValue(buf, len);
pDescriptor->m_pCallbacks->onWrite(pDescriptor);
return 0;
}
default:
break;
}
}
return BLE_ATT_ERR_UNLIKELY;
}
/**
* @brief Set the callback handlers for this descriptor.
* @param [in] pCallbacks An instance of a callback structure used to define any callbacks for the descriptor.
*/
void NimBLEDescriptor::setCallbacks(NimBLEDescriptorCallbacks* pCallbacks) {
if (pCallbacks != nullptr){
if (pCallbacks != nullptr) {
m_pCallbacks = pCallbacks;
} else {
m_pCallbacks = &defaultCallbacks;
}
} // setCallbacks
/**
* @brief Set the handle of this descriptor.
* Set the handle of this descriptor to be the supplied value.
* @param [in] handle The handle to be associated with this descriptor.
* @return N/A.
*/
void NimBLEDescriptor::setHandle(uint16_t handle) {
NIMBLE_LOGD(LOG_TAG, ">> setHandle(0x%.2x): Setting descriptor handle to be 0x%.2x", handle, handle);
m_handle = handle;
NIMBLE_LOGD(LOG_TAG, "<< setHandle()");
} // setHandle
/**
* @brief Set the value of the descriptor.
* @param [in] data The data to set for the descriptor.
* @param [in] length The length of the data in bytes.
*/
void NimBLEDescriptor::setValue(const uint8_t* data, size_t length) {
m_value.setValue(data, length);
} // setValue
/**
* @brief Set the value of the descriptor from a `std::vector<uint8_t>`.\n
* @param [in] vec The std::vector<uint8_t> reference to set the descriptor value from.
*/
void NimBLEDescriptor::setValue(const std::vector<uint8_t>& vec) {
return setValue((uint8_t*)&vec[0], vec.size());
} // setValue
/**
* @brief Set the characteristic this descriptor belongs to.
* @param [in] pChar A pointer to the characteristic this descriptior belongs to.
* @param [in] pChar A pointer to the characteristic this descriptor belongs to.
*/
void NimBLEDescriptor::setCharacteristic(NimBLECharacteristic* pChar) {
m_pCharacteristic = pChar;
} // setCharacteristic
/**
* @brief Return a string representation of the descriptor.
* @return A string representation of the descriptor.
*/
std::string NimBLEDescriptor::toString() {
std::string NimBLEDescriptor::toString() const {
char hex[5];
snprintf(hex, sizeof(hex), "%04x", m_handle);
snprintf(hex, sizeof(hex), "%04x", getHandle());
std::string res = "UUID: " + m_uuid.toString() + ", handle: 0x" + hex;
return res;
} // toString
void NimBLEDescriptor::readEvent(NimBLEConnInfo& connInfo) {
m_pCallbacks->onRead(this, connInfo);
} // readEvent
NimBLEDescriptorCallbacks::~NimBLEDescriptorCallbacks() {}
void NimBLEDescriptor::writeEvent(const uint8_t* val, uint16_t len, NimBLEConnInfo& connInfo) {
setValue(val, len);
m_pCallbacks->onWrite(this, connInfo);
} // writeEvent
/**
* @brief Callback function to support a read request.
* @param [in] pDescriptor The descriptor that is the source of the event.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
*/
void NimBLEDescriptorCallbacks::onRead(NimBLEDescriptor* pDescriptor) {
(void)pDescriptor;
void NimBLEDescriptorCallbacks::onRead(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo) {
NIMBLE_LOGD("NimBLEDescriptorCallbacks", "onRead: default");
} // onRead
/**
* @brief Callback function to support a write request.
* @param [in] pDescriptor The descriptor that is the source of the event.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
*/
void NimBLEDescriptorCallbacks::onWrite(NimBLEDescriptor* pDescriptor) {
(void)pDescriptor;
void NimBLEDescriptorCallbacks::onWrite(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo) {
NIMBLE_LOGD("NimBLEDescriptorCallbacks", "onWrite: default");
} // onWrite

View File

@ -12,93 +12,52 @@
* Author: kolban
*/
#ifndef MAIN_NIMBLEDESCRIPTOR_H_
#define MAIN_NIMBLEDESCRIPTOR_H_
#ifndef NIMBLE_CPP_DESCRIPTOR_H_
#define NIMBLE_CPP_DESCRIPTOR_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#include "NimBLECharacteristic.h"
#include "NimBLEUUID.h"
#include "NimBLEAttValue.h"
#include <string>
class NimBLEService;
class NimBLECharacteristic;
class NimBLEDescriptor;
class NimBLEDescriptorCallbacks;
# include "NimBLELocalValueAttribute.h"
# include "NimBLECharacteristic.h"
# include "NimBLEUUID.h"
# include "NimBLEAttValue.h"
# include "NimBLEConnInfo.h"
# include <string>
/**
* @brief A model of a %BLE descriptor.
* @brief A model of a BLE descriptor.
*/
class NimBLEDescriptor {
public:
NimBLEDescriptor(const char* uuid, uint16_t properties,
uint16_t max_len,
class NimBLEDescriptor : public NimBLELocalValueAttribute {
public:
NimBLEDescriptor(const char* uuid, uint16_t properties, uint16_t max_len, NimBLECharacteristic* pCharacteristic = nullptr);
NimBLEDescriptor(const NimBLEUUID& uuid,
uint16_t properties,
uint16_t max_len,
NimBLECharacteristic* pCharacteristic = nullptr);
~NimBLEDescriptor() = default;
NimBLEDescriptor(NimBLEUUID uuid, uint16_t properties,
uint16_t max_len,
NimBLECharacteristic* pCharacteristic = nullptr);
~NimBLEDescriptor();
uint16_t getHandle();
NimBLEUUID getUUID();
std::string toString();
std::string toString() const;
void setCallbacks(NimBLEDescriptorCallbacks* pCallbacks);
NimBLECharacteristic* getCharacteristic();
NimBLECharacteristic* getCharacteristic() const;
size_t getLength();
NimBLEAttValue getValue(time_t *timestamp = nullptr);
std::string getStringValue();
void setValue(const uint8_t* data, size_t size);
void setValue(const std::vector<uint8_t>& vec);
/*********************** Template Functions ************************/
/**
* @brief Template to set the characteristic value to <type\>val.
* @param [in] s The value to set.
*/
template<typename T>
void setValue(const T &s) { m_value.setValue<T>(s); }
/**
* @brief Template to convert the descriptor data to <type\>.
* @tparam T The type to convert the data to.
* @param [in] timestamp (Optional) A pointer to a time_t struct to store the time the value was read.
* @param [in] skipSizeCheck (Optional) If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>getValue<type>(&timestamp, skipSizeCheck);</tt>
*/
template<typename T>
T getValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) {
return m_value.getValue<T>(timestamp, skipSizeCheck);
}
private:
private:
friend class NimBLECharacteristic;
friend class NimBLEService;
friend class NimBLE2904;
static int handleGapEvent(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg);
void setHandle(uint16_t handle);
void setCharacteristic(NimBLECharacteristic* pChar);
void setCharacteristic(NimBLECharacteristic* pChar);
void readEvent(NimBLEConnInfo& connInfo) override;
void writeEvent(const uint8_t* val, uint16_t len, NimBLEConnInfo& connInfo) override;
NimBLEUUID m_uuid;
uint16_t m_handle;
NimBLEDescriptorCallbacks* m_pCallbacks;
NimBLECharacteristic* m_pCharacteristic;
uint8_t m_properties;
NimBLEAttValue m_value;
uint8_t m_removed;
NimBLEDescriptorCallbacks* m_pCallbacks{nullptr};
NimBLECharacteristic* m_pCharacteristic{nullptr};
}; // NimBLEDescriptor
/**
* @brief Callbacks that can be associated with a %BLE descriptors to inform of events.
*
@ -107,13 +66,13 @@ private:
* sub-classed instance of this class and will be notified when such an event happens.
*/
class NimBLEDescriptorCallbacks {
public:
virtual ~NimBLEDescriptorCallbacks();
virtual void onRead(NimBLEDescriptor* pDescriptor);
virtual void onWrite(NimBLEDescriptor* pDescriptor);
public:
virtual ~NimBLEDescriptorCallbacks() = default;
virtual void onRead(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo);
virtual void onWrite(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo);
};
#include "NimBLE2904.h"
# include "NimBLE2904.h"
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */
#endif /* MAIN_NIMBLEDESCRIPTOR_H_ */
#endif /* NIMBLE_CPP_DESCRIPTOR_H_ */

File diff suppressed because it is too large Load Diff

View File

@ -12,228 +12,263 @@
* Author: kolban
*/
#ifndef MAIN_NIMBLEDEVICE_H_
#define MAIN_NIMBLEDEVICE_H_
#ifndef NIMBLE_CPP_DEVICE_H_
#define NIMBLE_CPP_DEVICE_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
# ifdef ESP_PLATFORM
# include <esp_bt.h>
# endif
#if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
#include "NimBLEScan.h"
#endif
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include <host/ble_gap.h>
# else
# include <nimble/nimble/host/include/host/ble_gap.h>
# endif
#if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
/**** FIX COMPILATION ****/
# undef min
# undef max
/**************************/
# include <string>
# include <vector>
# if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
# include <array>
class NimBLEClient;
# endif
# if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
class NimBLEScan;
# endif
# if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
# if CONFIG_BT_NIMBLE_EXT_ADV
# include "NimBLEExtAdvertising.h"
class NimBLEExtAdvertising;
# else
# include "NimBLEAdvertising.h"
class NimBLEAdvertising;
# endif
#endif
# endif
#if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
#include "NimBLEClient.h"
#endif
# if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
class NimBLEServer;
# endif
#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#include "NimBLEServer.h"
#endif
# if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) || defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
class NimBLEConnInfo;
# endif
#include "NimBLEUtils.h"
#include "NimBLESecurity.h"
#include "NimBLEAddress.h"
class NimBLEAddress;
#ifdef ESP_PLATFORM
# include "esp_bt.h"
#endif
# define BLEDevice NimBLEDevice
# define BLEClient NimBLEClient
# define BLERemoteService NimBLERemoteService
# define BLERemoteCharacteristic NimBLERemoteCharacteristic
# define BLERemoteDescriptor NimBLERemoteDescriptor
# define BLEAdvertisedDevice NimBLEAdvertisedDevice
# define BLEScan NimBLEScan
# define BLEUUID NimBLEUUID
# define BLEAddress NimBLEAddress
# define BLEUtils NimBLEUtils
# define BLEClientCallbacks NimBLEClientCallbacks
# define BLEAdvertisedDeviceCallbacks NimBLEScanCallbacks
# define BLEScanResults NimBLEScanResults
# define BLEServer NimBLEServer
# define BLEService NimBLEService
# define BLECharacteristic NimBLECharacteristic
# define BLEAdvertising NimBLEAdvertising
# define BLEServerCallbacks NimBLEServerCallbacks
# define BLECharacteristicCallbacks NimBLECharacteristicCallbacks
# define BLEAdvertisementData NimBLEAdvertisementData
# define BLEDescriptor NimBLEDescriptor
# define BLE2904 NimBLE2904
# define BLEDescriptorCallbacks NimBLEDescriptorCallbacks
# define BLEBeacon NimBLEBeacon
# define BLEEddystoneTLM NimBLEEddystoneTLM
# define BLEEddystoneURL NimBLEEddystoneURL
# define BLEConnInfo NimBLEConnInfo
#include <map>
#include <string>
#include <list>
# ifdef CONFIG_BT_NIMBLE_MAX_CONNECTIONS
# define NIMBLE_MAX_CONNECTIONS CONFIG_BT_NIMBLE_MAX_CONNECTIONS
# else
# define NIMBLE_MAX_CONNECTIONS CONFIG_NIMBLE_MAX_CONNECTIONS
# endif
#define BLEDevice NimBLEDevice
#define BLEClient NimBLEClient
#define BLERemoteService NimBLERemoteService
#define BLERemoteCharacteristic NimBLERemoteCharacteristic
#define BLERemoteDescriptor NimBLERemoteDescriptor
#define BLEAdvertisedDevice NimBLEAdvertisedDevice
#define BLEScan NimBLEScan
#define BLEUUID NimBLEUUID
#define BLESecurity NimBLESecurity
#define BLESecurityCallbacks NimBLESecurityCallbacks
#define BLEAddress NimBLEAddress
#define BLEUtils NimBLEUtils
#define BLEClientCallbacks NimBLEClientCallbacks
#define BLEAdvertisedDeviceCallbacks NimBLEAdvertisedDeviceCallbacks
#define BLEScanResults NimBLEScanResults
#define BLEServer NimBLEServer
#define BLEService NimBLEService
#define BLECharacteristic NimBLECharacteristic
#define BLEAdvertising NimBLEAdvertising
#define BLEServerCallbacks NimBLEServerCallbacks
#define BLECharacteristicCallbacks NimBLECharacteristicCallbacks
#define BLEAdvertisementData NimBLEAdvertisementData
#define BLEDescriptor NimBLEDescriptor
#define BLE2902 NimBLE2902
#define BLE2904 NimBLE2904
#define BLEDescriptorCallbacks NimBLEDescriptorCallbacks
#define BLEBeacon NimBLEBeacon
#define BLEEddystoneTLM NimBLEEddystoneTLM
#define BLEEddystoneURL NimBLEEddystoneURL
#ifdef CONFIG_BT_NIMBLE_MAX_CONNECTIONS
#define NIMBLE_MAX_CONNECTIONS CONFIG_BT_NIMBLE_MAX_CONNECTIONS
#else
#define NIMBLE_MAX_CONNECTIONS CONFIG_NIMBLE_MAX_CONNECTIONS
#endif
typedef int (*gap_event_handler)(ble_gap_event *event, void *arg);
extern "C" void ble_store_config_init(void);
typedef int (*gap_event_handler)(ble_gap_event* event, void* arg);
/**
* @brief A model of a %BLE Device from which all the BLE roles are created.
* @brief A model of a BLE Device from which all the BLE roles are created.
*/
class NimBLEDevice {
public:
static void init(const std::string &deviceName);
static void deinit(bool clearAll = false);
static bool getInitialized();
static NimBLEAddress getAddress();
static std::string toString();
static bool whiteListAdd(const NimBLEAddress & address);
static bool whiteListRemove(const NimBLEAddress & address);
static bool onWhiteList(const NimBLEAddress & address);
static size_t getWhiteListCount();
static NimBLEAddress getWhiteListAddress(size_t index);
public:
static bool init(const std::string& deviceName);
static bool deinit(bool clearAll = false);
static bool setDeviceName(const std::string& deviceName);
static bool isInitialized();
static NimBLEAddress getAddress();
static std::string toString();
static bool whiteListAdd(const NimBLEAddress& address);
static bool whiteListRemove(const NimBLEAddress& address);
static bool onWhiteList(const NimBLEAddress& address);
static size_t getWhiteListCount();
static NimBLEAddress getWhiteListAddress(size_t index);
static bool setPower(int8_t dbm);
static int getPower();
static bool setOwnAddrType(uint8_t type);
static bool setOwnAddr(const NimBLEAddress& addr);
static bool setOwnAddr(const uint8_t* addr);
static void setScanDuplicateCacheSize(uint16_t cacheSize);
static void setScanFilterMode(uint8_t type);
static bool setCustomGapHandler(gap_event_handler handler);
static void setSecurityAuth(bool bonding, bool mitm, bool sc);
static void setSecurityAuth(uint8_t auth);
static void setSecurityIOCap(uint8_t iocap);
static void setSecurityInitKey(uint8_t initKey);
static void setSecurityRespKey(uint8_t respKey);
static void setSecurityPasskey(uint32_t passKey);
static uint32_t getSecurityPasskey();
static bool startSecurity(uint16_t connHandle);
static bool setMTU(uint16_t mtu);
static uint16_t getMTU();
static bool isIgnored(const NimBLEAddress& address);
static void addIgnored(const NimBLEAddress& address);
static void removeIgnored(const NimBLEAddress& address);
static void onReset(int reason);
static void onSync(void);
static void host_task(void* param);
#if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
static NimBLEScan* getScan();
#endif
# if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
static NimBLEScan* getScan();
# endif
#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
static NimBLEServer* createServer();
static NimBLEServer* getServer();
#endif
# if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
static NimBLEServer* createServer();
static NimBLEServer* getServer();
# endif
#ifdef ESP_PLATFORM
static void setPower(esp_power_level_t powerLevel, esp_ble_power_type_t powerType=ESP_BLE_PWR_TYPE_DEFAULT);
static int getPower(esp_ble_power_type_t powerType=ESP_BLE_PWR_TYPE_DEFAULT);
static void setOwnAddrType(uint8_t own_addr_type, bool useNRPA=false);
static void setScanDuplicateCacheSize(uint16_t cacheSize);
static void setScanFilterMode(uint8_t type);
#else
static void setPower(int dbm);
static int getPower();
#endif
# if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) || defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
static bool injectConfirmPasskey(const NimBLEConnInfo& peerInfo, bool accept);
static bool injectPassKey(const NimBLEConnInfo& peerInfo, uint32_t pin);
# endif
static void setCustomGapHandler(gap_event_handler handler);
static void setSecurityAuth(bool bonding, bool mitm, bool sc);
static void setSecurityAuth(uint8_t auth_req);
static void setSecurityIOCap(uint8_t iocap);
static void setSecurityInitKey(uint8_t init_key);
static void setSecurityRespKey(uint8_t init_key);
static void setSecurityPasskey(uint32_t pin);
static uint32_t getSecurityPasskey();
static void setSecurityCallbacks(NimBLESecurityCallbacks* pCallbacks);
static int startSecurity(uint16_t conn_id);
static int setMTU(uint16_t mtu);
static uint16_t getMTU();
static bool isIgnored(const NimBLEAddress &address);
static void addIgnored(const NimBLEAddress &address);
static void removeIgnored(const NimBLEAddress &address);
#if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
# if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
# if CONFIG_BT_NIMBLE_EXT_ADV
static NimBLEExtAdvertising* getAdvertising();
static bool startAdvertising(uint8_t inst_id,
int duration = 0,
int max_events = 0);
static bool stopAdvertising(uint8_t inst_id);
static bool stopAdvertising();
# else
static NimBLEAdvertising* getAdvertising();
static bool startAdvertising();
static bool startAdvertising(uint8_t instId, int duration = 0, int maxEvents = 0);
static bool stopAdvertising(uint8_t instId);
static bool stopAdvertising();
# endif
#endif
# if !CONFIG_BT_NIMBLE_EXT_ADV || defined(_DOXYGEN_)
static NimBLEAdvertising* getAdvertising();
static bool startAdvertising(uint32_t duration = 0);
static bool stopAdvertising();
# endif
# endif
#if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL)
static NimBLEClient* createClient(NimBLEAddress peerAddress = NimBLEAddress(""));
static bool deleteClient(NimBLEClient* pClient);
static NimBLEClient* getClientByID(uint16_t conn_id);
static NimBLEClient* getClientByPeerAddress(const NimBLEAddress &peer_addr);
static NimBLEClient* getDisconnectedClient();
static size_t getClientListSize();
static std::list<NimBLEClient*>* getClientList();
#endif
# if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
static NimBLEClient* createClient();
static NimBLEClient* createClient(const NimBLEAddress& peerAddress);
static bool deleteClient(NimBLEClient* pClient);
static NimBLEClient* getClientByHandle(uint16_t connHandle);
static NimBLEClient* getClientByPeerAddress(const NimBLEAddress& peerAddress);
static NimBLEClient* getDisconnectedClient();
static size_t getCreatedClientCount();
static std::vector<NimBLEClient*> getConnectedClients();
# endif
#if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) || defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
static bool deleteBond(const NimBLEAddress &address);
static int getNumBonds();
static bool isBonded(const NimBLEAddress &address);
static void deleteAllBonds();
static NimBLEAddress getBondedAddress(int index);
#endif
# if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) || defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
static bool deleteBond(const NimBLEAddress& address);
static int getNumBonds();
static bool isBonded(const NimBLEAddress& address);
static bool deleteAllBonds();
static NimBLEAddress getBondedAddress(int index);
# endif
private:
#if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL)
private:
static bool m_synced;
static bool m_initialized;
static std::vector<NimBLEAddress> m_ignoreList;
static uint32_t m_passkey;
static ble_gap_event_listener m_listener;
static uint8_t m_ownAddrType;
static std::vector<NimBLEAddress> m_whiteList;
# if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
static NimBLEScan* m_pScan;
# endif
# if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
static NimBLEServer* m_pServer;
# endif
# if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
# if CONFIG_BT_NIMBLE_EXT_ADV
static NimBLEExtAdvertising* m_bleAdvertising;
# else
static NimBLEAdvertising* m_bleAdvertising;
# endif
# endif
# if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
static std::array<NimBLEClient*, NIMBLE_MAX_CONNECTIONS> m_pClients;
# endif
# ifdef ESP_PLATFORM
# ifdef CONFIG_BTDM_BLE_SCAN_DUPL
static uint16_t m_scanDuplicateSize;
static uint8_t m_scanFilterMode;
# endif
# endif
# if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
friend class NimBLEClient;
#endif
# endif
#if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
# if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
friend class NimBLEScan;
#endif
# endif
#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
# if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
friend class NimBLEServer;
friend class NimBLECharacteristic;
#endif
# endif
#if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
# if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
friend class NimBLEAdvertising;
# if CONFIG_BT_NIMBLE_EXT_ADV
friend class NimBLEExtAdvertising;
friend class NimBLEExtAdvertisement;
# endif
#endif
static void onReset(int reason);
static void onSync(void);
static void host_task(void *param);
static bool m_synced;
#if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
static NimBLEScan* m_pScan;
#endif
#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
static NimBLEServer* m_pServer;
#endif
#if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
# if CONFIG_BT_NIMBLE_EXT_ADV
static NimBLEExtAdvertising* m_bleAdvertising;
# else
static NimBLEAdvertising* m_bleAdvertising;
# endif
#endif
#if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL)
static std::list <NimBLEClient*> m_cList;
#endif
static std::list <NimBLEAddress> m_ignoreList;
static NimBLESecurityCallbacks* m_securityCallbacks;
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;
#ifdef ESP_PLATFORM
static uint16_t m_scanDuplicateSize;
static uint8_t m_scanFilterMode;
#endif
static std::vector<NimBLEAddress> m_whiteList;
# endif
};
# if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
# include "NimBLEClient.h"
# include "NimBLERemoteService.h"
# include "NimBLERemoteCharacteristic.h"
# include "NimBLERemoteDescriptor.h"
# endif
# if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
# include "NimBLEScan.h"
# endif
# if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
# include "NimBLEServer.h"
# include "NimBLEService.h"
# include "NimBLECharacteristic.h"
# include "NimBLEDescriptor.h"
# endif
# if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
# if CONFIG_BT_NIMBLE_EXT_ADV
# include "NimBLEExtAdvertising.h"
# else
# include "NimBLEAdvertising.h"
# endif
# endif
#endif // CONFIG_BT_ENABLED
#endif // MAIN_NIMBLEDEVICE_H_
#endif // NIMBLE_CPP_DEVICE_H_

View File

@ -81,12 +81,12 @@ uint16_t NimBLEEddystoneTLM::getVolt() {
* @return The temperature value.
*/
float NimBLEEddystoneTLM::getTemp() {
return ENDIAN_CHANGE_U16(m_eddystoneData.temp) / 256.0f;
return (int16_t)ENDIAN_CHANGE_U16(m_eddystoneData.temp) / 256.0f;
} // getTemp
/**
* @brief Get the count of advertisments sent.
* @return The number of advertisments.
* @brief Get the count of advertisements sent.
* @return The number of advertisements.
*/
uint32_t NimBLEEddystoneTLM::getCount() {
return ENDIAN_CHANGE_U32(m_eddystoneData.advCount);
@ -94,8 +94,8 @@ uint32_t NimBLEEddystoneTLM::getCount() {
/**
* @brief Get the advertisment time.
* @return The advertisment time.
* @brief Get the advertisement time.
* @return The advertisement time.
*/
uint32_t NimBLEEddystoneTLM::getTime() {
return (ENDIAN_CHANGE_U32(m_eddystoneData.tmil)) / 10;
@ -158,7 +158,7 @@ std::string NimBLEEddystoneTLM::toString() {
/**
* @brief Set the raw data for the beacon advertisment.
* @brief Set the raw data for the beacon advertisement.
* @param [in] data The raw data to advertise.
*/
void NimBLEEddystoneTLM::setData(const std::string &data) {
@ -176,7 +176,11 @@ void NimBLEEddystoneTLM::setData(const std::string &data) {
* @param [in] l_uuid The UUID.
*/
void NimBLEEddystoneTLM::setUUID(const NimBLEUUID &l_uuid) {
beaconUUID = l_uuid.getNative()->u16.value;
if (l_uuid.bitSize() != 16) {
NIMBLE_LOGE(LOG_TAG, "UUID must be 16 bits");
return;
}
beaconUUID = *reinterpret_cast<const uint16_t*>(l_uuid.getValue());
} // setUUID
@ -203,13 +207,13 @@ void NimBLEEddystoneTLM::setVolt(uint16_t volt) {
* @param [in] temp The temperature value.
*/
void NimBLEEddystoneTLM::setTemp(float temp) {
m_eddystoneData.temp = (uint16_t)temp;
m_eddystoneData.temp = ENDIAN_CHANGE_U16((int16_t)(temp * 256.0f));
} // setTemp
/**
* @brief Set the advertisment count.
* @param [in] advCount The advertisment number.
* @brief Set the advertisement count.
* @param [in] advCount The advertisement number.
*/
void NimBLEEddystoneTLM::setCount(uint32_t advCount) {
m_eddystoneData.advCount = advCount;
@ -217,8 +221,8 @@ void NimBLEEddystoneTLM::setCount(uint32_t advCount) {
/**
* @brief Set the advertisment time.
* @param [in] tmil The advertisment time in milliseconds.
* @brief Set the advertisement time.
* @param [in] tmil The advertisement time in milliseconds.
*/
void NimBLEEddystoneTLM::setTime(uint32_t tmil) {
m_eddystoneData.tmil = tmil;

View File

@ -152,7 +152,7 @@ std::string NimBLEEddystoneURL::getDecodedURL() {
/**
* @brief Set the raw data for the beacon advertisment.
* @brief Set the raw data for the beacon advertisement.
* @param [in] data The raw data to advertise.
*/
void NimBLEEddystoneURL::setData(const std::string &data) {
@ -172,7 +172,11 @@ void NimBLEEddystoneURL::setData(const std::string &data) {
* @param [in] l_uuid The UUID.
*/
void NimBLEEddystoneURL::setUUID(const NimBLEUUID &l_uuid) {
beaconUUID = l_uuid.getNative()->u16.value;
if (l_uuid.bitSize() != 16) {
NIMBLE_LOGE(LOG_TAG, "UUID must be 16 bits");
return;
}
beaconUUID = *reinterpret_cast<const uint16_t*>(l_uuid.getValue());
} // setUUID

View File

@ -70,7 +70,7 @@ bool NimBLEExtAdvertising::setInstanceData(uint8_t inst_id, NimBLEExtAdvertiseme
NULL);
#else
int rc = ble_gap_ext_adv_configure(inst_id,
&data.m_params,
&adv.m_params,
NULL,
NimBLEExtAdvertising::handleGapEvent,
NULL);
@ -100,12 +100,8 @@ bool NimBLEExtAdvertising::setInstanceData(uint8_t inst_id, NimBLEExtAdvertiseme
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Invalid advertisement data: rc = %d", rc);
} else {
if (adv.m_advAddress != NimBLEAddress("")) {
ble_addr_t addr;
memcpy(&addr.val, adv.m_advAddress.getNative(), 6);
// Custom advertising address must be random.
addr.type = BLE_OWN_ADDR_RANDOM;
rc = ble_gap_ext_adv_set_addr(inst_id, &addr);
if (!adv.m_advAddress.isNull()) {
rc = ble_gap_ext_adv_set_addr(inst_id, adv.m_advAddress.getBase());
}
if (rc != 0) {
@ -272,7 +268,7 @@ bool NimBLEExtAdvertising::stop() {
/**
* @brief Set a callback to call when the advertisement stops.
* @param [in] pCallbacks A pointer to a callback to be invoked when an advertisment stops.
* @param [in] pCallbacks A pointer to a callback to be invoked when an advertisement stops.
* @param [in] deleteCallbacks if true callback class will be deleted when advertising is destructed.
*/
void NimBLEExtAdvertising::setCallbacks(NimBLEExtAdvertisingCallbacks* pCallbacks,
@ -341,7 +337,7 @@ int NimBLEExtAdvertising::handleGapEvent(struct ble_gap_event *event, void *arg)
case BLE_HS_EOS:
case BLE_HS_ECONTROLLER:
case BLE_HS_ENOTSYNCED:
NIMBLE_LOGC(LOG_TAG, "host reset, rc = %d", event->adv_complete.reason);
NIMBLE_LOGE(LOG_TAG, "host reset, rc = %d", event->adv_complete.reason);
NimBLEDevice::onReset(event->adv_complete.reason);
return 0;
default:
@ -388,10 +384,10 @@ void NimBLEExtAdvertisingCallbacks::onScanRequest(NimBLEExtAdvertising *pAdv,
* * BLE_HCI_LE_PHY_CODED
*/
NimBLEExtAdvertisement::NimBLEExtAdvertisement(uint8_t priPhy, uint8_t secPhy)
: m_advAddress("")
: m_advAddress{}
{
memset (&m_params, 0, sizeof(m_params));
m_params.own_addr_type = NimBLEDevice::m_own_addr_type;
m_params.own_addr_type = NimBLEDevice::m_ownAddrType;
m_params.primary_phy = priPhy;
m_params.secondary_phy = secPhy;
m_params.tx_power = 127;
@ -493,10 +489,7 @@ void NimBLEExtAdvertisement::setScanFilter(bool scanRequestWhitelistOnly, bool c
* @param [in] addr The address of the peer to direct the advertisements.
*/
void NimBLEExtAdvertisement::setDirectedPeer(const NimBLEAddress & addr) {
ble_addr_t peerAddr;
memcpy(&peerAddr.val, addr.getNative(), 6);
peerAddr.type = addr.getType();
m_params.peer = peerAddr;
m_params.peer = *addr.getBase();
} // setDirectedPeer
@ -623,9 +616,6 @@ void NimBLEExtAdvertisement::addData(const uint8_t * data, size_t length) {
/**
* @brief Set the appearance.
* @param [in] appearance The appearance code value.
*
* See also:
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.gap.appearance.xml
*/
void NimBLEExtAdvertisement::setAppearance(uint16_t appearance) {
char cdata[2];
@ -771,21 +761,9 @@ void NimBLEExtAdvertisement::setServices(const bool complete, const uint8_t size
for(auto &it : v_uuid){
if(it.bitSize() != size) {
NIMBLE_LOGE(LOG_TAG, "Service UUID(%d) invalid", size);
return;
continue;
} else {
switch(size) {
case 16:
uuids += std::string((char*)&it.getNative()->u16.value, 2);
break;
case 32:
uuids += std::string((char*)&it.getNative()->u32.value, 4);
break;
case 128:
uuids += std::string((char*)&it.getNative()->u128.value, 16);
break;
default:
return;
}
uuids += std::string(reinterpret_cast<const char*>(it.getValue()), size / 8);
}
}
@ -799,35 +777,30 @@ void NimBLEExtAdvertisement::setServices(const bool complete, const uint8_t size
* @param [in] data The data to be associated with the service data advertised.
*/
void NimBLEExtAdvertisement::setServiceData(const NimBLEUUID &uuid, const std::string &data) {
char cdata[2];
switch (uuid.bitSize()) {
case 16: {
uint8_t size = uuid.bitSize() / 8;
char cdata[2] = {static_cast<char>(1 + size + data.length()), BLE_HS_ADV_TYPE_SVC_DATA_UUID16};
switch (size) {
case 2: {
// [Len] [0x16] [UUID16] data
cdata[0] = data.length() + 3;
cdata[1] = BLE_HS_ADV_TYPE_SVC_DATA_UUID16; // 0x16
addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->u16.value, 2) + data);
break;
}
case 32: {
// [Len] [0x20] [UUID32] data
cdata[0] = data.length() + 5;
cdata[1] = BLE_HS_ADV_TYPE_SVC_DATA_UUID32; // 0x20
addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->u32.value, 4) + data);
break;
}
case 128: {
case 16: {
// [Len] [0x21] [UUID128] data
cdata[0] = data.length() + 17;
cdata[1] = BLE_HS_ADV_TYPE_SVC_DATA_UUID128; // 0x21
addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->u128.value, 16) + data);
break;
}
case 4: {
// [Len] [0x20] [UUID32] data
cdata[1] = BLE_HS_ADV_TYPE_SVC_DATA_UUID32; // 0x20
break;
}
default:
return;
}
addData(std::string(cdata, 2) + std::string(reinterpret_cast<const char*>(uuid.getValue()), size) + data);
} // setServiceData

View File

@ -26,28 +26,34 @@ 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));
m_batteryService = server->createService(NimBLEUUID((uint16_t) 0x180f));
m_deviceInfoService = server->createService(NimBLEUUID((uint16_t)0x180a));
m_hidService = server->createService(NimBLEUUID((uint16_t)0x1812));
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);
m_pnpCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t)0x2a50, NIMBLE_PROPERTY::READ);
/*
* Non-mandatory characteristics for device info service
* Will be created on demand
*/
m_manufacturerCharacteristic = nullptr;
/*
* 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);
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);
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);
@ -56,8 +62,8 @@ NimBLEHIDDevice::NimBLEHIDDevice(NimBLEServer* server) {
* 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);
const uint8_t pMode[] = {0x01};
protocolMode()->setValue((uint8_t*)pMode, 1);
}
NimBLEHIDDevice::~NimBLEHIDDevice() {
@ -86,7 +92,10 @@ void NimBLEHIDDevice::startServices() {
* @brief Create a manufacturer characteristic (this characteristic is optional).
*/
NimBLECharacteristic* NimBLEHIDDevice::manufacturer() {
m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a29, NIMBLE_PROPERTY::READ);
if (m_manufacturerCharacteristic == nullptr) {
m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t)0x2a29, NIMBLE_PROPERTY::READ);
}
return m_manufacturerCharacteristic;
}
@ -95,18 +104,26 @@ NimBLECharacteristic* NimBLEHIDDevice::manufacturer() {
* @param [in] name The manufacturer name of this HID device.
*/
void NimBLEHIDDevice::manufacturer(std::string name) {
m_manufacturerCharacteristic->setValue(name);
manufacturer()->setValue(name);
}
/**
* @brief Sets the Plug n Play characterisc value.
* @brief Sets the Plug n Play characteristic value.
* @param [in] sig The vendor ID source number.
* @param [in] vid The vendor ID number.
* @param [in] pid The product ID number.
* @param [in] version The produce version number.
*/
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 };
uint8_t pnp[] = {
sig,
((uint8_t*)&vid)[0],
((uint8_t*)&vid)[1],
((uint8_t*)&pid)[0],
((uint8_t*)&pid)[1],
((uint8_t*)&version)[0],
((uint8_t*)&version)[1]
};
m_pnpCharacteristic->setValue(pnp, sizeof(pnp));
}
@ -116,7 +133,7 @@ void NimBLEHIDDevice::pnp(uint8_t sig, uint16_t vid, uint16_t pid, uint16_t vers
* @param [in] flags The HID Class Specification release number to use.
*/
void NimBLEHIDDevice::hidInfo(uint8_t country, uint8_t flags) {
uint8_t info[] = { 0x11, 0x1, country, flags };
uint8_t info[] = {0x11, 0x1, country, flags};
m_hidInfoCharacteristic->setValue(info, sizeof(info));
}
@ -126,11 +143,11 @@ void NimBLEHIDDevice::hidInfo(uint8_t country, uint8_t flags) {
* @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 | NIMBLE_PROPERTY::READ_ENC);
NimBLEDescriptor* inputReportDescriptor = inputReportCharacteristic->createDescriptor((uint16_t) 0x2908, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_ENC);
NimBLECharacteristic *inputReportCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4d, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ_ENC);
NimBLEDescriptor *inputReportDescriptor = inputReportCharacteristic->createDescriptor((uint16_t)0x2908, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_ENC);
uint8_t desc1_val[] = { reportID, 0x01 };
inputReportDescriptor->setValue((uint8_t*) desc1_val, 2);
uint8_t desc1_val[] = {reportID, 0x01};
inputReportDescriptor->setValue((uint8_t*)desc1_val, 2);
return inputReportCharacteristic;
}
@ -141,11 +158,11 @@ NimBLECharacteristic* NimBLEHIDDevice::inputReport(uint8_t reportID) {
* @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, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::WRITE_ENC);
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, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::WRITE_ENC);
uint8_t desc1_val[] = { reportID, 0x02 };
outputReportDescriptor->setValue((uint8_t*) desc1_val, 2);
uint8_t desc1_val[] = {reportID, 0x02};
outputReportDescriptor->setValue((uint8_t*)desc1_val, 2);
return outputReportCharacteristic;
}
@ -156,11 +173,11 @@ NimBLECharacteristic* NimBLEHIDDevice::outputReport(uint8_t reportID) {
* @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, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::WRITE_ENC);
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, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::WRITE_ENC);
uint8_t desc1_val[] = { reportID, 0x03 };
featureReportDescriptor->setValue((uint8_t*) desc1_val, 2);
uint8_t desc1_val[] = {reportID, 0x03};
featureReportDescriptor->setValue((uint8_t*)desc1_val, 2);
return featureReportCharacteristic;
}
@ -169,14 +186,14 @@ NimBLECharacteristic* NimBLEHIDDevice::featureReport(uint8_t reportID) {
* @brief Creates a keyboard boot input report characteristic
*/
NimBLECharacteristic* NimBLEHIDDevice::bootInput() {
return m_hidService->createCharacteristic((uint16_t) 0x2a22, NIMBLE_PROPERTY::NOTIFY);
return m_hidService->createCharacteristic((uint16_t)0x2a22, NIMBLE_PROPERTY::NOTIFY);
}
/**
* @brief Create a keyboard boot output report characteristic
*/
NimBLECharacteristic* NimBLEHIDDevice::bootOutput() {
return m_hidService->createCharacteristic((uint16_t) 0x2a32, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR);
return m_hidService->createCharacteristic((uint16_t)0x2a32, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR);
}
/**
@ -203,17 +220,17 @@ void NimBLEHIDDevice::setBatteryLevel(uint8_t level) {
/*
* @brief Returns battery level characteristic
* @ return battery level characteristic
*//*
BLECharacteristic* BLEHIDDevice::batteryLevel() {
*/
NimBLECharacteristic* NimBLEHIDDevice::batteryLevel() {
return m_batteryLevelCharacteristic;
}
BLECharacteristic* BLEHIDDevice::reportMap() {
NimBLECharacteristic* NimBLEHIDDevice::reportMap() {
return m_reportMapCharacteristic;
}
/*
BLECharacteristic* BLEHIDDevice::pnp() {
return m_pnpCharacteristic;
}

View File

@ -33,6 +33,7 @@
#define HID_DIGITAL_PEN 0x03C7
#define HID_BARCODE 0x03C8
#define PNPVersionField(MajorVersion, MinorVersion, PatchVersion) ((MajorVersion << 16) & 0xFF00) | ((MinorVersion << 8) & 0x00F0) | (PatchVersion & 0x000F)
/**
* @brief A model of a %BLE Human Interface Device.
@ -55,11 +56,11 @@ public:
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();
NimBLECharacteristic* batteryLevel();
void setBatteryLevel(uint8_t level);
//NimBLECharacteristic* reportMap();
NimBLECharacteristic* reportMap();
NimBLECharacteristic* hidControl();
NimBLECharacteristic* inputReport(uint8_t reportID);
NimBLECharacteristic* outputReport(uint8_t reportID);

View File

@ -0,0 +1,48 @@
/*
* NimBLELocalAttribute.cpp
*
* Created: on July 28 2024
* Author H2zero
*/
#ifndef NIMBLE_CPP_LOCAL_ATTRIBUTE_H_
#define NIMBLE_CPP_LOCAL_ATTRIBUTE_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
# include "NimBLEAttribute.h"
/**
* @brief A base class for local BLE attributes.
*/
class NimBLELocalAttribute : public NimBLEAttribute {
public:
/**
* @brief Get the removed flag.
* @return The removed flag.
*/
uint8_t getRemoved() const { return m_removed; }
protected:
/**
* @brief Construct a local attribute.
*/
NimBLELocalAttribute(const NimBLEUUID& uuid, uint16_t handle) : NimBLEAttribute{uuid, handle}, m_removed{0} {}
/**
* @brief Destroy the local attribute.
*/
~NimBLELocalAttribute() = default;
/**
* @brief Set the removed flag.
* @param [in] removed The removed flag.
*/
void setRemoved(uint8_t removed) { m_removed = removed; }
uint8_t m_removed{0};
};
#endif // CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL
#endif // NIMBLE_CPP_LOCAL_ATTRIBUTE_H_

View File

@ -0,0 +1,156 @@
/*
* NimBLELocalValueAttribute.cpp
*
* Created: on July 28 2024
* Author H2zero
*/
#ifndef NIMBLE_LOCAL_VALUE_ATTRIBUTE_H_
#define NIMBLE_LOCAL_VALUE_ATTRIBUTE_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_hs.h"
# else
# include "nimble/nimble/host/include/host/ble_hs.h"
# endif
typedef enum {
READ = BLE_GATT_CHR_F_READ,
READ_ENC = BLE_GATT_CHR_F_READ_ENC,
READ_AUTHEN = BLE_GATT_CHR_F_READ_AUTHEN,
READ_AUTHOR = BLE_GATT_CHR_F_READ_AUTHOR,
WRITE = BLE_GATT_CHR_F_WRITE,
WRITE_NR = BLE_GATT_CHR_F_WRITE_NO_RSP,
WRITE_ENC = BLE_GATT_CHR_F_WRITE_ENC,
WRITE_AUTHEN = BLE_GATT_CHR_F_WRITE_AUTHEN,
WRITE_AUTHOR = BLE_GATT_CHR_F_WRITE_AUTHOR,
BROADCAST = BLE_GATT_CHR_F_BROADCAST,
NOTIFY = BLE_GATT_CHR_F_NOTIFY,
INDICATE = BLE_GATT_CHR_F_INDICATE
} NIMBLE_PROPERTY;
# include "NimBLELocalAttribute.h"
# include "NimBLEAttValue.h"
# include <vector>
class NimBLEConnInfo;
class NimBLELocalValueAttribute : public NimBLELocalAttribute {
public:
/**
* @brief Get the properties of the attribute.
*/
uint16_t getProperties() const { return m_properties; }
/**
* @brief Get the length of the attribute value.
* @return The length of the attribute value.
*/
size_t getLength() const { return m_value.size(); }
/**
* @brief Get a copy of the value of the attribute value.
* @param [in] timestamp (Optional) A pointer to a time_t struct to get the time the value set.
* @return A copy of the attribute value.
*/
NimBLEAttValue getValue(time_t* timestamp = nullptr) const { return m_value; }
/**
* @brief Set the value of the attribute value.
* @param [in] data The data to set the value to.
* @param [in] size The size of the data.
*/
void setValue(const uint8_t* data, size_t size) { m_value.setValue(data, size); }
/**
* @brief Set the value of the attribute value.
* @param [in] str The string to set the value to.
*/
void setValue(const char* str) { m_value.setValue(str); }
/**
* @brief Set the value of the attribute value.
* @param [in] vec The vector to set the value to.
*/
void setValue(const std::vector<uint8_t>& vec) { m_value.setValue(vec); }
/**
* @brief Template to set the value to <type\>val.
* @param [in] val The value to set.
*/
template <typename T>
void setValue(const T& val) {
m_value.setValue<T>(val);
}
/**
* @brief Template to convert the data to <type\>.
* @tparam T The type to convert the data to.
* @param [in] timestamp (Optional) A pointer to a time_t struct to get the time the value set.
* @param [in] skipSizeCheck (Optional) If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>getValue<type>(&timestamp, skipSizeCheck);</tt>
*/
template <typename T>
T getValue(time_t* timestamp = nullptr, bool skipSizeCheck = false) const {
return m_value.getValue<T>(timestamp, skipSizeCheck);
}
protected:
friend class NimBLEServer;
/**
* @brief Construct a new NimBLELocalValueAttribute object.
* @param [in] uuid The UUID of the attribute.
* @param [in] handle The handle of the attribute.
* @param [in] maxLen The maximum length of the attribute value.
* @param [in] initLen The initial length of the attribute value.
*/
NimBLELocalValueAttribute(const NimBLEUUID& uuid,
uint16_t handle,
uint16_t maxLen,
uint16_t initLen = CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH)
: NimBLELocalAttribute(uuid, handle), m_value(initLen, maxLen) {}
/**
* @brief Destroy the NimBLELocalValueAttribute object.
*/
virtual ~NimBLELocalValueAttribute() = default;
/**
* @brief Callback function to support a read request.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
* @details This function is called by NimBLEServer when a read request is received.
*/
virtual void readEvent(NimBLEConnInfo& connInfo) = 0;
/**
* @brief Callback function to support a write request.
* @param [in] val The value to write.
* @param [in] len The length of the value.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
* @details This function is called by NimBLEServer when a write request is received.
*/
virtual void writeEvent(const uint8_t* val, uint16_t len, NimBLEConnInfo& connInfo) = 0;
/**
* @brief Get a pointer to value of the attribute.
* @return A pointer to the value of the attribute.
* @details This function is used by NimBLEServer when handling read/write requests.
*/
const NimBLEAttValue& getAttVal() const { return m_value; }
/**
* @brief Set the properties of the attribute.
* @param [in] properties The properties of the attribute.
*/
void setProperties(uint16_t properties) { m_properties = properties; }
NimBLEAttValue m_value{};
uint16_t m_properties{0};
};
#endif // CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL
#endif // NIMBLE_LOCAL_VALUE_ATTRIBUTE_H_

View File

@ -14,6 +14,7 @@
#if defined(CONFIG_NIMBLE_CPP_IDF) // using esp-idf
# include "esp_log.h"
# include "console/console.h"
# ifndef CONFIG_NIMBLE_CPP_LOG_LEVEL
# define CONFIG_NIMBLE_CPP_LOG_LEVEL 0
# endif
@ -35,9 +36,6 @@
# define NIMBLE_LOGE(tag, format, ...) \
NIMBLE_CPP_LOG_PRINT(ESP_LOG_ERROR, tag, format, ##__VA_ARGS__)
# define NIMBLE_LOGC(tag, format, ...) \
NIMBLE_CPP_LOG_PRINT(ESP_LOG_ERROR, tag, format, ##__VA_ARGS__)
#else // using Arduino
# include "nimble/porting/nimble/include/syscfg/syscfg.h"
# include "nimble/console/console.h"
@ -69,12 +67,13 @@
# if CONFIG_NIMBLE_CPP_LOG_LEVEL >= 1
# define NIMBLE_LOGE( tag, format, ... ) console_printf("E %s: " format "\n", tag, ##__VA_ARGS__)
# define NIMBLE_LOGC( tag, format, ... ) console_printf("CRIT %s: " format "\n", tag, ##__VA_ARGS__)
# else
# define NIMBLE_LOGE( tag, format, ... ) (void)tag
# define NIMBLE_LOGC( tag, format, ... ) (void)tag
# endif
#endif /* CONFIG_NIMBLE_CPP_IDF */
#define NIMBLE_LOGC( tag, format, ... ) console_printf("CRIT %s: " format "\n", tag, ##__VA_ARGS__)
#endif /* CONFIG_BT_ENABLED */
#endif /* MAIN_NIMBLELOG_H_ */

File diff suppressed because it is too large Load Diff

View File

@ -12,179 +12,67 @@
* Author: kolban
*/
#ifndef COMPONENTS_NIMBLEREMOTECHARACTERISTIC_H_
#define COMPONENTS_NIMBLEREMOTECHARACTERISTIC_H_
#ifndef NIMBLE_CPP_REMOTE_CHARACTERISTIC_H_
#define NIMBLE_CPP_REMOTE_CHARACTERISTIC_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
#include "NimBLERemoteService.h"
#include "NimBLERemoteDescriptor.h"
#include <vector>
#include <functional>
#include "NimBLELog.h"
# include "NimBLERemoteValueAttribute.h"
# include <vector>
# include <functional>
class NimBLERemoteService;
class NimBLERemoteDescriptor;
typedef std::function<void (NimBLERemoteCharacteristic* pBLERemoteCharacteristic,
uint8_t* pData, size_t length, bool isNotify)> notify_callback;
typedef struct {
const NimBLEUUID *uuid;
void *task_data;
} desc_filter_t;
/**
* @brief A model of a remote %BLE characteristic.
* @brief A model of a remote BLE characteristic.
*/
class NimBLERemoteCharacteristic {
public:
class NimBLERemoteCharacteristic : public NimBLERemoteValueAttribute {
public:
std::string toString() const;
const NimBLERemoteService* getRemoteService() const;
void deleteDescriptors() const;
size_t deleteDescriptor(const NimBLEUUID& uuid) const;
bool canBroadcast() const;
bool canRead() const;
bool canWriteNoResponse() const;
bool canWrite() const;
bool canNotify() const;
bool canIndicate() const;
bool canWriteSigned() const;
bool hasExtendedProps() const;
NimBLEClient* getClient() const override;
typedef std::function<void(NimBLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify)> notify_callback;
bool subscribe(bool notifications = true, const notify_callback notifyCallback = nullptr, bool response = true) const;
bool unsubscribe(bool response = true) const;
std::vector<NimBLERemoteDescriptor*>::iterator begin() const;
std::vector<NimBLERemoteDescriptor*>::iterator end() const;
NimBLERemoteDescriptor* getDescriptor(const NimBLEUUID& uuid) const;
const std::vector<NimBLERemoteDescriptor*>& getDescriptors(bool refresh = false) const;
private:
friend class NimBLEClient;
friend class NimBLERemoteService;
NimBLERemoteCharacteristic(const NimBLERemoteService* pRemoteService, const ble_gatt_chr* chr);
~NimBLERemoteCharacteristic();
// Public member functions
bool canBroadcast();
bool canIndicate();
bool canNotify();
bool canRead();
bool canWrite();
bool canWriteNoResponse();
std::vector<NimBLERemoteDescriptor*>::iterator begin();
std::vector<NimBLERemoteDescriptor*>::iterator end();
NimBLERemoteDescriptor* getDescriptor(const NimBLEUUID &uuid);
std::vector<NimBLERemoteDescriptor*>* getDescriptors(bool refresh = false);
void deleteDescriptors();
size_t deleteDescriptor(const NimBLEUUID &uuid);
uint16_t getHandle();
uint16_t getDefHandle();
NimBLEUUID getUUID();
NimBLEAttValue readValue(time_t *timestamp = nullptr);
std::string toString();
NimBLERemoteService* getRemoteService();
bool setNotify(uint16_t val, notify_callback notifyCallback = nullptr, bool response = true) const;
bool retrieveDescriptors(const NimBLEUUID* uuidFilter = nullptr) const;
uint8_t readUInt8() __attribute__ ((deprecated("Use template readValue<uint8_t>()")));
uint16_t readUInt16() __attribute__ ((deprecated("Use template readValue<uint16_t>()")));
uint32_t readUInt32() __attribute__ ((deprecated("Use template readValue<uint32_t>()")));
float readFloat() __attribute__ ((deprecated("Use template readValue<float>()")));
NimBLEAttValue getValue(time_t *timestamp = nullptr);
static int descriptorDiscCB(
uint16_t conn_handle, const ble_gatt_error* error, uint16_t chr_val_handle, const ble_gatt_dsc* dsc, void* arg);
bool subscribe(bool notifications = true,
notify_callback notifyCallback = nullptr,
bool response = false);
bool unsubscribe(bool response = false);
bool registerForNotify(notify_callback notifyCallback,
bool notifications = true,
bool response = true)
__attribute__ ((deprecated("Use subscribe()/unsubscribe()")));
bool writeValue(const uint8_t* data,
size_t length,
bool response = false);
bool writeValue(const std::vector<uint8_t>& v, bool response = false);
bool writeValue(const char* s, bool response = false);
const NimBLERemoteService* m_pRemoteService{nullptr};
uint8_t m_properties{0};
mutable notify_callback m_notifyCallback{nullptr};
mutable std::vector<NimBLERemoteDescriptor*> m_vDescriptors{};
/*********************** Template Functions ************************/
/**
* @brief Template to set the remote characteristic value to <type\>val.
* @param [in] s The value to write.
* @param [in] response True == request write response.
* @details Only used for non-arrays and types without a `c_str()` method.
*/
template<typename T>
#ifdef _DOXYGEN_
bool
#else
typename std::enable_if<!std::is_array<T>::value && !Has_c_str_len<T>::value, bool>::type
#endif
writeValue(const T& s, bool response = false) {
return writeValue((uint8_t*)&s, sizeof(T), response);
}
/**
* @brief Template to set the remote characteristic value to <type\>val.
* @param [in] s The value to write.
* @param [in] response True == request write response.
* @details Only used if the <type\> has a `c_str()` method.
*/
template<typename T>
#ifdef _DOXYGEN_
bool
#else
typename std::enable_if<Has_c_str_len<T>::value, bool>::type
#endif
writeValue(const T& s, bool response = false) {
return writeValue((uint8_t*)s.c_str(), s.length(), response);
}
/**
* @brief Template to convert the remote characteristic data to <type\>.
* @tparam T The type to convert the data to.
* @param [in] timestamp A pointer to a time_t struct to store the time the value was read.
* @param [in] skipSizeCheck If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is
* less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>getValue<type>(&timestamp, skipSizeCheck);</tt>
*/
template<typename T>
T getValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) {
if(!skipSizeCheck && m_value.size() < sizeof(T)) return T();
return *((T *)m_value.getValue(timestamp));
}
/**
* @brief Template to convert the remote characteristic data to <type\>.
* @tparam T The type to convert the data to.
* @param [in] timestamp A pointer to a time_t struct to store the time the value was read.
* @param [in] skipSizeCheck If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is
* less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>readValue<type>(&timestamp, skipSizeCheck);</tt>
*/
template<typename T>
T readValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) {
NimBLEAttValue value = readValue();
if(!skipSizeCheck && value.size() < sizeof(T)) return T();
return *((T *)value.getValue(timestamp));
}
private:
NimBLERemoteCharacteristic(NimBLERemoteService *pRemoteservice, const struct ble_gatt_chr *chr);
friend class NimBLEClient;
friend class NimBLERemoteService;
friend class NimBLERemoteDescriptor;
// Private member functions
bool setNotify(uint16_t val, notify_callback notifyCallback = nullptr, bool response = true);
bool retrieveDescriptors(const NimBLEUUID *uuid_filter = nullptr);
static int onReadCB(uint16_t conn_handle, const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg);
static int onWriteCB(uint16_t conn_handle, const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg);
static int descriptorDiscCB(uint16_t conn_handle, const struct ble_gatt_error *error,
uint16_t chr_val_handle, const struct ble_gatt_dsc *dsc,
void *arg);
static int nextCharCB(uint16_t conn_handle, const struct ble_gatt_error *error,
const struct ble_gatt_chr *chr, void *arg);
// Private properties
NimBLEUUID m_uuid;
uint8_t m_charProp;
uint16_t m_handle;
uint16_t m_defHandle;
uint16_t m_endHandle;
NimBLERemoteService* m_pRemoteService;
NimBLEAttValue m_value;
notify_callback m_notifyCallback;
// We maintain a vector of descriptors owned by this characteristic.
std::vector<NimBLERemoteDescriptor*> m_descriptorVector;
}; // NimBLERemoteCharacteristic
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */
#endif /* COMPONENTS_NIMBLEREMOTECHARACTERISTIC_H_ */
#endif /* NIMBLE_CPP_REMOTE_CHARACTERISTIC_H_ */

View File

@ -15,213 +15,33 @@
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
#include "NimBLERemoteDescriptor.h"
#include "NimBLEUtils.h"
#include "NimBLELog.h"
#include <climits>
static const char* LOG_TAG = "NimBLERemoteDescriptor";
# include "NimBLERemoteDescriptor.h"
# include "NimBLERemoteCharacteristic.h"
/**
* @brief Remote descriptor constructor.
* @param [in] pRemoteCharacteristic A pointer to the Characteristic that this belongs to.
* @param [in] dsc A pointer to the struct that contains the descriptor information.
*/
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);
break;
case BLE_UUID_TYPE_32:
m_uuid = NimBLEUUID(dsc->uuid.u32.value);
break;
case BLE_UUID_TYPE_128:
m_uuid = NimBLEUUID(const_cast<ble_uuid128_t*>(&dsc->uuid.u128));
break;
default:
break;
}
m_handle = dsc->handle;
m_pRemoteCharacteristic = pRemoteCharacteristic;
NIMBLE_LOGD(LOG_TAG, "<< NimBLERemoteDescriptor(): %s", m_uuid.toString().c_str());
}
/**
* @brief Retrieve the handle associated with this remote descriptor.
* @return The handle associated with this remote descriptor.
*/
uint16_t NimBLERemoteDescriptor::getHandle() {
return m_handle;
} // getHandle
NimBLERemoteDescriptor::NimBLERemoteDescriptor(const NimBLERemoteCharacteristic* pRemoteCharacteristic,
const ble_gatt_dsc* dsc)
: NimBLERemoteValueAttribute{dsc->uuid, dsc->handle}, m_pRemoteCharacteristic{pRemoteCharacteristic} {} // NimBLERemoteDescriptor
/**
* @brief Get the characteristic that owns this descriptor.
* @return The characteristic that owns this descriptor.
*/
NimBLERemoteCharacteristic* NimBLERemoteDescriptor::getRemoteCharacteristic() {
return m_pRemoteCharacteristic;
NimBLERemoteCharacteristic* NimBLERemoteDescriptor::getRemoteCharacteristic() const {
return const_cast<NimBLERemoteCharacteristic*>(m_pRemoteCharacteristic);
} // getRemoteCharacteristic
/**
* @brief Retrieve the UUID associated this remote descriptor.
* @return The UUID associated this remote descriptor.
*/
NimBLEUUID NimBLERemoteDescriptor::getUUID() {
return m_uuid;
} // getUUID
/**
* @brief Read a byte value
* @return The value as a byte
* @deprecated Use readValue<uint8_t>().
*/
uint8_t NimBLERemoteDescriptor::readUInt8() {
return readValue<uint8_t>();
} // readUInt8
/**
* @brief Read an unsigned 16 bit value
* @return The unsigned 16 bit value.
* @deprecated Use readValue<uint16_t>().
*/
uint16_t NimBLERemoteDescriptor::readUInt16() {
return readValue<uint16_t>();
} // readUInt16
/**
* @brief Read an unsigned 32 bit value.
* @return the unsigned 32 bit value.
* @deprecated Use readValue<uint32_t>().
*/
uint32_t NimBLERemoteDescriptor::readUInt32() {
return readValue<uint32_t>();
} // readUInt32
/**
* @brief Read the value of the remote descriptor.
* @return The value of the remote descriptor.
*/
NimBLEAttValue NimBLERemoteDescriptor::readValue() {
NIMBLE_LOGD(LOG_TAG, ">> Descriptor readValue: %s", toString().c_str());
NimBLEClient* pClient = getRemoteCharacteristic()->getRemoteService()->getClient();
NimBLEAttValue value;
if (!pClient->isConnected()) {
NIMBLE_LOGE(LOG_TAG, "Disconnected");
return value;
}
int rc = 0;
int retryCount = 1;
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
ble_task_data_t taskData = {this, cur_task, 0, &value};
do {
rc = ble_gattc_read_long(pClient->getConnId(), m_handle, 0,
NimBLERemoteDescriptor::onReadCB,
&taskData);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Error: Failed to read descriptor; rc=%d, %s",
rc, NimBLEUtils::returnCodeToString(rc));
return value;
}
#ifdef ulTaskNotifyValueClear
// Clear the task notification value to ensure we block
ulTaskNotifyValueClear(cur_task, ULONG_MAX);
#endif
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
rc = taskData.rc;
switch(rc){
case 0:
case BLE_HS_EDONE:
rc = 0;
break;
// Descriptor is not long-readable, return with what we have.
case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG):
NIMBLE_LOGI(LOG_TAG, "Attribute not long");
rc = 0;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC):
if (retryCount && pClient->secureConnection())
break;
/* Else falls through. */
default:
return value;
}
} while(rc != 0 && retryCount--);
NIMBLE_LOGD(LOG_TAG, "<< Descriptor readValue(): length: %u rc=%d", value.length(), rc);
return value;
} // readValue
/**
* @brief Callback for Descriptor read operation.
* @return success == 0 or error code.
*/
int NimBLERemoteDescriptor::onReadCB(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg)
{
(void)attr;
ble_task_data_t *pTaskData = (ble_task_data_t*)arg;
NimBLERemoteDescriptor* desc = (NimBLERemoteDescriptor*)pTaskData->pATT;
uint16_t conn_id = desc->getRemoteCharacteristic()->getRemoteService()->getClient()->getConnId();
if(conn_id != conn_handle){
return 0;
}
NIMBLE_LOGD(LOG_TAG, "Read complete; status=%d conn_handle=%d", error->status, conn_handle);
NimBLEAttValue *valBuf = (NimBLEAttValue*)pTaskData->buf;
int rc = error->status;
if(rc == 0) {
if(attr) {
uint16_t data_len = OS_MBUF_PKTLEN(attr->om);
if((valBuf->size() + data_len) > BLE_ATT_ATTR_MAX_LEN) {
rc = BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} else {
NIMBLE_LOGD(LOG_TAG, "Got %u bytes", data_len);
valBuf->append(attr->om->om_data, data_len);
return 0;
}
}
}
pTaskData->rc = rc;
xTaskNotifyGive(pTaskData->task);
return rc;
}
/**
* @brief Return a string representation of this Remote Descriptor.
* @return A string representation of this Remote Descriptor.
*/
std::string NimBLERemoteDescriptor::toString() {
std::string NimBLERemoteDescriptor::toString() const {
std::string res = "Descriptor: uuid: " + getUUID().toString();
char val[6];
char val[6];
res += ", handle: ";
snprintf(val, sizeof(val), "%d", getHandle());
res += val;
@ -229,137 +49,8 @@ std::string NimBLERemoteDescriptor::toString() {
return res;
} // toString
/**
* @brief Callback for descriptor write operation.
* @return success == 0 or error code.
*/
int NimBLERemoteDescriptor::onWriteCB(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg)
{
ble_task_data_t *pTaskData = (ble_task_data_t*)arg;
NimBLERemoteDescriptor* descriptor = (NimBLERemoteDescriptor*)pTaskData->pATT;
if(descriptor->getRemoteCharacteristic()->getRemoteService()->getClient()->getConnId() != conn_handle){
return 0;
}
NIMBLE_LOGI(LOG_TAG, "Write complete; status=%d conn_handle=%d", error->status, conn_handle);
pTaskData->rc = error->status;
xTaskNotifyGive(pTaskData->task);
return 0;
NimBLEClient* NimBLERemoteDescriptor::getClient() const {
return m_pRemoteCharacteristic->getClient();
}
/**
* @brief Write a new value to a remote descriptor from a std::vector<uint8_t>.
* @param [in] vec A std::vector<uint8_t> value to write to the remote descriptor.
* @param [in] response Whether we require a response from the write.
* @return false if not connected or otherwise cannot perform write.
*/
bool NimBLERemoteDescriptor::writeValue(const std::vector<uint8_t>& vec, bool response) {
return writeValue((uint8_t*)&vec[0], vec.size(), response);
} // writeValue
/**
* @brief Write a new value to the remote descriptor from a const char*.
* @param [in] char_s A character string to write to the remote descriptor.
* @param [in] response Whether we require a response from the write.
* @return false if not connected or otherwise cannot perform write.
*/
bool NimBLERemoteDescriptor::writeValue(const char* char_s, bool response) {
return writeValue((uint8_t*)char_s, strlen(char_s), response);
} // writeValue
/**
* @brief Write a new value to a remote descriptor.
* @param [in] data The data to send to the remote descriptor.
* @param [in] length The length of the data to send.
* @param [in] response True if we expect a write response.
* @return false if not connected or otherwise cannot perform write.
*/
bool NimBLERemoteDescriptor::writeValue(const uint8_t* data, size_t length, bool response) {
NIMBLE_LOGD(LOG_TAG, ">> Descriptor writeValue: %s", toString().c_str());
NimBLEClient* pClient = getRemoteCharacteristic()->getRemoteService()->getClient();
// Check to see that we are connected.
if (!pClient->isConnected()) {
NIMBLE_LOGE(LOG_TAG, "Disconnected");
return false;
}
int rc = 0;
int retryCount = 1;
uint16_t mtu = ble_att_mtu(pClient->getConnId()) - 3;
// Check if the data length is longer than we can write in 1 connection event.
// If so we must do a long write which requires a response.
if(length <= mtu && !response) {
rc = ble_gattc_write_no_rsp_flat(pClient->getConnId(), m_handle, data, length);
return (rc == 0);
}
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
ble_task_data_t taskData = {this, cur_task, 0, nullptr};
do {
if(length > mtu) {
NIMBLE_LOGI(LOG_TAG,"long write %d bytes", length);
os_mbuf *om = ble_hs_mbuf_from_flat(data, length);
rc = ble_gattc_write_long(pClient->getConnId(), m_handle, 0, om,
NimBLERemoteDescriptor::onWriteCB,
&taskData);
} else {
rc = ble_gattc_write_flat(pClient->getConnId(), m_handle,
data, length,
NimBLERemoteDescriptor::onWriteCB,
&taskData);
}
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Error: Failed to write descriptor; rc=%d", rc);
return false;
}
#ifdef ulTaskNotifyValueClear
// Clear the task notification value to ensure we block
ulTaskNotifyValueClear(cur_task, ULONG_MAX);
#endif
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
rc = taskData.rc;
switch(rc) {
case 0:
case BLE_HS_EDONE:
rc = 0;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG):
NIMBLE_LOGE(LOG_TAG, "Long write not supported by peer; Truncating length to %d", mtu);
retryCount++;
length = mtu;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC):
if (retryCount && pClient->secureConnection())
break;
/* Else falls through. */
default:
return false;
}
} while(rc != 0 && retryCount--);
NIMBLE_LOGD(LOG_TAG, "<< Descriptor writeValue, rc: %d",rc);
return (rc == 0);
} // writeValue
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */

View File

@ -12,97 +12,34 @@
* Author: kolban
*/
#ifndef COMPONENTS_NIMBLEREMOTEDESCRIPTOR_H_
#define COMPONENTS_NIMBLEREMOTEDESCRIPTOR_H_
#ifndef NIMBLE_CPP_REMOTE_DESCRIPTOR_H_
#define NIMBLE_CPP_REMOTE_DESCRIPTOR_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
#include "NimBLERemoteCharacteristic.h"
# include "NimBLERemoteValueAttribute.h"
class NimBLERemoteCharacteristic;
class NimBLEClient;
/**
* @brief A model of remote %BLE descriptor.
* @brief A model of remote BLE descriptor.
*/
class NimBLERemoteDescriptor {
public:
uint16_t getHandle();
NimBLERemoteCharacteristic* getRemoteCharacteristic();
NimBLEUUID getUUID();
NimBLEAttValue readValue();
class NimBLERemoteDescriptor : public NimBLERemoteValueAttribute {
public:
NimBLERemoteCharacteristic* getRemoteCharacteristic() const;
std::string toString(void) const;
NimBLEClient* getClient() const override;
uint8_t readUInt8() __attribute__ ((deprecated("Use template readValue<uint8_t>()")));
uint16_t readUInt16() __attribute__ ((deprecated("Use template readValue<uint16_t>()")));
uint32_t readUInt32() __attribute__ ((deprecated("Use template readValue<uint32_t>()")));
std::string toString(void);
bool writeValue(const uint8_t* data, size_t length, bool response = false);
bool writeValue(const std::vector<uint8_t>& v, bool response = false);
bool writeValue(const char* s, bool response = false);
private:
friend class NimBLERemoteCharacteristic;
NimBLERemoteDescriptor(const NimBLERemoteCharacteristic* pRemoteCharacteristic, const ble_gatt_dsc* dsc);
~NimBLERemoteDescriptor() = default;
/*********************** Template Functions ************************/
/**
* @brief Template to set the remote descriptor value to <type\>val.
* @param [in] s The value to write.
* @param [in] response True == request write response.
* @details Only used for non-arrays and types without a `c_str()` method.
*/
template<typename T>
#ifdef _DOXYGEN_
bool
#else
typename std::enable_if<!std::is_array<T>::value && !Has_c_str_len<T>::value, bool>::type
#endif
writeValue(const T& s, bool response = false) {
return writeValue((uint8_t*)&s, sizeof(T), response);
}
/**
* @brief Template to set the remote descriptor value to <type\>val.
* @param [in] s The value to write.
* @param [in] response True == request write response.
* @details Only used if the <type\> has a `c_str()` method.
*/
template<typename T>
#ifdef _DOXYGEN_
bool
#else
typename std::enable_if<Has_c_str_len<T>::value, bool>::type
#endif
writeValue(const T& s, bool response = false) {
return writeValue((uint8_t*)s.c_str(), s.length(), response);
}
/**
* @brief Template to convert the remote descriptor data to <type\>.
* @tparam T The type to convert the data to.
* @param [in] skipSizeCheck If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is
* less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>readValue<type>(skipSizeCheck);</tt>
*/
template<typename T>
T readValue(bool skipSizeCheck = false) {
NimBLEAttValue value = readValue();
if(!skipSizeCheck && value.size() < sizeof(T)) return T();
return *((T *)value.data());
}
private:
friend class NimBLERemoteCharacteristic;
NimBLERemoteDescriptor (NimBLERemoteCharacteristic* pRemoteCharacteristic,
const struct ble_gatt_dsc *dsc);
static int onWriteCB(uint16_t conn_handle, const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg);
static int onReadCB(uint16_t conn_handle, const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg);
uint16_t m_handle;
NimBLEUUID m_uuid;
NimBLERemoteCharacteristic* m_pRemoteCharacteristic;
const NimBLERemoteCharacteristic* m_pRemoteCharacteristic;
};
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */
#endif /* COMPONENTS_NIMBLEREMOTEDESCRIPTOR_H_ */
#endif /* NIMBLE_CPP_REMOTE_DESCRIPTOR_H_ */

View File

@ -15,12 +15,14 @@
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
#include "NimBLERemoteService.h"
#include "NimBLEUtils.h"
#include "NimBLEDevice.h"
#include "NimBLELog.h"
# include "NimBLERemoteService.h"
# include "NimBLERemoteCharacteristic.h"
# include "NimBLEClient.h"
# include "NimBLEAttValue.h"
# include "NimBLEUtils.h"
# include "NimBLELog.h"
#include <climits>
# include <climits>
static const char* LOG_TAG = "NimBLERemoteService";
@ -29,28 +31,8 @@ static const char* LOG_TAG = "NimBLERemoteService";
* @param [in] pClient A pointer to the client this belongs to.
* @param [in] service A pointer to the structure with the service information.
*/
NimBLERemoteService::NimBLERemoteService(NimBLEClient* pClient, const struct ble_gatt_svc* service) {
NIMBLE_LOGD(LOG_TAG, ">> NimBLERemoteService()");
m_pClient = pClient;
switch (service->uuid.u.type) {
case BLE_UUID_TYPE_16:
m_uuid = NimBLEUUID(service->uuid.u16.value);
break;
case BLE_UUID_TYPE_32:
m_uuid = NimBLEUUID(service->uuid.u32.value);
break;
case BLE_UUID_TYPE_128:
m_uuid = NimBLEUUID(const_cast<ble_uuid128_t*>(&service->uuid.u128));
break;
default:
break;
}
m_startHandle = service->start_handle;
m_endHandle = service->end_handle;
NIMBLE_LOGD(LOG_TAG, "<< NimBLERemoteService(): %s", m_uuid.toString().c_str());
}
NimBLERemoteService::NimBLERemoteService(NimBLEClient* pClient, const ble_gatt_svc* service)
: NimBLEAttribute{service->uuid, service->start_handle}, m_pClient{pClient}, m_endHandle{service->end_handle} {}
/**
* @brief When deleting the service make sure we delete all characteristics and descriptors.
@ -59,66 +41,62 @@ NimBLERemoteService::~NimBLERemoteService() {
deleteCharacteristics();
}
/**
* @brief Get iterator to the beginning of the vector of remote characteristic pointers.
* @return An iterator to the beginning of the vector of remote characteristic pointers.
*/
std::vector<NimBLERemoteCharacteristic*>::iterator NimBLERemoteService::begin() {
return m_characteristicVector.begin();
std::vector<NimBLERemoteCharacteristic*>::iterator NimBLERemoteService::begin() const {
return m_vChars.begin();
}
/**
* @brief Get iterator to the end of the vector of remote characteristic pointers.
* @return An iterator to the end of the vector of remote characteristic pointers.
*/
std::vector<NimBLERemoteCharacteristic*>::iterator NimBLERemoteService::end() {
return m_characteristicVector.end();
std::vector<NimBLERemoteCharacteristic*>::iterator NimBLERemoteService::end() const {
return m_vChars.end();
}
/**
* @brief Get the remote characteristic object for the characteristic UUID.
* @param [in] uuid Remote characteristic uuid.
* @return A pointer to the remote characteristic object.
*/
NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const char* uuid) {
NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const char* uuid) const {
return getCharacteristic(NimBLEUUID(uuid));
} // getCharacteristic
/**
* @brief Get the characteristic object for the UUID.
* @param [in] uuid Characteristic uuid.
* @return A pointer to the characteristic object, or nullptr if not found.
*/
NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const NimBLEUUID &uuid) {
NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const NimBLEUUID& uuid) const {
NIMBLE_LOGD(LOG_TAG, ">> getCharacteristic: uuid: %s", uuid.toString().c_str());
NimBLERemoteCharacteristic* pChar = nullptr;
size_t prev_size = m_vChars.size();
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;
for (const auto& it : m_vChars) {
if (it->getUUID() == uuid) {
pChar = it;
goto Done;
}
}
size_t prev_size = m_characteristicVector.size();
if(retrieveCharacteristics(&uuid)) {
if(m_characteristicVector.size() > prev_size) {
return m_characteristicVector.back();
if (retrieveCharacteristics(&uuid)) {
if (m_vChars.size() > prev_size) {
pChar = m_vChars.back();
goto Done;
}
// If the request was successful but 16/32 bit uuid not found
// try again with the 128 bit uuid.
if(uuid.bitSize() == BLE_UUID_TYPE_16 ||
uuid.bitSize() == BLE_UUID_TYPE_32)
{
if (uuid.bitSize() == BLE_UUID_TYPE_16 || uuid.bitSize() == BLE_UUID_TYPE_32) {
NimBLEUUID uuid128(uuid);
uuid128.to128();
if (retrieveCharacteristics(&uuid128)) {
if(m_characteristicVector.size() > prev_size) {
return m_characteristicVector.back();
if (m_vChars.size() > prev_size) {
pChar = m_vChars.back();
}
}
} else {
@ -128,283 +106,193 @@ NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const NimBLEU
uuid16.to16();
// if the uuid was 128 bit but not of the BLE base type this check will fail
if (uuid16.bitSize() == BLE_UUID_TYPE_16) {
if(retrieveCharacteristics(&uuid16)) {
if(m_characteristicVector.size() > prev_size) {
return m_characteristicVector.back();
if (retrieveCharacteristics(&uuid16)) {
if (m_vChars.size() > prev_size) {
pChar = m_vChars.back();
}
}
}
}
}
NIMBLE_LOGD(LOG_TAG, "<< getCharacteristic: not found");
return nullptr;
Done:
NIMBLE_LOGD(LOG_TAG, "<< Characteristic %sfound", pChar ? "" : "not ");
return pChar;
} // getCharacteristic
/**
* @brief Get a pointer to the vector of found characteristics.
* @param [in] refresh If true the current characteristics vector will cleared and
* all characteristics for this service retrieved from the peripheral.
* If false the vector will be returned with the currently stored characteristics of this service.
* @return A pointer to the vector of descriptors for this characteristic.
* @return A read-only reference to the vector of characteristics retrieved for this service.
*/
std::vector<NimBLERemoteCharacteristic*>* NimBLERemoteService::getCharacteristics(bool refresh) {
if(refresh) {
const std::vector<NimBLERemoteCharacteristic*>& NimBLERemoteService::getCharacteristics(bool refresh) const {
if (refresh) {
deleteCharacteristics();
if (!retrieveCharacteristics()) {
NIMBLE_LOGE(LOG_TAG, "Error: Failed to get characteristics");
}
else{
NIMBLE_LOGI(LOG_TAG, "Found %d characteristics", m_characteristicVector.size());
}
retrieveCharacteristics();
}
return &m_characteristicVector;
return m_vChars;
} // getCharacteristics
/**
* @brief Callback for Characterisic discovery.
* @brief Callback for Characteristic discovery.
* @return success == 0 or error code.
*/
int NimBLERemoteService::characteristicDiscCB(uint16_t conn_handle,
const struct ble_gatt_error *error,
const struct ble_gatt_chr *chr, void *arg)
{
NIMBLE_LOGD(LOG_TAG,"Characteristic Discovered >> status: %d handle: %d",
error->status, (error->status == 0) ? chr->val_handle : -1);
int NimBLERemoteService::characteristicDiscCB(uint16_t conn_handle,
const ble_gatt_error* error,
const ble_gatt_chr* chr,
void* arg) {
NIMBLE_LOGD(LOG_TAG, "Characteristic Discovery >>");
auto pTaskData = (NimBLETaskData*)arg;
const auto pSvc = (NimBLERemoteService*)pTaskData->m_pInstance;
ble_task_data_t *pTaskData = (ble_task_data_t*)arg;
NimBLERemoteService *service = (NimBLERemoteService*)pTaskData->pATT;
if (error->status == BLE_HS_ENOTCONN) {
NIMBLE_LOGE(LOG_TAG, "<< Characteristic Discovery; Not connected");
NimBLEUtils::taskRelease(*pTaskData, error->status);
return error->status;
}
// Make sure the discovery is for this device
if(service->getClient()->getConnId() != conn_handle){
if (pSvc->getClient()->getConnHandle() != conn_handle) {
return 0;
}
if(error->status == 0) {
// Found a service - add it to the vector
NimBLERemoteCharacteristic* pRemoteCharacteristic = new NimBLERemoteCharacteristic(service, chr);
service->m_characteristicVector.push_back(pRemoteCharacteristic);
if (error->status == 0) {
pSvc->m_vChars.push_back(new NimBLERemoteCharacteristic(pSvc, chr));
return 0;
}
if(error->status == BLE_HS_EDONE) {
pTaskData->rc = 0;
} else {
NIMBLE_LOGE(LOG_TAG, "characteristicDiscCB() rc=%d %s",
error->status,
NimBLEUtils::returnCodeToString(error->status));
pTaskData->rc = error->status;
}
xTaskNotifyGive(pTaskData->task);
NIMBLE_LOGD(LOG_TAG,"<< Characteristic Discovered");
NimBLEUtils::taskRelease(*pTaskData, error->status);
NIMBLE_LOGD(LOG_TAG, "<< Characteristic Discovery");
return error->status;
}
/**
* @brief Retrieve all the characteristics for this service.
* This function will not return until we have all the characteristics.
* @return True if successful.
*/
bool NimBLERemoteService::retrieveCharacteristics(const NimBLEUUID *uuid_filter) {
NIMBLE_LOGD(LOG_TAG, ">> retrieveCharacteristics() for service: %s", getUUID().toString().c_str());
bool NimBLERemoteService::retrieveCharacteristics(const NimBLEUUID* uuidFilter) const {
NIMBLE_LOGD(LOG_TAG, ">> retrieveCharacteristics()");
int rc = 0;
NimBLETaskData taskData(const_cast<NimBLERemoteService*>(this));
int rc = 0;
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
ble_task_data_t taskData = {this, cur_task, 0, nullptr};
if(uuid_filter == nullptr) {
rc = ble_gattc_disc_all_chrs(m_pClient->getConnId(),
m_startHandle,
m_endHandle,
NimBLERemoteService::characteristicDiscCB,
&taskData);
if (uuidFilter == nullptr) {
rc = ble_gattc_disc_all_chrs(m_pClient->getConnHandle(),
getHandle(),
getEndHandle(),
NimBLERemoteService::characteristicDiscCB,
&taskData);
} else {
rc = ble_gattc_disc_chrs_by_uuid(m_pClient->getConnId(),
m_startHandle,
m_endHandle,
&uuid_filter->getNative()->u,
NimBLERemoteService::characteristicDiscCB,
&taskData);
rc = ble_gattc_disc_chrs_by_uuid(m_pClient->getConnHandle(),
getHandle(),
getEndHandle(),
uuidFilter->getBase(),
NimBLERemoteService::characteristicDiscCB,
&taskData);
}
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gattc_disc_all_chrs: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
NIMBLE_LOGE(LOG_TAG, "ble_gattc_disc_chrs rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
return false;
}
#ifdef ulTaskNotifyValueClear
// Clear the task notification value to ensure we block
ulTaskNotifyValueClear(cur_task, ULONG_MAX);
#endif
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if(taskData.rc == 0){
if (uuid_filter == nullptr) {
if (m_characteristicVector.size() > 1) {
for (auto it = m_characteristicVector.begin(); it != m_characteristicVector.end(); ++it ) {
auto nx = std::next(it, 1);
if (nx == m_characteristicVector.end()) {
break;
}
(*it)->m_endHandle = (*nx)->m_defHandle - 1;
}
}
if (m_characteristicVector.size() > 0) {
m_characteristicVector.back()->m_endHandle = getEndHandle();
}
}
NimBLEUtils::taskWait(taskData, BLE_NPL_TIME_FOREVER);
rc = taskData.m_flags;
if (rc == 0 || rc == BLE_HS_EDONE) {
NIMBLE_LOGD(LOG_TAG, "<< retrieveCharacteristics()");
return true;
}
NIMBLE_LOGE(LOG_TAG, "Could not retrieve characteristics");
NIMBLE_LOGE(LOG_TAG, "<< retrieveCharacteristics() rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
return false;
} // retrieveCharacteristics
/**
* @brief Get the client associated with this service.
* @return A reference to the client associated with this service.
*/
NimBLEClient* NimBLERemoteService::getClient() {
NimBLEClient* NimBLERemoteService::getClient() const {
return m_pClient;
} // getClient
/**
* @brief Get the service end handle.
*/
uint16_t NimBLERemoteService::getEndHandle() {
return m_endHandle;
} // getEndHandle
/**
* @brief Get the service start handle.
*/
uint16_t NimBLERemoteService::getStartHandle() {
return m_startHandle;
} // getStartHandle
/**
* @brief Get the service UUID.
*/
NimBLEUUID NimBLERemoteService::getUUID() {
return m_uuid;
}
/**
* @brief Read the value of a characteristic associated with this service.
* @param [in] characteristicUuid The characteristic to read.
* @param [in] uuid The characteristic to read.
* @returns a string containing the value or an empty string if not found or error.
*/
std::string NimBLERemoteService::getValue(const NimBLEUUID &characteristicUuid) {
NIMBLE_LOGD(LOG_TAG, ">> readValue: uuid: %s", characteristicUuid.toString().c_str());
std::string ret = "";
NimBLERemoteCharacteristic* pChar = getCharacteristic(characteristicUuid);
if(pChar != nullptr) {
ret = pChar->readValue();
NimBLEAttValue NimBLERemoteService::getValue(const NimBLEUUID& uuid) const {
const auto pChar = getCharacteristic(uuid);
if (pChar) {
return pChar->readValue();
}
NIMBLE_LOGD(LOG_TAG, "<< readValue");
return ret;
return NimBLEAttValue{};
} // readValue
/**
* @brief Set the value of a characteristic.
* @param [in] characteristicUuid The characteristic to set.
* @param [in] uuid The characteristic UUID to set.
* @param [in] value The value to set.
* @returns true on success, false if not found or error
*/
bool NimBLERemoteService::setValue(const NimBLEUUID &characteristicUuid, const std::string &value) {
NIMBLE_LOGD(LOG_TAG, ">> setValue: uuid: %s", characteristicUuid.toString().c_str());
bool ret = false;
NimBLERemoteCharacteristic* pChar = getCharacteristic(characteristicUuid);
if(pChar != nullptr) {
ret = pChar->writeValue(value);
bool NimBLERemoteService::setValue(const NimBLEUUID& uuid, const NimBLEAttValue& value) const {
const auto pChar = getCharacteristic(uuid);
if (pChar) {
return pChar->writeValue(value);
}
NIMBLE_LOGD(LOG_TAG, "<< setValue");
return ret;
return false;
} // setValue
/**
* @brief Delete the characteristics in the characteristics vector.
* @details We maintain a vector called m_characteristicsVector that contains pointers to BLERemoteCharacteristic
* object references. Since we allocated these in this class, we are also responsible for deleting
* them. This method does just that.
*/
void NimBLERemoteService::deleteCharacteristics() {
NIMBLE_LOGD(LOG_TAG, ">> deleteCharacteristics");
for(auto &it: m_characteristicVector) {
void NimBLERemoteService::deleteCharacteristics() const {
for (const auto& it : m_vChars) {
delete it;
}
m_characteristicVector.clear();
NIMBLE_LOGD(LOG_TAG, "<< deleteCharacteristics");
std::vector<NimBLERemoteCharacteristic*>{}.swap(m_vChars);
} // deleteCharacteristics
/**
* @brief Delete characteristic by UUID
* @param [in] uuid The UUID of the characteristic to be removed from the local database.
* @return Number of characteristics left.
*/
size_t NimBLERemoteService::deleteCharacteristic(const NimBLEUUID &uuid) {
NIMBLE_LOGD(LOG_TAG, ">> deleteCharacteristic");
for(auto it = m_characteristicVector.begin(); it != m_characteristicVector.end(); ++it) {
if((*it)->getUUID() == uuid) {
delete *it;
m_characteristicVector.erase(it);
size_t NimBLERemoteService::deleteCharacteristic(const NimBLEUUID& uuid) const {
for (auto it = m_vChars.begin(); it != m_vChars.end(); ++it) {
if ((*it)->getUUID() == uuid) {
delete (*it);
m_vChars.erase(it);
break;
}
}
NIMBLE_LOGD(LOG_TAG, "<< deleteCharacteristic");
return m_characteristicVector.size();
return m_vChars.size();
} // deleteCharacteristic
/**
* @brief Create a string representation of this remote service.
* @return A string representation of this remote service.
*/
std::string NimBLERemoteService::toString() {
std::string res = "Service: uuid: " + m_uuid.toString();
char val[6];
res += ", start_handle: ";
snprintf(val, sizeof(val), "%d", m_startHandle);
std::string NimBLERemoteService::toString() const {
std::string res = "Service: uuid: " + m_uuid.toString() + ", start_handle: 0x";
char val[5];
snprintf(val, sizeof(val), "%04x", getHandle());
res += val;
snprintf(val, sizeof(val), "%04x", m_startHandle);
res += " 0x";
res += val;
res += ", end_handle: ";
snprintf(val, sizeof(val), "%d", m_endHandle);
res += val;
snprintf(val, sizeof(val), "%04x", m_endHandle);
res += " 0x";
res += ", end_handle: 0x";
snprintf(val, sizeof(val), "%04x", getEndHandle());
res += val;
for (auto &it: m_characteristicVector) {
res += "\n" + it->toString();
for (const auto& chr : m_vChars) {
res += "\n" + chr->toString();
}
return res;

View File

@ -12,74 +12,54 @@
* Author: kolban
*/
#ifndef COMPONENTS_NIMBLEREMOTESERVICE_H_
#define COMPONENTS_NIMBLEREMOTESERVICE_H_
#ifndef NIMBLE_CPP_REMOTE_SERVICE_H_
#define NIMBLE_CPP_REMOTE_SERVICE_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
#include "NimBLEClient.h"
#include "NimBLEUUID.h"
#include "NimBLERemoteCharacteristic.h"
# include "NimBLEAttribute.h"
# include <vector>
#include <vector>
class NimBLEClient;
class NimBLERemoteCharacteristic;
class NimBLEClient;
class NimBLEAttValue;
/**
* @brief A model of a remote %BLE service.
* @brief A model of a remote BLE service.
*/
class NimBLERemoteService {
public:
virtual ~NimBLERemoteService();
class NimBLERemoteService : public NimBLEAttribute {
public:
NimBLERemoteCharacteristic* getCharacteristic(const char* uuid) const;
NimBLERemoteCharacteristic* getCharacteristic(const NimBLEUUID& uuid) const;
void deleteCharacteristics() const;
size_t deleteCharacteristic(const NimBLEUUID& uuid) const;
NimBLEClient* getClient(void) const;
NimBLEAttValue getValue(const NimBLEUUID& characteristicUuid) const;
bool setValue(const NimBLEUUID& characteristicUuid, const NimBLEAttValue& value) const;
std::string toString(void) const;
uint16_t getStartHandle() const { return getHandle(); }
uint16_t getEndHandle() const { return m_endHandle; }
// Public methods
std::vector<NimBLERemoteCharacteristic*>::iterator begin();
std::vector<NimBLERemoteCharacteristic*>::iterator end();
NimBLERemoteCharacteristic* getCharacteristic(const char* uuid);
NimBLERemoteCharacteristic* getCharacteristic(const NimBLEUUID &uuid);
void deleteCharacteristics();
size_t deleteCharacteristic(const NimBLEUUID &uuid);
NimBLEClient* getClient(void);
//uint16_t getHandle();
NimBLEUUID getUUID(void);
std::string getValue(const NimBLEUUID &characteristicUuid);
bool setValue(const NimBLEUUID &characteristicUuid,
const std::string &value);
std::string toString(void);
std::vector<NimBLERemoteCharacteristic*>* getCharacteristics(bool refresh = false);
const std::vector<NimBLERemoteCharacteristic*>& getCharacteristics(bool refresh = false) const;
std::vector<NimBLERemoteCharacteristic*>::iterator begin() const;
std::vector<NimBLERemoteCharacteristic*>::iterator end() const;
private:
// Private constructor ... never meant to be created by a user application.
NimBLERemoteService(NimBLEClient* pClient, const struct ble_gatt_svc *service);
// Friends
private:
friend class NimBLEClient;
friend class NimBLERemoteCharacteristic;
// Private methods
bool retrieveCharacteristics(const NimBLEUUID *uuid_filter = nullptr);
static int characteristicDiscCB(uint16_t conn_handle,
const struct ble_gatt_error *error,
const struct ble_gatt_chr *chr,
void *arg);
NimBLERemoteService(NimBLEClient* pClient, const struct ble_gatt_svc* service);
~NimBLERemoteService();
bool retrieveCharacteristics(const NimBLEUUID* uuidFilter = nullptr) const;
static int characteristicDiscCB(uint16_t conn_handle,
const struct ble_gatt_error* error,
const struct ble_gatt_chr* chr,
void* arg);
uint16_t getStartHandle();
uint16_t getEndHandle();
void releaseSemaphores();
// Properties
// We maintain a vector of characteristics owned by this service.
std::vector<NimBLERemoteCharacteristic*> m_characteristicVector;
NimBLEClient* m_pClient;
NimBLEUUID m_uuid;
uint16_t m_startHandle;
uint16_t m_endHandle;
mutable std::vector<NimBLERemoteCharacteristic*> m_vChars{};
NimBLEClient* m_pClient{nullptr};
uint16_t m_endHandle{0};
}; // NimBLERemoteService
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */
#endif /* COMPONENTS_NIMBLEREMOTESERVICE_H_ */
#endif /* NIMBLE_CPP_REMOTE_SERVICE_H_*/

View File

@ -0,0 +1,210 @@
/*
* NimBLERemoteValueAttribute.cpp
*
* Created: on July 28 2024
* Author H2zero
*/
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
# include "NimBLERemoteValueAttribute.h"
# include "NimBLEClient.h"
# include "NimBLEUtils.h"
# include <climits>
const char* LOG_TAG = "NimBLERemoteValueAttribute";
bool NimBLERemoteValueAttribute::writeValue(const uint8_t* data, size_t length, bool response) const {
NIMBLE_LOGD(LOG_TAG, ">> writeValue()");
const NimBLEClient* pClient = getClient();
int retryCount = 1;
int rc = 0;
uint16_t mtu = pClient->getMTU() - 3;
NimBLETaskData taskData(const_cast<NimBLERemoteValueAttribute*>(this));
// Check if the data length is longer than we can write in one connection event.
// If so we must do a long write which requires a response.
if (length <= mtu && !response) {
rc = ble_gattc_write_no_rsp_flat(pClient->getConnHandle(), getHandle(), data, length);
goto Done;
}
do {
if (length > mtu) {
NIMBLE_LOGI(LOG_TAG, "writeValue: long write");
os_mbuf* om = ble_hs_mbuf_from_flat(data, length);
rc = ble_gattc_write_long(pClient->getConnHandle(), getHandle(), 0, om, NimBLERemoteValueAttribute::onWriteCB, &taskData);
} else {
rc = ble_gattc_write_flat(pClient->getConnHandle(),
getHandle(),
data,
length,
NimBLERemoteValueAttribute::onWriteCB,
&taskData);
}
if (rc != 0) {
goto Done;
}
NimBLEUtils::taskWait(taskData, BLE_NPL_TIME_FOREVER);
rc = taskData.m_flags;
switch (rc) {
case 0:
case BLE_HS_EDONE:
rc = 0;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG):
NIMBLE_LOGE(LOG_TAG, "Long write not supported by peer; Truncating length to %d", mtu);
retryCount++;
length = mtu;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC):
if (retryCount && pClient->secureConnection()) break;
/* Else falls through. */
default:
goto Done;
}
} while (rc != 0 && retryCount--);
Done:
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "<< writeValue failed, rc: %d %s", rc, NimBLEUtils::returnCodeToString(rc));
} else {
NIMBLE_LOGD(LOG_TAG, "<< writeValue");
}
return (rc == 0);
} // writeValue
/**
* @brief Callback for characteristic write operation.
* @return success == 0 or error code.
*/
int NimBLERemoteValueAttribute::onWriteCB(uint16_t conn_handle, const ble_gatt_error* error, ble_gatt_attr* attr, void* arg) {
auto pTaskData = static_cast<NimBLETaskData*>(arg);
const auto pAtt = static_cast<NimBLERemoteValueAttribute*>(pTaskData->m_pInstance);
if (error->status == BLE_HS_ENOTCONN) {
NIMBLE_LOGE(LOG_TAG, "<< Write complete; Not connected");
NimBLEUtils::taskRelease(*pTaskData, error->status);
return error->status;
}
if (pAtt->getClient()->getConnHandle() != conn_handle) {
return 0;
}
NIMBLE_LOGI(LOG_TAG, "Write complete; status=%d", error->status);
NimBLEUtils::taskRelease(*pTaskData, error->status);
return 0;
}
/**
* @brief Read the value of the remote characteristic.
* @param [in] timestamp A pointer to a time_t struct to store the time the value was read.
* @return The value of the remote characteristic.
*/
NimBLEAttValue NimBLERemoteValueAttribute::readValue(time_t* timestamp) const {
NIMBLE_LOGD(LOG_TAG, ">> readValue()");
NimBLEAttValue value{};
const NimBLEClient* pClient = getClient();
int rc = 0;
int retryCount = 1;
NimBLETaskData taskData(const_cast<NimBLERemoteValueAttribute*>(this), 0, &value);
do {
rc = ble_gattc_read_long(pClient->getConnHandle(), getHandle(), 0, NimBLERemoteValueAttribute::onReadCB, &taskData);
if (rc != 0) {
goto Done;
}
NimBLEUtils::taskWait(taskData, BLE_NPL_TIME_FOREVER);
rc = taskData.m_flags;
switch (rc) {
case 0:
case BLE_HS_EDONE:
rc = 0;
break;
// Characteristic is not long-readable, return with what we have.
case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG):
NIMBLE_LOGI(LOG_TAG, "Attribute not long");
rc = ble_gattc_read(pClient->getConnHandle(), getHandle(), NimBLERemoteValueAttribute::onReadCB, &taskData);
if (rc != 0) {
goto Done;
}
retryCount++;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC):
if (retryCount && pClient->secureConnection()) break;
/* Else falls through. */
default:
goto Done;
}
} while (rc != 0 && retryCount--);
value.setTimeStamp();
m_value = value;
if (timestamp != nullptr) {
*timestamp = value.getTimeStamp();
}
Done:
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "<< readValue failed rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc));
} else {
NIMBLE_LOGD(LOG_TAG, "<< readValue");
}
return value;
} // readValue
/**
* @brief Callback for characteristic read operation.
* @return success == 0 or error code.
*/
int NimBLERemoteValueAttribute::onReadCB(uint16_t conn_handle, const ble_gatt_error* error, ble_gatt_attr* attr, void* arg) {
auto pTaskData = static_cast<NimBLETaskData*>(arg);
const auto pAtt = static_cast<NimBLERemoteValueAttribute*>(pTaskData->m_pInstance);
if (error->status == BLE_HS_ENOTCONN) {
NIMBLE_LOGE(LOG_TAG, "<< Read complete; Not connected");
NimBLEUtils::taskRelease(*pTaskData, error->status);
return error->status;
}
if (pAtt->getClient()->getConnHandle() != conn_handle) {
return 0;
}
int rc = error->status;
NIMBLE_LOGI(LOG_TAG, "Read complete; status=%d", rc);
if (rc == 0) {
if (attr) {
auto valBuf = static_cast<NimBLEAttValue*>(pTaskData->m_pBuf);
uint16_t data_len = OS_MBUF_PKTLEN(attr->om);
if ((valBuf->size() + data_len) > BLE_ATT_ATTR_MAX_LEN) {
rc = BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} else {
NIMBLE_LOGD(LOG_TAG, "Got %u bytes", data_len);
valBuf->append(attr->om->om_data, data_len);
return 0;
}
}
}
NimBLEUtils::taskRelease(*pTaskData, rc);
return rc;
} // onReadCB
#endif // CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL

View File

@ -0,0 +1,142 @@
/*
* NimBLERemoteValueAttribute.h
*
* Created: on July 28 2024
* Author H2zero
*/
#ifndef NIMBLE_CPP_REMOTE_VALUE_ATTRIBUTE_H_
#define NIMBLE_CPP_REMOTE_VALUE_ATTRIBUTE_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include <host/ble_gatt.h>
# else
# include <nimble/nimble/host/include/host/ble_gatt.h>
# endif
/**** FIX COMPILATION ****/
# undef min
# undef max
/**************************/
# include "NimBLEAttribute.h"
# include "NimBLEAttValue.h"
class NimBLEClient;
class NimBLERemoteValueAttribute : public NimBLEAttribute {
public:
/**
* @brief Read the value of the remote attribute.
* @param [in] timestamp A pointer to a time_t struct to store the time the value was read.
* @return The value of the remote attribute.
*/
NimBLEAttValue readValue(time_t* timestamp = nullptr) const;
/**
* @brief Get the length of the remote attribute value.
* @return The length of the remote attribute value.
*/
size_t getLength() const { return m_value.size(); }
/**
* @brief Get the value of the remote attribute.
* @return The value of the remote attribute.
* @details This returns a copy of the value to avoid potential race conditions.
*/
NimBLEAttValue getValue() const { return m_value; }
/**
* Get the client instance that owns this attribute.
*/
virtual NimBLEClient* getClient() const = 0;
/**
* @brief Write a new value to the remote characteristic from a data buffer.
* @param [in] data A pointer to a data buffer.
* @param [in] length The length of the data in the data buffer.
* @param [in] response Whether we require a response from the write.
* @return false if not connected or otherwise cannot perform write.
*/
bool writeValue(const uint8_t* data, size_t length, bool response = false) const;
/**
* @brief Write a new value to the remote characteristic from a const char*.
* @param [in] str A character string to write to the remote characteristic.
* @param [in] length (optional) The length of the character string, uses strlen if omitted.
* @param [in] response Whether we require a response from the write.
* @return false if not connected or otherwise cannot perform write.
*/
bool writeValue(const char* str, size_t length = 0, bool response = false) const {
return writeValue(reinterpret_cast<const uint8_t*>(str), length ? length : strlen(str), response);
}
/**
* @brief Template to set the remote characteristic value to <type\>val.
* @param [in] s The value to write.
* @param [in] response True == request write response.
* @note This function is only available if the type T is not a pointer.
*/
template <typename T>
typename std::enable_if<!std::is_pointer<T>::value, bool>::type
writeValue(const T& v, bool response = false) const {
if constexpr (Has_data_size<T>::value) {
return writeValue(reinterpret_cast<const uint8_t*>(v.data()), v.size(), response);
} else if constexpr (Has_c_str_length<T>::value) {
return writeValue(reinterpret_cast<const uint8_t*>(v.c_str()), v.length(), response);
} else {
return writeValue(reinterpret_cast<const uint8_t*>(&v), sizeof(v), response);
}
}
/**
* @brief Template to convert the remote characteristic data to <type\>.
* @tparam T The type to convert the data to.
* @param [in] timestamp A pointer to a time_t struct to store the time the value was read.
* @param [in] skipSizeCheck If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is
* less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>getValue<type>(&timestamp, skipSizeCheck);</tt>
*/
template <typename T>
T getValue(time_t* timestamp = nullptr, bool skipSizeCheck = false) const {
return m_value.getValue<T>(timestamp, skipSizeCheck);
}
/**
* @brief Template to convert the remote characteristic data to <type\>.
* @tparam T The type to convert the data to.
* @param [in] timestamp A pointer to a time_t struct to store the time the value was read.
* @param [in] skipSizeCheck If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is
* less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>readValue<type>(&timestamp, skipSizeCheck);</tt>
*/
template <typename T>
T readValue(time_t* timestamp = nullptr, bool skipSizeCheck = false) const {
readValue();
return m_value.getValue<T>(timestamp, skipSizeCheck);
}
protected:
/**
* @brief Construct a new NimBLERemoteValueAttribute object.
*/
NimBLERemoteValueAttribute(const ble_uuid_any_t& uuid, uint16_t handle) : NimBLEAttribute(uuid, handle) {}
/**
* @brief Destroy the NimBLERemoteValueAttribute object.
*/
virtual ~NimBLERemoteValueAttribute() = default;
static int onReadCB(uint16_t conn_handle, const ble_gatt_error* error, ble_gatt_attr* attr, void* arg);
static int onWriteCB(uint16_t conn_handle, const ble_gatt_error* error, ble_gatt_attr* attr, void* arg);
mutable NimBLEAttValue m_value{};
};
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */
#endif // NIMBLE_CPP_REMOTE_VALUE_ATTRIBUTE_H_

View File

@ -15,39 +15,40 @@
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
#include "NimBLEScan.h"
#include "NimBLEDevice.h"
#include "NimBLELog.h"
# include "NimBLEScan.h"
# include "NimBLEDevice.h"
# include "NimBLELog.h"
#include <string>
#include <climits>
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "nimble/nimble_port.h"
# else
# include "nimble/porting/nimble/include/nimble/nimble_port.h"
# endif
# include <string>
# include <climits>
static const char* LOG_TAG = "NimBLEScan";
/**
* @brief Scan constuctor.
* @brief Scan constructor.
*/
NimBLEScan::NimBLEScan() {
m_scan_params.filter_policy = BLE_HCI_SCAN_FILT_NO_WL;
m_scan_params.passive = 1; // If set, dont send scan requests to advertisers (i.e., dont 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)
m_scan_params.window = 0; // The duration of the LE scan. LE_Scan_Window shall be less than or equal to LE_Scan_Interval (units=0.625 msec)
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_ignoreResults = false;
m_pTaskData = nullptr;
m_duration = BLE_HS_FOREVER; // make sure this is non-zero in the event of a host reset
m_maxResults = 0xFF;
NimBLEScan::NimBLEScan()
: m_pScanCallbacks{nullptr},
m_scanParams{
.itvl{0}, .window{0}, .filter_policy{BLE_HCI_SCAN_FILT_NO_WL}, .limited{0}, .passive{1}, .filter_duplicates{1}},
m_duration{BLE_HS_FOREVER},
m_pTaskData{nullptr},
m_maxResults{0xFF} {
ble_npl_callout_init(&m_srTimer, nimble_port_get_dflt_eventq(), NimBLEScan::srTimerCb, nullptr);
}
/**
* @brief Scan destructor, release any allocated resources.
*/
NimBLEScan::~NimBLEScan() {
clearResults();
clearResults();
ble_npl_callout_deinit(&m_srTimer);
}
/**
@ -55,31 +56,26 @@ NimBLEScan::~NimBLEScan() {
* @param [in] event The event type for this event.
* @param [in] param Parameter data for this event.
*/
/*STATIC*/int NimBLEScan::handleGapEvent(ble_gap_event* event, void* arg) {
int NimBLEScan::handleGapEvent(ble_gap_event* event, void* arg) {
(void)arg;
NimBLEScan* pScan = NimBLEDevice::getScan();
switch(event->type) {
switch (event->type) {
case BLE_GAP_EVENT_EXT_DISC:
case BLE_GAP_EVENT_DISC: {
if(pScan->m_ignoreResults) {
NIMBLE_LOGI(LOG_TAG, "Scan op in progress - ignoring results");
return 0;
}
#if CONFIG_BT_NIMBLE_EXT_ADV
const auto& disc = event->ext_disc;
const bool isLegacyAdv = disc.props & BLE_HCI_ADV_LEGACY_MASK;
const auto event_type = isLegacyAdv ? disc.legacy_event_type : disc.props;
#else
const auto& disc = event->disc;
const bool isLegacyAdv = true;
const auto event_type = disc.event_type;
#endif
# if CONFIG_BT_NIMBLE_EXT_ADV
const auto& disc = event->ext_disc;
const bool isLegacyAdv = disc.props & BLE_HCI_ADV_LEGACY_MASK;
const auto event_type = isLegacyAdv ? disc.legacy_event_type : disc.props;
# else
const auto& disc = event->disc;
const bool isLegacyAdv = true;
const auto event_type = disc.event_type;
# endif
NimBLEAddress advertisedAddress(disc.addr);
// Examine our list of ignored addresses and stop processing if we don't want to see it or are already connected
if(NimBLEDevice::isIgnored(advertisedAddress)) {
// stop processing if we don't want to see it or are already connected
if (NimBLEDevice::isIgnored(advertisedAddress)) {
NIMBLE_LOGI(LOG_TAG, "Ignoring device: address: %s", advertisedAddress.toString().c_str());
return 0;
}
@ -87,100 +83,96 @@ NimBLEScan::~NimBLEScan() {
NimBLEAdvertisedDevice* advertisedDevice = nullptr;
// If we've seen this device before get a pointer to it from the vector
for(auto &it: pScan->m_scanResults.m_advertisedDevicesVector) {
#if CONFIG_BT_NIMBLE_EXT_ADV
for (auto& ad : pScan->m_scanResults.m_deviceVec) {
# if CONFIG_BT_NIMBLE_EXT_ADV
// Same address but different set ID should create a new advertised device.
if (it->getAddress() == advertisedAddress && it->getSetId() == disc.sid) {
#else
if (it->getAddress() == advertisedAddress) {
#endif
advertisedDevice = it;
if (ad->getAddress() == advertisedAddress && ad->getSetId() == disc.sid) {
# else
if (ad->getAddress() == advertisedAddress) {
# endif
advertisedDevice = ad;
break;
}
}
// If we haven't seen this device before; create a new instance and insert it in the vector.
// Otherwise just update the relevant parameters of the already known device.
if (advertisedDevice == nullptr &&
(!isLegacyAdv || event_type != BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP)) {
if (advertisedDevice == nullptr) {
if (event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP) {
NIMBLE_LOGW(LOG_TAG, "Scan response without advertisement: %s", advertisedAddress.toString().c_str());
}
// Check if we have reach the scan results limit, ignore this one if so.
// We still need to store each device when maxResults is 0 to be able to append the scan results
if (pScan->m_maxResults > 0 && pScan->m_maxResults < 0xFF &&
(pScan->m_scanResults.m_advertisedDevicesVector.size() >= pScan->m_maxResults)) {
(pScan->m_scanResults.m_deviceVec.size() >= pScan->m_maxResults)) {
return 0;
}
advertisedDevice = new NimBLEAdvertisedDevice();
advertisedDevice->setAddress(advertisedAddress);
advertisedDevice->setAdvType(event_type, isLegacyAdv);
#if CONFIG_BT_NIMBLE_EXT_ADV
advertisedDevice->setSetId(disc.sid);
advertisedDevice->setPrimaryPhy(disc.prim_phy);
advertisedDevice->setSecondaryPhy(disc.sec_phy);
advertisedDevice->setPeriodicInterval(disc.periodic_adv_itvl);
#endif
pScan->m_scanResults.m_advertisedDevicesVector.push_back(advertisedDevice);
advertisedDevice = new NimBLEAdvertisedDevice(event, event_type);
pScan->m_scanResults.m_deviceVec.push_back(advertisedDevice);
NIMBLE_LOGI(LOG_TAG, "New advertiser: %s", advertisedAddress.toString().c_str());
} else if (advertisedDevice != nullptr) {
advertisedDevice->update(event, event_type);
NIMBLE_LOGI(LOG_TAG, "Updated advertiser: %s", advertisedAddress.toString().c_str());
} else {
// Scan response from unknown device
// unknown device
return 0;
}
advertisedDevice->m_timestamp = time(nullptr);
advertisedDevice->setRSSI(disc.rssi);
advertisedDevice->setPayload(disc.data, disc.length_data, (isLegacyAdv &&
event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP));
if (pScan->m_pScanCallbacks) {
if (advertisedDevice->m_callbackSent == 0 || !pScan->m_scanParams.filter_duplicates) {
advertisedDevice->m_callbackSent = 1;
pScan->m_pScanCallbacks->onDiscovered(advertisedDevice);
}
if (pScan->m_scanParams.filter_duplicates && advertisedDevice->m_callbackSent >= 2) {
return 0;
}
if (pScan->m_pAdvertisedDeviceCallbacks) {
// If not active scanning or scan response is not available
// or extended advertisement scanning, report the result to the callback now.
if(pScan->m_scan_params.passive || !isLegacyAdv ||
(advertisedDevice->getAdvType() != BLE_HCI_ADV_TYPE_ADV_IND &&
advertisedDevice->getAdvType() != BLE_HCI_ADV_TYPE_ADV_SCAN_IND))
{
advertisedDevice->m_callbackSent = true;
pScan->m_pAdvertisedDeviceCallbacks->onResult(advertisedDevice);
if (pScan->m_scanParams.passive || !isLegacyAdv ||
(advertisedDevice->getAdvType() != BLE_HCI_ADV_TYPE_ADV_IND &&
advertisedDevice->getAdvType() != BLE_HCI_ADV_TYPE_ADV_SCAN_IND)) {
advertisedDevice->m_callbackSent = 2;
pScan->m_pScanCallbacks->onResult(advertisedDevice);
// Otherwise, wait for the scan response so we can report the complete data.
// Otherwise, wait for the scan response so we can report the complete data.
} else if (isLegacyAdv && event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP) {
advertisedDevice->m_callbackSent = true;
pScan->m_pAdvertisedDeviceCallbacks->onResult(advertisedDevice);
advertisedDevice->m_callbackSent = 2;
pScan->m_pScanCallbacks->onResult(advertisedDevice);
} else if (isLegacyAdv) {
ble_npl_time_t ticks;
ble_npl_time_ms_to_ticks(500, &ticks); // more than 500ms for scan response = not coming
advertisedDevice->m_srTimeout = ble_npl_time_get() + ticks;
if (!ble_npl_callout_is_active(&pScan->m_srTimer)) {
ble_npl_callout_reset(&pScan->m_srTimer, ticks);
}
}
// If not storing results and we have invoked the callback, delete the device.
if(pScan->m_maxResults == 0 && advertisedDevice->m_callbackSent) {
if (pScan->m_maxResults == 0 && advertisedDevice->m_callbackSent >= 2) {
pScan->erase(advertisedAddress);
}
}
return 0;
}
case BLE_GAP_EVENT_DISC_COMPLETE: {
NIMBLE_LOGD(LOG_TAG, "discovery complete; reason=%d",
event->disc_complete.reason);
NIMBLE_LOGD(LOG_TAG, "discovery complete; reason=%d", event->disc_complete.reason);
// If a device advertised with scan reponse available and it was not received
// the callback would not have been invoked, so do it here.
if(pScan->m_pAdvertisedDeviceCallbacks) {
for(auto &it : pScan->m_scanResults.m_advertisedDevicesVector) {
if(!it->m_callbackSent) {
pScan->m_pAdvertisedDeviceCallbacks->onResult(it);
}
}
}
if(pScan->m_maxResults == 0) {
if (pScan->m_maxResults == 0) {
pScan->clearResults();
}
if (pScan->m_scanCompleteCB != nullptr) {
pScan->m_scanCompleteCB(pScan->m_scanResults);
if (pScan->m_pScanCallbacks != nullptr) {
pScan->m_pScanCallbacks->onScanEnd(pScan->m_scanResults);
}
if(pScan->m_pTaskData != nullptr) {
pScan->m_pTaskData->rc = event->disc_complete.reason;
xTaskNotifyGive(pScan->m_pTaskData->task);
if (pScan->m_pTaskData != nullptr) {
NimBLEUtils::taskRelease(*pScan->m_pTaskData, event->disc_complete.reason);
}
return 0;
@ -189,8 +181,42 @@ NimBLEScan::~NimBLEScan() {
default:
return 0;
}
} // gapEventHandler
} // handleGapEvent
/**
* @brief This will call the scan result callback when a scan response is not received
* and will delete the device from the vector when maxResults is 0.
*/
void NimBLEScan::srTimerCb(ble_npl_event* event) {
NimBLEScan* pScan = NimBLEDevice::getScan();
ble_npl_time_t now = ble_npl_time_get();
ble_npl_time_t nextTimeout = BLE_NPL_TIME_FOREVER;
for (auto& ad : pScan->m_scanResults.m_deviceVec) {
if (!ad->m_srTimeout) {
continue;
}
if (ad->m_callbackSent == 1 && now > ad->m_srTimeout) {
ad->m_callbackSent = 2;
pScan->m_pScanCallbacks->onResult(ad);
if (pScan->m_maxResults == 0) {
pScan->erase(ad->getAddress());
}
continue;
}
if (ad->m_callbackSent == 1 && ad->m_srTimeout < nextTimeout) {
nextTimeout = ad->m_srTimeout;
}
}
if (nextTimeout != BLE_NPL_TIME_FOREVER) {
ble_npl_callout_reset(&pScan->m_srTimer, nextTimeout);
}
} // srTimerCb
/**
* @brief Should we perform an active or passive scan?
@ -198,32 +224,29 @@ NimBLEScan::~NimBLEScan() {
* @param [in] active If true, we perform an active scan otherwise a passive scan.
*/
void NimBLEScan::setActiveScan(bool active) {
m_scan_params.passive = !active;
m_scanParams.passive = !active;
} // setActiveScan
/**
* @brief Set whether or not the BLE controller should only report results
* from devices it has not already seen.
* @param [in] enabled If true, scanned devices will only be reported once.
* @details The controller has a limited buffer and will start reporting
* dupicate devices once the limit is reached.
* duplicate devices once the limit is reached.
*/
void NimBLEScan::setDuplicateFilter(bool enabled) {
m_scan_params.filter_duplicates = enabled;
m_scanParams.filter_duplicates = enabled;
} // setDuplicateFilter
/**
* @brief Set whether or not the BLE controller only report scan results
* from devices advertising in limited discovery mode, i.e. directed advertising.
* @param [in] enabled If true, only limited discovery devices will be in scan results.
*/
void NimBLEScan::setLimitedOnly(bool enabled) {
m_scan_params.limited = enabled;
m_scanParams.limited = enabled;
} // setLimited
/**
* @brief Sets the scan filter policy.
* @param [in] filter Can be one of:
@ -232,7 +255,7 @@ void NimBLEScan::setLimitedOnly(bool enabled) {
* directed, connectable advertising packets not sent to the scanner.
* * BLE_HCI_SCAN_FILT_USE_WL (1)
* Scanner processes advertisements from white list only. A connectable,\n
* directed advertisment is ignored unless it contains scanners address.
* directed advertisement is ignored unless it contains scanners address.
* * BLE_HCI_SCAN_FILT_NO_WL_INITA (2)
* Scanner process all advertising packets (white list not used). A\n
* connectable, directed advertisement shall not be ignored if the InitA
@ -243,10 +266,9 @@ void NimBLEScan::setLimitedOnly(bool enabled) {
* resolvable private address.
*/
void NimBLEScan::setFilterPolicy(uint8_t filter) {
m_scan_params.filter_policy = filter;
m_scanParams.filter_policy = filter;
} // setFilterPolicy
/**
* @brief Sets the max number of results to store.
* @param [in] maxResults The number of results to limit storage to\n
@ -256,37 +278,32 @@ void NimBLEScan::setMaxResults(uint8_t maxResults) {
m_maxResults = maxResults;
}
/**
* @brief Set the call backs to be invoked.
* @param [in] pAdvertisedDeviceCallbacks Call backs to be invoked.
* @param [in] pScanCallbacks Call backs to be invoked.
* @param [in] wantDuplicates True if we wish to be called back with duplicates. Default is false.
*/
void NimBLEScan::setAdvertisedDeviceCallbacks(NimBLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks,
bool wantDuplicates) {
void NimBLEScan::setScanCallbacks(NimBLEScanCallbacks* pScanCallbacks, bool wantDuplicates) {
setDuplicateFilter(!wantDuplicates);
m_pAdvertisedDeviceCallbacks = pAdvertisedDeviceCallbacks;
} // setAdvertisedDeviceCallbacks
m_pScanCallbacks = pScanCallbacks;
} // setScanCallbacks
/**
* @brief Set the interval to scan.
* @param [in] intervalMSecs The scan interval (how often) in milliseconds.
*/
void NimBLEScan::setInterval(uint16_t intervalMSecs) {
m_scan_params.itvl = intervalMSecs / 0.625;
m_scanParams.itvl = intervalMSecs / 0.625;
} // setInterval
/**
* @brief Set the window to actively scan.
* @param [in] windowMSecs How long to actively scan.
*/
void NimBLEScan::setWindow(uint16_t windowMSecs) {
m_scan_params.window = windowMSecs / 0.625;
m_scanParams.window = windowMSecs / 0.625;
} // setWindow
/**
* @brief Get the status of the scanner.
* @return true if scanning or scan starting.
@ -295,68 +312,68 @@ bool NimBLEScan::isScanning() {
return ble_gap_disc_active();
}
/**
* @brief Start scanning.
* @param [in] duration The duration in seconds for which to scan.
* @param [in] scanCompleteCB A function to be called when scanning has completed.
* @param [in] duration The duration in milliseconds for which to scan. 0 == scan forever.
* @param [in] is_continue Set to true to save previous scan results, false to clear them.
* @param [in] restart Set to true to restart the scan if already in progress.
* this is useful to clear the duplicate filter so all devices are reported again.
* @return True if scan started or false if there was an error.
*/
bool NimBLEScan::start(uint32_t duration, void (*scanCompleteCB)(NimBLEScanResults), bool is_continue) {
bool NimBLEScan::start(uint32_t duration, bool is_continue, bool restart) {
NIMBLE_LOGD(LOG_TAG, ">> start: duration=%" PRIu32, duration);
// Save the callback to be invoked when the scan completes.
m_scanCompleteCB = scanCompleteCB;
if (ble_gap_conn_active()) {
NIMBLE_LOGE(LOG_TAG, "Connection in progress, cannot start scan");
return false;
}
if (isScanning()) {
if (restart) {
// NIMBLE_LOGI(LOG_TAG, "Scan already in progress, stopping it");
if (!stop()) {
return false;
}
} else {
NIMBLE_LOGI(LOG_TAG, "Scan already in progress");
return true;
}
}
if (!is_continue) {
clearResults();
}
// Save the duration in the case that the host is reset so we can reuse it.
m_duration = duration;
// If 0 duration specified then we assume a continuous scan is desired.
if(duration == 0){
if (duration == 0) {
duration = BLE_HS_FOREVER;
}
else{
// convert duration to milliseconds
duration = duration * 1000;
}
// Set the flag to ignore the results while we are deleting the vector
if(!is_continue) {
m_ignoreResults = true;
}
# if CONFIG_BT_NIMBLE_EXT_ADV
ble_gap_ext_disc_params scan_params;
scan_params.passive = m_scan_params.passive;
scan_params.itvl = m_scan_params.itvl;
scan_params.window = m_scan_params.window;
int rc = ble_gap_ext_disc(NimBLEDevice::m_own_addr_type,
duration/10,
scan_params.passive = m_scanParams.passive;
scan_params.itvl = m_scanParams.itvl;
scan_params.window = m_scanParams.window;
int rc = ble_gap_ext_disc(NimBLEDevice::m_ownAddrType,
duration / 10,
0,
m_scan_params.filter_duplicates,
m_scan_params.filter_policy,
m_scan_params.limited,
m_scanParams.filter_duplicates,
m_scanParams.filter_policy,
m_scanParams.limited,
&scan_params,
&scan_params,
NimBLEScan::handleGapEvent,
NULL);
#else
int rc = ble_gap_disc(NimBLEDevice::m_own_addr_type,
duration,
&m_scan_params,
NimBLEScan::handleGapEvent,
NULL);
#endif
switch(rc) {
# else
int rc = ble_gap_disc(NimBLEDevice::m_ownAddrType, duration, &m_scanParams, NimBLEScan::handleGapEvent, NULL);
# endif
switch (rc) {
case 0:
if(!is_continue) {
clearResults();
}
break;
case BLE_HS_EALREADY:
// Clear the cache if already scanning in case an advertiser was missed.
clearDuplicateCache();
NIMBLE_LOGD(LOG_TAG, "Scan started");
break;
case BLE_HS_EBUSY:
@ -367,53 +384,18 @@ bool NimBLEScan::start(uint32_t duration, void (*scanCompleteCB)(NimBLEScanResul
case BLE_HS_EOS:
case BLE_HS_ECONTROLLER:
case BLE_HS_ENOTSYNCED:
NIMBLE_LOGC(LOG_TAG, "Unable to scan - Host Reset");
NIMBLE_LOGE(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));
NIMBLE_LOGE(LOG_TAG, "Error starting scan; 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;
}
return true;
return rc == 0 || rc == BLE_HS_EALREADY;
} // start
/**
* @brief Start scanning and block until scanning has been completed.
* @param [in] duration The duration in seconds for which to scan.
* @param [in] is_continue Set to true to save previous scan results, false to clear them.
* @return The NimBLEScanResults.
*/
NimBLEScanResults NimBLEScan::start(uint32_t duration, bool is_continue) {
if(duration == 0) {
NIMBLE_LOGW(LOG_TAG, "Blocking scan called with duration = forever");
}
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
ble_task_data_t taskData = {nullptr, cur_task, 0, nullptr};
m_pTaskData = &taskData;
if(start(duration, nullptr, is_continue)) {
#ifdef ulTaskNotifyValueClear
// Clear the task notification value to ensure we block
ulTaskNotifyValueClear(cur_task, ULONG_MAX);
#endif
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
}
m_pTaskData = nullptr;
return m_scanResults;
} // start
/**
* @brief Stop an in progress scan.
* @return True if successful.
@ -427,71 +409,75 @@ bool NimBLEScan::stop() {
return false;
}
if(m_maxResults == 0) {
if (m_pScanCallbacks) {
ble_npl_callout_stop(&m_srTimer);
}
if (m_maxResults == 0) {
clearResults();
}
if (rc != BLE_HS_EALREADY && m_scanCompleteCB != nullptr) {
m_scanCompleteCB(m_scanResults);
}
if(m_pTaskData != nullptr) {
xTaskNotifyGive(m_pTaskData->task);
if (m_pTaskData != nullptr) {
NimBLEUtils::taskRelease(*m_pTaskData);
}
NIMBLE_LOGD(LOG_TAG, "<< stop()");
return true;
} // stop
/**
* @brief Clears the duplicate scan filter cache.
*/
void NimBLEScan::clearDuplicateCache() {
#ifdef CONFIG_IDF_TARGET_ESP32 // Not available for ESP32C3
esp_ble_scan_dupilcate_list_flush();
#endif
}
/**
* @brief Delete peer device from the scan results vector.
* @param [in] address The address of the device to delete from the results.
* @details After disconnecting, it may be required in the case we were connected to a device without a public address.
*/
void NimBLEScan::erase(const NimBLEAddress &address) {
void NimBLEScan::erase(const NimBLEAddress& address) {
NIMBLE_LOGD(LOG_TAG, "erase device: %s", address.toString().c_str());
for(auto it = m_scanResults.m_advertisedDevicesVector.begin(); it != m_scanResults.m_advertisedDevicesVector.end(); ++it) {
if((*it)->getAddress() == address) {
for (auto it = m_scanResults.m_deviceVec.begin(); it != m_scanResults.m_deviceVec.end(); ++it) {
if ((*it)->getAddress() == address) {
delete *it;
m_scanResults.m_advertisedDevicesVector.erase(it);
m_scanResults.m_deviceVec.erase(it);
break;
}
}
}
/**
* @brief Called when host reset, we set a flag to stop scanning until synced.
*/
void NimBLEScan::onHostReset() {
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);
if (m_duration == 0 && m_pScanCallbacks != nullptr) {
start(0, false);
}
}
/**
* @brief Start scanning and block until scanning has been completed.
* @param [in] duration The duration in milliseconds for which to scan.
* @param [in] is_continue Set to true to save previous scan results, false to clear them.
* @return The scan results.
*/
NimBLEScanResults NimBLEScan::getResults(uint32_t duration, bool is_continue) {
if (duration == 0) {
NIMBLE_LOGW(LOG_TAG, "Blocking scan called with duration = forever");
}
if (m_pTaskData != nullptr) {
NIMBLE_LOGE(LOG_TAG, "Scan already in progress");
return m_scanResults;
}
NimBLETaskData taskData;
m_pTaskData = &taskData;
if (start(duration, is_continue)) {
NimBLEUtils::taskWait(taskData, BLE_NPL_TIME_FOREVER);
}
m_pTaskData = nullptr;
return m_scanResults;
} // getResults
/**
* @brief Get the results of the scan.
* @return NimBLEScanResults object.
@ -500,39 +486,34 @@ NimBLEScanResults NimBLEScan::getResults() {
return m_scanResults;
}
/**
* @brief Clear the results of the scan.
*/
void NimBLEScan::clearResults() {
for(auto &it: m_scanResults.m_advertisedDevicesVector) {
for (auto& it : m_scanResults.m_deviceVec) {
delete it;
}
m_scanResults.m_advertisedDevicesVector.clear();
clearDuplicateCache();
}
std::vector<NimBLEAdvertisedDevice*>().swap(m_scanResults.m_deviceVec);
} // clearResults
/**
* @brief Dump the scan results to the log.
*/
void NimBLEScanResults::dump() {
NIMBLE_LOGD(LOG_TAG, ">> Dump scan results:");
for (int i=0; i<getCount(); i++) {
for (int i = 0; i < getCount(); i++) {
NIMBLE_LOGI(LOG_TAG, "- %s", getDevice(i).toString().c_str());
}
} // dump
/**
* @brief Get the count of devices found in the last scan.
* @return The number of devices found in the last scan.
*/
int NimBLEScanResults::getCount() {
return m_advertisedDevicesVector.size();
return m_deviceVec.size();
} // getCount
/**
* @brief Return the specified device at the given index.
* The index should be between 0 and getCount()-1.
@ -540,38 +521,35 @@ int NimBLEScanResults::getCount() {
* @return The device at the specified index.
*/
NimBLEAdvertisedDevice NimBLEScanResults::getDevice(uint32_t i) {
return *m_advertisedDevicesVector[i];
return *m_deviceVec[i];
}
/**
* @brief Get iterator to the beginning of the vector of advertised device pointers.
* @return An iterator to the beginning of the vector of advertised device pointers.
*/
std::vector<NimBLEAdvertisedDevice*>::iterator NimBLEScanResults::begin() {
return m_advertisedDevicesVector.begin();
return m_deviceVec.begin();
}
/**
* @brief Get iterator to the end of the vector of advertised device pointers.
* @return An iterator to the end of the vector of advertised device pointers.
*/
std::vector<NimBLEAdvertisedDevice*>::iterator NimBLEScanResults::end() {
return m_advertisedDevicesVector.end();
return m_deviceVec.end();
}
/**
* @brief Get a pointer to the specified device at the given address.
* If the address is not found a nullptr is returned.
* @param [in] address The address of the device.
* @return A pointer to the device at the specified address.
*/
NimBLEAdvertisedDevice *NimBLEScanResults::getDevice(const NimBLEAddress &address) {
for(size_t index = 0; index < m_advertisedDevicesVector.size(); index++) {
if(m_advertisedDevicesVector[index]->getAddress() == address) {
return m_advertisedDevicesVector[index];
NimBLEAdvertisedDevice* NimBLEScanResults::getDevice(const NimBLEAddress& address) {
for (size_t index = 0; index < m_deviceVec.size(); index++) {
if (m_deviceVec[index]->getAddress() == address) {
return m_deviceVec[index];
}
}

View File

@ -17,21 +17,21 @@
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
#include "NimBLEAdvertisedDevice.h"
#include "NimBLEUtils.h"
# include "NimBLEAdvertisedDevice.h"
# include "NimBLEUtils.h"
#if defined(CONFIG_NIMBLE_CPP_IDF)
#include "host/ble_gap.h"
#else
#include "nimble/nimble/host/include/host/ble_gap.h"
#endif
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_gap.h"
# else
# include "nimble/nimble/host/include/host/ble_gap.h"
# endif
#include <vector>
# include <vector>
class NimBLEDevice;
class NimBLEScan;
class NimBLEAdvertisedDevice;
class NimBLEAdvertisedDeviceCallbacks;
class NimBLEScanCallbacks;
class NimBLEAddress;
/**
@ -42,17 +42,17 @@ class NimBLEAddress;
* index (starting at 0) of the desired device.
*/
class NimBLEScanResults {
public:
public:
void dump();
int getCount();
NimBLEAdvertisedDevice getDevice(uint32_t i);
std::vector<NimBLEAdvertisedDevice*>::iterator begin();
std::vector<NimBLEAdvertisedDevice*>::iterator end();
NimBLEAdvertisedDevice *getDevice(const NimBLEAddress &address);
NimBLEAdvertisedDevice* getDevice(const NimBLEAddress& address);
private:
private:
friend NimBLEScan;
std::vector<NimBLEAdvertisedDevice*> m_advertisedDevicesVector;
std::vector<NimBLEAdvertisedDevice*> m_deviceVec;
};
/**
@ -61,42 +61,65 @@ private:
* Scanning is associated with a %BLE client that is attempting to locate BLE servers.
*/
class NimBLEScan {
public:
bool start(uint32_t duration, void (*scanCompleteCB)(NimBLEScanResults), bool is_continue = false);
NimBLEScanResults start(uint32_t duration, bool is_continue = false);
bool isScanning();
void setAdvertisedDeviceCallbacks(NimBLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks, bool wantDuplicates = false);
void setActiveScan(bool active);
void setInterval(uint16_t intervalMSecs);
void setWindow(uint16_t windowMSecs);
void setDuplicateFilter(bool enabled);
void setLimitedOnly(bool enabled);
void setFilterPolicy(uint8_t filter);
void clearDuplicateCache();
bool stop();
void clearResults();
NimBLEScanResults getResults();
void setMaxResults(uint8_t maxResults);
void erase(const NimBLEAddress &address);
public:
bool start(uint32_t duration, bool is_continue = false, bool restart = true);
bool isScanning();
void setScanCallbacks(NimBLEScanCallbacks* pScanCallbacks, bool wantDuplicates = false);
void setActiveScan(bool active);
void setInterval(uint16_t intervalMSecs);
void setWindow(uint16_t windowMSecs);
void setDuplicateFilter(bool enabled);
void setLimitedOnly(bool enabled);
void setFilterPolicy(uint8_t filter);
bool stop();
void clearResults();
NimBLEScanResults getResults();
NimBLEScanResults getResults(uint32_t duration, bool is_continue = false);
void setMaxResults(uint8_t maxResults);
void erase(const NimBLEAddress& address);
private:
private:
friend class NimBLEDevice;
NimBLEScan();
~NimBLEScan();
static int handleGapEvent(ble_gap_event* event, void* arg);
void onHostReset();
void onHostSync();
static int handleGapEvent(ble_gap_event* event, void* arg);
static void srTimerCb(ble_npl_event* event);
void onHostSync();
NimBLEAdvertisedDeviceCallbacks* m_pAdvertisedDeviceCallbacks = nullptr;
void (*m_scanCompleteCB)(NimBLEScanResults scanResults);
ble_gap_disc_params m_scan_params;
bool m_ignoreResults;
NimBLEScanResults m_scanResults;
uint32_t m_duration;
ble_task_data_t *m_pTaskData;
uint8_t m_maxResults;
NimBLEScanCallbacks* m_pScanCallbacks;
ble_gap_disc_params m_scanParams;
NimBLEScanResults m_scanResults;
uint32_t m_duration;
NimBLETaskData* m_pTaskData;
uint8_t m_maxResults;
ble_npl_callout m_srTimer;
};
/**
* @brief A callback handler for callbacks associated device scanning.
*/
class NimBLEScanCallbacks {
public:
virtual ~NimBLEScanCallbacks() {}
/**
* @brief Called when a new device is discovered, before the scan result is received (if applicable).
* @param [in] advertisedDevice The device which was discovered.
*/
virtual void onDiscovered(NimBLEAdvertisedDevice* advertisedDevice) {};
/**
* @brief Called when a new scan result is complete, including scan response data (if applicable).
* @param [in] advertisedDevice The device for which the complete result is available.
*/
virtual void onResult(NimBLEAdvertisedDevice* advertisedDevice) {};
/**
* @brief Called when a scan operation ends.
* @param [in] scanResults The results of the scan that ended.
*/
virtual void onScanEnd(NimBLEScanResults scanResults) {};
};
#endif /* CONFIG_BT_ENABLED CONFIG_BT_NIMBLE_ROLE_OBSERVER */

View File

@ -1,158 +0,0 @@
/*
* NimBLESecurity.cpp
*
* Created: on Feb 22 2020
* Author H2zero
*
* Originally:
*
* BLESecurity.cpp
*
* Created on: Dec 17, 2017
* Author: chegewara
*/
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLESecurity.h"
#include "NimBLEDevice.h"
NimBLESecurity::NimBLESecurity() {
}
NimBLESecurity::~NimBLESecurity() {
}
/**
* @brief Set requested authentication mode
* @param [in] auth_req A bitmask containing one or more of:
* * ESP_LE_AUTH_NO_BOND 0x00
* * ESP_LE_AUTH_BOND 0x01
* * ESP_LE_AUTH_REQ_MITM (1 << 2)
* * ESP_LE_AUTH_REQ_BOND_MITM (ESP_LE_AUTH_BOND | ESP_LE_AUTH_REQ_MITM)
* * ESP_LE_AUTH_REQ_SC_ONLY (1 << 3)
* * ESP_LE_AUTH_REQ_SC_BOND (ESP_LE_AUTH_BOND | ESP_LE_AUTH_REQ_SC_ONLY)
* * ESP_LE_AUTH_REQ_SC_MITM (ESP_LE_AUTH_REQ_MITM | ESP_LE_AUTH_REQ_SC_ONLY)
* * ESP_LE_AUTH_REQ_SC_MITM_BOND (ESP_LE_AUTH_REQ_MITM | ESP_LE_AUTH_REQ_SC_ONLY | ESP_LE_AUTH_BOND)
*/
void NimBLESecurity::setAuthenticationMode(esp_ble_auth_req_t auth_req) {
NimBLEDevice::setSecurityAuth((auth_req & BLE_SM_PAIR_AUTHREQ_BOND)>0,
(auth_req & BLE_SM_PAIR_AUTHREQ_MITM)>0,
(auth_req & BLE_SM_PAIR_AUTHREQ_SC)>0);
}
/**
* @brief Set our device IO capability to let end user perform authorization
* either by displaying or entering generated 6-digit pin code or use \"just works\".
* @param [in] iocap The IO capabilites our device has.\n
* Can be set to one of:
* * ESP_IO_CAP_OUT 0
* * ESP_IO_CAP_IO 1
* * ESP_IO_CAP_IN 2
* * ESP_IO_CAP_NONE 3
* * ESP_IO_CAP_KBDISP 4
*/
void NimBLESecurity::setCapability(esp_ble_io_cap_t iocap) {
NimBLEDevice::setSecurityIOCap(iocap);
} // setCapability
/**
* @brief Sets the keys we will distibute during encryption.
* @param [in] init_key A bitmask of the keys we will distibute.\n
* Can be one or more of:
* * ESP_BLE_ENC_KEY_MASK (1 << 0)
* * ESP_BLE_ID_KEY_MASK (1 << 1)
* * ESP_BLE_CSR_KEY_MASK (1 << 2)
* * ESP_BLE_LINK_KEY_MASK (1 << 3)
*/
void NimBLESecurity::setInitEncryptionKey(uint8_t init_key) {
NimBLEDevice::setSecurityInitKey(init_key);
} // setInitEncryptionKey
/**
* @brief Sets the keys we will accept during encryption.
* @param [in] resp_key A bitmask of the keys we will accept.\n
* Can be one or more of:
* * ESP_BLE_ENC_KEY_MASK (1 << 0)
* * ESP_BLE_ID_KEY_MASK (1 << 1)
* * ESP_BLE_CSR_KEY_MASK (1 << 2)
* * ESP_BLE_LINK_KEY_MASK (1 << 3)
*/
void NimBLESecurity::setRespEncryptionKey(uint8_t resp_key) {
NimBLEDevice::setSecurityRespKey(resp_key);
} // setRespEncryptionKey
/**
*@todo Requires implementation
*/
void NimBLESecurity::setKeySize(uint8_t key_size) {
//m_keySize = key_size;
//esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &m_keySize, sizeof(uint8_t));
} //setKeySize
/**
* @brief Sets a static PIN used to authenticate/encrypt the connection.
* @param [in] pin The 6 digit pin code to accept.
*/
void NimBLESecurity::setStaticPIN(uint32_t pin){
//uint32_t passkey = pin;
//esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &passkey, sizeof(uint32_t));
NimBLEDevice::setSecurityPasskey(pin);
setCapability(ESP_IO_CAP_OUT);
setKeySize();
setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY);
setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);
}
/**
* @brief Debug function to display what keys are exchanged by peers
*/
/*
char* BLESecurity::esp_key_type_to_str(esp_ble_key_type_t key_type) {
char* key_str = nullptr;
switch (key_type) {
case ESP_LE_KEY_NONE:
key_str = (char*) "ESP_LE_KEY_NONE";
break;
case ESP_LE_KEY_PENC:
key_str = (char*) "ESP_LE_KEY_PENC";
break;
case ESP_LE_KEY_PID:
key_str = (char*) "ESP_LE_KEY_PID";
break;
case ESP_LE_KEY_PCSRK:
key_str = (char*) "ESP_LE_KEY_PCSRK";
break;
case ESP_LE_KEY_PLK:
key_str = (char*) "ESP_LE_KEY_PLK";
break;
case ESP_LE_KEY_LLK:
key_str = (char*) "ESP_LE_KEY_LLK";
break;
case ESP_LE_KEY_LENC:
key_str = (char*) "ESP_LE_KEY_LENC";
break;
case ESP_LE_KEY_LID:
key_str = (char*) "ESP_LE_KEY_LID";
break;
case ESP_LE_KEY_LCSRK:
key_str = (char*) "ESP_LE_KEY_LCSRK";
break;
default:
key_str = (char*) "INVALID BLE KEY TYPE";
break;
}
return key_str;
} // esp_key_type_to_str
*/
#endif // CONFIG_BT_ENABLED

View File

@ -1,131 +0,0 @@
/*
* NimBLESecurity.h
*
* Created: on Feb 22 2020
* Author H2zero
*
* Originally:
*
* BLESecurity.h
*
* Created on: Dec 17, 2017
* Author: chegewara
*/
#ifndef COMPONENTS_NIMBLESECURITY_H_
#define COMPONENTS_NIMBLESECURITY_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
#if defined(CONFIG_NIMBLE_CPP_IDF)
#include "host/ble_gap.h"
#else
#include "nimble/nimble/host/include/host/ble_gap.h"
#endif
/**** FIX COMPILATION ****/
#undef min
#undef max
/**************************/
#include <stdint.h>
#define ESP_LE_AUTH_NO_BOND 0x00 /*!< 0*/ /* relate to BTM_LE_AUTH_NO_BOND in stack/btm_api.h */
#define ESP_LE_AUTH_BOND 0x01 /*!< 1 << 0 */ /* relate to BTM_LE_AUTH_BOND in stack/btm_api.h */
#define ESP_LE_AUTH_REQ_MITM (1 << 2) /*!< 1 << 2 */ /* relate to BTM_LE_AUTH_REQ_MITM in stack/btm_api.h */
#define ESP_LE_AUTH_REQ_BOND_MITM (ESP_LE_AUTH_BOND | ESP_LE_AUTH_REQ_MITM)/*!< 0101*/
#define ESP_LE_AUTH_REQ_SC_ONLY (1 << 3) /*!< 1 << 3 */ /* relate to BTM_LE_AUTH_REQ_SC_ONLY in stack/btm_api.h */
#define ESP_LE_AUTH_REQ_SC_BOND (ESP_LE_AUTH_BOND | ESP_LE_AUTH_REQ_SC_ONLY) /*!< 1001 */ /* relate to BTM_LE_AUTH_REQ_SC_BOND in stack/btm_api.h */
#define ESP_LE_AUTH_REQ_SC_MITM (ESP_LE_AUTH_REQ_MITM | ESP_LE_AUTH_REQ_SC_ONLY) /*!< 1100 */ /* relate to BTM_LE_AUTH_REQ_SC_MITM in stack/btm_api.h */
#define ESP_LE_AUTH_REQ_SC_MITM_BOND (ESP_LE_AUTH_REQ_MITM | ESP_LE_AUTH_REQ_SC_ONLY | ESP_LE_AUTH_BOND) /*!< 1101 */ /* relate to BTM_LE_AUTH_REQ_SC_MITM_BOND in stack/btm_api.h */
#define ESP_IO_CAP_OUT 0 /*!< DisplayOnly */ /* relate to BTM_IO_CAP_OUT in stack/btm_api.h */
#define ESP_IO_CAP_IO 1 /*!< DisplayYesNo */ /* relate to BTM_IO_CAP_IO in stack/btm_api.h */
#define ESP_IO_CAP_IN 2 /*!< KeyboardOnly */ /* relate to BTM_IO_CAP_IN in stack/btm_api.h */
#define ESP_IO_CAP_NONE 3 /*!< NoInputNoOutput */ /* relate to BTM_IO_CAP_NONE in stack/btm_api.h */
#define ESP_IO_CAP_KBDISP 4 /*!< Keyboard display */ /* relate to BTM_IO_CAP_KBDISP in stack/btm_api.h */
/// Used to exchange the encryption key in the init key & response key
#define ESP_BLE_ENC_KEY_MASK (1 << 0) /* relate to BTM_BLE_ENC_KEY_MASK in stack/btm_api.h */
/// Used to exchange the IRK key in the init key & response key
#define ESP_BLE_ID_KEY_MASK (1 << 1) /* relate to BTM_BLE_ID_KEY_MASK in stack/btm_api.h */
/// Used to exchange the CSRK key in the init key & response key
#define ESP_BLE_CSR_KEY_MASK (1 << 2) /* relate to BTM_BLE_CSR_KEY_MASK in stack/btm_api.h */
/// Used to exchange the link key(this key just used in the BLE & BR/EDR coexist mode) in the init key & response key
#define ESP_BLE_LINK_KEY_MASK (1 << 3) /* relate to BTM_BLE_LINK_KEY_MASK in stack/btm_api.h */
typedef uint8_t esp_ble_auth_req_t; /*!< combination of the above bit pattern */
typedef uint8_t esp_ble_io_cap_t; /*!< combination of the io capability */
/**
* @brief A class to handle BLE security operations.
* <b>Deprecated - provided for backward compatibility only.</b>
* @deprecated Use the security methods provided in NimBLEDevice instead.
*/
class NimBLESecurity {
public:
NimBLESecurity();
virtual ~NimBLESecurity();
void setAuthenticationMode(esp_ble_auth_req_t auth_req);
void setCapability(esp_ble_io_cap_t iocap);
void setInitEncryptionKey(uint8_t init_key);
void setRespEncryptionKey(uint8_t resp_key);
void setKeySize(uint8_t key_size = 16);
void setStaticPIN(uint32_t pin);
//static char* esp_key_type_to_str(esp_ble_key_type_t key_type);
/*
private:
esp_ble_auth_req_t m_authReq;
esp_ble_io_cap_t m_iocap;
uint8_t m_initKey;
uint8_t m_respKey;
uint8_t m_keySize;
*/
}; // BLESecurity
/**
* @brief Callbacks to handle GAP events related to authorization.
* <b>Deprecated - provided for backward compatibility only.</b>
* @deprecated Use the callbacks provided in NimBLEClientCallbacks and NimBLEServerCallbacks instead.
*/
class NimBLESecurityCallbacks {
public:
virtual ~NimBLESecurityCallbacks() {};
/**
* @brief Its request from peer device to input authentication pin code displayed on peer device.
* It requires that our device is capable to input 6-digits code by end user
* @return Return 6-digits integer value from input device
*/
virtual uint32_t onPassKeyRequest() = 0;
/**
* @brief Provide us 6-digits code to perform authentication.
* It requires that our device is capable to display this code to end user
* @param [in] pass_key The PIN provided by the peer.
*/
virtual void onPassKeyNotify(uint32_t pass_key) = 0;
/**
* @brief Here we can make decision if we want to let negotiate authorization with peer device or not
* @return Return true if we accept this peer device request
*/
virtual bool onSecurityRequest() = 0 ;
/**
* @brief Provides us information when authentication process is completed
*/
virtual void onAuthenticationComplete(ble_gap_conn_desc*) = 0;
/**
* @brief Called when using numeric comparison for authentication.
* @param [in] pin The PIN to compare.
* @return True to accept and pair.
*/
virtual bool onConfirmPIN(uint32_t pin) = 0;
}; // BLESecurityCallbacks
#endif // CONFIG_BT_ENABLED
#endif // COMPONENTS_NIMBLESECURITY_H_

View File

@ -27,6 +27,12 @@
#include "nimble/nimble/host/services/gatt/include/services/gatt/ble_svc_gatt.h"
#endif
#include <limits.h>
#include <algorithm>
#define NIMBLE_SERVER_GET_PEER_NAME_ON_CONNECT_CB 0
#define NIMBLE_SERVER_GET_PEER_NAME_ON_AUTH_CB 1
static const char* LOG_TAG = "NimBLEServer";
static NimBLEServerCallbacks defaultCallbacks;
@ -47,6 +53,7 @@ NimBLEServer::NimBLEServer() {
#endif
m_svcChanged = false;
m_deleteCallbacks = true;
m_getPeerNameOnConnect = false;
} // NimBLEServer
@ -54,7 +61,7 @@ NimBLEServer::NimBLEServer() {
* @brief Destructor: frees all resources / attributes created.
*/
NimBLEServer::~NimBLEServer() {
for(auto &it : m_svcVec) {
for(const auto &it : m_svcVec) {
delete it;
}
@ -186,9 +193,8 @@ void NimBLEServer::start() {
int rc = ble_gatts_start();
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gatts_start; rc=%d, %s", rc,
NimBLEUtils::returnCodeToString(rc));
abort();
NIMBLE_LOGE(LOG_TAG, "ble_gatts_start; rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc));
return;
}
#if CONFIG_NIMBLE_CPP_LOG_LEVEL >= 4
@ -212,20 +218,29 @@ void NimBLEServer::start() {
// Get the assigned service handles and build a vector of characteristics
// with Notify / Indicate capabilities for event handling
for(auto &svc : m_svcVec) {
if(svc->m_removed == 0) {
rc = ble_gatts_find_svc(&svc->getUUID().getNative()->u, &svc->m_handle);
if(svc->getRemoved() == 0) {
rc = ble_gatts_find_svc(svc->getUUID().getBase(), &svc->m_handle);
if(rc != 0) {
abort();
NIMBLE_LOGW(LOG_TAG, "GATT Server started without service: %s, Service %s",
svc->getUUID().toString().c_str(), svc->isStarted() ? "missing" : "not started");
continue; // Skip this service as it was not started
}
}
for(auto &chr : svc->m_chrVec) {
for(auto &chr : svc->m_vChars) {
// if Notify / Indicate is enabled but we didn't create the descriptor
// we do it now.
if((chr->m_properties & BLE_GATT_CHR_F_INDICATE) ||
(chr->m_properties & BLE_GATT_CHR_F_NOTIFY)) {
m_notifyChrVec.push_back(chr);
}
for (auto &desc : chr->m_vDescriptors) {
ble_gatts_find_dsc(svc->getUUID().getBase(),
chr->getUUID().getBase(),
desc->getUUID().getBase(),
&desc->m_handle);
}
}
}
@ -252,6 +267,15 @@ int NimBLEServer::disconnect(uint16_t connId, uint8_t reason) {
return rc;
} // disconnect
/**
* @brief Disconnect the specified client with optional reason.
* @param [in] connInfo Connection of the client to disconnect.
* @param [in] reason code for disconnecting.
* @return NimBLE host return code.
*/
int NimBLEServer::disconnect(const NimBLEConnInfo &connInfo, uint8_t reason) {
return disconnect(connInfo.getConnHandle(), reason);
} // disconnect
#if !CONFIG_BT_NIMBLE_EXT_ADV || defined(_DOXYGEN_)
/**
@ -263,6 +287,15 @@ void NimBLEServer::advertiseOnDisconnect(bool aod) {
} // advertiseOnDisconnect
#endif
/**
* @brief Set the server to automatically read the name from the connected peer before
* the onConnect callback is called and enables the override callback with name parameter.
* @param [in] enable Enable reading the connected peer name upon connection.
*/
void NimBLEServer::getPeerNameOnConnect(bool enable) {
m_getPeerNameOnConnect = enable;
} // getPeerNameOnConnect
/**
* @brief Return the number of connected clients.
* @return The number of connected clients.
@ -299,12 +332,8 @@ NimBLEConnInfo NimBLEServer::getPeerInfo(size_t index) {
* @param [in] address The address of the peer.
*/
NimBLEConnInfo NimBLEServer::getPeerInfo(const NimBLEAddress& address) {
ble_addr_t peerAddr;
memcpy(&peerAddr.val, address.getNative(),6);
peerAddr.type = address.getType();
NimBLEConnInfo peerInfo;
int rc = ble_gap_conn_find_by_addr(&peerAddr, &peerInfo.m_desc);
int rc = ble_gap_conn_find_by_addr(address.getBase(), &peerInfo.m_desc);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Peer info not found");
}
@ -328,6 +357,107 @@ NimBLEConnInfo NimBLEServer::getPeerIDInfo(uint16_t id) {
return peerInfo;
} // getPeerIDInfo
/**
* @brief Callback that is called after reading from the peer name characteristic.
* @details This will check the task pointer in the task data struct to determine
* the action to take once the name has been read. If there is a task waiting then
* it will be woken, if not, the the RC value is checked to determine which callback
* should be called.
*/
int NimBLEServer::peerNameCB(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr,
void *arg) {
NimBLETaskData *pTaskData = (NimBLETaskData*)arg;
std::string *name = (std::string*)pTaskData->m_pBuf;
int rc = error->status;
if (rc == 0) {
if (attr) {
name->append(OS_MBUF_DATA(attr->om, char*), OS_MBUF_PKTLEN(attr->om));
return rc;
}
}
if (rc == BLE_HS_EDONE) {
if (pTaskData->m_flags != -1) {
NimBLEServer* pServer = (NimBLEServer*)pTaskData->m_pInstance;
NimBLEConnInfo peerInfo{};
ble_gap_conn_find(conn_handle, &peerInfo.m_desc);
// check the flag to indicate which callback should be called.
if (pTaskData->m_flags == NIMBLE_SERVER_GET_PEER_NAME_ON_CONNECT_CB) {
pServer->m_pServerCallbacks->onConnect(pServer, peerInfo, *name);
} else if (pTaskData->m_flags == NIMBLE_SERVER_GET_PEER_NAME_ON_AUTH_CB) {
pServer->m_pServerCallbacks->onAuthenticationComplete(peerInfo, *name);
}
}
} else {
NIMBLE_LOGE(LOG_TAG, "NimBLEServerPeerNameCB rc=%d; %s", rc, NimBLEUtils::returnCodeToString(rc));
}
if (pTaskData->m_flags == -1) {
NimBLEUtils::taskRelease(*pTaskData, rc);
} else {
// If the read was triggered for callback use then these were allocated.
delete name;
delete pTaskData;
}
return rc;
}
/**
* @brief Internal method that sends the read command.
*/
std::string NimBLEServer::getPeerNameInternal(uint16_t conn_handle, int cb_type) {
std::string *buf = new std::string{};
NimBLETaskData *pTaskData = new NimBLETaskData(this, cb_type, buf);
ble_uuid16_t uuid {{BLE_UUID_TYPE_16}, BLE_SVC_GAP_CHR_UUID16_DEVICE_NAME};
int rc = ble_gattc_read_by_uuid(conn_handle,
1,
0xffff,
&uuid.u,
NimBLEServer::peerNameCB,
pTaskData);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gattc_read_by_uuid rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc));
NimBLEConnInfo peerInfo{};
ble_gap_conn_find(conn_handle, &peerInfo.m_desc);
if (cb_type == NIMBLE_SERVER_GET_PEER_NAME_ON_CONNECT_CB) {
m_pServerCallbacks->onConnect(this, peerInfo, *buf);
} else if (cb_type == NIMBLE_SERVER_GET_PEER_NAME_ON_AUTH_CB) {
m_pServerCallbacks->onAuthenticationComplete(peerInfo, *buf);
}
delete buf;
delete pTaskData;
} else if (cb_type == -1) {
NimBLEUtils::taskWait(*pTaskData, BLE_NPL_TIME_FOREVER);
rc = pTaskData->m_flags;
std::string name{*(std::string*)pTaskData->m_pBuf};
delete buf;
delete pTaskData;
if (rc != 0 && rc != BLE_HS_EDONE) {
NIMBLE_LOGE(LOG_TAG, "getPeerName rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
}
return name;
}
// TaskData and name buffer will be deleted in the callback.
return "";
}
/**
* @brief Get the name of the connected peer.
* @param connInfo A reference to a NimBLEConnInfo instance to read the name from.
* @returns A string containing the name.
* @note This is a blocking call and should NOT be called from any callbacks!
*/
std::string NimBLEServer::getPeerName(const NimBLEConnInfo& connInfo) {
std::string name = getPeerNameInternal(connInfo.getConnHandle());
return name;
}
/**
* @brief Handle a GATT Server Event.
@ -339,11 +469,11 @@ NimBLEConnInfo NimBLEServer::getPeerIDInfo(uint16_t id) {
*/
/*STATIC*/
int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) {
NimBLEServer* server = NimBLEDevice::getServer();
NIMBLE_LOGD(LOG_TAG, ">> handleGapEvent: %s",
NimBLEUtils::gapEventToString(event->type));
NIMBLE_LOGD(LOG_TAG, ">> handleGapEvent: %s", NimBLEUtils::gapEventToString(event->type));
int rc = 0;
struct ble_gap_conn_desc desc;
NimBLEConnInfo peerInfo;
NimBLEServer* pServer = NimBLEDevice::getServer();
switch(event->type) {
@ -354,17 +484,21 @@ int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) {
#if !CONFIG_BT_NIMBLE_EXT_ADV
NimBLEDevice::startAdvertising();
#endif
}
else {
server->m_connectedPeersVec.push_back(event->connect.conn_handle);
rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
} else {
rc = ble_gap_conn_find(event->connect.conn_handle, &peerInfo.m_desc);
if (rc != 0) {
return 0;
}
server->m_pServerCallbacks->onConnect(server);
server->m_pServerCallbacks->onConnect(server, &desc);
pServer->m_connectedPeersVec.push_back(event->connect.conn_handle);
if (pServer->m_getPeerNameOnConnect) {
pServer->getPeerNameInternal(event->connect.conn_handle,
NIMBLE_SERVER_GET_PEER_NAME_ON_CONNECT_CB);
} else {
pServer->m_pServerCallbacks->onConnect(pServer, peerInfo);
}
}
return 0;
@ -373,34 +507,34 @@ int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) {
case BLE_GAP_EVENT_DISCONNECT: {
// If Host reset tell the device now before returning to prevent
// any errors caused by calling host functions before resyncing.
// any errors caused by calling host functions before resync.
switch(event->disconnect.reason) {
case BLE_HS_ETIMEOUT_HCI:
case BLE_HS_EOS:
case BLE_HS_ECONTROLLER:
case BLE_HS_ENOTSYNCED:
NIMBLE_LOGC(LOG_TAG, "Disconnect - host reset, rc=%d", event->disconnect.reason);
NIMBLE_LOGE(LOG_TAG, "Disconnect - host reset, rc=%d", event->disconnect.reason);
NimBLEDevice::onReset(event->disconnect.reason);
break;
default:
break;
}
server->m_connectedPeersVec.erase(std::remove(server->m_connectedPeersVec.begin(),
server->m_connectedPeersVec.end(),
pServer->m_connectedPeersVec.erase(std::remove(pServer->m_connectedPeersVec.begin(),
pServer->m_connectedPeersVec.end(),
event->disconnect.conn.conn_handle),
server->m_connectedPeersVec.end());
pServer->m_connectedPeersVec.end());
if(server->m_svcChanged) {
server->resetGATT();
if(pServer->m_svcChanged) {
pServer->resetGATT();
}
server->m_pServerCallbacks->onDisconnect(server);
server->m_pServerCallbacks->onDisconnect(server, &event->disconnect.conn);
NimBLEConnInfo peerInfo(event->disconnect.conn);
pServer->m_pServerCallbacks->onDisconnect(pServer, peerInfo, event->disconnect.reason);
#if !CONFIG_BT_NIMBLE_EXT_ADV
if(server->m_advertiseOnDisconnect) {
server->startAdvertising();
if(pServer->m_advertiseOnDisconnect) {
pServer->startAdvertising();
}
#endif
return 0;
@ -411,23 +545,21 @@ int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) {
event->subscribe.attr_handle,
(event->subscribe.cur_notify ? "true":"false"));
for(auto &it : server->m_notifyChrVec) {
for(auto &it : pServer->m_notifyChrVec) {
if(it->getHandle() == event->subscribe.attr_handle) {
if((it->getProperties() & BLE_GATT_CHR_F_READ_AUTHEN) ||
(it->getProperties() & BLE_GATT_CHR_F_READ_AUTHOR) ||
(it->getProperties() & BLE_GATT_CHR_F_READ_ENC))
{
rc = ble_gap_conn_find(event->subscribe.conn_handle, &desc);
if (rc != 0) {
break;
}
if(!desc.sec_state.encrypted) {
NimBLEDevice::startSecurity(event->subscribe.conn_handle);
}
rc = ble_gap_conn_find(event->subscribe.conn_handle, &peerInfo.m_desc);
if (rc != 0) {
break;
}
it->setSubscribe(event);
if(((it->getProperties() & BLE_GATT_CHR_F_READ_AUTHEN) ||
(it->getProperties() & BLE_GATT_CHR_F_READ_AUTHOR) ||
(it->getProperties() & BLE_GATT_CHR_F_READ_ENC)) &&
!peerInfo.isEncrypted()) {
NimBLEDevice::startSecurity(event->subscribe.conn_handle);
}
it->setSubscribe(event, peerInfo);
break;
}
}
@ -439,19 +571,20 @@ int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) {
NIMBLE_LOGI(LOG_TAG, "mtu update event; conn_handle=%d mtu=%d",
event->mtu.conn_handle,
event->mtu.value);
rc = ble_gap_conn_find(event->mtu.conn_handle, &desc);
rc = ble_gap_conn_find(event->mtu.conn_handle, &peerInfo.m_desc);
if (rc != 0) {
return 0;
}
server->m_pServerCallbacks->onMTUChange(event->mtu.value, &desc);
pServer->m_pServerCallbacks->onMTUChange(event->mtu.value, peerInfo);
return 0;
} // BLE_GAP_EVENT_MTU
case BLE_GAP_EVENT_NOTIFY_TX: {
NimBLECharacteristic *pChar = nullptr;
for(auto &it : server->m_notifyChrVec) {
for(auto &it : pServer->m_notifyChrVec) {
if(it->getHandle() == event->notify_tx.attr_handle) {
pChar = it;
}
@ -461,31 +594,14 @@ int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) {
return 0;
}
NimBLECharacteristicCallbacks::Status statusRC;
if(event->notify_tx.indication) {
if(event->notify_tx.status != 0) {
if(event->notify_tx.status == BLE_HS_EDONE) {
statusRC = NimBLECharacteristicCallbacks::Status::SUCCESS_INDICATE;
} else if(rc == BLE_HS_ETIMEOUT) {
statusRC = NimBLECharacteristicCallbacks::Status::ERROR_INDICATE_TIMEOUT;
} else {
statusRC = NimBLECharacteristicCallbacks::Status::ERROR_INDICATE_FAILURE;
}
} else {
return 0;
}
server->clearIndicateWait(event->notify_tx.conn_handle);
} else {
if(event->notify_tx.status == 0) {
statusRC = NimBLECharacteristicCallbacks::Status::SUCCESS_NOTIFY;
} else {
statusRC = NimBLECharacteristicCallbacks::Status::ERROR_GATT;
return 0; // Indication sent but not yet acknowledged.
}
pServer->clearIndicateWait(event->notify_tx.conn_handle);
}
pChar->m_pCallbacks->onStatus(pChar, statusRC, event->notify_tx.status);
pChar->m_pCallbacks->onStatus(pChar, event->notify_tx.status);
return 0;
} // BLE_GAP_EVENT_NOTIFY_TX
@ -501,7 +617,12 @@ int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) {
// BLE_GAP_EVENT_ADV_COMPLETE | BLE_GAP_EVENT_SCAN_REQ_RCVD
case BLE_GAP_EVENT_CONN_UPDATE: {
NIMBLE_LOGD(LOG_TAG, "Connection parameters updated.");
rc = ble_gap_conn_find(event->connect.conn_handle, &peerInfo.m_desc);
if (rc != 0) {
return 0;
}
pServer->m_pServerCallbacks->onConnParamsUpdate(peerInfo);
return 0;
} // BLE_GAP_EVENT_CONN_UPDATE
@ -512,12 +633,12 @@ int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) {
*/
/* Delete the old bond. */
rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &peerInfo.m_desc);
if (rc != 0){
return BLE_GAP_REPEAT_PAIRING_IGNORE;
}
ble_store_util_delete_peer(&desc.peer_id_addr);
ble_store_util_delete_peer(&peerInfo.m_desc.peer_id_addr);
/* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should
* continue with the pairing operation.
@ -526,21 +647,30 @@ int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) {
} // BLE_GAP_EVENT_REPEAT_PAIRING
case BLE_GAP_EVENT_ENC_CHANGE: {
rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc);
rc = ble_gap_conn_find(event->enc_change.conn_handle, &peerInfo.m_desc);
if(rc != 0) {
return BLE_ATT_ERR_INVALID_HANDLE;
}
// Compatibility only - Do not use, should be removed the in future
if(NimBLEDevice::m_securityCallbacks != nullptr) {
NimBLEDevice::m_securityCallbacks->onAuthenticationComplete(&desc);
/////////////////////////////////////////////
} else {
server->m_pServerCallbacks->onAuthenticationComplete(&desc);
}
if (pServer->m_getPeerNameOnConnect) {
pServer->getPeerNameInternal(event->enc_change.conn_handle,
NIMBLE_SERVER_GET_PEER_NAME_ON_AUTH_CB);
} else {
pServer->m_pServerCallbacks->onAuthenticationComplete(peerInfo);
}
return 0;
} // BLE_GAP_EVENT_ENC_CHANGE
case BLE_GAP_EVENT_IDENTITY_RESOLVED: {
rc = ble_gap_conn_find(event->identity_resolved.conn_handle, &peerInfo.m_desc);
if(rc != 0) {
return BLE_ATT_ERR_INVALID_HANDLE;
}
pServer->m_pServerCallbacks->onIdentity(peerInfo);
return 0;
} // BLE_GAP_EVENT_IDENTITY_RESOLVED
case BLE_GAP_EVENT_PASSKEY_ACTION: {
struct ble_sm_io pkey = {0,0};
@ -551,25 +681,20 @@ int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) {
// if the (static)passkey is the default, check the callback for custom value
// both values default to the same.
if(pkey.passkey == 123456) {
pkey.passkey = server->m_pServerCallbacks->onPassKeyRequest();
pkey.passkey = pServer->m_pServerCallbacks->onPassKeyDisplay();
}
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
NIMBLE_LOGD(LOG_TAG, "BLE_SM_IOACT_DISP; ble_sm_inject_io result: %d", rc);
} else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) {
NIMBLE_LOGD(LOG_TAG, "Passkey on device's display: %" PRIu32, event->passkey.params.numcmp);
pkey.action = event->passkey.params.action;
// Compatibility only - Do not use, should be removed the in future
if(NimBLEDevice::m_securityCallbacks != nullptr) {
pkey.numcmp_accept = NimBLEDevice::m_securityCallbacks->onConfirmPIN(event->passkey.params.numcmp);
/////////////////////////////////////////////
} else {
pkey.numcmp_accept = server->m_pServerCallbacks->onConfirmPIN(event->passkey.params.numcmp);
rc = ble_gap_conn_find(event->passkey.conn_handle, &peerInfo.m_desc);
if(rc != 0) {
return BLE_ATT_ERR_INVALID_HANDLE;
}
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
NIMBLE_LOGD(LOG_TAG, "BLE_SM_IOACT_NUMCMP; ble_sm_inject_io result: %d", rc);
pServer->m_pServerCallbacks->onConfirmPassKey(peerInfo, event->passkey.params.numcmp);
//TODO: Handle out of band pairing
} else if (event->passkey.params.action == BLE_SM_IOACT_OOB) {
static uint8_t tem_oob[16] = {0};
@ -580,21 +705,6 @@ int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) {
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
NIMBLE_LOGD(LOG_TAG, "BLE_SM_IOACT_OOB; ble_sm_inject_io result: %d", rc);
//////////////////////////////////
} else if (event->passkey.params.action == BLE_SM_IOACT_INPUT) {
NIMBLE_LOGD(LOG_TAG, "Enter the passkey");
pkey.action = event->passkey.params.action;
// Compatibility only - Do not use, should be removed the in future
if(NimBLEDevice::m_securityCallbacks != nullptr) {
pkey.passkey = NimBLEDevice::m_securityCallbacks->onPassKeyRequest();
/////////////////////////////////////////////
} else {
pkey.passkey = server->m_pServerCallbacks->onPassKeyRequest();
}
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
NIMBLE_LOGD(LOG_TAG, "BLE_SM_IOACT_INPUT; ble_sm_inject_io result: %d", rc);
} else if (event->passkey.params.action == BLE_SM_IOACT_NONE) {
NIMBLE_LOGD(LOG_TAG, "No passkey action required");
}
@ -612,6 +722,68 @@ int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) {
} // handleGapEvent
/**
* @brief STATIC callback to handle events from the NimBLE stack.
*/
int NimBLEServer::handleGattEvent(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg) {
NIMBLE_LOGD(LOG_TAG, "Gatt %s event", (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR ||
ctxt->op == BLE_GATT_ACCESS_OP_READ_DSC) ? "Read" : "Write");
auto pAtt = static_cast<NimBLELocalValueAttribute*>(arg);
const auto& val = pAtt->getAttVal();
NimBLEConnInfo peerInfo{};
ble_gap_conn_find(conn_handle, &peerInfo.m_desc);
switch(ctxt->op) {
case BLE_GATT_ACCESS_OP_READ_DSC:
case BLE_GATT_ACCESS_OP_READ_CHR: {
// If the packet header is only 8 bytes this is a follow up of a long read
// so we don't want to call the onRead() callback again.
if(ctxt->om->om_pkthdr_len > 8 ||
conn_handle == BLE_HS_CONN_HANDLE_NONE ||
val.size() <= (ble_att_mtu(conn_handle) - 3)) {
pAtt->readEvent(peerInfo);
}
ble_npl_hw_enter_critical();
int rc = os_mbuf_append(ctxt->om, val.data(), val.size());
ble_npl_hw_exit_critical(0);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
case BLE_GATT_ACCESS_OP_WRITE_DSC:
case BLE_GATT_ACCESS_OP_WRITE_CHR: {
uint16_t att_max_len = val.max_size();
if (ctxt->om->om_len > att_max_len) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
uint8_t buf[att_max_len];
uint16_t len = ctxt->om->om_len;
memcpy(buf, ctxt->om->om_data,len);
os_mbuf *next;
next = SLIST_NEXT(ctxt->om, om_next);
while(next != NULL){
if((len + next->om_len) > att_max_len) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
memcpy(&buf[len], next->om_data, next->om_len);
len += next->om_len;
next = SLIST_NEXT(next, om_next);
}
pAtt->writeEvent(buf, len, peerInfo);
return 0;
}
default:
break;
}
return BLE_ATT_ERR_UNLIKELY;
} // handleGattEvent
/**
* @brief Set the server callbacks.
*
@ -636,7 +808,7 @@ void NimBLEServer::setCallbacks(NimBLEServerCallbacks* pCallbacks, bool deleteCa
* @brief Remove a service from the server.
*
* @details Immediately removes access to the service by clients, sends a service changed indication,
* and removes the service (if applicable) from the advertisments.
* and removes the service (if applicable) from the advertisements.
* The service is not deleted unless the deleteSvc parameter is true, otherwise the service remains
* available and can be re-added in the future. If desired a removed but not deleted service can
* be deleted later by calling this method with deleteSvc set to true.
@ -654,7 +826,7 @@ void NimBLEServer::removeService(NimBLEService* service, bool deleteSvc) {
// Check if the service was already removed and if so check if this
// is being called to delete the object and do so if requested.
// Otherwise, ignore the call and return.
if(service->m_removed > 0) {
if(service->getRemoved() > 0) {
if(deleteSvc) {
for(auto it = m_svcVec.begin(); it != m_svcVec.end(); ++it) {
if ((*it) == service) {
@ -673,7 +845,7 @@ void NimBLEServer::removeService(NimBLEService* service, bool deleteSvc) {
return;
}
service->m_removed = deleteSvc ? NIMBLE_ATT_REMOVE_DELETE : NIMBLE_ATT_REMOVE_HIDE;
service->setRemoved(deleteSvc ? NIMBLE_ATT_REMOVE_DELETE : NIMBLE_ATT_REMOVE_HIDE);
serviceChanged();
#if !CONFIG_BT_NIMBLE_EXT_ADV
NimBLEDevice::getAdvertising()->removeServiceUUID(service->getUUID());
@ -697,12 +869,12 @@ void NimBLEServer::addService(NimBLEService* service) {
// If adding a service that was not removed add it and return.
// Else reset GATT and send service changed notification.
if(service->m_removed == 0) {
if(service->getRemoved() == 0) {
m_svcVec.push_back(service);
return;
}
service->m_removed = 0;
service->setRemoved(0);
serviceChanged();
}
@ -721,8 +893,8 @@ void NimBLEServer::resetGATT() {
ble_svc_gatt_init();
for(auto it = m_svcVec.begin(); it != m_svcVec.end(); ) {
if ((*it)->m_removed > 0) {
if ((*it)->m_removed == NIMBLE_ATT_REMOVE_DELETE) {
if ((*it)->getRemoved() > 0) {
if ((*it)->getRemoved() == NIMBLE_ATT_REMOVE_DELETE) {
delete *it;
it = m_svcVec.erase(it);
} else {
@ -767,15 +939,16 @@ bool NimBLEServer::stopAdvertising(uint8_t inst_id) {
} // stopAdvertising
#endif
#if !CONFIG_BT_NIMBLE_EXT_ADV|| defined(_DOXYGEN_)
#if !CONFIG_BT_NIMBLE_EXT_ADV || defined(_DOXYGEN_)
/**
* @brief Start advertising.
* @param [in] duration The duration in milliseconds to advertise for, default = forever.
* @return True if advertising started successfully.
* @details Start the server advertising its existence. This is a convenience function and is equivalent to
* @details Start the server advertising its existence. This is a convenience function and is equivalent to
* retrieving the advertising object and invoking start upon it.
*/
bool NimBLEServer::startAdvertising() {
return getAdvertising()->start();
bool NimBLEServer::startAdvertising(uint32_t duration) {
return getAdvertising()->start(duration);
} // startAdvertising
#endif
@ -873,49 +1046,47 @@ void NimBLEServer::clearIndicateWait(uint16_t conn_handle) {
/** Default callback handlers */
void NimBLEServerCallbacks::onConnect(NimBLEServer* pServer) {
void NimBLEServerCallbacks::onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) {
NIMBLE_LOGD("NimBLEServerCallbacks", "onConnect(): Default");
} // onConnect
void NimBLEServerCallbacks::onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) {
void NimBLEServerCallbacks::onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, std::string& name) {
NIMBLE_LOGD("NimBLEServerCallbacks", "onConnect(): Default");
} // onConnect
void NimBLEServerCallbacks::onDisconnect(NimBLEServer* pServer) {
void NimBLEServerCallbacks::onDisconnect(NimBLEServer* pServer,
NimBLEConnInfo& connInfo, int reason) {
NIMBLE_LOGD("NimBLEServerCallbacks", "onDisconnect(): Default");
} // onDisconnect
void NimBLEServerCallbacks::onDisconnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) {
NIMBLE_LOGD("NimBLEServerCallbacks", "onDisconnect(): Default");
} // onDisconnect
void NimBLEServerCallbacks::onMTUChange(uint16_t MTU, ble_gap_conn_desc* desc) {
void NimBLEServerCallbacks::onMTUChange(uint16_t MTU, NimBLEConnInfo& connInfo) {
NIMBLE_LOGD("NimBLEServerCallbacks", "onMTUChange(): Default");
} // onMTUChange
uint32_t NimBLEServerCallbacks::onPassKeyRequest(){
NIMBLE_LOGD("NimBLEServerCallbacks", "onPassKeyRequest: default: 123456");
uint32_t NimBLEServerCallbacks::onPassKeyDisplay(){
NIMBLE_LOGD("NimBLEServerCallbacks", "onPassKeyDisplay: default: 123456");
return 123456;
}
/*
void NimBLEServerCallbacks::onPassKeyNotify(uint32_t pass_key){
NIMBLE_LOGD("NimBLEServerCallbacks", "onPassKeyNotify: default: %d", pass_key);
}
} //onPassKeyDisplay
bool NimBLEServerCallbacks::onSecurityRequest(){
NIMBLE_LOGD("NimBLEServerCallbacks", "onSecurityRequest: default: true");
return true;
}
*/
void NimBLEServerCallbacks::onAuthenticationComplete(ble_gap_conn_desc*){
NIMBLE_LOGD("NimBLEServerCallbacks", "onAuthenticationComplete: default");
}
bool NimBLEServerCallbacks::onConfirmPIN(uint32_t pin){
void NimBLEServerCallbacks::onConfirmPassKey(NimBLEConnInfo& connInfo, uint32_t pin){
NIMBLE_LOGD("NimBLEServerCallbacks", "onConfirmPIN: default: true");
return true;
}
NimBLEDevice::injectConfirmPasskey(connInfo, true);
} // onConfirmPIN
void NimBLEServerCallbacks::onIdentity(NimBLEConnInfo& connInfo){
NIMBLE_LOGD("NimBLEServerCallbacks", "onIdentity: default");
} // onIdentity
void NimBLEServerCallbacks::onAuthenticationComplete(NimBLEConnInfo& connInfo){
NIMBLE_LOGD("NimBLEServerCallbacks", "onAuthenticationComplete: default");
} // onAuthenticationComplete
void NimBLEServerCallbacks::onAuthenticationComplete(NimBLEConnInfo& connInfo, const std::string& name){
NIMBLE_LOGD("NimBLEServerCallbacks", "onAuthenticationComplete: default");
} // onAuthenticationComplete
void NimBLEServerCallbacks::onConnParamsUpdate(NimBLEConnInfo& connInfo){
NIMBLE_LOGD("NimBLEServerCallbacks", "onConnParamsUpdate: default");
} // onConnParamsUpdate
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */

View File

@ -18,10 +18,8 @@
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#define NIMBLE_ATT_REMOVE_HIDE 1
#define NIMBLE_ATT_REMOVE_DELETE 2
#define onMtuChanged onMTUChange
class NimBLEServer;
class NimBLEServerCallbacks;
#include "NimBLEUtils.h"
#include "NimBLEAddress.h"
@ -31,14 +29,13 @@
#include "NimBLEAdvertising.h"
#endif
#include "NimBLEService.h"
#include "NimBLESecurity.h"
#include "NimBLECharacteristic.h"
#include "NimBLEConnInfo.h"
#define NIMBLE_ATT_REMOVE_HIDE 1
#define NIMBLE_ATT_REMOVE_DELETE 2
class NimBLEService;
class NimBLECharacteristic;
class NimBLEServerCallbacks;
#define onMtuChanged onMTUChange
/**
* @brief The model of a %BLE server.
@ -58,9 +55,10 @@ public:
int duration = 0,
int max_events = 0);
bool stopAdvertising(uint8_t inst_id);
#else
#endif
# if !CONFIG_BT_NIMBLE_EXT_ADV || defined(_DOXYGEN_)
NimBLEAdvertising* getAdvertising();
bool startAdvertising();
bool startAdvertising(uint32_t duration = 0);
#endif
bool stopAdvertising();
void start();
@ -69,6 +67,8 @@ public:
NimBLEService* getServiceByHandle(uint16_t handle);
int disconnect(uint16_t connID,
uint8_t reason = BLE_ERR_REM_USER_CONN_TERM);
int disconnect(const NimBLEConnInfo &connInfo,
uint8_t reason = BLE_ERR_REM_USER_CONN_TERM);
void updateConnParams(uint16_t conn_handle,
uint16_t minInterval, uint16_t maxInterval,
uint16_t latency, uint16_t timeout);
@ -78,6 +78,8 @@ public:
NimBLEConnInfo getPeerInfo(size_t index);
NimBLEConnInfo getPeerInfo(const NimBLEAddress& address);
NimBLEConnInfo getPeerIDInfo(uint16_t id);
std::string getPeerName(const NimBLEConnInfo& connInfo);
void getPeerNameOnConnect(bool enable);
#if !CONFIG_BT_NIMBLE_EXT_ADV || defined(_DOXYGEN_)
void advertiseOnDisconnect(bool);
#endif
@ -98,6 +100,7 @@ private:
#if !CONFIG_BT_NIMBLE_EXT_ADV
bool m_advertiseOnDisconnect;
#endif
bool m_getPeerNameOnConnect;
bool m_svcChanged;
NimBLEServerCallbacks* m_pServerCallbacks;
bool m_deleteCallbacks;
@ -110,10 +113,17 @@ private:
std::vector<NimBLECharacteristic*> m_notifyChrVec;
static int handleGapEvent(struct ble_gap_event *event, void *arg);
static int peerNameCB(uint16_t conn_handle, const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg);
std::string getPeerNameInternal(uint16_t conn_handle, int cb_type = -1);
void serviceChanged();
void resetGATT();
bool setIndicateWait(uint16_t conn_handle);
void clearIndicateWait(uint16_t conn_handle);
static int handleGattEvent(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg);
}; // NimBLEServer
@ -128,64 +138,81 @@ public:
* @brief Handle a client connection.
* This is called when a client connects.
* @param [in] pServer A pointer to the %BLE server that received the client connection.
* @param [in] connInfo A reference to a NimBLEConnInfo instance with information.
* about the peer connection parameters.
*/
virtual void onConnect(NimBLEServer* pServer);
virtual void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo);
/**
* @brief Handle a client connection.
* This is called when a client connects.
* @param [in] pServer A pointer to the %BLE server that received the client connection.
* @param [in] desc A pointer to the connection description structure containig information
* about the connection parameters.
* @param [in] connInfo A reference to a NimBLEConnInfo instance with information.
* @param [in] name The name of the connected peer device.
* about the peer connection parameters.
*/
virtual void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc);
virtual void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, std::string& name);
/**
* @brief Handle a client disconnection.
* This is called when a client disconnects.
* @param [in] pServer A reference to the %BLE server that received the existing client disconnection.
*/
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.
* @param [in] connInfo A reference to a NimBLEConnInfo instance with information
* about the peer connection parameters.
* @param [in] reason The reason code for the disconnection.
*/
virtual void onDisconnect(NimBLEServer* pServer, ble_gap_conn_desc* desc);
virtual void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason);
/**
/**
* @brief Called when the connection MTU changes.
* @param [in] MTU The new MTU value.
* @param [in] desc A pointer to the connection description structure containig information
* about the connection.
* @param [in] connInfo A reference to a NimBLEConnInfo instance with information
* about the peer connection parameters.
*/
virtual void onMTUChange(uint16_t MTU, ble_gap_conn_desc* desc);
virtual void onMTUChange(uint16_t MTU, NimBLEConnInfo& connInfo);
/**
* @brief Called when a client requests a passkey for pairing.
* @brief Called when a client requests a passkey for pairing (display).
* @return The passkey to be sent to the client.
*/
virtual uint32_t onPassKeyRequest();
//virtual void onPassKeyNotify(uint32_t pass_key);
//virtual bool onSecurityRequest();
/**
* @brief Called when the pairing procedure is complete.
* @param [in] desc A pointer to the struct containing the connection information.\n
* This can be used to check the status of the connection encryption/pairing.
*/
virtual void onAuthenticationComplete(ble_gap_conn_desc* desc);
virtual uint32_t onPassKeyDisplay();
/**
* @brief Called when using numeric comparision for pairing.
* @param [in] connInfo A reference to a NimBLEConnInfo instance with information
* Should be passed back to NimBLEDevice::injectConfirmPasskey
* @param [in] pin The pin to compare with the client.
* @return True to accept the pin.
*/
virtual bool onConfirmPIN(uint32_t pin);
virtual void onConfirmPassKey(NimBLEConnInfo& connInfo, uint32_t pin);
/**
* @brief Called when the pairing procedure is complete.
* @param [in] connInfo A reference to a NimBLEConnInfo instance with information
* about the peer connection parameters.
*/
virtual void onAuthenticationComplete(NimBLEConnInfo& connInfo);
/**
* @brief Called when the pairing procedure is complete.
* @param [in] connInfo A reference to a NimBLEConnInfo instance with information
* @param [in] name The name of the connected peer device.
* about the peer connection parameters.
*/
virtual void onAuthenticationComplete(NimBLEConnInfo& connInfo, const std::string& name);
/**
* @brief Called when the peer identity address is resolved.
* @param [in] connInfo A reference to a NimBLEConnInfo instance with information
*/
virtual void onIdentity(NimBLEConnInfo& connInfo);
/**
* @brief Called when connection parameters are updated following a request to
* update via NimBLEServer::updateConnParams
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the
* updated connection parameters.
*/
virtual void onConnParamsUpdate(NimBLEConnInfo& connInfo);
}; // NimBLEServerCallbacks
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */

View File

@ -17,92 +17,67 @@
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#include "NimBLEDevice.h"
#include "NimBLEService.h"
#include "NimBLEUtils.h"
#include "NimBLELog.h"
# include "NimBLEDevice.h"
# include "NimBLEService.h"
# include "NimBLEUtils.h"
# include "NimBLELog.h"
#include <string>
static const char* LOG_TAG = "NimBLEService"; // Tag for logging.
#define NULL_HANDLE (0xffff)
# include <string>
static const char* LOG_TAG = "NimBLEService";
/**
* @brief Construct an instance of the NimBLEService
* @param [in] uuid The UUID of the service.
*/
NimBLEService::NimBLEService(const char* uuid)
: NimBLEService(NimBLEUUID(uuid)) {
}
NimBLEService::NimBLEService(const char* uuid) : NimBLEService(NimBLEUUID(uuid)) {}
/**
* @brief Construct an instance of the BLEService
* @param [in] uuid The UUID of the service.
*/
NimBLEService::NimBLEService(const NimBLEUUID &uuid) {
m_uuid = uuid;
m_handle = NULL_HANDLE;
m_pSvcDef = nullptr;
m_removed = 0;
} // NimBLEService
NimBLEService::NimBLEService(const NimBLEUUID& uuid)
: NimBLELocalAttribute{uuid, 0}, m_pSvcDef{{0, getUUID().getBase(), nullptr, nullptr},{}} {}
/**
* @brief Destructor, make sure we release the resources allocated for the service.
*/
NimBLEService::~NimBLEService() {
if(m_pSvcDef != nullptr) {
if(m_pSvcDef->characteristics != nullptr) {
for(int i=0; m_pSvcDef->characteristics[i].uuid != NULL; ++i) {
if(m_pSvcDef->characteristics[i].descriptors) {
delete(m_pSvcDef->characteristics[i].descriptors);
}
}
delete(m_pSvcDef->characteristics);
if (m_pSvcDef->characteristics) {
if (m_pSvcDef->characteristics->descriptors) {
delete[] m_pSvcDef->characteristics->descriptors;
}
delete(m_pSvcDef);
delete[] m_pSvcDef->characteristics;
}
for(auto &it : m_chrVec) {
for (const auto& it : m_vChars) {
delete it;
}
}
} // ~NimBLEService
/**
* @brief Dump details of this BLE GATT service.
* @return N/A.
*/
void NimBLEService::dump() {
NIMBLE_LOGD(LOG_TAG, "Service: uuid:%s, handle: 0x%2x",
m_uuid.toString().c_str(),
m_handle);
void NimBLEService::dump() const {
NIMBLE_LOGD(LOG_TAG, "Service: uuid:%s, handle: 0x%2x", getUUID().toString().c_str(), getHandle());
std::string res;
int count = 0;
char hex[5];
for (auto &it: m_chrVec) {
if (count > 0) {res += "\n";}
int count = 0;
char hex[5];
for (const auto& it : m_vChars) {
if (count > 0) {
res += "\n";
}
snprintf(hex, sizeof(hex), "%04x", it->getHandle());
count++;
res += "handle: 0x";
res += hex;
res += ", uuid: " + std::string(it->getUUID());
}
NIMBLE_LOGD(LOG_TAG, "Characteristics:\n%s", res.c_str());
} // dump
/**
* @brief Get the UUID of the service.
* @return the UUID of the service.
*/
NimBLEUUID NimBLEService::getUUID() {
return m_uuid;
} // getUUID
/**
* @brief Builds the database of characteristics/descriptors for the service
* and registers it with the NimBLE stack.
@ -112,146 +87,97 @@ bool NimBLEService::start() {
NIMBLE_LOGD(LOG_TAG, ">> start(): Starting service: %s", toString().c_str());
// Rebuild the service definition if the server attributes have changed.
if(getServer()->m_svcChanged && m_pSvcDef != nullptr) {
if(m_pSvcDef[0].characteristics) {
if(m_pSvcDef[0].characteristics[0].descriptors) {
delete(m_pSvcDef[0].characteristics[0].descriptors);
if (getServer()->m_svcChanged) {
if (m_pSvcDef->characteristics) {
if (m_pSvcDef->characteristics->descriptors) {
delete[] m_pSvcDef->characteristics->descriptors;
}
delete(m_pSvcDef[0].characteristics);
delete[] m_pSvcDef->characteristics;
}
delete(m_pSvcDef);
m_pSvcDef = nullptr;
m_pSvcDef->type = 0;
}
if(m_pSvcDef == nullptr) {
// Nimble requires an array of services to be sent to the api
// Since we are adding 1 at a time we create an array of 2 and set the type
// of the second service to 0 to indicate the end of the array.
ble_gatt_svc_def* svc = new ble_gatt_svc_def[2];
ble_gatt_chr_def* pChr_a = nullptr;
ble_gatt_dsc_def* pDsc_a = nullptr;
svc[0].type = BLE_GATT_SVC_TYPE_PRIMARY;
svc[0].uuid = &m_uuid.getNative()->u;
svc[0].includes = NULL;
int removedCount = 0;
for(auto it = m_chrVec.begin(); it != m_chrVec.end(); ) {
if ((*it)->m_removed > 0) {
if ((*it)->m_removed == NIMBLE_ATT_REMOVE_DELETE) {
delete *it;
it = m_chrVec.erase(it);
} else {
++removedCount;
++it;
}
if (!m_pSvcDef->type) {
m_pSvcDef->type = BLE_GATT_SVC_TYPE_PRIMARY;
size_t numChrs = 0;
for (const auto& chr : m_vChars) {
if (chr->getRemoved()) {
continue;
}
++it;
++numChrs;
}
size_t numChrs = m_chrVec.size() - removedCount;
NIMBLE_LOGD(LOG_TAG,"Adding %d characteristics for service %s", numChrs, toString().c_str());
NIMBLE_LOGD(LOG_TAG, "Adding %d characteristics for service %s", numChrs, toString().c_str());
if (numChrs) {
int i = 0;
if(!numChrs){
svc[0].characteristics = NULL;
}else{
// Nimble requires the last characteristic to have it's uuid = 0 to indicate the end
// of the characteristics for the service. We create 1 extra and set it to null
// for this purpose.
pChr_a = new ble_gatt_chr_def[numChrs + 1];
uint8_t i = 0;
for(auto chr_it = m_chrVec.begin(); chr_it != m_chrVec.end(); ++chr_it) {
if((*chr_it)->m_removed > 0) {
ble_gatt_chr_def* pChrs = new ble_gatt_chr_def[numChrs + 1]{};
for (const auto& chr : m_vChars) {
if (chr->getRemoved()) {
continue;
}
removedCount = 0;
for(auto it = (*chr_it)->m_dscVec.begin(); it != (*chr_it)->m_dscVec.end(); ) {
if ((*it)->m_removed > 0) {
if ((*it)->m_removed == NIMBLE_ATT_REMOVE_DELETE) {
delete *it;
it = (*chr_it)->m_dscVec.erase(it);
} else {
++removedCount;
++it;
}
size_t numDscs = 0;
for (const auto& dsc : chr->m_vDescriptors) {
if (dsc->getRemoved()) {
continue;
}
++it;
++numDscs;
}
size_t numDscs = (*chr_it)->m_dscVec.size() - removedCount;
if (numDscs) {
int j = 0;
if(!numDscs){
pChr_a[i].descriptors = NULL;
} else {
// Must have last descriptor uuid = 0 so we have to create 1 extra
pDsc_a = new ble_gatt_dsc_def[numDscs+1];
uint8_t d = 0;
for(auto dsc_it = (*chr_it)->m_dscVec.begin(); dsc_it != (*chr_it)->m_dscVec.end(); ++dsc_it ) {
if((*dsc_it)->m_removed > 0) {
ble_gatt_dsc_def* pDscs = new ble_gatt_dsc_def[numDscs + 1]{};
for (const auto& dsc : chr->m_vDescriptors) {
if (dsc->getRemoved()) {
continue;
}
pDsc_a[d].uuid = &(*dsc_it)->m_uuid.getNative()->u;
pDsc_a[d].att_flags = (*dsc_it)->m_properties;
pDsc_a[d].min_key_size = 0;
pDsc_a[d].access_cb = NimBLEDescriptor::handleGapEvent;
pDsc_a[d].arg = (*dsc_it);
++d;
pDscs[j].uuid = dsc->getUUID().getBase();
pDscs[j].att_flags = dsc->getProperties();
pDscs[j].min_key_size = 0;
pDscs[j].access_cb = NimBLEServer::handleGattEvent;
pDscs[j].arg = dsc;
++j;
}
pDsc_a[numDscs].uuid = NULL;
pChr_a[i].descriptors = pDsc_a;
pChrs[i].descriptors = pDscs;
}
pChr_a[i].uuid = &(*chr_it)->m_uuid.getNative()->u;
pChr_a[i].access_cb = NimBLECharacteristic::handleGapEvent;
pChr_a[i].arg = (*chr_it);
pChr_a[i].flags = (*chr_it)->m_properties;
pChr_a[i].min_key_size = 0;
pChr_a[i].val_handle = &(*chr_it)->m_handle;
pChrs[i].uuid = chr->getUUID().getBase();
pChrs[i].access_cb = NimBLEServer::handleGattEvent;
pChrs[i].arg = chr;
pChrs[i].flags = chr->getProperties();
pChrs[i].min_key_size = 0;
pChrs[i].val_handle = &chr->m_handle;
++i;
}
pChr_a[numChrs].uuid = NULL;
svc[0].characteristics = pChr_a;
m_pSvcDef->characteristics = pChrs;
}
// end of services must indicate to api with type = 0
svc[1].type = 0;
m_pSvcDef = svc;
}
int rc = ble_gatts_count_cfg((const ble_gatt_svc_def*)m_pSvcDef);
int rc = ble_gatts_count_cfg(m_pSvcDef);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gatts_count_cfg failed, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc));
return false;
}
rc = ble_gatts_add_svcs((const ble_gatt_svc_def*)m_pSvcDef);
rc = ble_gatts_add_svcs(m_pSvcDef);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gatts_add_svcs, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc));
return false;
}
NIMBLE_LOGD(LOG_TAG, "<< start()");
return true;
} // start
/**
* @brief Get the handle associated with this service.
* @return The handle associated with this service.
*/
uint16_t NimBLEService::getHandle() {
return m_handle;
} // getHandle
/**
* @brief Create a new BLE Characteristic associated with this service.
* @param [in] uuid - The UUID of the characteristic.
@ -261,8 +187,7 @@ uint16_t NimBLEService::getHandle() {
*/
NimBLECharacteristic* NimBLEService::createCharacteristic(const char* uuid, uint32_t properties, uint16_t max_len) {
return createCharacteristic(NimBLEUUID(uuid), properties, max_len);
}
} // createCharacteristic
/**
* @brief Create a new BLE Characteristic associated with this service.
@ -271,59 +196,62 @@ NimBLECharacteristic* NimBLEService::createCharacteristic(const char* uuid, uint
* @param [in] max_len - The maximum length in bytes that the characteristic value can hold.
* @return The new BLE characteristic.
*/
NimBLECharacteristic* NimBLEService::createCharacteristic(const NimBLEUUID &uuid, uint32_t properties, uint16_t max_len) {
NimBLECharacteristic* pCharacteristic = new NimBLECharacteristic(uuid, properties, max_len, this);
NimBLECharacteristic* NimBLEService::createCharacteristic(const NimBLEUUID& uuid, uint32_t properties, uint16_t max_len) {
NimBLECharacteristic* pChar = new NimBLECharacteristic(uuid, properties, max_len, this);
if (getCharacteristic(uuid) != nullptr) {
NIMBLE_LOGD(LOG_TAG, "<< Adding a duplicate characteristic with UUID: %s",
std::string(uuid).c_str());
NIMBLE_LOGD(LOG_TAG, "Adding a duplicate characteristic with UUID: %s", std::string(uuid).c_str());
}
addCharacteristic(pCharacteristic);
return pCharacteristic;
addCharacteristic(pChar);
return pChar;
} // createCharacteristic
/**
* @brief Add a characteristic to the service.
* @param[in] pCharacteristic A pointer to the characteristic instance to add to the service.
* @param[in] pChar A pointer to the characteristic instance to add to the service.
*/
void NimBLEService::addCharacteristic(NimBLECharacteristic* pCharacteristic) {
void NimBLEService::addCharacteristic(NimBLECharacteristic* pChar) {
bool foundRemoved = false;
if(pCharacteristic->m_removed > 0) {
for(auto& it : m_chrVec) {
if(it == pCharacteristic) {
if (pChar->getRemoved() > 0) {
for (const auto& chr : m_vChars) {
if (chr == pChar) {
foundRemoved = true;
pCharacteristic->m_removed = 0;
pChar->setRemoved(0);
}
}
}
if(!foundRemoved) {
m_chrVec.push_back(pCharacteristic);
// Check if the characteristic is already in the service and if so, return.
for (const auto& chr : m_vChars) {
if (chr == pChar) {
pChar->setService(this); // Update the service pointer in the characteristic.
return;
}
}
pCharacteristic->setService(this);
if (!foundRemoved) {
m_vChars.push_back(pChar);
}
pChar->setService(this);
getServer()->serviceChanged();
} // addCharacteristic
/**
* @brief Remove a characteristic from the service.
* @param[in] pCharacteristic A pointer to the characteristic instance to remove from the service.
* @param[in] pChar A pointer to the characteristic instance to remove from the service.
* @param[in] deleteChr If true it will delete the characteristic instance and free it's resources.
*/
void NimBLEService::removeCharacteristic(NimBLECharacteristic* pCharacteristic, bool deleteChr) {
void NimBLEService::removeCharacteristic(NimBLECharacteristic* pChar, bool deleteChr) {
// Check if the characteristic was already removed and if so, check if this
// is being called to delete the object and do so if requested.
// Otherwise, ignore the call and return.
if(pCharacteristic->m_removed > 0) {
if(deleteChr) {
for(auto it = m_chrVec.begin(); it != m_chrVec.end(); ++it) {
if ((*it) == pCharacteristic) {
m_chrVec.erase(it);
delete *it;
if (pChar->getRemoved() > 0) {
if (deleteChr) {
for (auto it = m_vChars.begin(); it != m_vChars.end(); ++it) {
if ((*it) == pChar) {
delete (*it);
m_vChars.erase(it);
break;
}
}
@ -332,80 +260,82 @@ void NimBLEService::removeCharacteristic(NimBLECharacteristic* pCharacteristic,
return;
}
pCharacteristic->m_removed = deleteChr ? NIMBLE_ATT_REMOVE_DELETE : NIMBLE_ATT_REMOVE_HIDE;
pChar->setRemoved(deleteChr ? NIMBLE_ATT_REMOVE_DELETE : NIMBLE_ATT_REMOVE_HIDE);
getServer()->serviceChanged();
} // removeCharacteristic
/**
* @brief Get a pointer to the characteristic object with the specified UUID.
* @param [in] uuid The UUID of the characteristic.
* @param idx The index of the characteristic to return (used when multiple characteristics have the same UUID).
* @return A pointer to the characteristic object or nullptr if not found.
*/
NimBLECharacteristic* NimBLEService::getCharacteristic(const char* uuid, uint16_t idx) const {
return getCharacteristic(NimBLEUUID(uuid), idx);
} // getCharacteristic
/**
* @brief Get a pointer to the characteristic object with the specified UUID.
* @param [in] uuid The UUID of the characteristic.
* @param instanceId The index of the characteristic to return (used when multiple characteristics have the same UUID).
* @param idx The index of the characteristic to return (used when multiple characteristics have the same UUID).
* @return A pointer to the characteristic object or nullptr if not found.
*/
NimBLECharacteristic* NimBLEService::getCharacteristic(const char* uuid, uint16_t instanceId) {
return getCharacteristic(NimBLEUUID(uuid), instanceId);
}
/**
* @brief Get a pointer to the characteristic object with the specified UUID.
* @param [in] uuid The UUID of the characteristic.
* @param instanceId The index of the characteristic to return (used when multiple characteristics have the same UUID).
* @return A pointer to the characteristic object or nullptr if not found.
*/
NimBLECharacteristic* NimBLEService::getCharacteristic(const NimBLEUUID &uuid, uint16_t instanceId) {
NimBLECharacteristic* NimBLEService::getCharacteristic(const NimBLEUUID& uuid, uint16_t idx) const {
uint16_t position = 0;
for (auto &it : m_chrVec) {
if (it->getUUID() == uuid) {
if (position == instanceId) {
return it;
for (const auto& chr : m_vChars) {
if (chr->getUUID() == uuid) {
if (position == idx) {
return chr;
}
position++;
}
}
return nullptr;
}
} // getCharacteristic
/**
* @brief Get a pointer to the characteristic object with the specified handle.
* @param handle The handle of the characteristic.
* @return A pointer to the characteristic object or nullptr if not found.
*/
NimBLECharacteristic *NimBLEService::getCharacteristicByHandle(uint16_t handle) {
for (auto &it : m_chrVec) {
if (it->getHandle() == handle) {
return it;
NimBLECharacteristic* NimBLEService::getCharacteristicByHandle(uint16_t handle) const {
for (const auto& chr : m_vChars) {
if (chr->getHandle() == handle) {
return chr;
}
}
return nullptr;
}
} // getCharacteristicByHandle
/**
* @return A vector containing pointers to each characteristic associated with this service.
*/
std::vector<NimBLECharacteristic *> NimBLEService::getCharacteristics() {
return m_chrVec;
}
const std::vector<NimBLECharacteristic*>& NimBLEService::getCharacteristics() const {
return m_vChars;
} // getCharacteristics
/**
* @return A vector containing pointers to each characteristic with the provided UUID associated with this service.
*/
std::vector<NimBLECharacteristic *> NimBLEService::getCharacteristics(const char *uuid) {
std::vector<NimBLECharacteristic*> NimBLEService::getCharacteristics(const char* uuid) const {
return getCharacteristics(NimBLEUUID(uuid));
}
} // getCharacteristics
/**
* @return A vector containing pointers to each characteristic with the provided UUID associated with this service.
*/
std::vector<NimBLECharacteristic *> NimBLEService::getCharacteristics(const NimBLEUUID &uuid) {
std::vector<NimBLECharacteristic*> NimBLEService::getCharacteristics(const NimBLEUUID& uuid) const {
std::vector<NimBLECharacteristic*> result;
for (auto &it : m_chrVec) {
if (it->getUUID() == uuid) {
result.push_back(it);
for (const auto& chr : m_vChars) {
if (chr->getUUID() == uuid) {
result.push_back(chr);
}
}
return result;
}
} // getCharacteristics
/**
* @brief Return a string representation of this service.
@ -414,22 +344,29 @@ std::vector<NimBLECharacteristic *> NimBLEService::getCharacteristics(const NimB
* * Its handle
* @return A string representation of this service.
*/
std::string NimBLEService::toString() {
std::string NimBLEService::toString() const {
std::string res = "UUID: " + getUUID().toString();
char hex[5];
char hex[5];
snprintf(hex, sizeof(hex), "%04x", getHandle());
res += ", handle: 0x";
res += hex;
return res;
} // toString
/**
* @brief Get the BLE server associated with this service.
* @return The BLEServer associated with this service.
*/
NimBLEServer* NimBLEService::getServer() {
NimBLEServer* NimBLEService::getServer() const {
return NimBLEDevice::getServer();
}// getServer
} // getServer
/**
* @brief Checks if the service has been started.
* @return True if the service has been started.
*/
bool NimBLEService::isStarted() const {
return m_pSvcDef->type > 0;
} // isStarted
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */

View File

@ -12,76 +12,59 @@
* Author: kolban
*/
#ifndef MAIN_NIMBLESERVICE_H_
#define MAIN_NIMBLESERVICE_H_
#ifndef NIMBLE_CPP_SERVICE_H_
#define NIMBLE_CPP_SERVICE_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#include "NimBLEServer.h"
#include "NimBLECharacteristic.h"
#include "NimBLEUUID.h"
class NimBLEServer;
class NimBLECharacteristic;
class NimBLEService;
# include "NimBLEAttribute.h"
# include "NimBLEServer.h"
# include "NimBLECharacteristic.h"
/**
* @brief The model of a %BLE service.
* @brief The model of a BLE service.
*
*/
class NimBLEService {
public:
class NimBLEService : public NimBLELocalAttribute {
public:
NimBLEService(const char* uuid);
NimBLEService(const NimBLEUUID &uuid);
NimBLEService(const NimBLEUUID& uuid);
~NimBLEService();
NimBLEServer* getServer();
NimBLEUUID getUUID();
uint16_t getHandle();
std::string toString();
void dump();
NimBLEServer* getServer() const;
std::string toString() const;
void dump() const;
bool isStarted() const;
bool start();
NimBLECharacteristic* createCharacteristic(const char* uuid,
uint32_t properties =
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);
NimBLECharacteristic* createCharacteristic(const NimBLEUUID &uuid,
uint32_t properties =
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);
uint32_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);
NimBLECharacteristic* createCharacteristic(const NimBLEUUID& uuid,
uint32_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);
void addCharacteristic(NimBLECharacteristic* pCharacteristic);
void removeCharacteristic(NimBLECharacteristic* pCharacteristic, bool deleteChr = false);
NimBLECharacteristic* getCharacteristic(const char* uuid, uint16_t instanceId = 0);
NimBLECharacteristic* getCharacteristic(const NimBLEUUID &uuid, uint16_t instanceId = 0);
NimBLECharacteristic* getCharacteristicByHandle(uint16_t handle);
NimBLECharacteristic* getCharacteristic(const char* uuid, uint16_t instanceId = 0) const;
NimBLECharacteristic* getCharacteristic(const NimBLEUUID& uuid, uint16_t instanceId = 0) const;
NimBLECharacteristic* getCharacteristicByHandle(uint16_t handle) const;
std::vector<NimBLECharacteristic*> getCharacteristics();
std::vector<NimBLECharacteristic*> getCharacteristics(const char* uuid);
std::vector<NimBLECharacteristic*> getCharacteristics(const NimBLEUUID &uuid);
const std::vector<NimBLECharacteristic*>& getCharacteristics() const;
std::vector<NimBLECharacteristic*> getCharacteristics(const char* uuid) const;
std::vector<NimBLECharacteristic*> getCharacteristics(const NimBLEUUID& uuid) const;
private:
friend class NimBLEServer;
private:
friend class NimBLEServer;
friend class NimBLEDevice;
uint16_t m_handle;
NimBLEUUID m_uuid;
ble_gatt_svc_def* m_pSvcDef;
uint8_t m_removed;
std::vector<NimBLECharacteristic*> m_chrVec;
std::vector<NimBLECharacteristic*> m_vChars{};
// Nimble requires an array of services to be sent to the api
// Since we are adding 1 at a time we create an array of 2 and set the type
// of the second service to 0 to indicate the end of the array.
ble_gatt_svc_def m_pSvcDef[2]{};
}; // NimBLEService
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */
#endif /* MAIN_NIMBLESERVICE_H_ */
#endif /* NIMBLE_CPP_SERVICE_H_ */

View File

@ -15,21 +15,33 @@
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEUtils.h"
#include "NimBLEUUID.h"
#include "NimBLELog.h"
# include "NimBLEUtils.h"
# include "NimBLEUUID.h"
# include "NimBLELog.h"
#include <algorithm>
/**** FIX COMPILATION ****/
# undef min
# undef max
/**************************/
static const char* LOG_TAG = "NimBLEUUID";
# include <algorithm>
static const char* LOG_TAG = "NimBLEUUID";
static const uint8_t ble_base_uuid[] = {
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
/**
* @brief Create a UUID from the native UUID.
* @param [in] uuid The native UUID.
*/
NimBLEUUID::NimBLEUUID(const ble_uuid_any_t& uuid) : m_uuid{uuid} {}
/**
* @brief Create a UUID from a string.
*
* Create a UUID from a string. There will be two possible stories here. Either the string represents
* a binary data field or the string represents a hex encoding of a UUID.
* For the hex encoding, here is an example:
* Create a UUID from a string. There will be two possible stories here. Either
* the string represents a binary data field or the string represents a hex
* encoding of a UUID. For the hex encoding, here is an example:
*
* ```
* "beb5483e-36e1-4688-b7f5-ea07361b26a8"
@ -41,104 +53,68 @@ static const char* LOG_TAG = "NimBLEUUID";
*
* @param [in] value The string to build a UUID from.
*/
NimBLEUUID::NimBLEUUID(const std::string &value) {
m_valueSet = true;
NimBLEUUID::NimBLEUUID(const std::string& value) {
if (value.length() == 4) {
m_uuid.u.type = BLE_UUID_TYPE_16;
m_uuid.u16.value = strtoul(value.c_str(), NULL, 16);
}
else if (value.length() == 8) {
m_uuid.u.type = BLE_UUID_TYPE_32;
m_uuid.u32.value = strtoul(value.c_str(), NULL, 16);
}
else if (value.length() == 16) {
*this = NimBLEUUID((uint8_t*)value.data(), 16, true);
}
else if (value.length() == 36) {
// If the length of the string is 36 bytes then we will assume it is a long hex string in
// UUID format.
char * position = const_cast<char *>(value.c_str());
uint32_t first = strtoul(position, &position, 16);
uint16_t second = strtoul(position + 1, &position, 16);
uint16_t third = strtoul(position + 1, &position, 16);
uint16_t fourth = strtoul(position + 1, &position, 16);
uint64_t fifth = strtoull(position + 1, NULL, 16);
*this = NimBLEUUID(first, second, third, (uint64_t(fourth) << 48) + fifth);
}
else {
m_valueSet = false;
m_uuid.u.type = BLE_UUID_TYPE_16;
m_uuid.u16.value = strtoul(value.c_str(), nullptr, 16);
} else if (value.length() == 8) {
m_uuid.u.type = BLE_UUID_TYPE_32;
m_uuid.u32.value = strtoul(value.c_str(), nullptr, 16);
} else if (value.length() == 16) {
memcpy(m_uuid.u128.value, &value[0], 16);
m_uuid.u.type = BLE_UUID_TYPE_128;
} else if (value.length() == 36) {
char* position;
uint64_t first_half = (strtoull(&value[0], &position, 16) << 32) +
(strtoull(position + 1, &position, 16) << 16) + strtoull(position + 1, &position, 16);
uint64_t second_half = (strtoull(position + 1, &position, 16) << 48) + strtoull(position + 1, nullptr, 16);
memcpy(m_uuid.u128.value + 8, &first_half, 8);
memcpy(m_uuid.u128.value, &second_half, 8);
m_uuid.u.type = BLE_UUID_TYPE_128;
} else {
NIMBLE_LOGE(LOG_TAG, "Invalid UUID length");
m_uuid.u.type = 0;
}
} // NimBLEUUID(std::string)
/**
* @brief Create a UUID from 2, 4, 16 bytes of memory.
* @param [in] pData The pointer to the start of the UUID.
* @param [in] size The size of the data.
* @param [in] msbFirst Is the MSB first in pData memory?
*/
NimBLEUUID::NimBLEUUID(const uint8_t* pData, size_t size, bool msbFirst) {
uint8_t *uuidValue = nullptr;
switch(size) {
case 2:
uuidValue = (uint8_t*)&m_uuid.u16.value;
m_uuid.u.type = BLE_UUID_TYPE_16;
break;
case 4:
uuidValue = (uint8_t*)&m_uuid.u32.value;
m_uuid.u.type = BLE_UUID_TYPE_32;
break;
case 16:
uuidValue = m_uuid.u128.value;
m_uuid.u.type = BLE_UUID_TYPE_128;
break;
default:
m_valueSet = false;
NIMBLE_LOGE(LOG_TAG, "Invalid UUID size");
return;
NimBLEUUID::NimBLEUUID(const uint8_t* pData, size_t size) {
if (ble_uuid_init_from_buf(&m_uuid, pData, size)) {
NIMBLE_LOGE(LOG_TAG, "Invalid UUID size");
m_uuid.u.type = 0;
}
if (msbFirst) {
std::reverse_copy(pData, pData + size, uuidValue);
} else {
memcpy(uuidValue, pData, size);
}
m_valueSet = true;
} // NimBLEUUID
} // NimBLEUUID(const uint8_t* pData, size_t size)
/**
* @brief Create a UUID from the 16bit value.
* @param [in] uuid The 16bit short form UUID.
*/
NimBLEUUID::NimBLEUUID(uint16_t uuid) {
m_uuid.u.type = BLE_UUID_TYPE_16;
m_uuid.u16.value = uuid;
m_valueSet = true;
} // NimBLEUUID
m_uuid.u.type = BLE_UUID_TYPE_16;
m_uuid.u16.value = uuid;
} // NimBLEUUID(uint16_t uuid)
/**
* @brief Create a UUID from the 32bit value.
* @param [in] uuid The 32bit short form UUID.
*/
NimBLEUUID::NimBLEUUID(uint32_t uuid) {
m_uuid.u.type = BLE_UUID_TYPE_32;
m_uuid.u32.value = uuid;
m_valueSet = true;
} // NimBLEUUID
m_uuid.u.type = BLE_UUID_TYPE_32;
m_uuid.u32.value = uuid;
} // NimBLEUUID(uint32_t uuid)
/**
* @brief Create a UUID from the native UUID.
* @param [in] uuid The native UUID.
*/
NimBLEUUID::NimBLEUUID(const ble_uuid128_t* uuid) {
m_uuid.u.type = BLE_UUID_TYPE_128;
m_uuid.u.type = BLE_UUID_TYPE_128;
memcpy(m_uuid.u128.value, uuid->value, 16);
m_valueSet = true;
} // NimBLEUUID
} // NimBLEUUID(const ble_uuid128_t* uuid)
/**
* @brief Create a UUID from the 128bit value using hex parts instead of string,
@ -151,32 +127,47 @@ NimBLEUUID::NimBLEUUID(const ble_uuid128_t* uuid) {
* @param [in] fourth The last 64bit of the UUID, combining the last 2 parts of the string equivalent
*/
NimBLEUUID::NimBLEUUID(uint32_t first, uint16_t second, uint16_t third, uint64_t fourth) {
m_uuid.u.type = BLE_UUID_TYPE_128;
memcpy(m_uuid.u128.value + 12, &first, 4);
m_uuid.u.type = BLE_UUID_TYPE_128;
memcpy(m_uuid.u128.value + 12, &first, 4);
memcpy(m_uuid.u128.value + 10, &second, 2);
memcpy(m_uuid.u128.value + 8, &third, 2);
memcpy(m_uuid.u128.value, &fourth, 8);
m_valueSet = true;
}
memcpy(m_uuid.u128.value + 8, &third, 2);
memcpy(m_uuid.u128.value, &fourth, 8);
} // NimBLEUUID(uint32_t first, uint16_t second, uint16_t third, uint64_t fourth)
/**
* @brief Creates an empty UUID.
*/
NimBLEUUID::NimBLEUUID() {
m_valueSet = false;
} // NimBLEUUID
/**
* @brief Get the number of bits in this uuid.
* @return The number of bits in the UUID. One of 16, 32 or 128.
* @brief Get the bit size of the UUID, 16, 32 or 128.
* @return The bit size of the UUID or 0 if not initialized.
*/
uint8_t NimBLEUUID::bitSize() const {
if (!m_valueSet) return 0;
return m_uuid.u.type;
return this->m_uuid.u.type;
} // bitSize
/**
* @brief Get the uuid value.
* @return A pointer to the UUID value or nullptr if not initialized.
* @note This should be checked with `bitSize()` before accessing.
*/
const uint8_t* NimBLEUUID::getValue() const {
switch (bitSize()) {
case BLE_UUID_TYPE_16:
return reinterpret_cast<const uint8_t*>(&m_uuid.u16.value);
case BLE_UUID_TYPE_32:
return reinterpret_cast<const uint8_t*>(&m_uuid.u32.value);
case BLE_UUID_TYPE_128:
return m_uuid.u128.value;
default:
return nullptr;
}
} // getValue
/**
* @brief Get a pointer to the NimBLE UUID base structure.
* @return A Read-only pointer to the NimBLE UUID base structure.
* @note The type value should be checked, if no 16, 32 or 128 then the UUID is not initialized.
*/
const ble_uuid_t* NimBLEUUID::getBase() const {
return &this->m_uuid.u;
} // getBase
/**
* @brief Compare a UUID against this UUID.
@ -184,10 +175,9 @@ uint8_t NimBLEUUID::bitSize() const {
* @param [in] uuid The UUID to compare against.
* @return True if the UUIDs are equal and false otherwise.
*/
bool NimBLEUUID::equals(const NimBLEUUID &uuid) const {
bool NimBLEUUID::equals(const NimBLEUUID& uuid) const {
return *this == uuid;
}
} // equals
/**
* Create a NimBLEUUID from a string of the form:
@ -200,14 +190,14 @@ bool NimBLEUUID::equals(const NimBLEUUID &uuid) const {
*
* @param [in] uuid The string to create the UUID from.
*/
NimBLEUUID NimBLEUUID::fromString(const std::string &uuid) {
NimBLEUUID NimBLEUUID::fromString(const std::string& uuid) {
uint8_t start = 0;
if (strstr(uuid.c_str(), "0x") != nullptr) { // If the string starts with 0x, skip those characters.
start = 2;
}
uint8_t len = uuid.length() - start; // Calculate the length of the string we are going to use.
if(len == 4) {
uint8_t len = uuid.length() - start; // Calculate the length of the string we are going to use.
if (len == 4) {
uint16_t x = strtoul(uuid.substr(start, len).c_str(), NULL, 16);
return NimBLEUUID(x);
} else if (len == 8) {
@ -216,47 +206,29 @@ NimBLEUUID NimBLEUUID::fromString(const std::string &uuid) {
} else if (len == 36) {
return NimBLEUUID(uuid);
}
return NimBLEUUID();
} // fromString
/**
* @brief Get the native UUID value.
* @return The native UUID value or nullptr if not set.
*/
const ble_uuid_any_t* NimBLEUUID::getNative() const {
if (m_valueSet == false) {
NIMBLE_LOGD(LOG_TAG,"<< Return of un-initialized UUID!");
return nullptr;
}
return &m_uuid;
} // getNative
/**
* @brief Convert a UUID to its 128 bit representation.
* @details A UUID can be internally represented as 16bit, 32bit or the full 128bit.
* This method will convert 16 or 32bit representations to the full 128bit.
* @return The NimBLEUUID converted to 128bit.
*/
const NimBLEUUID &NimBLEUUID::to128() {
const NimBLEUUID& NimBLEUUID::to128() {
// If we either don't have a value or are already a 128 bit UUID, nothing further to do.
if (!m_valueSet || m_uuid.u.type == BLE_UUID_TYPE_128) {
return *this;
}
// If we are 16 bit or 32 bit, then set the other bytes of the UUID.
if (m_uuid.u.type == BLE_UUID_TYPE_16) {
*this = NimBLEUUID(m_uuid.u16.value, 0x0000, 0x1000, 0x800000805f9b34fb);
}
else if (m_uuid.u.type == BLE_UUID_TYPE_32) {
*this = NimBLEUUID(m_uuid.u32.value, 0x0000, 0x1000, 0x800000805f9b34fb);
if (bitSize() != BLE_UUID_TYPE_128) {
uint32_t val = bitSize() == BLE_UUID_TYPE_16 ? *reinterpret_cast<const uint16_t*>(getValue())
: *reinterpret_cast<const uint32_t*>(getValue());
memcpy(m_uuid.u128.value, &ble_base_uuid, sizeof(ble_base_uuid) - 4);
memcpy(m_uuid.u128.value + 12, &val, 4);
m_uuid.u.type = BLE_UUID_TYPE_128;
}
return *this;
} // to128
/**
* @brief Convert 128 bit UUID to its 16 bit representation.
* @details A UUID can be internally represented as 16bit, 32bit or the full 128bit.
@ -264,21 +236,16 @@ const NimBLEUUID &NimBLEUUID::to128() {
* @return The NimBLEUUID converted to 16bit if successful, otherwise the original uuid.
*/
const NimBLEUUID& NimBLEUUID::to16() {
if (!m_valueSet || m_uuid.u.type == BLE_UUID_TYPE_16) {
return *this;
}
if (m_uuid.u.type == BLE_UUID_TYPE_128) {
uint8_t base128[] = {0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00,
0x00, 0x80, 0x00, 0x10, 0x00, 0x00};
if (memcmp(m_uuid.u128.value, base128, sizeof(base128)) == 0 ) {
*this = NimBLEUUID(*(uint16_t*)(m_uuid.u128.value + 12));
if (bitSize() == BLE_UUID_TYPE_128) {
const uint8_t* val = getValue();
if (memcmp(val, ble_base_uuid, sizeof(ble_base_uuid) - 4) == 0) {
m_uuid.u16.value = *reinterpret_cast<const uint16_t*>(val + 12);
m_uuid.u.type = BLE_UUID_TYPE_16;
}
}
return *this;
}
} // to16
/**
* @brief Get a string representation of the UUID.
@ -295,66 +262,76 @@ std::string NimBLEUUID::toString() const {
return std::string(*this);
} // toString
/**
* @brief Convienience operator to check if this UUID is equal to another.
* @brief Reverse the byte order of the UUID.
* @return The NimBLEUUID with the byte order reversed.
* @details This is useful when comparing UUIDs or when the advertisement data is reversed.
*/
bool NimBLEUUID::operator ==(const NimBLEUUID & rhs) const {
if(m_valueSet && rhs.m_valueSet) {
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;
const NimBLEUUID& NimBLEUUID::reverseByteOrder() {
if (bitSize() == BLE_UUID_TYPE_128) {
std::reverse(m_uuid.u128.value, m_uuid.u128.value + 16);
} else if (bitSize() == BLE_UUID_TYPE_32) {
m_uuid.u32.value = __builtin_bswap32(m_uuid.u32.value);
} else if (bitSize() == BLE_UUID_TYPE_16) {
m_uuid.u16.value = __builtin_bswap16(m_uuid.u16.value);
}
return m_valueSet == rhs.m_valueSet;
}
return *this;
} // reverseByteOrder
/**
* @brief Convienience operator to check if this UUID is not equal to another.
* @brief Convenience operator to check if this UUID is equal to another.
*/
bool NimBLEUUID::operator !=(const NimBLEUUID & rhs) const {
return !this->operator==(rhs);
}
bool NimBLEUUID::operator==(const NimBLEUUID& rhs) const {
if (!this->bitSize() || !rhs.bitSize()) {
return false;
}
if (this->bitSize() != rhs.bitSize()) {
uint8_t uuid128[sizeof(ble_base_uuid)];
memcpy(uuid128, &ble_base_uuid, sizeof(ble_base_uuid));
if (this->bitSize() == BLE_UUID_TYPE_128) {
memcpy(uuid128 + 12, rhs.getValue(), rhs.bitSize() == BLE_UUID_TYPE_16 ? 2 : 4);
return memcmp(this->getValue(), uuid128, sizeof(ble_base_uuid)) == 0;
} else if (rhs.bitSize() == BLE_UUID_TYPE_128) {
memcpy(uuid128 + 12, getValue(), this->bitSize() == BLE_UUID_TYPE_16 ? 2 : 4);
return memcmp(rhs.getValue(), uuid128, sizeof(ble_base_uuid)) == 0;
} else {
return false;
}
}
if (this->bitSize() == BLE_UUID_TYPE_16) {
return *reinterpret_cast<const uint16_t*>(this->getValue()) == *reinterpret_cast<const uint16_t*>(rhs.getValue());
}
if (this->bitSize() == BLE_UUID_TYPE_32) {
return *reinterpret_cast<const uint32_t*>(this->getValue()) == *reinterpret_cast<const uint32_t*>(rhs.getValue());
}
if (this->bitSize() == BLE_UUID_TYPE_128) {
return memcmp(this->getValue(), rhs.getValue(), 16) == 0;
}
return false;
} // operator==
/**
* @brief Convienience operator to convert this UUID to string representation.
* @brief Convenience operator to check if this UUID is not equal to another.
*/
bool NimBLEUUID::operator!=(const NimBLEUUID& rhs) const {
return !this->operator==(rhs);
} // operator!=
/**
* @brief Convenience operator to convert this UUID to string representation.
* @details This allows passing NimBLEUUID to functions
* that accept std::string and/or or it's methods as a parameter.
*/
NimBLEUUID::operator std::string() const {
if (!m_valueSet) return std::string(); // If we have no value, nothing to format.
char buf[BLE_UUID_STR_LEN];
return ble_uuid_to_str(&m_uuid.u, buf);
}
} // operator std::string
#endif /* CONFIG_BT_ENABLED */
# endif /* CONFIG_BT_ENABLED */

View File

@ -12,53 +12,55 @@
* Author: kolban
*/
#ifndef COMPONENTS_NIMBLEUUID_H_
#define COMPONENTS_NIMBLEUUID_H_
#ifndef NIMBLE_CPP_UUID_H_
#define NIMBLE_CPP_UUID_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
#if defined(CONFIG_NIMBLE_CPP_IDF)
#include "host/ble_uuid.h"
#else
#include "nimble/nimble/host/include/host/ble_uuid.h"
#endif
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_uuid.h"
# else
# include "nimble/nimble/host/include/host/ble_uuid.h"
# endif
/**** FIX COMPILATION ****/
#undef min
#undef max
/**************************/
#include <string>
# include <string>
# include <cstring>
/**
* @brief A model of a %BLE UUID.
*/
class NimBLEUUID {
public:
NimBLEUUID(const std::string &uuid);
public:
/**
* @brief Created a blank UUID.
*/
NimBLEUUID() = default;
NimBLEUUID(const ble_uuid_any_t& uuid);
NimBLEUUID(const std::string& uuid);
NimBLEUUID(uint16_t uuid);
NimBLEUUID(uint32_t uuid);
NimBLEUUID(const ble_uuid128_t* uuid);
NimBLEUUID(const uint8_t* pData, size_t size, bool msbFirst);
NimBLEUUID(const uint8_t* pData, size_t size);
NimBLEUUID(uint32_t first, uint16_t second, uint16_t third, uint64_t fourth);
NimBLEUUID();
uint8_t bitSize() const;
bool equals(const NimBLEUUID &uuid) const;
const ble_uuid_any_t* getNative() const;
const NimBLEUUID & to128();
const NimBLEUUID& to16();
std::string toString() const;
static NimBLEUUID fromString(const std::string &uuid);
uint8_t bitSize() const;
const uint8_t* getValue() const;
const ble_uuid_t* getBase() const;
bool equals(const NimBLEUUID& uuid) const;
std::string toString() const;
static NimBLEUUID fromString(const std::string& uuid);
const NimBLEUUID& to128();
const NimBLEUUID& to16();
const NimBLEUUID& reverseByteOrder();
bool operator ==(const NimBLEUUID & rhs) const;
bool operator !=(const NimBLEUUID & rhs) const;
operator std::string() const;
bool operator==(const NimBLEUUID& rhs) const;
bool operator!=(const NimBLEUUID& rhs) const;
operator std::string() const;
private:
ble_uuid_any_t m_uuid;
bool m_valueSet = false;
private:
ble_uuid_any_t m_uuid{};
}; // NimBLEUUID
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_NIMBLEUUID_H_ */
#endif /* NIMBLE_CPP_UUID_H_ */

View File

@ -9,51 +9,112 @@
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEUtils.h"
#include "NimBLELog.h"
# include "NimBLEUtils.h"
# include "NimBLEAddress.h"
# include "NimBLELog.h"
#include <stdlib.h>
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_hs.h"
# else
# include "nimble/nimble/host/include/host/ble_hs.h"
# endif
/**** FIX COMPILATION ****/
# undef min
# undef max
/**************************/
# include <stdlib.h>
# include <climits>
# if defined INC_FREERTOS_H
constexpr uint32_t TASK_BLOCK_BIT = (1 << CONFIG_NIMBLE_CPP_FREERTOS_TASK_BLOCK_BIT);
# endif
static const char* LOG_TAG = "NimBLEUtils";
/**
* @brief Construct a NimBLETaskData instance.
* @param [in] pInstance An instance of the class that will be waiting.
* @param [in] flags General purpose flags for the caller.
* @param [in] buf A buffer for data.
*/
NimBLETaskData::NimBLETaskData(void* pInstance, int flags, void* buf)
: m_pInstance{pInstance},
m_flags{flags},
m_pBuf{buf}
# if defined INC_FREERTOS_H
,
m_pHandle{xTaskGetCurrentTaskHandle()} {
}
# else
{
ble_npl_sem* sem = new ble_npl_sem;
if (ble_npl_sem_init(sem, 0) != BLE_NPL_OK) {
NIMBLE_LOGE(LOG_TAG, "Failed to init semaphore");
delete sem;
m_pHandle = nullptr;
} else {
m_pHandle = sem;
}
}
# endif
/**
* @brief A function for checking validity of connection parameters.
* @param [in] params A pointer to the structure containing the parameters to check.
* @return valid == 0 or error code.
* @brief Destructor.
*/
int NimBLEUtils::checkConnParams(ble_gap_conn_params* params) {
/* Check connection interval min */
if ((params->itvl_min < BLE_HCI_CONN_ITVL_MIN) ||
(params->itvl_min > BLE_HCI_CONN_ITVL_MAX)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
NimBLETaskData::~NimBLETaskData() {
# if !defined INC_FREERTOS_H
if (m_pHandle != nullptr) {
ble_npl_sem_deinit(static_cast<ble_npl_sem*>(m_pHandle));
delete static_cast<ble_npl_sem*>(m_pHandle);
}
/* Check connection interval max */
if ((params->itvl_max < BLE_HCI_CONN_ITVL_MIN) ||
(params->itvl_max > BLE_HCI_CONN_ITVL_MAX) ||
(params->itvl_max < params->itvl_min)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
/* Check connection latency */
if (params->latency > BLE_HCI_CONN_LATENCY_MAX) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
/* Check supervision timeout */
if ((params->supervision_timeout < BLE_HCI_CONN_SPVN_TIMEOUT_MIN) ||
(params->supervision_timeout > BLE_HCI_CONN_SPVN_TIMEOUT_MAX)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
/* Check connection event length */
if (params->min_ce_len > params->max_ce_len) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
return 0;
# endif
}
/**
* @brief Blocks the calling task until released or timeout.
* @param [in] taskData A pointer to the task data structure.
* @param [in] timeout The time to wait in milliseconds.
* @return True if the task completed, false if the timeout was reached.
*/
bool NimBLEUtils::taskWait(const NimBLETaskData& taskData, uint32_t timeout) {
ble_npl_time_t ticks;
if (timeout == BLE_NPL_TIME_FOREVER) {
ticks = BLE_NPL_TIME_FOREVER;
} else {
ble_npl_time_ms_to_ticks(timeout, &ticks);
}
# if defined INC_FREERTOS_H
uint32_t notificationValue;
xTaskNotifyWait(0, TASK_BLOCK_BIT, &notificationValue, 0);
if (notificationValue & TASK_BLOCK_BIT) {
return true;
}
return xTaskNotifyWait(0, TASK_BLOCK_BIT, nullptr, ticks) == pdTRUE;
# else
return ble_npl_sem_pend(static_cast<ble_npl_sem*>(taskData.m_pHandle), ticks) == BLE_NPL_OK;
# endif
} // taskWait
/**
* @brief Release a task.
* @param [in] taskData A pointer to the task data structure.
* @param [in] flags A return value to set in the task data structure.
*/
void NimBLEUtils::taskRelease(const NimBLETaskData& taskData, int flags) {
taskData.m_flags = flags;
if (taskData.m_pHandle != nullptr) {
# if defined INC_FREERTOS_H
xTaskNotify(static_cast<TaskHandle_t>(taskData.m_pHandle), TASK_BLOCK_BIT, eSetBits);
# else
ble_npl_sem_release(static_cast<ble_npl_sem*>(taskData.m_pHandle));
# endif
}
} // taskRelease
/**
* @brief Converts a return code from the NimBLE stack to a text string.
@ -61,458 +122,445 @@ int NimBLEUtils::checkConnParams(ble_gap_conn_params* params) {
* @return A string representation of the return code.
*/
const char* NimBLEUtils::returnCodeToString(int rc) {
#if defined(CONFIG_NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT)
switch(rc) {
# if defined(CONFIG_NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT)
switch (rc) {
case 0:
return "SUCCESS";
case BLE_HS_EAGAIN:
return "Temporary failure; try again.";
return "Temporary failure; try again";
case BLE_HS_EALREADY:
return "Operation already in progress or completed.";
return "Operation already in progress or complete";
case BLE_HS_EINVAL:
return "One or more arguments are invalid.";
return "One or more arguments are invalid";
case BLE_HS_EMSGSIZE:
return "The provided buffer is too small.";
return "Buffer too small";
case BLE_HS_ENOENT:
return "No entry matching the specified criteria.";
return "No matching entry found";
case BLE_HS_ENOMEM:
return "Operation failed due to resource exhaustion.";
return "Not enough memory";
case BLE_HS_ENOTCONN:
return "No open connection with the specified handle.";
return "No open connection with handle";
case BLE_HS_ENOTSUP:
return "Operation disabled at compile time.";
return "Operation disabled at compile time";
case BLE_HS_EAPP:
return "Application callback behaved unexpectedly.";
return "Operation canceled by app";
case BLE_HS_EBADDATA:
return "Command from peer is invalid.";
return "Invalid command from peer";
case BLE_HS_EOS:
return "Mynewt OS error.";
return "OS error";
case BLE_HS_ECONTROLLER:
return "Event from controller is invalid.";
return "Controller error";
case BLE_HS_ETIMEOUT:
return "Operation timed out.";
return "Operation timed out";
case BLE_HS_EDONE:
return "Operation completed successfully.";
return "Operation completed successfully";
case BLE_HS_EBUSY:
return "Operation cannot be performed until procedure completes.";
return "Busy";
case BLE_HS_EREJECT:
return "Peer rejected a connection parameter update request.";
return "Peer rejected request";
case BLE_HS_EUNKNOWN:
return "Unexpected failure; catch all.";
return "Unknown failure";
case BLE_HS_EROLE:
return "Operation requires different role (e.g., central vs. peripheral).";
return "Operation requires different role";
case BLE_HS_ETIMEOUT_HCI:
return "HCI request timed out; controller unresponsive.";
return "HCI request timed out";
case BLE_HS_ENOMEM_EVT:
return "Controller failed to send event due to memory exhaustion (combined host-controller only).";
return "Controller error; not enough memory";
case BLE_HS_ENOADDR:
return "Operation requires an identity address but none configured.";
return "No identity address";
case BLE_HS_ENOTSYNCED:
return "Attempt to use the host before it is synced with controller.";
return "Host not synced with controller";
case BLE_HS_EAUTHEN:
return "Insufficient authentication.";
return "Insufficient authentication";
case BLE_HS_EAUTHOR:
return "Insufficient authorization.";
return "Insufficient authorization";
case BLE_HS_EENCRYPT:
return "Insufficient encryption level.";
return "Insufficient encryption level";
case BLE_HS_EENCRYPT_KEY_SZ:
return "Insufficient key size.";
return "Insufficient key size";
case BLE_HS_ESTORE_CAP:
return "Storage at capacity.";
return "Storage at capacity";
case BLE_HS_ESTORE_FAIL:
return "Storage IO error.";
case (0x0100+BLE_ATT_ERR_INVALID_HANDLE ):
return "The attribute handle given was not valid on this server.";
case (0x0100+BLE_ATT_ERR_READ_NOT_PERMITTED ):
return "The attribute cannot be read.";
case (0x0100+BLE_ATT_ERR_WRITE_NOT_PERMITTED ):
return "The attribute cannot be written.";
case (0x0100+BLE_ATT_ERR_INVALID_PDU ):
return "The attribute PDU was invalid.";
case (0x0100+BLE_ATT_ERR_INSUFFICIENT_AUTHEN ):
return "The attribute requires authentication before it can be read or written.";
case (0x0100+BLE_ATT_ERR_REQ_NOT_SUPPORTED ):
return "Attribute server does not support the request received from the client.";
case (0x0100+BLE_ATT_ERR_INVALID_OFFSET ):
return "Offset specified was past the end of the attribute.";
case (0x0100+BLE_ATT_ERR_INSUFFICIENT_AUTHOR ):
return "The attribute requires authorization before it can be read or written.";
case (0x0100+BLE_ATT_ERR_PREPARE_QUEUE_FULL ):
return "Too many prepare writes have been queued.";
case (0x0100+BLE_ATT_ERR_ATTR_NOT_FOUND ):
return "No attribute found within the given attribute handle range.";
case (0x0100+BLE_ATT_ERR_ATTR_NOT_LONG ):
return "The attribute cannot be read or written using the Read Blob Request.";
case (0x0100+BLE_ATT_ERR_INSUFFICIENT_KEY_SZ ):
return "The Encryption Key Size used for encrypting this link is insufficient.";
case (0x0100+BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN ):
return "The attribute value length is invalid for the operation.";
case (0x0100+BLE_ATT_ERR_UNLIKELY ):
return "The attribute request has encountered an error that was unlikely, could not be completed as requested.";
case (0x0100+BLE_ATT_ERR_INSUFFICIENT_ENC ):
return "The attribute requires encryption before it can be read or written.";
case (0x0100+BLE_ATT_ERR_UNSUPPORTED_GROUP ):
return "The attribute type is not a supported grouping attribute as defined by a higher layer specification.";
case (0x0100+BLE_ATT_ERR_INSUFFICIENT_RES ):
return "Insufficient Resources to complete the request.";
case (0x0200+BLE_ERR_UNKNOWN_HCI_CMD ):
return "Storage IO error";
case BLE_HS_EPREEMPTED:
return "Host preempted";
case BLE_HS_EDISABLED:
return "Host disabled";
case BLE_HS_ESTALLED:
return "CoC module is stalled";
case (BLE_HS_ERR_ATT_BASE + BLE_ATT_ERR_INVALID_HANDLE):
return "Invalid attribute handle";
case (BLE_HS_ERR_ATT_BASE + BLE_ATT_ERR_READ_NOT_PERMITTED):
return "The attribute cannot be read";
case (BLE_HS_ERR_ATT_BASE + BLE_ATT_ERR_WRITE_NOT_PERMITTED):
return "The attribute cannot be written";
case (BLE_HS_ERR_ATT_BASE + BLE_ATT_ERR_INVALID_PDU):
return "Invalid attribute PDU";
case (BLE_HS_ERR_ATT_BASE + BLE_ATT_ERR_INSUFFICIENT_AUTHEN):
return "Insufficient authentication";
case (BLE_HS_ERR_ATT_BASE + BLE_ATT_ERR_REQ_NOT_SUPPORTED):
return "Unsupported request";
case (BLE_HS_ERR_ATT_BASE + BLE_ATT_ERR_INVALID_OFFSET):
return "Invalid offset";
case (BLE_HS_ERR_ATT_BASE + BLE_ATT_ERR_INSUFFICIENT_AUTHOR):
return "Insufficient authorization";
case (BLE_HS_ERR_ATT_BASE + BLE_ATT_ERR_PREPARE_QUEUE_FULL):
return "Prepare write queue full";
case (BLE_HS_ERR_ATT_BASE + BLE_ATT_ERR_ATTR_NOT_FOUND):
return "Attribute not found";
case (BLE_HS_ERR_ATT_BASE + BLE_ATT_ERR_ATTR_NOT_LONG):
return "Long read not supported";
case (BLE_HS_ERR_ATT_BASE + BLE_ATT_ERR_INSUFFICIENT_KEY_SZ):
return "Insufficient encryption key size";
case (BLE_HS_ERR_ATT_BASE + BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN):
return "Invalid attribute value length";
case (BLE_HS_ERR_ATT_BASE + BLE_ATT_ERR_UNLIKELY):
return "Unlikely attribute error";
case (BLE_HS_ERR_ATT_BASE + BLE_ATT_ERR_INSUFFICIENT_ENC):
return "Insufficient encryption";
case (BLE_HS_ERR_ATT_BASE + BLE_ATT_ERR_UNSUPPORTED_GROUP):
return "Not a supported grouping attribute type";
case (BLE_HS_ERR_ATT_BASE + BLE_ATT_ERR_INSUFFICIENT_RES):
return "Insufficient Resources";
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_UNKNOWN_HCI_CMD):
return "Unknown HCI Command";
case (0x0200+BLE_ERR_UNK_CONN_ID ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_UNK_CONN_ID):
return "Unknown Connection Identifier";
case (0x0200+BLE_ERR_HW_FAIL ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_HW_FAIL):
return "Hardware Failure";
case (0x0200+BLE_ERR_PAGE_TMO ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_PAGE_TMO):
return "Page Timeout";
case (0x0200+BLE_ERR_AUTH_FAIL ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_AUTH_FAIL):
return "Authentication Failure";
case (0x0200+BLE_ERR_PINKEY_MISSING ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_PINKEY_MISSING):
return "PIN or Key Missing";
case (0x0200+BLE_ERR_MEM_CAPACITY ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_MEM_CAPACITY):
return "Memory Capacity Exceeded";
case (0x0200+BLE_ERR_CONN_SPVN_TMO ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_CONN_SPVN_TMO):
return "Connection Timeout";
case (0x0200+BLE_ERR_CONN_LIMIT ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_CONN_LIMIT):
return "Connection Limit Exceeded";
case (0x0200+BLE_ERR_SYNCH_CONN_LIMIT ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_SYNCH_CONN_LIMIT):
return "Synchronous Connection Limit To A Device Exceeded";
case (0x0200+BLE_ERR_ACL_CONN_EXISTS ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_ACL_CONN_EXISTS):
return "ACL Connection Already Exists";
case (0x0200+BLE_ERR_CMD_DISALLOWED ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_CMD_DISALLOWED):
return "Command Disallowed";
case (0x0200+BLE_ERR_CONN_REJ_RESOURCES ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_CONN_REJ_RESOURCES):
return "Connection Rejected due to Limited Resources";
case (0x0200+BLE_ERR_CONN_REJ_SECURITY ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_CONN_REJ_SECURITY):
return "Connection Rejected Due To Security Reasons";
case (0x0200+BLE_ERR_CONN_REJ_BD_ADDR ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_CONN_REJ_BD_ADDR):
return "Connection Rejected due to Unacceptable BD_ADDR";
case (0x0200+BLE_ERR_CONN_ACCEPT_TMO ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_CONN_ACCEPT_TMO):
return "Connection Accept Timeout Exceeded";
case (0x0200+BLE_ERR_UNSUPPORTED ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_UNSUPPORTED):
return "Unsupported Feature or Parameter Value";
case (0x0200+BLE_ERR_INV_HCI_CMD_PARMS ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_INV_HCI_CMD_PARMS):
return "Invalid HCI Command Parameters";
case (0x0200+BLE_ERR_REM_USER_CONN_TERM ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_REM_USER_CONN_TERM):
return "Remote User Terminated Connection";
case (0x0200+BLE_ERR_RD_CONN_TERM_RESRCS ):
return "Remote Device Terminated Connection due to Low Resources";
case (0x0200+BLE_ERR_RD_CONN_TERM_PWROFF ):
return "Remote Device Terminated Connection due to Power Off";
case (0x0200+BLE_ERR_CONN_TERM_LOCAL ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_RD_CONN_TERM_RESRCS):
return "Remote Device Terminated Connection; Low Resources";
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_RD_CONN_TERM_PWROFF):
return "Remote Device Terminated Connection; Power Off";
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_CONN_TERM_LOCAL):
return "Connection Terminated By Local Host";
case (0x0200+BLE_ERR_REPEATED_ATTEMPTS ):
return "Repeated Attempts";
case (0x0200+BLE_ERR_NO_PAIRING ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_REPEATED_ATTEMPTS):
return "Repeated Pairing Attempts";
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_NO_PAIRING):
return "Pairing Not Allowed";
case (0x0200+BLE_ERR_UNK_LMP ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_UNK_LMP):
return "Unknown LMP PDU";
case (0x0200+BLE_ERR_UNSUPP_REM_FEATURE ):
return "Unsupported Remote Feature / Unsupported LMP Feature";
case (0x0200+BLE_ERR_SCO_OFFSET ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_UNSUPP_REM_FEATURE):
return "Unsupported Remote Feature";
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_SCO_OFFSET):
return "SCO Offset Rejected";
case (0x0200+BLE_ERR_SCO_ITVL ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_SCO_ITVL):
return "SCO Interval Rejected";
case (0x0200+BLE_ERR_SCO_AIR_MODE ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_SCO_AIR_MODE):
return "SCO Air Mode Rejected";
case (0x0200+BLE_ERR_INV_LMP_LL_PARM ):
return "Invalid LMP Parameters / Invalid LL Parameters";
case (0x0200+BLE_ERR_UNSPECIFIED ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_INV_LMP_LL_PARM):
return "Invalid LL Parameters";
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_UNSPECIFIED):
return "Unspecified Error";
case (0x0200+BLE_ERR_UNSUPP_LMP_LL_PARM ):
return "Unsupported LMP Parameter Value / Unsupported LL Parameter Value";
case (0x0200+BLE_ERR_NO_ROLE_CHANGE ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_UNSUPP_LMP_LL_PARM):
return "Unsupported LL Parameter Value";
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_NO_ROLE_CHANGE):
return "Role Change Not Allowed";
case (0x0200+BLE_ERR_LMP_LL_RSP_TMO ):
return "LMP Response Timeout / LL Response Timeout";
case (0x0200+BLE_ERR_LMP_COLLISION ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_LMP_LL_RSP_TMO):
return "LL Response Timeout";
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_LMP_COLLISION):
return "LMP Error Transaction Collision";
case (0x0200+BLE_ERR_LMP_PDU ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_LMP_PDU):
return "LMP PDU Not Allowed";
case (0x0200+BLE_ERR_ENCRYPTION_MODE ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_ENCRYPTION_MODE):
return "Encryption Mode Not Acceptable";
case (0x0200+BLE_ERR_LINK_KEY_CHANGE ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_LINK_KEY_CHANGE):
return "Link Key cannot be Changed";
case (0x0200+BLE_ERR_UNSUPP_QOS ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_UNSUPP_QOS):
return "Requested QoS Not Supported";
case (0x0200+BLE_ERR_INSTANT_PASSED ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_INSTANT_PASSED):
return "Instant Passed";
case (0x0200+BLE_ERR_UNIT_KEY_PAIRING ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_UNIT_KEY_PAIRING):
return "Pairing With Unit Key Not Supported";
case (0x0200+BLE_ERR_DIFF_TRANS_COLL ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_DIFF_TRANS_COLL):
return "Different Transaction Collision";
case (0x0200+BLE_ERR_QOS_PARM ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_QOS_PARM):
return "QoS Unacceptable Parameter";
case (0x0200+BLE_ERR_QOS_REJECTED ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_QOS_REJECTED):
return "QoS Rejected";
case (0x0200+BLE_ERR_CHAN_CLASS ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_CHAN_CLASS):
return "Channel Classification Not Supported";
case (0x0200+BLE_ERR_INSUFFICIENT_SEC ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_INSUFFICIENT_SEC):
return "Insufficient Security";
case (0x0200+BLE_ERR_PARM_OUT_OF_RANGE ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_PARM_OUT_OF_RANGE):
return "Parameter Out Of Mandatory Range";
case (0x0200+BLE_ERR_PENDING_ROLE_SW ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_PENDING_ROLE_SW):
return "Role Switch Pending";
case (0x0200+BLE_ERR_RESERVED_SLOT ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_RESERVED_SLOT):
return "Reserved Slot Violation";
case (0x0200+BLE_ERR_ROLE_SW_FAIL ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_ROLE_SW_FAIL):
return "Role Switch Failed";
case (0x0200+BLE_ERR_INQ_RSP_TOO_BIG ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_INQ_RSP_TOO_BIG):
return "Extended Inquiry Response Too Large";
case (0x0200+BLE_ERR_SEC_SIMPLE_PAIR ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_SEC_SIMPLE_PAIR):
return "Secure Simple Pairing Not Supported By Host";
case (0x0200+BLE_ERR_HOST_BUSY_PAIR ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_HOST_BUSY_PAIR):
return "Host Busy - Pairing";
case (0x0200+BLE_ERR_CONN_REJ_CHANNEL ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_CONN_REJ_CHANNEL):
return "Connection Rejected, No Suitable Channel Found";
case (0x0200+BLE_ERR_CTLR_BUSY ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_CTLR_BUSY):
return "Controller Busy";
case (0x0200+BLE_ERR_CONN_PARMS ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_CONN_PARMS):
return "Unacceptable Connection Parameters";
case (0x0200+BLE_ERR_DIR_ADV_TMO ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_DIR_ADV_TMO):
return "Directed Advertising Timeout";
case (0x0200+BLE_ERR_CONN_TERM_MIC ):
return "Connection Terminated due to MIC Failure";
case (0x0200+BLE_ERR_CONN_ESTABLISHMENT ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_CONN_TERM_MIC):
return "Connection Terminated; MIC Failure";
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_CONN_ESTABLISHMENT):
return "Connection Failed to be Established";
case (0x0200+BLE_ERR_MAC_CONN_FAIL ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_MAC_CONN_FAIL):
return "MAC Connection Failed";
case (0x0200+BLE_ERR_COARSE_CLK_ADJ ):
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_COARSE_CLK_ADJ):
return "Coarse Clock Adjustment Rejected";
case (0x0300+BLE_L2CAP_SIG_ERR_CMD_NOT_UNDERSTOOD ):
return "Invalid or unsupported incoming L2CAP sig command.";
case (0x0300+BLE_L2CAP_SIG_ERR_MTU_EXCEEDED ):
return "Incoming packet too large.";
case (0x0300+BLE_L2CAP_SIG_ERR_INVALID_CID ):
return "No channel with specified ID.";
case (0x0400+BLE_SM_ERR_PASSKEY ):
return "The user input of passkey failed, for example, the user cancelled the operation.";
case (0x0400+BLE_SM_ERR_OOB ):
return "The OOB data is not available.";
case (0x0400+BLE_SM_ERR_AUTHREQ ):
return "The pairing procedure cannot be performed as authentication requirements cannot be met due to IO capabilities of one or both devices.";
case (0x0400+BLE_SM_ERR_CONFIRM_MISMATCH ):
return "The confirm value does not match the calculated compare value.";
case (0x0400+BLE_SM_ERR_PAIR_NOT_SUPP ):
return "Pairing is not supported by the device.";
case (0x0400+BLE_SM_ERR_ENC_KEY_SZ ):
return "The resultant encryption key size is insufficient for the security requirements of this device.";
case (0x0400+BLE_SM_ERR_CMD_NOT_SUPP ):
return "The SMP command received is not supported on this device.";
case (0x0400+BLE_SM_ERR_UNSPECIFIED ):
return "Pairing failed due to an unspecified reason.";
case (0x0400+BLE_SM_ERR_REPEATED ):
return "Pairing or authentication procedure disallowed, too little time has elapsed since last pairing request or security request.";
case (0x0400+BLE_SM_ERR_INVAL ):
return "Command length is invalid or that a parameter is outside of the specified range.";
case (0x0400+BLE_SM_ERR_DHKEY ):
return "DHKey Check value received doesn't match the one calculated by the local device.";
case (0x0400+BLE_SM_ERR_NUMCMP ):
return "Confirm values in the numeric comparison protocol do not match.";
case (0x0400+BLE_SM_ERR_ALREADY ):
return "Pairing over the LE transport failed - Pairing Request sent over the BR/EDR transport in process.";
case (0x0400+BLE_SM_ERR_CROSS_TRANS ):
return "BR/EDR Link Key generated on the BR/EDR transport cannot be used to derive and distribute keys for the LE transport.";
case (0x0500+BLE_SM_ERR_PASSKEY ):
return "The user input of passkey failed or the user cancelled the operation.";
case (0x0500+BLE_SM_ERR_OOB ):
return "The OOB data is not available.";
case (0x0500+BLE_SM_ERR_AUTHREQ ):
return "The pairing procedure cannot be performed as authentication requirements cannot be met due to IO capabilities of one or both devices.";
case (0x0500+BLE_SM_ERR_CONFIRM_MISMATCH ):
return "The confirm value does not match the calculated compare value.";
case (0x0500+BLE_SM_ERR_PAIR_NOT_SUPP ):
return "Pairing is not supported by the device.";
case (0x0500+BLE_SM_ERR_ENC_KEY_SZ ):
return "The resultant encryption key size is insufficient for the security requirements of this device.";
case (0x0500+BLE_SM_ERR_CMD_NOT_SUPP ):
return "The SMP command received is not supported on this device.";
case (0x0500+BLE_SM_ERR_UNSPECIFIED ):
return "Pairing failed due to an unspecified reason.";
case (0x0500+BLE_SM_ERR_REPEATED ):
return "Pairing or authentication procedure is disallowed because too little time has elapsed since last pairing request or security request.";
case (0x0500+BLE_SM_ERR_INVAL ):
return "Command length is invalid or a parameter is outside of the specified range.";
case (0x0500+BLE_SM_ERR_DHKEY ):
return "Indicates to the remote device that the DHKey Check value received doesnt match the one calculated by the local device.";
case (0x0500+BLE_SM_ERR_NUMCMP ):
return "Confirm values in the numeric comparison protocol do not match.";
case (0x0500+BLE_SM_ERR_ALREADY ):
return "Pairing over the LE transport failed - Pairing Request sent over the BR/EDR transport in process.";
case (0x0500+BLE_SM_ERR_CROSS_TRANS ):
return "BR/EDR Link Key generated on the BR/EDR transport cannot be used to derive and distribute keys for the LE transport.";
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_TYPE0_SUBMAP_NDEF):
return "Type0 Submap Not Defined";
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_UNK_ADV_INDENT):
return "Unknown Advertising Identifier";
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_LIMIT_REACHED):
return "Limit Reached";
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_OPERATION_CANCELLED):
return "Operation Cancelled by Host";
case (BLE_HS_ERR_HCI_BASE + BLE_ERR_PACKET_TOO_LONG):
return "Packet Too Long";
case (BLE_HS_ERR_L2C_BASE + BLE_L2CAP_SIG_ERR_CMD_NOT_UNDERSTOOD):
return "Invalid or unsupported incoming L2CAP sig command";
case (BLE_HS_ERR_L2C_BASE + BLE_L2CAP_SIG_ERR_MTU_EXCEEDED):
return "Incoming packet too large";
case (BLE_HS_ERR_L2C_BASE + BLE_L2CAP_SIG_ERR_INVALID_CID):
return "No channel with specified ID";
case (BLE_HS_ERR_SM_US_BASE + BLE_SM_ERR_PASSKEY):
case (BLE_HS_ERR_SM_PEER_BASE + BLE_SM_ERR_PASSKEY):
return "Incorrect passkey or the user cancelled";
case (BLE_HS_ERR_SM_US_BASE + BLE_SM_ERR_OOB):
case (BLE_HS_ERR_SM_PEER_BASE + BLE_SM_ERR_OOB):
return "The OOB data is not available";
case (BLE_HS_ERR_SM_US_BASE + BLE_SM_ERR_AUTHREQ):
case (BLE_HS_ERR_SM_PEER_BASE + BLE_SM_ERR_AUTHREQ):
return "Authentication requirements cannot be met due to IO capabilities";
case (BLE_HS_ERR_SM_US_BASE + BLE_SM_ERR_CONFIRM_MISMATCH):
case (BLE_HS_ERR_SM_PEER_BASE + BLE_SM_ERR_CONFIRM_MISMATCH):
return "The confirm value does not match the calculated compare value";
case (BLE_HS_ERR_SM_US_BASE + BLE_SM_ERR_PAIR_NOT_SUPP):
case (BLE_HS_ERR_SM_PEER_BASE + BLE_SM_ERR_PAIR_NOT_SUPP):
return "Pairing is not supported by the device";
case (BLE_HS_ERR_SM_US_BASE + BLE_SM_ERR_ENC_KEY_SZ):
case (BLE_HS_ERR_SM_PEER_BASE + BLE_SM_ERR_ENC_KEY_SZ):
return "Insufficient encryption key size for this device";
case (BLE_HS_ERR_SM_US_BASE + BLE_SM_ERR_CMD_NOT_SUPP):
case (BLE_HS_ERR_SM_PEER_BASE + BLE_SM_ERR_CMD_NOT_SUPP):
return "The SMP command received is not supported on this device";
case (BLE_HS_ERR_SM_US_BASE + BLE_SM_ERR_UNSPECIFIED):
case (BLE_HS_ERR_SM_PEER_BASE + BLE_SM_ERR_UNSPECIFIED):
return "Pairing failed; unspecified reason";
case (BLE_HS_ERR_SM_US_BASE + BLE_SM_ERR_REPEATED):
case (BLE_HS_ERR_SM_PEER_BASE + BLE_SM_ERR_REPEATED):
return "Repeated pairing attempt";
case (BLE_HS_ERR_SM_US_BASE + BLE_SM_ERR_INVAL):
case (BLE_HS_ERR_SM_PEER_BASE + BLE_SM_ERR_INVAL):
return "Invalid command length or parameter";
case (BLE_HS_ERR_SM_US_BASE + BLE_SM_ERR_DHKEY):
case (BLE_HS_ERR_SM_PEER_BASE + BLE_SM_ERR_DHKEY):
return "DHKey check value received doesn't match";
case (BLE_HS_ERR_SM_US_BASE + BLE_SM_ERR_NUMCMP):
case (BLE_HS_ERR_SM_PEER_BASE + BLE_SM_ERR_NUMCMP):
return "Confirm values do not match";
case (BLE_HS_ERR_SM_US_BASE + BLE_SM_ERR_ALREADY):
case (BLE_HS_ERR_SM_PEER_BASE + BLE_SM_ERR_ALREADY):
return "Pairing already in process";
case (BLE_HS_ERR_SM_US_BASE + BLE_SM_ERR_CROSS_TRANS):
case (BLE_HS_ERR_SM_PEER_BASE + BLE_SM_ERR_CROSS_TRANS):
return "Invalid link key for the LE transport";
default:
return "Unknown";
}
#else // #if defined(CONFIG_NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT)
# else // #if defined(CONFIG_NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT)
(void)rc;
return "";
#endif // #if defined(CONFIG_NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT)
# endif // #if defined(CONFIG_NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT)
}
/**
* @brief Convert the advertising type flag to a string.
* @param advType The type to convert.
* @return A string representation of the advertising flags.
*/
const char* NimBLEUtils::advTypeToString(uint8_t advType) {
#if defined(CONFIG_NIMBLE_CPP_ENABLE_ADVERTISMENT_TYPE_TEXT)
switch(advType) {
case BLE_HCI_ADV_TYPE_ADV_IND : //0
# if defined(CONFIG_NIMBLE_CPP_ENABLE_ADVERTISEMENT_TYPE_TEXT)
switch (advType) {
case BLE_HCI_ADV_TYPE_ADV_IND: // 0
return "Undirected - Connectable / Scannable";
case BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_HD: //1
case BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_HD: // 1
return "Directed High Duty - Connectable";
case BLE_HCI_ADV_TYPE_ADV_SCAN_IND: //2
case BLE_HCI_ADV_TYPE_ADV_SCAN_IND: // 2
return "Non-Connectable - Scan Response Available";
case BLE_HCI_ADV_TYPE_ADV_NONCONN_IND: //3
case BLE_HCI_ADV_TYPE_ADV_NONCONN_IND: // 3
return "Non-Connectable - No Scan Response";
case BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_LD: //4
case BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_LD: // 4
return "Directed Low Duty - Connectable";
default:
return "Unknown flag";
}
#else // #if defined(CONFIG_NIMBLE_CPP_ENABLE_ADVERTISMENT_TYPE_TEXT)
# else // #if defined(CONFIG_NIMBLE_CPP_ENABLE_ADVERTISEMENT_TYPE_TEXT)
(void)advType;
return "";
#endif // #if defined(CONFIG_NIMBLE_CPP_ENABLE_ADVERTISMENT_TYPE_TEXT)
# endif // #if defined(CONFIG_NIMBLE_CPP_ENABLE_ADVERTISEMENT_TYPE_TEXT)
} // adFlagsToString
/**
* @brief Create a hex representation of data.
*
* @param [in] target Where to write the hex string. If this is null, we malloc storage.
* @param [in] source The start of the binary data.
* @param [in] length The length of the data to convert.
* @return A pointer to the formatted buffer.
*/
char* NimBLEUtils::buildHexData(uint8_t* target, const uint8_t* source, uint8_t length) {
// Guard against too much data.
if (length > 100) length = 100;
if (target == nullptr) {
target = (uint8_t*) malloc(length * 2 + 1);
if (target == nullptr) {
NIMBLE_LOGE(LOG_TAG, "buildHexData: malloc failed");
return nullptr;
}
}
char* startOfData = (char*) target;
for (int i = 0; i < length; i++) {
sprintf((char*) target, "%.2x", (char) *source);
source++;
target += 2;
}
// Handle the special case where there was no data.
if (length == 0) {
*startOfData = 0;
}
return startOfData;
} // buildHexData
/**
* @brief Utility function to log the gap event info.
* @param [in] event A pointer to the gap event structure.
* @param [in] arg Unused.
*/
void NimBLEUtils::dumpGapEvent(ble_gap_event *event, void *arg){
(void)arg;
#if defined(CONFIG_NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT)
NIMBLE_LOGD(LOG_TAG, "Received a GAP event: %s", gapEventToString(event->type));
#else
(void)event;
#endif
}
/**
* @brief Convert a GAP event type to a string representation.
* @param [in] eventType The type of event.
* @return A string representation of the event type.
*/
const char* NimBLEUtils::gapEventToString(uint8_t eventType) {
#if defined(CONFIG_NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT)
# if defined(CONFIG_NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT)
switch (eventType) {
case BLE_GAP_EVENT_CONNECT : //0
case BLE_GAP_EVENT_CONNECT: // 0
return "BLE_GAP_EVENT_CONNECT ";
case BLE_GAP_EVENT_DISCONNECT: //1
case BLE_GAP_EVENT_DISCONNECT: // 1
return "BLE_GAP_EVENT_DISCONNECT";
case BLE_GAP_EVENT_CONN_UPDATE: //3
case BLE_GAP_EVENT_CONN_UPDATE: // 3
return "BLE_GAP_EVENT_CONN_UPDATE";
case BLE_GAP_EVENT_CONN_UPDATE_REQ: //4
case BLE_GAP_EVENT_CONN_UPDATE_REQ: // 4
return "BLE_GAP_EVENT_CONN_UPDATE_REQ";
case BLE_GAP_EVENT_L2CAP_UPDATE_REQ: //5
case BLE_GAP_EVENT_L2CAP_UPDATE_REQ: // 5
return "BLE_GAP_EVENT_L2CAP_UPDATE_REQ";
case BLE_GAP_EVENT_TERM_FAILURE: //6
case BLE_GAP_EVENT_TERM_FAILURE: // 6
return "BLE_GAP_EVENT_TERM_FAILURE";
case BLE_GAP_EVENT_DISC: //7
case BLE_GAP_EVENT_DISC: // 7
return "BLE_GAP_EVENT_DISC";
case BLE_GAP_EVENT_DISC_COMPLETE: //8
case BLE_GAP_EVENT_DISC_COMPLETE: // 8
return "BLE_GAP_EVENT_DISC_COMPLETE";
case BLE_GAP_EVENT_ADV_COMPLETE: //9
case BLE_GAP_EVENT_ADV_COMPLETE: // 9
return "BLE_GAP_EVENT_ADV_COMPLETE";
case BLE_GAP_EVENT_ENC_CHANGE: //10
case BLE_GAP_EVENT_ENC_CHANGE: // 10
return "BLE_GAP_EVENT_ENC_CHANGE";
case BLE_GAP_EVENT_PASSKEY_ACTION : //11
case BLE_GAP_EVENT_PASSKEY_ACTION: // 11
return "BLE_GAP_EVENT_PASSKEY_ACTION";
case BLE_GAP_EVENT_NOTIFY_RX: //12
case BLE_GAP_EVENT_NOTIFY_RX: // 12
return "BLE_GAP_EVENT_NOTIFY_RX";
case BLE_GAP_EVENT_NOTIFY_TX : //13
case BLE_GAP_EVENT_NOTIFY_TX: // 13
return "BLE_GAP_EVENT_NOTIFY_TX";
case BLE_GAP_EVENT_SUBSCRIBE : //14
case BLE_GAP_EVENT_SUBSCRIBE: // 14
return "BLE_GAP_EVENT_SUBSCRIBE";
case BLE_GAP_EVENT_MTU: //15
case BLE_GAP_EVENT_MTU: // 15
return "BLE_GAP_EVENT_MTU";
case BLE_GAP_EVENT_IDENTITY_RESOLVED: //16
case BLE_GAP_EVENT_IDENTITY_RESOLVED: // 16
return "BLE_GAP_EVENT_IDENTITY_RESOLVED";
case BLE_GAP_EVENT_REPEAT_PAIRING: //17
case BLE_GAP_EVENT_REPEAT_PAIRING: // 17
return "BLE_GAP_EVENT_REPEAT_PAIRING";
case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: //18
case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: // 18
return "BLE_GAP_EVENT_PHY_UPDATE_COMPLETE";
case BLE_GAP_EVENT_EXT_DISC: //19
case BLE_GAP_EVENT_EXT_DISC: // 19
return "BLE_GAP_EVENT_EXT_DISC";
#ifdef BLE_GAP_EVENT_PERIODIC_SYNC // IDF 4.0 does not support these
case BLE_GAP_EVENT_PERIODIC_SYNC: //20
# ifdef BLE_GAP_EVENT_PERIODIC_SYNC // IDF 4.0 does not support these
case BLE_GAP_EVENT_PERIODIC_SYNC: // 20
return "BLE_GAP_EVENT_PERIODIC_SYNC";
case BLE_GAP_EVENT_PERIODIC_REPORT: //21
case BLE_GAP_EVENT_PERIODIC_REPORT: // 21
return "BLE_GAP_EVENT_PERIODIC_REPORT";
case BLE_GAP_EVENT_PERIODIC_SYNC_LOST: //22
case BLE_GAP_EVENT_PERIODIC_SYNC_LOST: // 22
return "BLE_GAP_EVENT_PERIODIC_SYNC_LOST";
case BLE_GAP_EVENT_SCAN_REQ_RCVD: //23
case BLE_GAP_EVENT_SCAN_REQ_RCVD: // 23
return "BLE_GAP_EVENT_SCAN_REQ_RCVD";
#endif
case BLE_GAP_EVENT_PERIODIC_TRANSFER: // 24
return "BLE_GAP_EVENT_PERIODIC_TRANSFER";
case BLE_GAP_EVENT_PATHLOSS_THRESHOLD: // 25
return "BLE_GAP_EVENT_PATHLOSS_THRESHOLD";
case BLE_GAP_EVENT_TRANSMIT_POWER: // 26
return "BLE_GAP_EVENT_TRANSMIT_POWER";
case BLE_GAP_EVENT_SUBRATE_CHANGE: // 27
return "BLE_GAP_EVENT_SUBRATE_CHANGE";
case BLE_GAP_EVENT_VS_HCI: // 28
return "BLE_GAP_EVENT_VS_HCI";
case BLE_GAP_EVENT_REATTEMPT_COUNT: // 29
return "BLE_GAP_EVENT_REATTEMPT_COUNT";
case BLE_GAP_EVENT_AUTHORIZE: // 30
return "BLE_GAP_EVENT_AUTHORIZE";
case BLE_GAP_EVENT_TEST_UPDATE: // 31
return "BLE_GAP_EVENT_TEST_UPDATE";
# ifdef BLE_GAP_EVENT_DATA_LEN_CHG
case BLE_GAP_EVENT_DATA_LEN_CHG: // 32
return "BLE_GAP_EVENT_DATA_LEN_CHG";
# endif
# ifdef BLE_GAP_EVENT_LINK_ESTAB
case BLE_GAP_EVENT_LINK_ESTAB: // 33
return "BLE_GAP_EVENT_LINK_ESTAB";
# endif
# endif
default:
NIMBLE_LOGD(LOG_TAG, "gapEventToString: Unknown event type %d 0x%.2x", eventType, eventType);
NIMBLE_LOGD(LOG_TAG, "Unknown event type %d 0x%.2x", eventType, eventType);
return "Unknown event type";
}
#else // #if defined(CONFIG_NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT)
# else // #if defined(CONFIG_NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT)
(void)eventType;
return "";
#endif // #if defined(CONFIG_NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT)
# endif // #if defined(CONFIG_NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT)
} // gapEventToString
#endif //CONFIG_BT_ENABLED
/**
* @brief Create a hexadecimal string representation of the input data.
* @param [in] source The start of the binary data.
* @param [in] length The length of the data to convert.
* @return A string representation of the data.
*/
std::string NimBLEUtils::dataToHexString(const uint8_t* source, uint8_t length) {
constexpr char hexmap[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
std::string str{};
str.resize(length << 1);
for (uint8_t i = 0; i < length; i++) {
str[2 * i] = hexmap[(source[i] & 0xF0) >> 4];
str[2 * i + 1] = hexmap[source[i] & 0x0F];
}
return str;
} // dataToHexString
/**
* @brief Generate a random BLE address.
* @param [in] nrpa True to generate a non-resolvable private address,
* false to generate a random static address
* @return The generated address or a NULL address if there was an error.
*/
NimBLEAddress NimBLEUtils::generateAddr(bool nrpa) {
ble_addr_t addr{};
int rc = ble_hs_id_gen_rnd(nrpa, &addr);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Generate address failed, rc=%d", rc);
}
return NimBLEAddress{addr};
} // generateAddr
#endif // CONFIG_BT_ENABLED

View File

@ -6,46 +6,45 @@
*
*/
#ifndef COMPONENTS_NIMBLEUTILS_H_
#define COMPONENTS_NIMBLEUTILS_H_
#ifndef NIMBLE_CPP_UTILS_H_
#define NIMBLE_CPP_UTILS_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
# include <string>
#if defined(CONFIG_NIMBLE_CPP_IDF)
#include "host/ble_gap.h"
#else
#include "nimble/nimble/host/include/host/ble_gap.h"
#endif
class NimBLEAddress;
/**** FIX COMPILATION ****/
#undef min
#undef max
/**************************/
#include <string>
typedef struct {
void *pATT;
TaskHandle_t task;
int rc;
void *buf;
} ble_task_data_t;
/**
* @brief A structure to hold data for a task that is waiting for a response.
* @details This structure is used in conjunction with NimBLEUtils::taskWait() and NimBLEUtils::taskRelease().
* All items are optional, the m_pHandle will be set in taskWait().
*/
struct NimBLETaskData {
NimBLETaskData(void* pInstance = nullptr, int flags = 0, void* buf = nullptr);
~NimBLETaskData();
void* m_pInstance{nullptr};
mutable int m_flags{0};
void* m_pBuf{nullptr};
private:
mutable void* m_pHandle{nullptr}; // semaphore or task handle
friend class NimBLEUtils;
};
/**
* @brief A BLE Utility class with methods for debugging and general purpose use.
*/
class NimBLEUtils {
public:
static void dumpGapEvent(ble_gap_event *event, void *arg);
static const char* gapEventToString(uint8_t eventType);
static char* buildHexData(uint8_t* target, const uint8_t* source, uint8_t length);
static const char* advTypeToString(uint8_t advType);
static const char* returnCodeToString(int rc);
static int checkConnParams(ble_gap_conn_params* params);
public:
static const char* gapEventToString(uint8_t eventType);
static std::string dataToHexString(const uint8_t* source, uint8_t length);
static const char* advTypeToString(uint8_t advType);
static const char* returnCodeToString(int rc);
static NimBLEAddress generateAddr(bool nrpa);
static bool taskWait(const NimBLETaskData& taskData, uint32_t timeout);
static void taskRelease(const NimBLETaskData& taskData, int rc = 0);
};
#endif // CONFIG_BT_ENABLED
#endif // COMPONENTS_NIMBLEUTILS_H_
#endif // NIMBLE_CPP_UTILS_H_

View File

@ -32,6 +32,18 @@
# endif
#endif
#if CONFIG_NIMBLE_CPP_DEBUG_ASSERT_ENABLED && !defined NDEBUG
void nimble_cpp_assert(const char *file, unsigned line) __attribute((weak, noreturn));
# define NIMBLE_ATT_VAL_FILE (__builtin_strrchr(__FILE__, '/') ? \
__builtin_strrchr (__FILE__, '/') + 1 : __FILE__)
# define NIMBLE_CPP_DEBUG_ASSERT(cond) \
if (!(cond)) { \
nimble_cpp_assert(NIMBLE_ATT_VAL_FILE, __LINE__); \
}
#else
# define NIMBLE_CPP_DEBUG_ASSERT(cond) (void(0))
#endif
#endif /* CONFIG_BT_ENABLED */
#ifdef _DOXYGEN_