Initial commit.

This commit is contained in:
h2zero 2020-03-29 17:44:20 -06:00
parent 5ac8cc0e29
commit 5d4332e945
55 changed files with 11651 additions and 3 deletions

207
API_DIFFERENCES.md Normal file
View File

@ -0,0 +1,207 @@
# Server API differnces:
### Characteristics:
When creating a characteristic the properties are now set with `NIMBLE_PROPERTY::XXXX` instead of `BLECharacteristic::XXXX`.
#### Previous:
```
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE
```
#### Changed to:
```
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE
```
#### 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
```
### Descriptors:
Descriptors are now created using the NimBLEcharacteristic method `createDescriptor()`.
The previous method `addDescriptor()` is now a private function in the library.
This was done because the NimBLE host automatically creates a 0x2902 descriptor if a characteristic has notify or indicate properties applied.
Due to this fact, this library also creates one automatically for your application.
The only reason to manually create this descriptor now is to assign callback functions.
If you do not require this functionality you can safely exclude the manual creation of that descriptor.
For any other descriptor, (except 0x2904, see below) it should now be created just as characteristics are
by invoking the `NimBLECharacteristic::createDescriptor` methods.
Which are defined as:
```
NimBLEDescriptor* createDescriptor(const char* uuid,
uint32_t properties = NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = 100);
NimBLEDescriptor* createDescriptor(NimBLEUUID uuid,
uint32_t properties = NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = 100);
```
##### Example:
```
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.
For the 0x2904 descriptor, 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 `NimBLE2904*` to access the specific class methods.
##### Example:
```
p2904 = (NimBLE2904*)pCharacteristic->createDescriptor("2904");
```
#### 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
```
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 confirmation. See below for details.
# Client API Differences:
The `BLEAdvertisedDeviceCallbacks` class `onResult()` method now receives a pointer to the
`NimBLEAdvertisedDevice` object instead of a copy.
`NimBLEClient::connect()` now takes an extra parameter to indicate if the client should download the services
database from the peripheral, default value is true.
Defined as:
```
bool connect(NimBLEAdvertisedDevice* device, bool refreshServices = true);
bool connect(NimBLEAddress address, uint8_t type = BLE_ADDR_TYPE_PUBLIC, bool refreshServices = true);
```
If set to false the client will use the services database it retrieved from the peripheral last time it connected.
This allows for faster connections and power saving if the devices just dropped connection and want to reconnect.
```
NimBLERemoteCharacteristic::writeValue();
NimBLERemoteCharacteristic::registerForNotify();
```
Now return true or false to indicate success or failure so you can choose to disconnect or try again.
#### Client Security:
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.
# Security:
Security callback functions are now incorporated in the client/server Callbacks class.
However backward compatibility with the `BLESecurity` class is retained to minimize app code changes.
The relevent server callbacks are defined as:
```
bool onConfirmPIN(uint32_t pin); // accept or reject the passkey
void onAuthenticationComplete(ble_gap_conn_desc* desc); // auth complete - details in desc
bool onPassKeyNotify(uint32_t pass_key); // receive the passkey sent by the client, accept or reject
```
The relevent client callbacks are defined as:
```
bool onConfirmPIN(uint32_t pin); // accept or reject the passkey
void onAuthenticationComplete(ble_gap_conn_desc* desc); // auth complete - details in desc
uint32_t onPassKeyRequest(); // return the passkey to send to the server
```
Security settings and IO capabilities are now set by the corresponding method of `NimBLEDevice::`.
```
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);
/**
* @brief Set the authorization mode for this device.
* @param bonding, if true we allow bonding, false no bonding will be performed.
* @param mitm, if true we are capable of man in the middle protection, false if not.
* @param sc, if true we will perform secure connection pairing, false we will use legacy pairing.
*/
void NimBLEDevice::setSecuityAuth(bool bonding, bool mitm, bool sc)
/**
* @brief Set the authorization mode for this device.
* @param A bitmap indicating what modes are supported.
* The bits are defined as follows:
** 0x01 BLE_SM_PAIR_AUTHREQ_BOND
** 0x04 BLE_SM_PAIR_AUTHREQ_MITM
** 0x08 BLE_SM_PAIR_AUTHREQ_SC
** 0x10 BLE_SM_PAIR_AUTHREQ_KEYPRESS - not yet supported.
** 0xe2 BLE_SM_PAIR_AUTHREQ_RESERVED - for reference only.
*/
void NimBLEDevice::setSecuityAuth(uint8_t auth_req)
/**
* @brief Set the Input/Output capabilities of this device.
* @param One of the following:
** 0x00 BLE_HS_IO_DISPLAY_ONLY DisplayOnly IO capability
** 0x01 BLE_HS_IO_DISPLAY_YESNO DisplayYesNo IO capability
** 0x02 BLE_HS_IO_KEYBOARD_ONLY KeyboardOnly IO capability
** 0x03 BLE_HS_IO_NO_INPUT_OUTPUT NoInputNoOutput IO capability
** 0x04 BLE_HS_IO_KEYBOARD_DISPLAY KeyboardDisplay Only IO capability
*/
void NimBLEDevice::setSecurityIOCap(uint8_t iocap)
/**
* @brief If we are the initiator of the security procedure this sets the keys we will distribute.
* @param A bitmap indicating which keys to distribute during pairing.
* The bits are defined as follows:
** 0x01: BLE_SM_PAIR_KEY_DIST_ENC - Distribute the encryption key.
** 0x02: BLE_SM_PAIR_KEY_DIST_ID - Distribute the ID key (IRK).
** 0x04: BLE_SM_PAIR_KEY_DIST_SIGN
** 0x08: BLE_SM_PAIR_KEY_DIST_LINK
*/
void NimBLEDevice::setSecurityInitKey(uint8_t init_key)
/**
* @brief Set the keys we are willing to accept during pairing.
* @param A bitmap indicating which keys to accept during pairing.
* The bits are defined as follows:
** 0x01: BLE_SM_PAIR_KEY_DIST_ENC - Accept the encryption key.
** 0x02: BLE_SM_PAIR_KEY_DIST_ID - Accept the ID key (IRK).
** 0x04: BLE_SM_PAIR_KEY_DIST_SIGN
** 0x08: BLE_SM_PAIR_KEY_DIST_LINK
*/
void NimBLEDevice::setSecurityRespKey(uint8_t init_key)
```
I'm sure there are more things I have forgotten but this is all the majors.
I will update this document as necessary.

View File

@ -1,4 +1,4 @@
Apache License
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
@ -178,7 +178,7 @@
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright {2020} {Ryan Powell}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -199,3 +199,5 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
This product partly derives from esp32-snippets; Copyright 2017 Neil Kolban.

41
README.md Normal file
View File

@ -0,0 +1,41 @@
# *** UPDATE ***
This library is now ready with (mostly)all original BLE library compatiblity.
Check the examples and API_DIFFERENCES document for details of using this library.
3 simultaneous connections tested stable so far on both client and server.
# esp-nimble-cpp
A fork of the NimBLE stack restructured for compilation in the Ardruino IDE with a CPP library for use with ESP32.
Why? Because the Bluedroid library is too bulky.
Initial client code testing has resulted in code size reduction of ~115k and reduced ram consumption of ~37k.
# Installation:
Download as .zip and extract to components folder in your esp-idf project.
`#include "NimBLEDevice.h"` in main.cpp.
# Usage:
This library is intended to be compatible with the original ESP32 BLE functions and types with minor changes.
# Acknowledgments:
* @nkolban and @chegewara for the [original esp32 BLE library](https://github.com/nkolban/esp32-snippets) this project was derived from.
* @beegee-tokyo for contributing your time to test/debug and contributing the beacon examples.
# Todo:
1. Code cleanup.
2. Create documentation.
3. Expose more NimBLE features.
4. Add BLE Mesh code.

305
src/FreeRTOS.cpp Normal file
View File

@ -0,0 +1,305 @@
/*
* FreeRTOS.cpp
*
* Created on: Feb 24, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#include "FreeRTOS.h"
#include "NimBLELog.h"
#include <freertos/FreeRTOS.h> // Include the base FreeRTOS definitions
#include <freertos/task.h> // Include the task definitions
#include <freertos/semphr.h> // Include the semaphore definitions
#include <string>
static const char* LOG_TAG = "FreeRTOS";
/**
* Sleep for the specified number of milliseconds.
* @param[in] ms The period in milliseconds for which to sleep.
*/
void FreeRTOS::sleep(uint32_t ms) {
::vTaskDelay(ms / portTICK_PERIOD_MS);
} // sleep
/**
* Start a new task.
* @param[in] task The function pointer to the function to be run in the task.
* @param[in] taskName A string identifier for the task.
* @param[in] param An optional parameter to be passed to the started task.
* @param[in] stackSize An optional paremeter supplying the size of the stack in which to run the task.
*/
void FreeRTOS::startTask(void task(void*), std::string taskName, void* param, uint32_t stackSize) {
::xTaskCreate(task, taskName.data(), stackSize, param, 5, NULL);
} // startTask
/**
* Delete the task.
* @param[in] pTask An optional handle to the task to be deleted. If not supplied the calling task will be deleted.
*/
void FreeRTOS::deleteTask(TaskHandle_t pTask) {
::vTaskDelete(pTask);
} // deleteTask
/**
* Get the time in milliseconds since the %FreeRTOS scheduler started.
* @return The time in milliseconds since the %FreeRTOS scheduler started.
*/
uint32_t FreeRTOS::getTimeSinceStart() {
return (uint32_t) (xTaskGetTickCount() * portTICK_PERIOD_MS);
} // getTimeSinceStart
/**
* @brief Wait for a semaphore to be released by trying to take it and
* then releasing it again.
* @param [in] owner A debug tag.
* @return The value associated with the semaphore.
*/
uint32_t FreeRTOS::Semaphore::wait(std::string owner) {
NIMBLE_LOGD(LOG_TAG, ">> wait: Semaphore waiting: %s for %s", toString().c_str(), owner.c_str());
if (m_usePthreads) {
pthread_mutex_lock(&m_pthread_mutex);
} else {
xSemaphoreTake(m_semaphore, portMAX_DELAY);
}
if (m_usePthreads) {
pthread_mutex_unlock(&m_pthread_mutex);
} else {
xSemaphoreGive(m_semaphore);
}
NIMBLE_LOGD(LOG_TAG, "<< wait: Semaphore released: %s", toString().c_str());
return m_value;
} // wait
/**
* @brief Wait for a semaphore to be released in a given period of time by trying to take it and
* then releasing it again. The value associated with the semaphore can be taken by value() call after return
* @param [in] owner A debug tag.
* @param [in] timeoutMs timeout to wait in ms.
* @return True if we took the semaphore within timeframe.
*/
bool FreeRTOS::Semaphore::timedWait(std::string owner, uint32_t timeoutMs) {
NIMBLE_LOGD(LOG_TAG, ">> wait: Semaphore waiting: %s for %s", toString().c_str(), owner.c_str());
if (m_usePthreads && timeoutMs != portMAX_DELAY) {
assert(false); // We apparently don't have a timed wait for pthreads.
}
auto ret = pdTRUE;
if (m_usePthreads) {
pthread_mutex_lock(&m_pthread_mutex);
} else {
ret = xSemaphoreTake(m_semaphore, timeoutMs);
}
if (m_usePthreads) {
pthread_mutex_unlock(&m_pthread_mutex);
} else {
xSemaphoreGive(m_semaphore);
}
NIMBLE_LOGD(LOG_TAG, "<< wait: Semaphore %s released: %d", toString().c_str(), ret);
return ret;
} // wait
FreeRTOS::Semaphore::Semaphore(std::string name) {
m_usePthreads = false; // Are we using pThreads or FreeRTOS?
if (m_usePthreads) {
pthread_mutex_init(&m_pthread_mutex, nullptr);
} else {
//m_semaphore = xSemaphoreCreateMutex();
m_semaphore = xSemaphoreCreateBinary();
xSemaphoreGive(m_semaphore);
}
m_name = name;
m_owner = std::string("<N/A>");
m_value = 0;
}
FreeRTOS::Semaphore::~Semaphore() {
if (m_usePthreads) {
pthread_mutex_destroy(&m_pthread_mutex);
} else {
vSemaphoreDelete(m_semaphore);
}
}
/**
* @brief Give a semaphore.
* The Semaphore is given.
*/
void FreeRTOS::Semaphore::give() {
NIMBLE_LOGD(LOG_TAG, "Semaphore giving: %s", toString().c_str());
m_owner = std::string("<N/A>");
if (m_usePthreads) {
pthread_mutex_unlock(&m_pthread_mutex);
} else {
xSemaphoreGive(m_semaphore);
}
// #ifdef ARDUINO_ARCH_ESP32
// FreeRTOS::sleep(10);
// #endif
} // Semaphore::give
/**
* @brief Give a semaphore.
* The Semaphore is given with an associated value.
* @param [in] value The value to associate with the semaphore.
*/
void FreeRTOS::Semaphore::give(uint32_t value) {
m_value = value;
give();
} // give
/**
* @brief Give a semaphore from an ISR.
*/
void FreeRTOS::Semaphore::giveFromISR() {
BaseType_t higherPriorityTaskWoken;
if (m_usePthreads) {
assert(false);
} else {
xSemaphoreGiveFromISR(m_semaphore, &higherPriorityTaskWoken);
}
} // giveFromISR
/**
* @brief Take a semaphore.
* Take a semaphore and wait indefinitely.
* @param [in] owner The new owner (for debugging)
* @return True if we took the semaphore.
*/
bool FreeRTOS::Semaphore::take(std::string owner) {
NIMBLE_LOGD(LOG_TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str());
bool rc = false;
if (m_usePthreads) {
pthread_mutex_lock(&m_pthread_mutex);
} else {
rc = ::xSemaphoreTake(m_semaphore, portMAX_DELAY) == pdTRUE;
}
m_owner = owner;
if (rc) {
NIMBLE_LOGD(LOG_TAG, "Semaphore taken: %s", toString().c_str());
} else {
NIMBLE_LOGE(LOG_TAG, "Semaphore NOT taken: %s", toString().c_str());
}
return rc;
} // Semaphore::take
/**
* @brief Take a semaphore.
* Take a semaphore but return if we haven't obtained it in the given period of milliseconds.
* @param [in] timeoutMs Timeout in milliseconds.
* @param [in] owner The new owner (for debugging)
* @return True if we took the semaphore.
*/
bool FreeRTOS::Semaphore::take(uint32_t timeoutMs, std::string owner) {
NIMBLE_LOGD(LOG_TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str());
bool rc = false;
if (m_usePthreads) {
assert(false); // We apparently don't have a timed wait for pthreads.
} else {
rc = ::xSemaphoreTake(m_semaphore, timeoutMs / portTICK_PERIOD_MS) == pdTRUE;
}
m_owner = owner;
if (rc) {
NIMBLE_LOGD(LOG_TAG, "Semaphore taken: %s", toString().c_str());
} else {
NIMBLE_LOGE(LOG_TAG, "Semaphore NOT taken: %s", toString().c_str());
}
return rc;
} // Semaphore::take
/**
* @brief Create a string representation of the semaphore.
* @return A string representation of the semaphore.
*/
std::string FreeRTOS::Semaphore::toString() {
char hex[9];
std::string res = "name: " + m_name + " (0x";
snprintf(hex, sizeof(hex), "%08x", (uint32_t)m_semaphore);
res += hex;
res += "), owner: " + m_owner;
return res;
} // toString
/**
* @brief Set the name of the semaphore.
* @param [in] name The name of the semaphore.
*/
void FreeRTOS::Semaphore::setName(std::string name) {
m_name = name;
} // setName
/**
* @brief Create a ring buffer.
* @param [in] length The amount of storage to allocate for the ring buffer.
* @param [in] type The type of buffer. One of RINGBUF_TYPE_NOSPLIT, RINGBUF_TYPE_ALLOWSPLIT, RINGBUF_TYPE_BYTEBUF.
*/
Ringbuffer::Ringbuffer(size_t length, ringbuf_type_t type) {
m_handle = ::xRingbufferCreate(length, type);
} // Ringbuffer
Ringbuffer::~Ringbuffer() {
::vRingbufferDelete(m_handle);
} // ~Ringbuffer
/**
* @brief Receive data from the buffer.
* @param [out] size On return, the size of data returned.
* @param [in] wait How long to wait.
* @return A pointer to the storage retrieved.
*/
void* Ringbuffer::receive(size_t* size, TickType_t wait) {
return ::xRingbufferReceive(m_handle, size, wait);
} // receive
/**
* @brief Return an item.
* @param [in] item The item to be returned/released.
*/
void Ringbuffer::returnItem(void* item) {
::vRingbufferReturnItem(m_handle, item);
} // returnItem
/**
* @brief Send data to the buffer.
* @param [in] data The data to place into the buffer.
* @param [in] length The length of data to place into the buffer.
* @param [in] wait How long to wait before giving up. The default is to wait indefinitely.
* @return
*/
bool Ringbuffer::send(void* data, size_t length, TickType_t wait) {
return ::xRingbufferSend(m_handle, data, length, wait) == pdTRUE;
} // send

74
src/FreeRTOS.h Normal file
View File

@ -0,0 +1,74 @@
/*
* FreeRTOS.h
*
* Created on: Feb 24, 2017
* Author: kolban
*/
#ifndef MAIN_FREERTOS_H_
#define MAIN_FREERTOS_H_
#include <freertos/FreeRTOS.h> // Include the base FreeRTOS definitions.
#include <freertos/task.h> // Include the task definitions.
#include <freertos/semphr.h> // Include the semaphore definitions.
#include <freertos/ringbuf.h> // Include the ringbuffer definitions.
#include <stdint.h>
#include <string>
#include <pthread.h>
/**
* @brief Interface to %FreeRTOS functions.
*/
class FreeRTOS {
public:
static void sleep(uint32_t ms);
static void startTask(void task(void*), std::string taskName, void* param = nullptr, uint32_t stackSize = 2048);
static void deleteTask(TaskHandle_t pTask = nullptr);
static uint32_t getTimeSinceStart();
class Semaphore {
public:
Semaphore(std::string owner = "<Unknown>");
~Semaphore();
void give();
void give(uint32_t value);
void giveFromISR();
void setName(std::string name);
bool take(std::string owner = "<Unknown>");
bool take(uint32_t timeoutMs, std::string owner = "<Unknown>");
std::string toString();
bool timedWait(std::string owner = "<Unknown>", uint32_t timeoutMs = portMAX_DELAY);
uint32_t wait(std::string owner = "<Unknown>");
uint32_t value(){ return m_value; };
private:
SemaphoreHandle_t m_semaphore;
pthread_mutex_t m_pthread_mutex;
std::string m_name;
std::string m_owner;
uint32_t m_value;
bool m_usePthreads;
};
};
/**
* @brief Ringbuffer.
*/
class Ringbuffer {
public:
Ringbuffer(size_t length, ringbuf_type_t type = RINGBUF_TYPE_NOSPLIT);
~Ringbuffer();
void* receive(size_t* size, TickType_t wait = portMAX_DELAY);
void returnItem(void* item);
bool send(void* data, size_t length, TickType_t wait = portMAX_DELAY);
private:
RingbufHandle_t m_handle;
};
#endif /* MAIN_FREERTOS_H_ */

402
src/HIDKeyboardTypes.h Normal file
View File

@ -0,0 +1,402 @@
/* Copyright (c) 2015 mbed.org, MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Note: this file was pulled from different parts of the USBHID library, in mbed SDK
*/
#ifndef KEYBOARD_DEFS_H
#define KEYBOARD_DEFS_H
#define REPORT_ID_KEYBOARD 1
#define REPORT_ID_VOLUME 3
/* Modifiers */
enum MODIFIER_KEY {
KEY_CTRL = 1,
KEY_SHIFT = 2,
KEY_ALT = 4,
};
enum MEDIA_KEY {
KEY_NEXT_TRACK, /*!< next Track Button */
KEY_PREVIOUS_TRACK, /*!< Previous track Button */
KEY_STOP, /*!< Stop Button */
KEY_PLAY_PAUSE, /*!< Play/Pause Button */
KEY_MUTE, /*!< Mute Button */
KEY_VOLUME_UP, /*!< Volume Up Button */
KEY_VOLUME_DOWN, /*!< Volume Down Button */
};
enum FUNCTION_KEY {
KEY_F1 = 128, /* F1 key */
KEY_F2, /* F2 key */
KEY_F3, /* F3 key */
KEY_F4, /* F4 key */
KEY_F5, /* F5 key */
KEY_F6, /* F6 key */
KEY_F7, /* F7 key */
KEY_F8, /* F8 key */
KEY_F9, /* F9 key */
KEY_F10, /* F10 key */
KEY_F11, /* F11 key */
KEY_F12, /* F12 key */
KEY_PRINT_SCREEN, /* Print Screen key */
KEY_SCROLL_LOCK, /* Scroll lock */
KEY_CAPS_LOCK, /* caps lock */
KEY_NUM_LOCK, /* num lock */
KEY_INSERT, /* Insert key */
KEY_HOME, /* Home key */
KEY_PAGE_UP, /* Page Up key */
KEY_PAGE_DOWN, /* Page Down key */
RIGHT_ARROW, /* Right arrow */
LEFT_ARROW, /* Left arrow */
DOWN_ARROW, /* Down arrow */
UP_ARROW, /* Up arrow */
};
typedef struct {
unsigned char usage;
unsigned char modifier;
} KEYMAP;
#ifdef US_KEYBOARD
/* US keyboard (as HID standard) */
#define KEYMAP_SIZE (152)
const KEYMAP keymap[KEYMAP_SIZE] = {
{0, 0}, /* NUL */
{0, 0}, /* SOH */
{0, 0}, /* STX */
{0, 0}, /* ETX */
{0, 0}, /* EOT */
{0, 0}, /* ENQ */
{0, 0}, /* ACK */
{0, 0}, /* BEL */
{0x2a, 0}, /* BS */ /* Keyboard Delete (Backspace) */
{0x2b, 0}, /* TAB */ /* Keyboard Tab */
{0x28, 0}, /* LF */ /* Keyboard Return (Enter) */
{0, 0}, /* VT */
{0, 0}, /* FF */
{0, 0}, /* CR */
{0, 0}, /* SO */
{0, 0}, /* SI */
{0, 0}, /* DEL */
{0, 0}, /* DC1 */
{0, 0}, /* DC2 */
{0, 0}, /* DC3 */
{0, 0}, /* DC4 */
{0, 0}, /* NAK */
{0, 0}, /* SYN */
{0, 0}, /* ETB */
{0, 0}, /* CAN */
{0, 0}, /* EM */
{0, 0}, /* SUB */
{0, 0}, /* ESC */
{0, 0}, /* FS */
{0, 0}, /* GS */
{0, 0}, /* RS */
{0, 0}, /* US */
{0x2c, 0}, /* */
{0x1e, KEY_SHIFT}, /* ! */
{0x34, KEY_SHIFT}, /* " */
{0x20, KEY_SHIFT}, /* # */
{0x21, KEY_SHIFT}, /* $ */
{0x22, KEY_SHIFT}, /* % */
{0x24, KEY_SHIFT}, /* & */
{0x34, 0}, /* ' */
{0x26, KEY_SHIFT}, /* ( */
{0x27, KEY_SHIFT}, /* ) */
{0x25, KEY_SHIFT}, /* * */
{0x2e, KEY_SHIFT}, /* + */
{0x36, 0}, /* , */
{0x2d, 0}, /* - */
{0x37, 0}, /* . */
{0x38, 0}, /* / */
{0x27, 0}, /* 0 */
{0x1e, 0}, /* 1 */
{0x1f, 0}, /* 2 */
{0x20, 0}, /* 3 */
{0x21, 0}, /* 4 */
{0x22, 0}, /* 5 */
{0x23, 0}, /* 6 */
{0x24, 0}, /* 7 */
{0x25, 0}, /* 8 */
{0x26, 0}, /* 9 */
{0x33, KEY_SHIFT}, /* : */
{0x33, 0}, /* ; */
{0x36, KEY_SHIFT}, /* < */
{0x2e, 0}, /* = */
{0x37, KEY_SHIFT}, /* > */
{0x38, KEY_SHIFT}, /* ? */
{0x1f, KEY_SHIFT}, /* @ */
{0x04, KEY_SHIFT}, /* A */
{0x05, KEY_SHIFT}, /* B */
{0x06, KEY_SHIFT}, /* C */
{0x07, KEY_SHIFT}, /* D */
{0x08, KEY_SHIFT}, /* E */
{0x09, KEY_SHIFT}, /* F */
{0x0a, KEY_SHIFT}, /* G */
{0x0b, KEY_SHIFT}, /* H */
{0x0c, KEY_SHIFT}, /* I */
{0x0d, KEY_SHIFT}, /* J */
{0x0e, KEY_SHIFT}, /* K */
{0x0f, KEY_SHIFT}, /* L */
{0x10, KEY_SHIFT}, /* M */
{0x11, KEY_SHIFT}, /* N */
{0x12, KEY_SHIFT}, /* O */
{0x13, KEY_SHIFT}, /* P */
{0x14, KEY_SHIFT}, /* Q */
{0x15, KEY_SHIFT}, /* R */
{0x16, KEY_SHIFT}, /* S */
{0x17, KEY_SHIFT}, /* T */
{0x18, KEY_SHIFT}, /* U */
{0x19, KEY_SHIFT}, /* V */
{0x1a, KEY_SHIFT}, /* W */
{0x1b, KEY_SHIFT}, /* X */
{0x1c, KEY_SHIFT}, /* Y */
{0x1d, KEY_SHIFT}, /* Z */
{0x2f, 0}, /* [ */
{0x31, 0}, /* \ */
{0x30, 0}, /* ] */
{0x23, KEY_SHIFT}, /* ^ */
{0x2d, KEY_SHIFT}, /* _ */
{0x35, 0}, /* ` */
{0x04, 0}, /* a */
{0x05, 0}, /* b */
{0x06, 0}, /* c */
{0x07, 0}, /* d */
{0x08, 0}, /* e */
{0x09, 0}, /* f */
{0x0a, 0}, /* g */
{0x0b, 0}, /* h */
{0x0c, 0}, /* i */
{0x0d, 0}, /* j */
{0x0e, 0}, /* k */
{0x0f, 0}, /* l */
{0x10, 0}, /* m */
{0x11, 0}, /* n */
{0x12, 0}, /* o */
{0x13, 0}, /* p */
{0x14, 0}, /* q */
{0x15, 0}, /* r */
{0x16, 0}, /* s */
{0x17, 0}, /* t */
{0x18, 0}, /* u */
{0x19, 0}, /* v */
{0x1a, 0}, /* w */
{0x1b, 0}, /* x */
{0x1c, 0}, /* y */
{0x1d, 0}, /* z */
{0x2f, KEY_SHIFT}, /* { */
{0x31, KEY_SHIFT}, /* | */
{0x30, KEY_SHIFT}, /* } */
{0x35, KEY_SHIFT}, /* ~ */
{0,0}, /* DEL */
{0x3a, 0}, /* F1 */
{0x3b, 0}, /* F2 */
{0x3c, 0}, /* F3 */
{0x3d, 0}, /* F4 */
{0x3e, 0}, /* F5 */
{0x3f, 0}, /* F6 */
{0x40, 0}, /* F7 */
{0x41, 0}, /* F8 */
{0x42, 0}, /* F9 */
{0x43, 0}, /* F10 */
{0x44, 0}, /* F11 */
{0x45, 0}, /* F12 */
{0x46, 0}, /* PRINT_SCREEN */
{0x47, 0}, /* SCROLL_LOCK */
{0x39, 0}, /* CAPS_LOCK */
{0x53, 0}, /* NUM_LOCK */
{0x49, 0}, /* INSERT */
{0x4a, 0}, /* HOME */
{0x4b, 0}, /* PAGE_UP */
{0x4e, 0}, /* PAGE_DOWN */
{0x4f, 0}, /* RIGHT_ARROW */
{0x50, 0}, /* LEFT_ARROW */
{0x51, 0}, /* DOWN_ARROW */
{0x52, 0}, /* UP_ARROW */
};
#else
/* UK keyboard */
#define KEYMAP_SIZE (152)
const KEYMAP keymap[KEYMAP_SIZE] = {
{0, 0}, /* NUL */
{0, 0}, /* SOH */
{0, 0}, /* STX */
{0, 0}, /* ETX */
{0, 0}, /* EOT */
{0, 0}, /* ENQ */
{0, 0}, /* ACK */
{0, 0}, /* BEL */
{0x2a, 0}, /* BS */ /* Keyboard Delete (Backspace) */
{0x2b, 0}, /* TAB */ /* Keyboard Tab */
{0x28, 0}, /* LF */ /* Keyboard Return (Enter) */
{0, 0}, /* VT */
{0, 0}, /* FF */
{0, 0}, /* CR */
{0, 0}, /* SO */
{0, 0}, /* SI */
{0, 0}, /* DEL */
{0, 0}, /* DC1 */
{0, 0}, /* DC2 */
{0, 0}, /* DC3 */
{0, 0}, /* DC4 */
{0, 0}, /* NAK */
{0, 0}, /* SYN */
{0, 0}, /* ETB */
{0, 0}, /* CAN */
{0, 0}, /* EM */
{0, 0}, /* SUB */
{0, 0}, /* ESC */
{0, 0}, /* FS */
{0, 0}, /* GS */
{0, 0}, /* RS */
{0, 0}, /* US */
{0x2c, 0}, /* */
{0x1e, KEY_SHIFT}, /* ! */
{0x1f, KEY_SHIFT}, /* " */
{0x32, 0}, /* # */
{0x21, KEY_SHIFT}, /* $ */
{0x22, KEY_SHIFT}, /* % */
{0x24, KEY_SHIFT}, /* & */
{0x34, 0}, /* ' */
{0x26, KEY_SHIFT}, /* ( */
{0x27, KEY_SHIFT}, /* ) */
{0x25, KEY_SHIFT}, /* * */
{0x2e, KEY_SHIFT}, /* + */
{0x36, 0}, /* , */
{0x2d, 0}, /* - */
{0x37, 0}, /* . */
{0x38, 0}, /* / */
{0x27, 0}, /* 0 */
{0x1e, 0}, /* 1 */
{0x1f, 0}, /* 2 */
{0x20, 0}, /* 3 */
{0x21, 0}, /* 4 */
{0x22, 0}, /* 5 */
{0x23, 0}, /* 6 */
{0x24, 0}, /* 7 */
{0x25, 0}, /* 8 */
{0x26, 0}, /* 9 */
{0x33, KEY_SHIFT}, /* : */
{0x33, 0}, /* ; */
{0x36, KEY_SHIFT}, /* < */
{0x2e, 0}, /* = */
{0x37, KEY_SHIFT}, /* > */
{0x38, KEY_SHIFT}, /* ? */
{0x34, KEY_SHIFT}, /* @ */
{0x04, KEY_SHIFT}, /* A */
{0x05, KEY_SHIFT}, /* B */
{0x06, KEY_SHIFT}, /* C */
{0x07, KEY_SHIFT}, /* D */
{0x08, KEY_SHIFT}, /* E */
{0x09, KEY_SHIFT}, /* F */
{0x0a, KEY_SHIFT}, /* G */
{0x0b, KEY_SHIFT}, /* H */
{0x0c, KEY_SHIFT}, /* I */
{0x0d, KEY_SHIFT}, /* J */
{0x0e, KEY_SHIFT}, /* K */
{0x0f, KEY_SHIFT}, /* L */
{0x10, KEY_SHIFT}, /* M */
{0x11, KEY_SHIFT}, /* N */
{0x12, KEY_SHIFT}, /* O */
{0x13, KEY_SHIFT}, /* P */
{0x14, KEY_SHIFT}, /* Q */
{0x15, KEY_SHIFT}, /* R */
{0x16, KEY_SHIFT}, /* S */
{0x17, KEY_SHIFT}, /* T */
{0x18, KEY_SHIFT}, /* U */
{0x19, KEY_SHIFT}, /* V */
{0x1a, KEY_SHIFT}, /* W */
{0x1b, KEY_SHIFT}, /* X */
{0x1c, KEY_SHIFT}, /* Y */
{0x1d, KEY_SHIFT}, /* Z */
{0x2f, 0}, /* [ */
{0x64, 0}, /* \ */
{0x30, 0}, /* ] */
{0x23, KEY_SHIFT}, /* ^ */
{0x2d, KEY_SHIFT}, /* _ */
{0x35, 0}, /* ` */
{0x04, 0}, /* a */
{0x05, 0}, /* b */
{0x06, 0}, /* c */
{0x07, 0}, /* d */
{0x08, 0}, /* e */
{0x09, 0}, /* f */
{0x0a, 0}, /* g */
{0x0b, 0}, /* h */
{0x0c, 0}, /* i */
{0x0d, 0}, /* j */
{0x0e, 0}, /* k */
{0x0f, 0}, /* l */
{0x10, 0}, /* m */
{0x11, 0}, /* n */
{0x12, 0}, /* o */
{0x13, 0}, /* p */
{0x14, 0}, /* q */
{0x15, 0}, /* r */
{0x16, 0}, /* s */
{0x17, 0}, /* t */
{0x18, 0}, /* u */
{0x19, 0}, /* v */
{0x1a, 0}, /* w */
{0x1b, 0}, /* x */
{0x1c, 0}, /* y */
{0x1d, 0}, /* z */
{0x2f, KEY_SHIFT}, /* { */
{0x64, KEY_SHIFT}, /* | */
{0x30, KEY_SHIFT}, /* } */
{0x32, KEY_SHIFT}, /* ~ */
{0,0}, /* DEL */
{0x3a, 0}, /* F1 */
{0x3b, 0}, /* F2 */
{0x3c, 0}, /* F3 */
{0x3d, 0}, /* F4 */
{0x3e, 0}, /* F5 */
{0x3f, 0}, /* F6 */
{0x40, 0}, /* F7 */
{0x41, 0}, /* F8 */
{0x42, 0}, /* F9 */
{0x43, 0}, /* F10 */
{0x44, 0}, /* F11 */
{0x45, 0}, /* F12 */
{0x46, 0}, /* PRINT_SCREEN */
{0x47, 0}, /* SCROLL_LOCK */
{0x39, 0}, /* CAPS_LOCK */
{0x53, 0}, /* NUM_LOCK */
{0x49, 0}, /* INSERT */
{0x4a, 0}, /* HOME */
{0x4b, 0}, /* PAGE_UP */
{0x4e, 0}, /* PAGE_DOWN */
{0x4f, 0}, /* RIGHT_ARROW */
{0x50, 0}, /* LEFT_ARROW */
{0x51, 0}, /* DOWN_ARROW */
{0x52, 0}, /* UP_ARROW */
};
#endif
#endif

96
src/HIDTypes.h Normal file
View File

@ -0,0 +1,96 @@
/* Copyright (c) 2010-2011 mbed.org, MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef USBCLASS_HID_TYPES
#define USBCLASS_HID_TYPES
#include <stdint.h>
/* */
#define HID_VERSION_1_11 (0x0111)
/* HID Class */
#define HID_CLASS (3)
#define HID_SUBCLASS_NONE (0)
#define HID_PROTOCOL_NONE (0)
/* Descriptors */
#define HID_DESCRIPTOR (33)
#define HID_DESCRIPTOR_LENGTH (0x09)
#define REPORT_DESCRIPTOR (34)
/* Class requests */
#define GET_REPORT (0x1)
#define GET_IDLE (0x2)
#define SET_REPORT (0x9)
#define SET_IDLE (0xa)
/* HID Class Report Descriptor */
/* Short items: size is 0, 1, 2 or 3 specifying 0, 1, 2 or 4 (four) bytes */
/* of data as per HID Class standard */
/* Main items */
#ifdef ARDUINO_ARCH_ESP32
#define HIDINPUT(size) (0x80 | size)
#define HIDOUTPUT(size) (0x90 | size)
#else
#define INPUT(size) (0x80 | size)
#define OUTPUT(size) (0x90 | size)
#endif
#define FEATURE(size) (0xb0 | size)
#define COLLECTION(size) (0xa0 | size)
#define END_COLLECTION(size) (0xc0 | size)
/* Global items */
#define USAGE_PAGE(size) (0x04 | size)
#define LOGICAL_MINIMUM(size) (0x14 | size)
#define LOGICAL_MAXIMUM(size) (0x24 | size)
#define PHYSICAL_MINIMUM(size) (0x34 | size)
#define PHYSICAL_MAXIMUM(size) (0x44 | size)
#define UNIT_EXPONENT(size) (0x54 | size)
#define UNIT(size) (0x64 | size)
#define REPORT_SIZE(size) (0x74 | size) //bits
#define REPORT_ID(size) (0x84 | size)
#define REPORT_COUNT(size) (0x94 | size) //bytes
#define PUSH(size) (0xa4 | size)
#define POP(size) (0xb4 | size)
/* Local items */
#define USAGE(size) (0x08 | size)
#define USAGE_MINIMUM(size) (0x18 | size)
#define USAGE_MAXIMUM(size) (0x28 | size)
#define DESIGNATOR_INDEX(size) (0x38 | size)
#define DESIGNATOR_MINIMUM(size) (0x48 | size)
#define DESIGNATOR_MAXIMUM(size) (0x58 | size)
#define STRING_INDEX(size) (0x78 | size)
#define STRING_MINIMUM(size) (0x88 | size)
#define STRING_MAXIMUM(size) (0x98 | size)
#define DELIMITER(size) (0xa8 | size)
/* HID Report */
/* Where report IDs are used the first byte of 'data' will be the */
/* report ID and 'length' will include this report ID byte. */
#define MAX_HID_REPORT_SIZE (64)
typedef struct {
uint32_t length;
uint8_t data[MAX_HID_REPORT_SIZE];
} HID_REPORT;
#endif

75
src/NimBLE2902.cpp Normal file
View File

@ -0,0 +1,75 @@
/*
* NimBLE2902.cpp
*
* Created: on March 10, 2020
* Author H2zero
*
* Originally:
*
* BLE2902.cpp
*
* Created on: Jun 25, 2017
* Author: kolban
*/
/*
* See also:
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLE2902.h"
NimBLE2902::NimBLE2902(NimBLECharacteristic* pCharacterisitic)
: NimBLEDescriptor(NimBLEUUID((uint16_t) 0x2902),
BLE_GATT_CHR_PROP_READ |
BLE_GATT_CHR_PROP_WRITE,
2, pCharacterisitic)
{
uint8_t data[2] = { 0, 0 };
setValue(data, 2);
} // NimBLE2902
/**
* @brief Get the notifications value.
* @return The notifications value. True if notifications are enabled and false if not.
*/
bool NimBLE2902::getNotifications() {
return (getValue()[0] & (1 << 0)) != 0;
} // getNotifications
/**
* @brief Get the indications value.
* @return The indications value. True if indications are enabled and false if not.
*/
bool NimBLE2902::getIndications() {
return (getValue()[0] & (1 << 1)) != 0;
} // getIndications
/**
* @brief Set the indications flag.
* @param [in] flag The indications flag.
*/
void NimBLE2902::setIndications(bool flag) {
uint8_t *pValue = getValue();
if (flag) pValue[0] |= 1 << 1;
else pValue[0] &= ~(1 << 1);
} // setIndications
/**
* @brief Set the notifications flag.
* @param [in] flag The notifications flag.
*/
void NimBLE2902::setNotifications(bool flag) {
uint8_t *pValue = getValue();
if (flag) pValue[0] |= 1 << 0;
else pValue[0] &= ~(1 << 0);
} // setNotifications
#endif

50
src/NimBLE2902.h Normal file
View File

@ -0,0 +1,50 @@
/*
* NimBLE2902.h
*
* Created: on March 10, 2020
* Author H2zero
*
* Originally:
*
* BLE2902.h
*
* Created on: Jun 25, 2017
* Author: kolban
*/
#ifndef MAIN_NIMBLE2902_H_
#define MAIN_NIMBLE2902_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEDescriptor.h"
#include <map>
#define NIMBLE_DESC_FLAG_NOTIFY 0x0001
#define NIMBLE_DESC_FLAG_INDICATE 0x0002
/**
* @brief Descriptor for Client Characteristic Configuration.
*
* This is a convenience descriptor for the Client Characteristic Configuration which has a UUID of 0x2902.
*
* See also:
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
*/
class NimBLE2902: public NimBLEDescriptor {
public:
bool getNotifications();
bool getIndications();
void setNotifications(bool flag);
void setIndications(bool flag);
private:
NimBLE2902(NimBLECharacteristic* pCharacterisitic);
friend class NimBLECharacteristic;
std::map<uint16_t, uint16_t> m_subscribedMap;
}; // NimBLE2902
#endif /* CONFIG_BT_ENABLED */
#endif /* MAIN_NIMBLE2902_H_ */

86
src/NimBLE2904.cpp Normal file
View File

@ -0,0 +1,86 @@
/*
* NimBLE2904.cpp
*
* Created: on March 13, 2020
* Author H2zero
*
* Originally:
*
* BLE2904.cpp
*
* Created on: Dec 23, 2017
* Author: kolban
*/
/*
* See also:
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLE2904.h"
NimBLE2904::NimBLE2904(NimBLECharacteristic* pCharacterisitic)
: NimBLEDescriptor(NimBLEUUID((uint16_t) 0x2904),
BLE_GATT_CHR_F_READ,
sizeof(BLE2904_Data),
pCharacterisitic)
{
m_data.m_format = 0;
m_data.m_exponent = 0;
m_data.m_namespace = 1; // 1 = Bluetooth SIG Assigned Numbers
m_data.m_unit = 0;
m_data.m_description = 0;
setValue((uint8_t*) &m_data, sizeof(m_data));
} // BLE2902
/**
* @brief Set the description.
*/
void NimBLE2904::setDescription(uint16_t description) {
m_data.m_description = description;
setValue((uint8_t*) &m_data, sizeof(m_data));
}
/**
* @brief Set the exponent.
*/
void NimBLE2904::setExponent(int8_t exponent) {
m_data.m_exponent = exponent;
setValue((uint8_t*) &m_data, sizeof(m_data));
} // setExponent
/**
* @brief Set the format.
*/
void NimBLE2904::setFormat(uint8_t format) {
m_data.m_format = format;
setValue((uint8_t*) &m_data, sizeof(m_data));
} // setFormat
/**
* @brief Set the namespace.
*/
void NimBLE2904::setNamespace(uint8_t namespace_value) {
m_data.m_namespace = namespace_value;
setValue((uint8_t*) &m_data, sizeof(m_data));
} // setNamespace
/**
* @brief Set the units for this value. It should be one of the encoded values defined here:
* https://www.bluetooth.com/specifications/assigned-numbers/units
* @param [in] unit The type of units of this characteristic as defined by assigned numbers.
*/
void NimBLE2904::setUnit(uint16_t unit) {
m_data.m_unit = unit;
setValue((uint8_t*) &m_data, sizeof(m_data));
} // setUnit
#endif

82
src/NimBLE2904.h Normal file
View File

@ -0,0 +1,82 @@
/*
* NimBLE2904.h
*
* Created: on March 13, 2020
* Author H2zero
*
* Originally:
*
* BLE2904.h
*
* Created on: Dec 23, 2017
* Author: kolban
*/
#ifndef MAIN_NIMBLE2904_H_
#define MAIN_NIMBLE2904_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEDescriptor.h"
struct BLE2904_Data {
uint8_t m_format;
int8_t m_exponent;
uint16_t m_unit; // See https://www.bluetooth.com/specifications/assigned-numbers/units
uint8_t m_namespace;
uint16_t m_description;
} __attribute__((packed));
/**
* @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:
static const uint8_t FORMAT_BOOLEAN = 1;
static const uint8_t FORMAT_UINT2 = 2;
static const uint8_t FORMAT_UINT4 = 3;
static const uint8_t FORMAT_UINT8 = 4;
static const uint8_t FORMAT_UINT12 = 5;
static const uint8_t FORMAT_UINT16 = 6;
static const uint8_t FORMAT_UINT24 = 7;
static const uint8_t FORMAT_UINT32 = 8;
static const uint8_t FORMAT_UINT48 = 9;
static const uint8_t FORMAT_UINT64 = 10;
static const uint8_t FORMAT_UINT128 = 11;
static const uint8_t FORMAT_SINT8 = 12;
static const uint8_t FORMAT_SINT12 = 13;
static const uint8_t FORMAT_SINT16 = 14;
static const uint8_t FORMAT_SINT24 = 15;
static const uint8_t FORMAT_SINT32 = 16;
static const uint8_t FORMAT_SINT48 = 17;
static const uint8_t FORMAT_SINT64 = 18;
static const uint8_t FORMAT_SINT128 = 19;
static const uint8_t FORMAT_FLOAT32 = 20;
static const uint8_t FORMAT_FLOAT64 = 21;
static const uint8_t FORMAT_SFLOAT16 = 22;
static const uint8_t FORMAT_SFLOAT32 = 23;
static const uint8_t FORMAT_IEEE20601 = 24;
static const uint8_t FORMAT_UTF8 = 25;
static const uint8_t FORMAT_UTF16 = 26;
static const uint8_t FORMAT_OPAQUE = 27;
void setDescription(uint16_t);
void setExponent(int8_t exponent);
void setFormat(uint8_t format);
void setNamespace(uint8_t namespace_value);
void setUnit(uint16_t unit);
private:
NimBLE2904(NimBLECharacteristic* pCharacterisitic);
friend class NimBLECharacteristic;
BLE2904_Data m_data;
}; // BLE2904
#endif /* CONFIG_BT_ENABLED */
#endif /* MAIN_NIMBLE2904_H_ */

108
src/NimBLEAddress.cpp Normal file
View File

@ -0,0 +1,108 @@
/*
* NimBLEAddress.cpp
*
* Created: on Jan 24 2020
* Author H2zero
*
* Originally:
*
* BLEAddress.cpp
*
* Created on: Jul 2, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEAddress.h"
#include "NimBLEUtils.h"
/*************************************************
NOTE: NimBLE addresses are in INVERSE ORDER!
We will accomodate that fact in these methods.
*************************************************/
/**
* @brief Create an address from the native ESP32 representation.
* @param [in] address The native representation.
*/
NimBLEAddress::NimBLEAddress(ble_addr_t address) {
memcpy(m_address, address.val, 6);
} // BLEAddress
/**
* @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 representation of the address.
*/
NimBLEAddress::NimBLEAddress(std::string stringAddress) {
if (stringAddress.length() != 17) return;
int data[6];
sscanf(stringAddress.c_str(), "%x:%x:%x:%x:%x:%x", &data[5], &data[4], &data[3], &data[2], &data[1], &data[0]);
m_address[0] = (uint8_t) data[0];
m_address[1] = (uint8_t) data[1];
m_address[2] = (uint8_t) data[2];
m_address[3] = (uint8_t) data[3];
m_address[4] = (uint8_t) data[4];
m_address[5] = (uint8_t) data[5];
} // BLEAddress
/**
* @brief Constructor for compatibility with bluedrioid esp library.
* @param [in] esp_bd_addr_t struct containing the address.
*/
NimBLEAddress::NimBLEAddress(esp_bd_addr_t address) {
NimBLEUtils::memrcpy(m_address, address, 6);
} // 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(NimBLEAddress otherAddress) {
return memcmp(otherAddress.getNative(), m_address, 6) == 0;
} // equals
/**
* @brief Return the native representation of the address.
* @return The native representation of the address.
*/
uint8_t *NimBLEAddress::getNative() {
return m_address;
} // getNative
/**
* @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.
*/
std::string NimBLEAddress::toString() {
auto size = 18;
char *res = (char*)malloc(size);
snprintf(res, size, "%02x:%02x:%02x:%02x:%02x:%02x", m_address[5], m_address[4], m_address[3], m_address[2], m_address[1], m_address[0]);
std::string ret(res);
free(res);
return ret;
} // toString
#endif

63
src/NimBLEAddress.h Normal file
View File

@ -0,0 +1,63 @@
/*
* NimBLEAddress.h
*
* Created: on Jan 24 2020
* Author H2zero
*
* Originally:
*
* BLEAddress.h
*
* Created on: Jul 2, 2017
* Author: kolban
*/
#ifndef COMPONENTS_NIMBLEADDRESS_H_
#define COMPONENTS_NIMBLEADDRESS_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "nimble/ble.h"
/**** FIX COMPILATION ****/
#undef min
#undef max
/**************************/
#include <string>
typedef enum {
BLE_ADDR_TYPE_PUBLIC = 0x00,
BLE_ADDR_TYPE_RANDOM = 0x01,
BLE_ADDR_TYPE_RPA_PUBLIC = 0x02,
BLE_ADDR_TYPE_RPA_RANDOM = 0x03,
} esp_nimble_addr_type_t;
typedef uint8_t esp_ble_addr_type_t ;
/// Bluetooth address length
#define ESP_BD_ADDR_LEN 6
/// Bluetooth device address
typedef uint8_t esp_bd_addr_t[ESP_BD_ADDR_LEN];
/**
* @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(ble_addr_t address);
NimBLEAddress(esp_bd_addr_t address);
NimBLEAddress(std::string stringAddress);
bool equals(NimBLEAddress otherAddress);
uint8_t* getNative();
std::string toString();
private:
uint8_t m_address[6];
};
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_NIMBLEADDRESS_H_ */

View File

@ -0,0 +1,546 @@
/*
* NimBLEAdvertisedDevice.cpp
*
* Created: on Jan 24 2020
* Author H2zero
*
* Originally:
*
* BLEAdvertisedDevice.cpp
*
* Created on: Jul 3, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEAdvertisedDevice.h"
#include "NimBLEUtils.h"
#include "NimBLELog.h"
static const char* LOG_TAG = "NimBLEAdvertisedDevice";
/**
* @brief Constructor
*/
NimBLEAdvertisedDevice::NimBLEAdvertisedDevice() {
m_advType = 0;
m_appearance = 0;
m_deviceType = 0;
m_manufacturerData = "";
m_name = "";
m_rssi = -9999;
m_serviceData = "";
m_txPower = 0;
m_pScan = nullptr;
m_haveAppearance = false;
m_haveManufacturerData = false;
m_haveName = false;
m_haveRSSI = false;
m_haveServiceData = false;
m_haveServiceUUID = false;
m_haveTXPower = false;
} // NimBLEAdvertisedDevice
/**
* @brief Get the address.
*
* Every %BLE device exposes an address that is used to identify it and subsequently connect to it.
* Call this function to obtain the address of the advertised device.
*
* @return The address of the advertised device.
*/
NimBLEAddress NimBLEAdvertisedDevice::getAddress() {
return m_address;
} // getAddress
/**
* @brief Get the appearance.
*
* A %BLE device can declare its own appearance. The appearance is how it would like to be shown to an end user
* typcially in the form of an icon.
*
* @return The appearance of the advertised device.
*/
uint16_t NimBLEAdvertisedDevice::getAppearance() {
return m_appearance;
} // getAppearance
/**
* @brief Get the manufacturer data.
* @return The manufacturer data of the advertised device.
*/
std::string NimBLEAdvertisedDevice::getManufacturerData() {
return m_manufacturerData;
} // getManufacturerData
/**
* @brief Get the name.
* @return The name of the advertised device.
*/
std::string NimBLEAdvertisedDevice::getName() {
return m_name;
} // getName
/**
* @brief Get the RSSI.
* @return The RSSI of the advertised device.
*/
int NimBLEAdvertisedDevice::getRSSI() {
return m_rssi;
} // getRSSI
/**
* @brief Get the scan object that created this advertisement.
* @return The scan object.
*/
NimBLEScan* NimBLEAdvertisedDevice::getScan() {
return m_pScan;
} // getScan
/**
* @brief Get the service data.
* @return The ServiceData of the advertised device.
*/
std::string NimBLEAdvertisedDevice::getServiceData() {
return m_serviceData;
} //getServiceData
/**
* @brief Get the service data UUID.
* @return The service data UUID.
*/
NimBLEUUID NimBLEAdvertisedDevice::getServiceDataUUID() {
return m_serviceDataUUID;
} // getServiceDataUUID
/**
* @brief Get the Service UUID.
* @return The Service UUID of the advertised device.
*/
NimBLEUUID NimBLEAdvertisedDevice::getServiceUUID() { //TODO Remove it eventually, is no longer useful
return m_serviceUUIDs[0];
} // getServiceUUID
/**
* @brief Check advertised serviced for existence required UUID
* @return Return true if service is advertised
*/
bool NimBLEAdvertisedDevice::isAdvertisingService(NimBLEUUID uuid){
for (int i = 0; i < m_serviceUUIDs.size(); i++) {
NIMBLE_LOGI(LOG_TAG, "Comparing UUIDS: %s %s", m_serviceUUIDs[i].toString().c_str(), uuid.toString().c_str());
if (m_serviceUUIDs[i].equals(uuid)) return true;
}
return false;
}
/**
* @brief Get the TX Power.
* @return The TX Power of the advertised device.
*/
int8_t NimBLEAdvertisedDevice::getTXPower() {
return m_txPower;
} // getTXPower
/**
* @brief Does this advertisement have an appearance value?
* @return True if there is an appearance value present.
*/
bool NimBLEAdvertisedDevice::haveAppearance() {
return m_haveAppearance;
} // haveAppearance
/**
* @brief Does this advertisement have manufacturer data?
* @return True if there is manufacturer data present.
*/
bool NimBLEAdvertisedDevice::haveManufacturerData() {
return m_haveManufacturerData;
} // haveManufacturerData
/**
* @brief Does this advertisement have a name value?
* @return True if there is a name value present.
*/
bool NimBLEAdvertisedDevice::haveName() {
return m_haveName;
} // haveName
/**
* @brief Does this advertisement have a signal strength value?
* @return True if there is a signal strength value present.
*/
bool NimBLEAdvertisedDevice::haveRSSI() {
return m_haveRSSI;
} // haveRSSI
/**
* @brief Does this advertisement have a service data value?
* @return True if there is a service data value present.
*/
bool NimBLEAdvertisedDevice::haveServiceData() {
return m_haveServiceData;
} // haveServiceData
/**
* @brief Does this advertisement have a service UUID value?
* @return True if there is a service UUID value present.
*/
bool NimBLEAdvertisedDevice::haveServiceUUID() {
return m_haveServiceUUID;
} // haveServiceUUID
/**
* @brief Does this advertisement have a transmission power value?
* @return True if there is a transmission power value present.
*/
bool NimBLEAdvertisedDevice::haveTXPower() {
return m_haveTXPower;
} // haveTXPower
/**
* @brief Parse the advertising pay load.
*
* The pay load is a buffer of bytes that is either 31 bytes long or terminated by
* a 0 length value. Each entry in the buffer has the format:
* [length][type][data...]
*
* The length does not include itself but does include everything after it until the next record. A record
* with a length value of 0 indicates a terminator.
*
* https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile
*/
void NimBLEAdvertisedDevice::parseAdvertisement(ble_hs_adv_fields *fields) {
//char s[BLE_HS_ADV_MAX_SZ];
uint8_t *u8p;
uint8_t length;
int i;
if (fields->uuids16 != NULL) {
for (i = 0; i < fields->num_uuids16; i++) {
setServiceUUID(NimBLEUUID(fields->uuids16[i].value));
}
}
if (fields->uuids32 != NULL) {
for (i = 0; i < fields->num_uuids32; i++) {
setServiceUUID(NimBLEUUID(fields->uuids32[i].value));
}
}
if (fields->uuids128 != NULL) {
for (i = 0; i < fields->num_uuids128; i++) {
setServiceUUID(NimBLEUUID(&fields->uuids128[i]));
}
}
if (fields->name != NULL) {
setName(std::string(reinterpret_cast<char*>(fields->name), fields->name_len));
}
if (fields->tx_pwr_lvl_is_present) {
setTXPower(fields->tx_pwr_lvl);
}
if (fields->svc_data_uuid16 != NULL) {
u8p = fields->svc_data_uuid16;
length = fields->svc_data_uuid16_len;
if (length < 2) {
NIMBLE_LOGE(LOG_TAG,"Length too small for ESP_BLE_AD_TYPE_SERVICE_DATA");
}
else{
uint16_t uuid = *(uint16_t*)u8p;
setServiceDataUUID(NimBLEUUID(uuid));
if (length > 2) {
setServiceData(std::string(reinterpret_cast<char*>(u8p + 2), length - 2));
}
}
}
if (fields->svc_data_uuid32 != NULL) {
u8p = fields->svc_data_uuid16;
length = fields->svc_data_uuid16_len;
if (length < 4) {
NIMBLE_LOGE(LOG_TAG,"Length too small for ESP_BLE_AD_TYPE_32SERVICE_DATA");
}
uint32_t uuid = *(uint32_t*) u8p;
setServiceDataUUID(NimBLEUUID(uuid));
if (length > 4) {
setServiceData(std::string(reinterpret_cast<char*>(u8p + 4), length - 4));
}
}
if (fields->svc_data_uuid128 != NULL) {
u8p = fields->svc_data_uuid16;
length = fields->svc_data_uuid16_len;
if (length < 16) {
NIMBLE_LOGE(LOG_TAG,"Length too small for ESP_BLE_AD_TYPE_128SERVICE_DATA");
}
setServiceDataUUID(NimBLEUUID(u8p, (size_t)16, false));
if (length > 16) {
setServiceData(std::string(reinterpret_cast<char*>(u8p + 16), length - 16));
}
}
if (fields->appearance_is_present) {
NIMBLE_LOGD(LOG_TAG, " appearance=0x%04x", fields->appearance);
setAppearance(fields->appearance);
}
/**** TODO: create storage and fucntions for these parameters
if (fields->public_tgt_addr != NULL) {
NIMBLE_LOGD(LOG_TAG, " public_tgt_addr=");
u8p = fields->public_tgt_addr;
for (i = 0; i < fields->num_public_tgt_addrs; i++) {
NIMBLE_LOGD(LOG_TAG, "public_tgt_addr=%s ", addr_str(u8p));
u8p += BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN;
}
NIMBLE_LOGD(LOG_TAG, "\n");
}
if (fields->slave_itvl_range != NULL) {
NIMBLE_LOGD(LOG_TAG, " slave_itvl_range=");
print_bytes(fields->slave_itvl_range, BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN);
NIMBLE_LOGD(LOG_TAG, "\n");
}
if (fields->adv_itvl_is_present) {
NIMBLE_LOGD(LOG_TAG, " adv_itvl=0x%04x\n", fields->adv_itvl);
}
if (fields->uri != NULL) {
NIMBLE_LOGD(LOG_TAG, " uri=");
print_bytes(fields->uri, fields->uri_len);
NIMBLE_LOGD(LOG_TAG, "\n");
}
*/
if (fields->mfg_data != NULL) {
setManufacturerData(std::string(reinterpret_cast<char*>(fields->mfg_data), fields->mfg_data_len));
}
} //parseAdvertisement
/**
* @brief Set the address of the advertised device.
* @param [in] address The address of the advertised device.
*/
void NimBLEAdvertisedDevice::setAddress(NimBLEAddress address) {
m_address = address;
} // setAddress
/**
* @brief Set the adFlag for this device.
* @param [in] The discovered adFlag.
*/
void NimBLEAdvertisedDevice::setAdvType(uint8_t advType) {
m_advType = advType;
} // setAdvType
/**
* @brief Set the appearance for this device.
* @param [in] The discovered appearance.
*/
void NimBLEAdvertisedDevice::setAppearance(uint16_t appearance) {
m_appearance = appearance;
m_haveAppearance = true;
NIMBLE_LOGD(LOG_TAG,"- appearance: %d", m_appearance);
} // setAppearance
/**
* @brief Set the manufacturer data for this device.
* @param [in] The discovered manufacturer data.
*/
void NimBLEAdvertisedDevice::setManufacturerData(std::string manufacturerData) {
m_manufacturerData = manufacturerData;
m_haveManufacturerData = true;
char* pHex = NimBLEUtils::buildHexData(nullptr, (uint8_t*) m_manufacturerData.data(), (uint8_t) m_manufacturerData.length());
NIMBLE_LOGD(LOG_TAG,"- manufacturer data: %s", pHex);
free(pHex);
} // setManufacturerData
/**
* @brief Set the name for this device.
* @param [in] name The discovered name.
*/
void NimBLEAdvertisedDevice::setName(std::string name) {
m_name = name;
m_haveName = true;
NIMBLE_LOGD(LOG_TAG,"- setName(): name: %s", m_name.c_str());
} // setName
/**
* @brief Set the RSSI for this device.
* @param [in] rssi The discovered RSSI.
*/
void NimBLEAdvertisedDevice::setRSSI(int rssi) {
m_rssi = rssi;
m_haveRSSI = true;
NIMBLE_LOGD(LOG_TAG,"- setRSSI(): rssi: %d", m_rssi);
} // setRSSI
/**
* @brief Set the Scan that created this advertised device.
* @param pScan The Scan that created this advertised device.
*/
void NimBLEAdvertisedDevice::setScan(NimBLEScan* pScan) {
m_pScan = pScan;
} // setScan
/**
* @brief Set the Service UUID for this device.
* @param [in] serviceUUID The discovered serviceUUID
*/
void NimBLEAdvertisedDevice::setServiceUUID(const char* serviceUUID) {
return setServiceUUID(NimBLEUUID(serviceUUID));
} // setServiceUUID
/**
* @brief Set the Service UUID for this device.
* @param [in] serviceUUID The discovered serviceUUID
*/
void NimBLEAdvertisedDevice::setServiceUUID(NimBLEUUID serviceUUID) {
m_serviceUUIDs.push_back(serviceUUID);
m_haveServiceUUID = true;
NIMBLE_LOGD(LOG_TAG,"- addServiceUUID(): serviceUUID: %s", serviceUUID.toString().c_str());
} // setServiceUUID
/**
* @brief Set the ServiceData value.
* @param [in] data ServiceData value.
*/
void NimBLEAdvertisedDevice::setServiceData(std::string serviceData) {
m_haveServiceData = true; // Set the flag that indicates we have service data.
m_serviceData = serviceData; // Save the service data that we received.
} //setServiceData
/**
* @brief Set the ServiceDataUUID value.
* @param [in] data ServiceDataUUID value.
*/
void NimBLEAdvertisedDevice::setServiceDataUUID(NimBLEUUID uuid) {
m_haveServiceData = true; // Set the flag that indicates we have service data.
m_serviceDataUUID = uuid;
} // setServiceDataUUID
/**
* @brief Set the power level for this device.
* @param [in] txPower The discovered power level.
*/
void NimBLEAdvertisedDevice::setTXPower(int8_t txPower) {
m_txPower = txPower;
m_haveTXPower = true;
NIMBLE_LOGD(LOG_TAG,"- txPower: %d", m_txPower);
} // setTXPower
/**
* @brief Create a string representation of this device.
* @return A string representation of this device.
*/
std::string NimBLEAdvertisedDevice::toString() {
std::string res = "Name: " + getName() + ", Address: " + getAddress().toString();
if (haveAppearance()) {
char val[6];
snprintf(val, sizeof(val), "%d", getAppearance());
res += ", appearance: ";
res += val;
}
if (haveManufacturerData()) {
char *pHex = NimBLEUtils::buildHexData(nullptr, (uint8_t*)getManufacturerData().data(), getManufacturerData().length());
res += ", manufacturer data: ";
res += pHex;
free(pHex);
}
if (haveServiceUUID()) {
res += ", serviceUUID: " + getServiceUUID().toString();
}
if (haveTXPower()) {
char val[4];
snprintf(val, sizeof(val), "%d", getTXPower());
res += ", txPower: ";
res += val;
}
res += ", advType: " + std::string(NimBLEUtils::advTypeToString(m_advType));
return res;
} // toString
uint8_t* NimBLEAdvertisedDevice::getPayload() {
return m_payload;
}
uint8_t NimBLEAdvertisedDevice::getAddressType() {
return m_addressType;
}
void NimBLEAdvertisedDevice::setAddressType(uint8_t type) {
m_addressType = type;
}
size_t NimBLEAdvertisedDevice::getPayloadLength() {
return m_payloadLength;
}
void NimBLEAdvertisedDevice::setAdvertisementResult(uint8_t* payload, uint8_t length){
m_payload = payload;
m_payloadLength = length;
}
#endif /* CONFIG_BT_ENABLED */

View File

@ -0,0 +1,133 @@
/*
* NimBLEAdvertisedDevice.h
*
* Created: on Jan 24 2020
* Author H2zero
*
* Originally:
*
* BLEAdvertisedDevice.h
*
* Created on: Jul 3, 2017
* Author: kolban
*/
#ifndef COMPONENTS_NIMBLEADVERTISEDDEVICE_H_
#define COMPONENTS_NIMBLEADVERTISEDDEVICE_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEAddress.h"
#include "NimBLEScan.h"
#include "NimBLEUUID.h"
#include "host/ble_hs_adv.h"
#include <map>
#include <vector>
class NimBLEScan;
/**
* @brief A representation of a %BLE advertised device found by a scan.
*
* When we perform a %BLE scan, the result will be a set of devices that are advertising. This
* class provides a model of a detected device.
*/
class NimBLEAdvertisedDevice {
public:
NimBLEAdvertisedDevice();
NimBLEAddress getAddress();
uint16_t getAppearance();
std::string getManufacturerData();
std::string getName();
int getRSSI();
NimBLEScan* getScan();
std::string getServiceData();
NimBLEUUID getServiceDataUUID();
NimBLEUUID getServiceUUID();
int8_t getTXPower();
uint8_t* getPayload();
size_t getPayloadLength();
uint8_t getAddressType();
void setAddressType(uint8_t type);
bool isAdvertisingService(NimBLEUUID uuid);
bool haveAppearance();
bool haveManufacturerData();
bool haveName();
bool haveRSSI();
bool haveServiceData();
bool haveServiceUUID();
bool haveTXPower();
std::string toString();
private:
friend class NimBLEScan;
void parseAdvertisement(ble_hs_adv_fields *fields);
void setAddress(NimBLEAddress address);
void setAdvType(uint8_t advType);
void setAdvertisementResult(uint8_t* payload, uint8_t length);
void setAppearance(uint16_t appearance);
void setManufacturerData(std::string manufacturerData);
void setName(std::string name);
void setRSSI(int rssi);
void setScan(NimBLEScan* pScan);
void setServiceData(std::string data);
void setServiceDataUUID(NimBLEUUID uuid);
void setServiceUUID(const char* serviceUUID);
void setServiceUUID(NimBLEUUID serviceUUID);
void setTXPower(int8_t txPower);
bool m_haveAppearance;
bool m_haveManufacturerData;
bool m_haveName;
bool m_haveRSSI;
bool m_haveServiceData;
bool m_haveServiceUUID;
bool m_haveTXPower;
NimBLEAddress m_address = NimBLEAddress("\0\0\0\0\0\0");
uint8_t m_advType;
uint16_t m_appearance;
int m_deviceType;
std::string m_manufacturerData;
std::string m_name;
NimBLEScan* m_pScan;
int m_rssi;
std::vector<NimBLEUUID> m_serviceUUIDs;
int8_t m_txPower;
std::string m_serviceData;
NimBLEUUID m_serviceDataUUID;
uint8_t* m_payload;
size_t m_payloadLength = 0;
uint8_t m_addressType;
};
/**
* @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;
virtual void onResult(NimBLEAdvertisedDevice* advertisedDevice) = 0;
};
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_NIMBLEADVERTISEDDEVICE_H_ */

617
src/NimBLEAdvertising.cpp Normal file
View File

@ -0,0 +1,617 @@
/*
* NimBLEAdvertising.cpp
*
* Created: on March 3, 2020
* Author H2zero
*
* Originally:
*
* BLEAdvertising.cpp
*
* This class encapsulates advertising a BLE Server.
* Created on: Jun 21, 2017
* Author: kolban
*
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "services/gap/ble_svc_gap.h"
#include "NimBLEAdvertising.h"
#include "NimBLEDevice.h"
#include "NimBLEServer.h"
#include "NimBLEUtils.h"
#include "NimBLELog.h"
static const char* LOG_TAG = "NimBLEAdvertising";
/**
* @brief Construct a default advertising object.
*
*/
NimBLEAdvertising::NimBLEAdvertising() {
memset(&m_advData, 0, sizeof m_advData);
memset(&m_scanData, 0, sizeof m_scanData);
memset(&m_advParams, 0, sizeof m_advParams);
const char *name = ble_svc_gap_device_name();
m_advData.name = (uint8_t *)name;
m_advData.name_len = strlen(name);
m_advData.name_is_complete = 1;
m_scanData.tx_pwr_lvl_is_present = 1;
m_scanData.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
m_advData.flags = (BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP);
m_advData.appearance = 0;
m_advData.appearance_is_present = 0;
m_advData.mfg_data_len = 0;
m_advData.mfg_data = nullptr;
m_advParams.conn_mode = BLE_GAP_CONN_MODE_UND;
m_advParams.disc_mode = BLE_GAP_DISC_MODE_GEN;
m_advParams.itvl_min = 0;
m_advParams.itvl_max = 0;
} // NimBLEAdvertising
/**
* @brief Add a service uuid to exposed list of services.
* @param [in] serviceUUID The UUID of the service to expose.
*/
void NimBLEAdvertising::addServiceUUID(NimBLEUUID serviceUUID) {
m_serviceUUIDs.push_back(serviceUUID);
} // addServiceUUID
/**
* @brief Add a service uuid to exposed list of services.
* @param [in] serviceUUID The string representation of the service to expose.
*/
void NimBLEAdvertising::addServiceUUID(const char* serviceUUID) {
addServiceUUID(NimBLEUUID(serviceUUID));
} // addServiceUUID
/**
* @brief Set the device appearance in the advertising data.
* The appearance attribute is of type 0x19. The codes for distinct appearances can be found here:
* 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.
* @return N/A.
*/
void NimBLEAdvertising::setAppearance(uint16_t appearance) {
m_advData.appearance = appearance;
m_advData.appearance_is_present = 1;
} // setAppearance
void NimBLEAdvertising::setAdvertisementType(uint8_t adv_type){
m_advParams.conn_mode = adv_type;
} // setAdvertisementType
void NimBLEAdvertising::setMinInterval(uint16_t mininterval) {
m_advParams.itvl_min = mininterval;
} // setMinInterval
void NimBLEAdvertising::setMaxInterval(uint16_t maxinterval) {
m_advParams.itvl_max = maxinterval;
} // setMaxInterval
// These are dummy functions for now for compatibility
void NimBLEAdvertising::setMinPreferred(uint16_t mininterval) {
//m_advData.min_interval = mininterval;
} //
void NimBLEAdvertising::setMaxPreferred(uint16_t maxinterval) {
//m_advData.max_interval = maxinterval;
} //
//////////////////////////////////////////////////////////
void NimBLEAdvertising::setScanResponse(bool set) {
m_scanResp = set;
}
/**
* @brief Set the filtering for the scan filter.
* @param [in] scanRequestWhitelistOnly If true, only allow scan requests from those on the white list.
* @param [in] connectWhitelistOnly If true, only allow connections from those on the white list.
*/
void NimBLEAdvertising::setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly) {
NIMBLE_LOGD(LOG_TAG, ">> setScanFilter: scanRequestWhitelistOnly: %d, connectWhitelistOnly: %d", scanRequestWhitelistOnly, connectWhitelistOnly);
if (!scanRequestWhitelistOnly && !connectWhitelistOnly) {
m_advParams.filter_policy = BLE_HCI_ADV_FILT_NONE;
NIMBLE_LOGD(LOG_TAG, "<< setScanFilter");
return;
}
if (scanRequestWhitelistOnly && !connectWhitelistOnly) {
m_advParams.filter_policy = BLE_HCI_ADV_FILT_SCAN;
NIMBLE_LOGD(LOG_TAG, "<< setScanFilter");
return;
}
if (!scanRequestWhitelistOnly && connectWhitelistOnly) {
m_advParams.filter_policy = BLE_HCI_ADV_FILT_CONN;
NIMBLE_LOGD(LOG_TAG, "<< setScanFilter");
return;
}
if (scanRequestWhitelistOnly && connectWhitelistOnly) {
m_advParams.filter_policy = BLE_HCI_ADV_FILT_BOTH;
NIMBLE_LOGD(LOG_TAG, "<< setScanFilter");
return;
}
} // setScanFilter
/**
* @brief Set the advertisement data that is to be published in a regular advertisement.
* @param [in] advertisementData The data to be advertised.
*/
void NimBLEAdvertising::setAdvertisementData(NimBLEAdvertisementData& advertisementData) {
NIMBLE_LOGD(LOG_TAG, ">> setAdvertisementData");
int rc = ble_gap_adv_set_data(
(uint8_t*)advertisementData.getPayload().data(),
advertisementData.getPayload().length());
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gap_adv_set_data: %d %s", rc, NimBLEUtils::returnCodeToString(rc));
}
m_customAdvData = true; // Set the flag that indicates we are using custom advertising data.
NIMBLE_LOGD(LOG_TAG, "<< setAdvertisementData");
} // setAdvertisementData
/**
* @brief Set the advertisement data that is to be published in a scan response.
* @param [in] advertisementData The data to be advertised.
*/
void NimBLEAdvertising::setScanResponseData(NimBLEAdvertisementData& advertisementData) {
NIMBLE_LOGD(LOG_TAG, ">> setScanResponseData");
int rc = ble_gap_adv_rsp_set_data(
(uint8_t*)advertisementData.getPayload().data(),
advertisementData.getPayload().length());
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gap_adv_rsp_set_data: %d %s", rc, NimBLEUtils::returnCodeToString(rc));
}
m_customScanResponseData = true; // Set the flag that indicates we are using custom scan response data.
NIMBLE_LOGD(LOG_TAG, "<< setScanResponseData");
} // setScanResponseData
/**
* @brief Start advertising.
* Start advertising.
* @return N/A.
*/
void NimBLEAdvertising::start() {
NIMBLE_LOGD(LOG_TAG, ">> Advertising start: customAdvData: %d, customScanResponseData: %d", m_customAdvData, m_customScanResponseData);
// If Host is not synced we cannot start advertising.
if(!NimBLEDevice::m_synced) {
NIMBLE_LOGE(LOG_TAG, "Host reset, wait for sync.");
return;
}
if(NimBLEDevice::createServer()->getConnectedCount() >= NIMBLE_MAX_CONNECTIONS) {
NIMBLE_LOGW(LOG_TAG, "Max connections reached - not advertising");
return;
}
int numServices = m_serviceUUIDs.size();
int rc = 0;
uint8_t addressType;
uint8_t payloadLen = 3; //start with 3 bytes for the flags data
// If already advertising just return
if(ble_gap_adv_active()) {
return;
}
NimBLEServer* pServer = NimBLEDevice::createServer();
if(!pServer->m_gattsStarted){
pServer->start();
}
if (!m_customAdvData && !m_advSvcsSet && numServices > 0) {
for (int i = 0; i < numServices; i++) {
if(m_serviceUUIDs[i].getNative()->u.type == BLE_UUID_TYPE_16) {
int add = (m_advData.num_uuids16 > 0) ? 2 : 4;
if((payloadLen + add) > 31){
m_advData.uuids16_is_complete = 0;
continue;
}
payloadLen += add;
if(nullptr == (m_advData.uuids16 = (ble_uuid16_t*)realloc(m_advData.uuids16,
(m_advData.num_uuids16 + 1) * sizeof(ble_uuid16_t))))
{
NIMBLE_LOGE(LOG_TAG, "Error, no mem");
abort();
}
memcpy(&m_advData.uuids16[m_advData.num_uuids16].value,
&m_serviceUUIDs[i].getNative()->u16.value, sizeof(uint16_t));
m_advData.uuids16[m_advData.num_uuids16].u.type = BLE_UUID_TYPE_16;
/*
char buf[BLE_UUID_STR_LEN];
ble_uuid_to_str(&m_advData.uuids16[m_advData.num_uuids16].u, buf);
NIMBLE_LOGI(LOG_TAG, "Advertising UUID: %s", buf);
*/
m_advData.uuids16_is_complete = 1;
m_advData.num_uuids16++;
}
if(m_serviceUUIDs[i].getNative()->u.type == BLE_UUID_TYPE_32) {
int add = (m_advData.num_uuids32 > 0) ? 4 : 6;
if((payloadLen + add) > 31){
m_advData.uuids32_is_complete = 0;
continue;
}
payloadLen += add;
if(nullptr == (m_advData.uuids32 = (ble_uuid32_t*)realloc(m_advData.uuids32,
(m_advData.num_uuids32 + 1) * sizeof(ble_uuid32_t))))
{
NIMBLE_LOGE(LOG_TAG, "Error, no mem");
abort();
}
memcpy(&m_advData.uuids32[m_advData.num_uuids32].value,
&m_serviceUUIDs[i].getNative()->u32.value, sizeof(uint32_t));
m_advData.uuids32[m_advData.num_uuids32].u.type = BLE_UUID_TYPE_32;
/*
char buf[BLE_UUID_STR_LEN];
ble_uuid_to_str(&m_advData.uuids32[m_advData.num_uuids32].u, buf);
NIMBLE_LOGI(LOG_TAG, "Advertising UUID: %s", buf);
*/
m_advData.uuids32_is_complete = 1;
m_advData.num_uuids32++;
}
if(m_serviceUUIDs[i].getNative()->u.type == BLE_UUID_TYPE_128){
int add = (m_advData.num_uuids128 > 0) ? 16 : 18;
if((payloadLen + add) > 31){
m_advData.uuids128_is_complete = 0;
continue;
}
payloadLen += add;
if(nullptr == (m_advData.uuids128 = (ble_uuid128_t*)realloc(m_advData.uuids128,
(m_advData.num_uuids128 + 1) * sizeof(ble_uuid128_t)))) {
NIMBLE_LOGE(LOG_TAG, "Error, no mem");
abort();
}
memcpy(&m_advData.uuids128[m_advData.num_uuids128].value,
&m_serviceUUIDs[i].getNative()->u128.value, 16);
m_advData.uuids128[m_advData.num_uuids128].u.type = BLE_UUID_TYPE_128;
/*
char buf[BLE_UUID_STR_LEN];
ble_uuid_to_str(&m_advData.uuids128[m_advData.num_uuids128].u, buf);
NIMBLE_LOGI(LOG_TAG, "Advertising UUID: %s", buf);
*/
m_advData.uuids128_is_complete = 1;
m_advData.num_uuids128++;
}
}
// check if there is room for the name, if not put it in scan data
if((payloadLen + m_advData.name_len) > 29) {
if(m_scanResp){
m_scanData.name = m_advData.name;
m_scanData.name_len = m_advData.name_len;
m_scanData.name_is_complete = m_advData.name_is_complete;
m_advData.name = nullptr;
m_advData.name_len = 0;
} else {
// if not using scan response just cut the name down
// leaving 2 bytes for the data specifier.
m_advData.name_len = (29 - payloadLen);
}
m_advData.name_is_complete = 0;
}
if(m_advData.name_len > 0) {
payloadLen += (m_advData.name_len + 2);
}
if(m_scanResp) {
// name length + type byte + length byte + tx power type + length + data
if((m_scanData.name_len + 5) > 31) {
// prioritize name data over tx power
m_scanData.tx_pwr_lvl_is_present = 0;
m_scanData.tx_pwr_lvl = 0;
// limit name to 29 to leave room for the data specifiers
if(m_scanData.name_len > 29) {
m_scanData.name_len = 29;
m_scanData.name_is_complete = false;
}
}
rc = ble_gap_adv_rsp_set_fields(&m_scanData);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "error setting scan response data; rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc));
abort();
}
// if not using scan response and there is room,
// throw the tx power data into the advertisment
} else if (payloadLen < 29) {
m_advData.tx_pwr_lvl_is_present = 1;
m_advData.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
}
rc = ble_gap_adv_set_fields(&m_advData);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "error setting advertisement data; rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc));
abort();
}
if(m_advData.num_uuids128 > 0) {
free(m_advData.uuids128);
m_advData.uuids128 = nullptr;
m_advData.num_uuids128 = 0;
}
if(m_advData.num_uuids32 > 0) {
free(m_advData.uuids32);
m_advData.uuids32 = nullptr;
m_advData.num_uuids32 = 0;
}
if(m_advData.num_uuids16 > 0) {
free(m_advData.uuids16);
m_advData.uuids16 = nullptr;
m_advData.num_uuids16 = 0;
}
m_advSvcsSet = true;
}
rc = ble_hs_id_infer_auto(0, &addressType);
if (rc != 0) {
NIMBLE_LOGC(LOG_TAG, "Error determining address type; rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc));
abort();
}
rc = ble_gap_adv_start(addressType, NULL, BLE_HS_FOREVER,
&m_advParams, NimBLEServer::handleGapEvent, NimBLEDevice::createServer()); //get a reference to the server (does not create a new one)
if (rc != 0) {
NIMBLE_LOGC(LOG_TAG, "Error enabling advertising; rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc));
abort();
}
NIMBLE_LOGD(LOG_TAG, "<< Advertising start");
} // start
/**
* @brief Stop advertising.
* Stop advertising.
* @return N/A.
*/
void NimBLEAdvertising::stop() {
NIMBLE_LOGD(LOG_TAG, ">> stop");
int rc = ble_gap_adv_stop();
if (rc != 0 && rc != BLE_HS_EALREADY) {
NIMBLE_LOGE(LOG_TAG, "ble_gap_adv_stop rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
return;
}
NIMBLE_LOGD(LOG_TAG, "<< stop");
} // stop
/*
void NimBLEAdvertising::onHostReset() {
// m_advSvcsSet = false;
}
*/
/**
* @brief Add data to the payload to be advertised.
* @param [in] data The data to be added to the payload.
*/
void NimBLEAdvertisementData::addData(std::string data) {
if ((m_payload.length() + data.length()) > BLE_HS_ADV_MAX_SZ) {
return;
}
m_payload.append(data);
} // addData
/**
* @brief Set the appearance.
* @param [in] appearance The appearance code value.
*
* See also:
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.gap.appearance.xml
*/
void NimBLEAdvertisementData::setAppearance(uint16_t appearance) {
char cdata[2];
cdata[0] = 3;
cdata[1] = BLE_HS_ADV_TYPE_APPEARANCE; // 0x19
addData(std::string(cdata, 2) + std::string((char*) &appearance, 2));
} // setAppearance
/**
* @brief Set the complete services.
* @param [in] uuid The single service to advertise.
*/
void NimBLEAdvertisementData::setCompleteServices(NimBLEUUID uuid) {
char cdata[2];
switch (uuid.bitSize()) {
case 16: {
// [Len] [0x02] [LL] [HH]
cdata[0] = 3;
cdata[1] = BLE_HS_ADV_TYPE_COMP_UUIDS16; // 0x03
addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->u16.value, 2));
break;
}
case 32: {
// [Len] [0x04] [LL] [LL] [HH] [HH]
cdata[0] = 5;
cdata[1] = BLE_HS_ADV_TYPE_COMP_UUIDS32; // 0x05
addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->u32.value, 4));
break;
}
case 128: {
// [Len] [0x04] [0] [1] ... [15]
cdata[0] = 17;
cdata[1] = BLE_HS_ADV_TYPE_COMP_UUIDS128; // 0x07
addData(std::string(cdata, 2) + std::string((char*) uuid.getNative()->u128.value, 16));
break;
}
default:
return;
}
} // setCompleteServices
/**
* @brief Set the advertisement flags.
* @param [in] The flags to be set in the advertisement.
* * ****DO NOT USE THESE****
* * ESP_BLE_ADV_FLAG_LIMIT_DISC
* * ESP_BLE_ADV_FLAG_GEN_DISC
* * ESP_BLE_ADV_FLAG_BREDR_NOT_SPT
* * ESP_BLE_ADV_FLAG_DMT_CONTROLLER_SPT
* * ESP_BLE_ADV_FLAG_DMT_HOST_SPT
* * ESP_BLE_ADV_FLAG_NON_LIMIT_DISC
* *
* * ****THESE ARE SUPPORTED****
* * BLE_HS_ADV_F_DISC_LTD
* * BLE_HS_ADV_F_DISC_GEN
* * BLE_HS_ADV_F_BREDR_UNSUP - must always use with NimBLE
*/
void NimBLEAdvertisementData::setFlags(uint8_t flag) {
char cdata[3];
cdata[0] = 2;
cdata[1] = BLE_HS_ADV_TYPE_FLAGS; // 0x01
cdata[2] = flag | BLE_HS_ADV_F_BREDR_UNSUP;
addData(std::string(cdata, 3));
} // setFlag
/**
* @brief Set manufacturer specific data.
* @param [in] data Manufacturer data.
*/
void NimBLEAdvertisementData::setManufacturerData(std::string data) {
NIMBLE_LOGD("NimBLEAdvertisementData", ">> setManufacturerData");
char cdata[2];
cdata[0] = data.length() + 1;
cdata[1] = BLE_HS_ADV_TYPE_MFG_DATA ; // 0xff
addData(std::string(cdata, 2) + data);
NIMBLE_LOGD("NimBLEAdvertisementData", "<< setManufacturerData");
} // setManufacturerData
/**
* @brief Set the name.
* @param [in] The complete name of the device.
*/
void NimBLEAdvertisementData::setName(std::string name) {
NIMBLE_LOGD("NimBLEAdvertisementData", ">> setName: %s", name.c_str());
char cdata[2];
cdata[0] = name.length() + 1;
cdata[1] = BLE_HS_ADV_TYPE_COMP_NAME; // 0x09
addData(std::string(cdata, 2) + name);
NIMBLE_LOGD("NimBLEAdvertisementData", "<< setName");
} // setName
/**
* @brief Set the partial services.
* @param [in] uuid The single service to advertise.
*/
void NimBLEAdvertisementData::setPartialServices(NimBLEUUID uuid) {
char cdata[2];
switch (uuid.bitSize()) {
case 16: {
// [Len] [0x02] [LL] [HH]
cdata[0] = 3;
cdata[1] = BLE_HS_ADV_TYPE_INCOMP_UUIDS16; // 0x02
addData(std::string(cdata, 2) + std::string((char *) &uuid.getNative()->u16.value, 2));
break;
}
case 32: {
// [Len] [0x04] [LL] [LL] [HH] [HH]
cdata[0] = 5;
cdata[1] = BLE_HS_ADV_TYPE_INCOMP_UUIDS32; // 0x04
addData(std::string(cdata, 2) + std::string((char *) &uuid.getNative()->u32.value, 4));
break;
}
case 128: {
// [Len] [0x04] [0] [1] ... [15]
cdata[0] = 17;
cdata[1] = BLE_HS_ADV_TYPE_INCOMP_UUIDS128; // 0x06
addData(std::string(cdata, 2) + std::string((char *) &uuid.getNative()->u128.value, 16));
break;
}
default:
return;
}
} // setPartialServices
/**
* @brief Set the service data (UUID + data)
* @param [in] uuid The UUID to set with the service data. Size of UUID will be used.
* @param [in] data The data to be associated with the service data advert.
*/
void NimBLEAdvertisementData::setServiceData(NimBLEUUID uuid, std::string data) {
char cdata[2];
switch (uuid.bitSize()) {
case 16: {
// [Len] [0x16] [UUID16] data
cdata[0] = data.length() + 3;
cdata[1] = BLE_HS_ADV_TYPE_SVC_DATA_UUID16; // 0x16
addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->u16.value, 2) + data);
break;
}
case 32: {
// [Len] [0x20] [UUID32] data
cdata[0] = data.length() + 5;
cdata[1] = BLE_HS_ADV_TYPE_SVC_DATA_UUID32; // 0x20
addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->u32.value, 4) + data);
break;
}
case 128: {
// [Len] [0x21] [UUID128] data
cdata[0] = data.length() + 17;
cdata[1] = BLE_HS_ADV_TYPE_SVC_DATA_UUID128; // 0x21
addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->u128.value, 16) + data);
break;
}
default:
return;
}
} // setServiceData
/**
* @brief Set the short name.
* @param [in] The short name of the device.
*/
void NimBLEAdvertisementData::setShortName(std::string name) {
NIMBLE_LOGD("NimBLEAdvertisementData", ">> setShortName: %s", name.c_str());
char cdata[2];
cdata[0] = name.length() + 1;
cdata[1] = BLE_HS_ADV_TYPE_INCOMP_NAME; // 0x08
addData(std::string(cdata, 2) + name);
NIMBLE_LOGD("NimBLEAdvertisementData", "<< setShortName");
} // setShortName
/**
* @brief Retrieve the payload that is to be advertised.
* @return The payload that is to be advertised.
*/
std::string NimBLEAdvertisementData::getPayload() {
return m_payload;
} // getPayload
#endif /* CONFIG_BT_ENABLED */

101
src/NimBLEAdvertising.h Normal file
View File

@ -0,0 +1,101 @@
/*
* NimBLEAdvertising.h
*
* Created: on March 3, 2020
* Author H2zero
*
* Originally:
*
* BLEAdvertising.h
*
* Created on: Jun 21, 2017
* Author: kolban
*/
#ifndef MAIN_BLEADVERTISING_H_
#define MAIN_BLEADVERTISING_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "host/ble_gap.h"
#include "NimBLEUUID.h"
#include "FreeRTOS.h"
#include <vector>
/* COMPATIBILITY - DO NOT USE */
#define ESP_BLE_ADV_FLAG_LIMIT_DISC (0x01 << 0)
#define ESP_BLE_ADV_FLAG_GEN_DISC (0x01 << 1)
#define ESP_BLE_ADV_FLAG_BREDR_NOT_SPT (0x01 << 2)
#define ESP_BLE_ADV_FLAG_DMT_CONTROLLER_SPT (0x01 << 3)
#define ESP_BLE_ADV_FLAG_DMT_HOST_SPT (0x01 << 4)
#define ESP_BLE_ADV_FLAG_NON_LIMIT_DISC (0x00 )
/* ************************* */
/**
* @brief Advertisement data set by the programmer to be published by the %BLE server.
*/
class NimBLEAdvertisementData {
// Only a subset of the possible BLE architected advertisement fields are currently exposed. Others will
// be exposed on demand/request or as time permits.
//
public:
void setAppearance(uint16_t appearance);
void setCompleteServices(NimBLEUUID uuid);
void setFlags(uint8_t);
void setManufacturerData(std::string data);
void setName(std::string name);
void setPartialServices(NimBLEUUID uuid);
void setServiceData(NimBLEUUID uuid, std::string data);
void setShortName(std::string name);
void addData(std::string data); // Add data to the payload.
std::string getPayload(); // Retrieve the current advert payload.
private:
friend class NimBLEAdvertising;
std::string m_payload; // The payload of the advertisement.
}; // NimBLEAdvertisementData
/**
* @brief Perform and manage %BLE advertising.
*
* A %BLE server will want to perform advertising in order to make itself known to %BLE clients.
*/
class NimBLEAdvertising {
public:
NimBLEAdvertising();
void addServiceUUID(NimBLEUUID serviceUUID);
void addServiceUUID(const char* serviceUUID);
void start();
void stop();
void setAppearance(uint16_t appearance);
void setAdvertisementType(uint8_t adv_type);
void setMaxInterval(uint16_t maxinterval);
void setMinInterval(uint16_t mininterval);
void setAdvertisementData(NimBLEAdvertisementData& advertisementData);
void setScanFilter(bool scanRequertWhitelistOnly, bool connectWhitelistOnly);
void setScanResponseData(NimBLEAdvertisementData& advertisementData);
void setPrivateAddress(uint8_t type = BLE_ADDR_RANDOM);
void setMinPreferred(uint16_t);
void setMaxPreferred(uint16_t);
void setScanResponse(bool);
private:
friend class NimBLEDevice;
// void onHostReset();
ble_hs_adv_fields m_advData;
ble_hs_adv_fields m_scanData;
ble_gap_adv_params m_advParams;
std::vector<NimBLEUUID> m_serviceUUIDs;
bool m_customAdvData = false; // Are we using custom advertising data?
bool m_customScanResponseData = false; // Are we using custom scan response data?
bool m_scanResp = true;
bool m_advSvcsSet = false;
};
#endif /* CONFIG_BT_ENABLED */
#endif /* MAIN_BLEADVERTISING_H_ */

92
src/NimBLEBeacon.cpp Normal file
View File

@ -0,0 +1,92 @@
/*
* NimBLEBeacon2.cpp
*
* Created: on March 15 2020
* Author H2zero
*
* Originally:
*
* BLEBeacon.cpp
*
* Created on: Jan 4, 2018
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <string.h>
#include "NimBLEBeacon.h"
#include "NimBLELog.h"
#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00)>>8) + (((x)&0xFF)<<8))
static const char* LOG_TAG = "NimBLEBeacon";
NimBLEBeacon::NimBLEBeacon() {
m_beaconData.manufacturerId = 0x4c00;
m_beaconData.subType = 0x02;
m_beaconData.subTypeLength = 0x15;
m_beaconData.major = 0;
m_beaconData.minor = 0;
m_beaconData.signalPower = 0;
memset(m_beaconData.proximityUUID, 0, sizeof(m_beaconData.proximityUUID));
} // NimBLEBeacon
std::string NimBLEBeacon::getData() {
return std::string((char*) &m_beaconData, sizeof(m_beaconData));
} // getData
uint16_t NimBLEBeacon::getMajor() {
return m_beaconData.major;
}
uint16_t NimBLEBeacon::getManufacturerId() {
return m_beaconData.manufacturerId;
}
uint16_t NimBLEBeacon::getMinor() {
return m_beaconData.minor;
}
NimBLEUUID NimBLEBeacon::getProximityUUID() {
return NimBLEUUID(m_beaconData.proximityUUID, 16, false);
}
int8_t NimBLEBeacon::getSignalPower() {
return m_beaconData.signalPower;
}
/**
* Set the raw data for the beacon record.
*/
void NimBLEBeacon::setData(std::string data) {
if (data.length() != sizeof(m_beaconData)) {
NIMBLE_LOGE(LOG_TAG, "Unable to set the data ... length passed in was %d and expected %d",
data.length(), sizeof(m_beaconData));
return;
}
memcpy(&m_beaconData, data.data(), sizeof(m_beaconData));
} // setData
void NimBLEBeacon::setMajor(uint16_t major) {
m_beaconData.major = ENDIAN_CHANGE_U16(major);
} // setMajor
void NimBLEBeacon::setManufacturerId(uint16_t manufacturerId) {
m_beaconData.manufacturerId = ENDIAN_CHANGE_U16(manufacturerId);
} // setManufacturerId
void NimBLEBeacon::setMinor(uint16_t minor) {
m_beaconData.minor = ENDIAN_CHANGE_U16(minor);
} // setMinior
void NimBLEBeacon::setProximityUUID(NimBLEUUID uuid) {
uuid = uuid.to128();
memcpy(m_beaconData.proximityUUID, uuid.getNative()->u128.value, 16);
} // setProximityUUID
void NimBLEBeacon::setSignalPower(int8_t signalPower) {
m_beaconData.signalPower = signalPower;
} // setSignalPower
#endif

50
src/NimBLEBeacon.h Normal file
View File

@ -0,0 +1,50 @@
/*
* NimBLEBeacon2.h
*
* Created: on March 15 2020
* Author H2zero
*
* Originally:
*
* BLEBeacon2.h
*
* Created on: Jan 4, 2018
* Author: kolban
*/
#ifndef MAIN_NIMBLEBEACON_H_
#define MAIN_NIMBLEBEACON_H_
#include "NimBLEUUID.h"
/**
* @brief Representation of a beacon.
* See:
* * https://en.wikipedia.org/wiki/IBeacon
*/
class NimBLEBeacon {
private:
struct {
uint16_t manufacturerId;
uint8_t subType;
uint8_t subTypeLength;
uint8_t proximityUUID[16];
uint16_t major;
uint16_t minor;
int8_t signalPower;
} __attribute__((packed)) m_beaconData;
public:
NimBLEBeacon();
std::string getData();
uint16_t getMajor();
uint16_t getMinor();
uint16_t getManufacturerId();
NimBLEUUID getProximityUUID();
int8_t getSignalPower();
void setData(std::string data);
void setMajor(uint16_t major);
void setMinor(uint16_t minor);
void setManufacturerId(uint16_t manufacturerId);
void setProximityUUID(NimBLEUUID uuid);
void setSignalPower(int8_t signalPower);
}; // NimBLEBeacon
#endif /* MAIN_NIMBLEBEACON_H_ */

View File

@ -0,0 +1,615 @@
/*
* NimBLECharacteristic.cpp
*
* Created: on March 3, 2020
* Author H2zero
*
* BLECharacteristic.cpp
*
* Created on: Jun 22, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLECharacteristic.h"
#include "NimBLE2902.h"
#include "NimBLE2904.h"
#include "NimBLEDevice.h"
#include "NimBLEUtils.h"
#include "NimBLELog.h"
#include <string>
#define NULL_HANDLE (0xffff)
static NimBLECharacteristicCallbacks defaultCallback;
static const char* LOG_TAG = "NimBLECharacteristic";
/**
* @brief Construct a characteristic
* @param [in] uuid - UUID (const char*) for the characteristic.
* @param [in] properties - Properties for the characteristic.
*/
NimBLECharacteristic::NimBLECharacteristic(const char* uuid, uint16_t properties, NimBLEService* pService)
: NimBLECharacteristic(NimBLEUUID(uuid), properties, pService) {
}
/**
* @brief Construct a characteristic
* @param [in] uuid - UUID for the characteristic.
* @param [in] properties - Properties for the characteristic.
*/
NimBLECharacteristic::NimBLECharacteristic(NimBLEUUID uuid, uint16_t properties, NimBLEService* pService) {
m_uuid = uuid;
m_handle = NULL_HANDLE;
m_properties = properties;
m_pCallbacks = &defaultCallback;
m_pService = pService;
// Backward Compatibility - to be removed
/* setBroadcastProperty((properties & PROPERTY_BROADCAST) != 0);
setReadProperty((properties & PROPERTY_READ) != 0);
setWriteProperty((properties & PROPERTY_WRITE) != 0);
setNotifyProperty((properties & PROPERTY_NOTIFY) != 0);
setIndicateProperty((properties & PROPERTY_INDICATE) != 0);
setWriteNoResponseProperty((properties & PROPERTY_WRITE_NR) != 0);
*/
///////////////////////////////////////////
} // NimBLECharacteristic
/**
* @brief Destructor.
*/
NimBLECharacteristic::~NimBLECharacteristic() {
} // ~NimBLECharacteristic
/**
* @brief Associate a descriptor with this characteristic.
* @param [in] pDescriptor
* @return N/A.
*/
void NimBLECharacteristic::addDescriptor(NimBLEDescriptor* pDescriptor) {
NIMBLE_LOGD(LOG_TAG, ">> addDescriptor(): Adding %s to %s", pDescriptor->toString().c_str(), toString().c_str());
// Check that we don't add the same descriptor twice.
if (m_descriptorMap.getByUUID(pDescriptor->getUUID()) != nullptr) {
NIMBLE_LOGW(LOG_TAG, "<< Adding a new descriptor with the same UUID as a previous one");
//return;
}
m_descriptorMap.setByUUID(pDescriptor->getUUID(), pDescriptor);
NIMBLE_LOGD(LOG_TAG, "<< addDescriptor()");
} // addDescriptor
/**
* @brief Create a new BLE Descriptor associated with this characteristic.
* @param [in] uuid - The UUID of the descriptor.
* @param [in] properties - The properties of the descriptor.
* @return The new BLE descriptor.
*/
NimBLEDescriptor* NimBLECharacteristic::createDescriptor(const char* uuid, uint32_t properties, uint16_t max_len) {
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.
* @param [in] properties - The properties of the descriptor.
* @return The new BLE descriptor.
*/
NimBLEDescriptor* NimBLECharacteristic::createDescriptor(NimBLEUUID uuid, uint32_t properties, uint16_t max_len) {
NimBLEDescriptor* pDescriptor = nullptr;
if(uuid.equals(NimBLEUUID((uint16_t)0x2902))) {
pDescriptor = new NimBLE2902(this);
} else if (uuid.equals(NimBLEUUID((uint16_t)0x2904))) {
pDescriptor = new NimBLE2904(this);
} else {
pDescriptor = new NimBLEDescriptor(uuid, properties, max_len, this);
}
addDescriptor(pDescriptor);
return pDescriptor;
} // createCharacteristic
/**
* @brief Return the BLE Descriptor for the given UUID if associated with this characteristic.
* @param [in] descriptorUUID The UUID of the descriptor that we wish to retrieve.
* @return The BLE Descriptor. If no such descriptor is associated with the characteristic, nullptr is returned.
*/
NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const char* descriptorUUID) {
return m_descriptorMap.getByUUID(NimBLEUUID(descriptorUUID));
} // getDescriptorByUUID
/**
* @brief Return the BLE Descriptor for the given UUID if associated with this characteristic.
* @param [in] descriptorUUID The UUID of the descriptor that we wish to retrieve.
* @return The BLE Descriptor. If no such descriptor is associated with the characteristic, nullptr is returned.
*/
NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(NimBLEUUID descriptorUUID) {
return m_descriptorMap.getByUUID(descriptorUUID);
} // getDescriptorByUUID
/**
* @brief Get the handle of the characteristic.
* @return The handle of the characteristic.
*/
uint16_t NimBLECharacteristic::getHandle() {
return m_handle;
} // getHandle
/*
void NimBLECharacteristic::setAccessPermissions(uint16_t perm) {
m_permissions = perm;
}
*/
uint8_t NimBLECharacteristic::getProperties() {
return m_properties;
} // getProperties
/**
* @brief Get the service associated with this characteristic.
*/
NimBLEService* NimBLECharacteristic::getService() {
return m_pService;
} // getService
/**
* @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 A pointer to storage containing the current characteristic value.
*/
std::string NimBLECharacteristic::getValue() {
return m_value.getValue();
} // getValue
/**
* @brief Retrieve the current raw data of the characteristic.
* @return A pointer to storage containing the current characteristic data.
*/
uint8_t* NimBLECharacteristic::getData() {
return m_value.getData();
} // getData
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;
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: {
pCharacteristic->m_pCallbacks->onRead(pCharacteristic);
rc = os_mbuf_append(ctxt->om, pCharacteristic->getData(), pCharacteristic->m_value.getLength());
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
case BLE_GATT_ACCESS_OP_WRITE_CHR: {
if (ctxt->om->om_len > BLE_ATT_ATTR_MAX_LEN) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
pCharacteristic->setValue(ctxt->om->om_data, ctxt->om->om_len);
pCharacteristic->m_pCallbacks->onWrite(pCharacteristic);
return 0;
}
default:
break;
}
}
return BLE_ATT_ERR_UNLIKELY;
}
/**
* @brief Set the subscribe status for this characteristic.
* This will maintain a map of subscribed clients and their indicate/notify status.
* @return N/A
*/
void NimBLECharacteristic::setSubscribe(struct ble_gap_event *event) {
uint16_t subVal = 0;
if(event->subscribe.cur_notify) {
subVal |= NIMBLE_DESC_FLAG_NOTIFY;
}
if(event->subscribe.cur_indicate) {
subVal |= NIMBLE_DESC_FLAG_INDICATE;
}
m_semaphoreConfEvt.give((subVal | NIMBLE_DESC_FLAG_INDICATE) ? 0 :
NimBLECharacteristicCallbacks::Status::ERROR_INDICATE_DISABLED);
NIMBLE_LOGI(LOG_TAG, "New subscribe value for conn: %d val: %d", event->subscribe.conn_handle, subVal);
NimBLE2902* p2902 = (NimBLE2902*)getDescriptorByUUID((uint16_t)0x2902);
if(p2902 == nullptr){
ESP_LOGE(LOG_TAG, "No 2902 descriptor found for %s", getUUID().toString().c_str());
return;
}
p2902->setNotifications(subVal & NIMBLE_DESC_FLAG_NOTIFY);
p2902->setIndications(subVal & NIMBLE_DESC_FLAG_INDICATE);
p2902->m_pCallbacks->onWrite(p2902);
auto it = p2902->m_subscribedMap.find(event->subscribe.conn_handle);
if(subVal > 0 && it == p2902->m_subscribedMap.cend()) {
p2902->m_subscribedMap.insert(std::pair<uint16_t, uint16_t>(event->subscribe.conn_handle, subVal));
return;
} else if(it != p2902->m_subscribedMap.cend()) {
p2902->m_subscribedMap.erase(event->subscribe.conn_handle);
return;
}
/*
if(event->subscribe.reason == BLE_GAP_SUBSCRIBE_REASON_TERM) {
p2902->m_subscribedMap.erase(event->subscribe.conn_handle);
return;
}
*/
(*it).second = subVal;
}
/**
* @brief Send an indication.
* An indication is a transmission of up to the first 20 bytes of the characteristic value. An indication
* will block waiting a positive confirmation from the client.
* @return N/A
*/
void NimBLECharacteristic::indicate() {
NIMBLE_LOGD(LOG_TAG, ">> indicate: length: %d", m_value.getValue().length());
notify(false);
NIMBLE_LOGD(LOG_TAG, "<< indicate");
} // indicate
/**
* @brief Send a notify.
* A notification is a transmission of up to the first 20 bytes of the characteristic value. An notification
* will not block; it is a fire and forget.
* @return N/A.
*/
void NimBLECharacteristic::notify(bool is_notification) {
NIMBLE_LOGD(LOG_TAG, ">> notify: length: %d", m_value.getValue().length());
assert(getService() != nullptr);
assert(getService()->getServer() != nullptr);
if (getService()->getServer()->getConnectedCount() == 0) {
NIMBLE_LOGD(LOG_TAG, "<< notify: No connected clients.");
return;
}
m_pCallbacks->onNotify(this);
int rc = 0;
NimBLE2902* p2902 = (NimBLE2902*)getDescriptorByUUID((uint16_t)0x2902);
for (auto it = p2902->m_subscribedMap.cbegin(); it != p2902->m_subscribedMap.cend(); ++it) {
uint16_t _mtu = getService()->getServer()->getPeerMTU((*it).first);
// Must rebuild the data on each loop iteration as NimBLE will release it.
size_t length = m_value.getValue().length();
uint8_t* data = (uint8_t*)m_value.getValue().data();
os_mbuf *om;
if(_mtu == 0) {
//NIMBLE_LOGD(LOG_TAG, "peer not connected, removing from map");
p2902->m_subscribedMap.erase((*it).first);
it = p2902->m_subscribedMap.cbegin();
if(it == p2902->m_subscribedMap.cend()) {
return;
}
continue;
}
if (length > _mtu - 3) {
NIMBLE_LOGW(LOG_TAG, "- Truncating to %d bytes (maximum notify size)", _mtu - 3);
}
if((*it).second == 0) {
//NIMBLE_LOGI(LOG_TAG, "Skipping unsubscribed client");
continue;
}
if(is_notification && (!((*it).second & NIMBLE_DESC_FLAG_NOTIFY))) {
NIMBLE_LOGW(LOG_TAG,
"Sending notification to client subscribed to indications, sending indication instead");
is_notification = false;
}
if(!is_notification && (!((*it).second & NIMBLE_DESC_FLAG_INDICATE))) {
NIMBLE_LOGW(LOG_TAG,
"Sending indication to client subscribed to notifications, sending notifications 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.
om = ble_hs_mbuf_from_flat(data, length);
if(!is_notification) {
m_semaphoreConfEvt.take("indicate");
rc = ble_gattc_indicate_custom((*it).first, m_handle, om);
if(rc != 0){
m_semaphoreConfEvt.give();
m_pCallbacks->onStatus(this, NimBLECharacteristicCallbacks::Status::ERROR_GATT, rc);
return;
}
rc = m_semaphoreConfEvt.wait();
if(rc == BLE_HS_ETIMEOUT) {
m_pCallbacks->onStatus(this, NimBLECharacteristicCallbacks::Status::ERROR_INDICATE_TIMEOUT, rc);
} else if(rc == BLE_HS_EDONE) {
m_pCallbacks->onStatus(this, NimBLECharacteristicCallbacks::Status::SUCCESS_INDICATE, rc);
} else {
m_pCallbacks->onStatus(this, NimBLECharacteristicCallbacks::Status::ERROR_INDICATE_FAILURE, rc);
}
} else {
rc = ble_gattc_notify_custom((*it).first, m_handle, om);
if(rc == 0) {
m_pCallbacks->onStatus(this, NimBLECharacteristicCallbacks::Status::SUCCESS_NOTIFY, 0);
} else {
m_pCallbacks->onStatus(this, NimBLECharacteristicCallbacks::Status::ERROR_GATT, rc);
}
}
}
NIMBLE_LOGD(LOG_TAG, "<< notify");
} // Notify
/**
* @brief Set the callback handlers for this characteristic.
* @param [in] pCallbacks An instance of a callbacks structure used to define any callbacks for the characteristic.
*/
void NimBLECharacteristic::setCallbacks(NimBLECharacteristicCallbacks* pCallbacks) {
if (pCallbacks != nullptr){
m_pCallbacks = pCallbacks;
} else {
m_pCallbacks = &defaultCallback;
}
} // setCallbacks
// Backward compatibility - to be removed ////////////////////////////////
/**
* @brief Set the permission to broadcast.
* A characteristics has properties associated with it which define what it is capable of doing.
* One of these is the broadcast flag.
* @param [in] value The flag value of the property.
* @return N/A
*/
void NimBLECharacteristic::setBroadcastProperty(bool value) {
if (value) {
m_properties = (m_properties | BLE_GATT_CHR_F_BROADCAST);
} else {
m_properties = (m_properties & ~BLE_GATT_CHR_F_BROADCAST);
}
} // setBroadcastProperty
/**
* @brief Set the Indicate property value.
* @param [in] value Set to true if we are to allow indicate messages.
*/
void NimBLECharacteristic::setIndicateProperty(bool value) {
if (value) {
m_properties = (m_properties | BLE_GATT_CHR_F_INDICATE);
} else {
m_properties = (m_properties & ~BLE_GATT_CHR_F_INDICATE);
}
} // setIndicateProperty
/**
* @brief Set the Notify property value.
* @param [in] value Set to true if we are to allow notification messages.
*/
void NimBLECharacteristic::setNotifyProperty(bool value) {
if (value) {
m_properties = (m_properties | BLE_GATT_CHR_F_NOTIFY);
} else {
m_properties = (m_properties & ~BLE_GATT_CHR_F_NOTIFY);
}
} // setNotifyProperty
/**
* @brief Set the Read property value.
* @param [in] value Set to true if we are to allow reads.
*/
void NimBLECharacteristic::setReadProperty(bool value) {
if (value) {
m_properties = (m_properties | BLE_GATT_CHR_F_READ);
} else {
m_properties = (m_properties & ~BLE_GATT_CHR_F_READ);
}
} // setReadProperty
/**
* @brief Set the Write No Response property value.
* @param [in] value Set to true if we are to allow writes with no response.
*/
void NimBLECharacteristic::setWriteNoResponseProperty(bool value) {
if (value) {
m_properties = (m_properties | BLE_GATT_CHR_F_WRITE_NO_RSP);
} else {
m_properties = (m_properties & ~BLE_GATT_CHR_F_WRITE_NO_RSP);
}
} // setWriteNoResponseProperty
/**
* @brief Set the Write property value.
* @param [in] value Set to true if we are to allow writes.
*/
void NimBLECharacteristic::setWriteProperty(bool value) {
if (value) {
m_properties = (m_properties | BLE_GATT_CHR_F_WRITE );
} else {
m_properties = (m_properties & ~BLE_GATT_CHR_F_WRITE );
}
} // setWriteProperty
//////////////////////////////////////////////////////////////////////////////////
/**
* @brief Set the value of the characteristic.
* @param [in] data The data to set for the characteristic.
* @param [in] length The length of the data in bytes.
*/
void NimBLECharacteristic::setValue(uint8_t* data, size_t length) {
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);
if (length > BLE_ATT_ATTR_MAX_LEN) {
NIMBLE_LOGE(LOG_TAG, "Size %d too large, must be no bigger than %d", length, BLE_ATT_ATTR_MAX_LEN);
return;
}
m_value.setValue(data, length);
// if(m_handle != NULL_HANDLE) {
//ble_gatts_chr_updated(m_handle);
// ble_gattc_notify(getService()->getServer()->m_connId, m_handle);
// }
NIMBLE_LOGD(LOG_TAG, "<< setValue");
} // setValue
/**
* @brief Set the value of the characteristic from string data.
* We set the value of the characteristic from the bytes contained in the
* string.
* @param [in] Set the value of the characteristic.
* @return N/A.
*/
void NimBLECharacteristic::setValue(std::string value) {
setValue((uint8_t*)(value.data()), value.length());
} // setValue
void NimBLECharacteristic::setValue(uint16_t& data16) {
uint8_t temp[2];
temp[0] = data16;
temp[1] = data16 >> 8;
setValue(temp, 2);
} // setValue
void NimBLECharacteristic::setValue(uint32_t& data32) {
uint8_t temp[4];
temp[0] = data32;
temp[1] = data32 >> 8;
temp[2] = data32 >> 16;
temp[3] = data32 >> 24;
setValue(temp, 4);
} // setValue
void NimBLECharacteristic::setValue(int& data32) {
uint8_t temp[4];
temp[0] = data32;
temp[1] = data32 >> 8;
temp[2] = data32 >> 16;
temp[3] = data32 >> 24;
setValue(temp, 4);
} // setValue
void NimBLECharacteristic::setValue(float& data32) {
float temp = data32;
setValue((uint8_t*)&temp, 4);
} // setValue
void NimBLECharacteristic::setValue(double& data64) {
double temp = data64;
setValue((uint8_t*)&temp, 8);
} // setValue
/**
* @brief Return a string representation of the characteristic.
* @return A string representation of the characteristic.
*/
std::string NimBLECharacteristic::toString() {
std::string res = "UUID: " + m_uuid.toString() + ", handle : 0x";
char hex[5];
snprintf(hex, sizeof(hex), "%04x", m_handle);
res += hex;
res += " ";
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 ";
if (m_properties & BLE_GATT_CHR_PROP_NOTIFY) res += "Notify ";
if (m_properties & BLE_GATT_CHR_PROP_INDICATE) res += "Indicate ";
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.
*/
void NimBLECharacteristicCallbacks::onRead(NimBLECharacteristic* pCharacteristic) {
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.
*/
void NimBLECharacteristicCallbacks::onWrite(NimBLECharacteristic* pCharacteristic) {
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 code of underlying errors
*/
void NimBLECharacteristicCallbacks::onStatus(NimBLECharacteristic* pCharacteristic, Status s, int code) {
NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onStatus: default");
} // onStatus
#endif /* CONFIG_BT_ENABLED */

190
src/NimBLECharacteristic.h Normal file
View File

@ -0,0 +1,190 @@
/*
* NimBLECharacteristic.h
*
* Created: on March 3, 2020
* Author H2zero
*
* Originally:
* BLECharacteristic.h
*
* Created on: Jun 22, 2017
* Author: kolban
*/
#ifndef MAIN_NIMBLECHARACTERISTIC_H_
#define MAIN_NIMBLECHARACTERISTIC_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "host/ble_hs.h"
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 "NimBLEUUID.h"
#include "NimBLEValue.h"
#include "FreeRTOS.h"
#include <string>
#include <map>
class NimBLEService;
class NimBLEDescriptor;
class NimBLECharacteristicCallbacks;
/**
* @brief A management structure for %BLE descriptors.
*/
class NimBLEDescriptorMap {
public:
void setByUUID(const char* uuid, NimBLEDescriptor* pDescriptor);
void setByUUID(NimBLEUUID uuid, NimBLEDescriptor* pDescriptor);
// void setByHandle(uint16_t handle, NimBLEDescriptor* pDescriptor);
NimBLEDescriptor* getByUUID(const char* uuid);
NimBLEDescriptor* getByUUID(NimBLEUUID uuid);
// NimBLEDescriptor* getByHandle(uint16_t handle);
std::string toString();
NimBLEDescriptor* getFirst();
NimBLEDescriptor* getNext();
uint8_t getSize();
private:
std::map<NimBLEDescriptor*, std::string> m_uuidMap;
// std::map<uint16_t, BLEDescriptor*> m_handleMap;
std::map<NimBLEDescriptor*, std::string>::iterator m_iterator;
};
/**
* @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.
*/
class NimBLECharacteristic {
public:
NimBLEDescriptor* createDescriptor(const char* uuid,
uint32_t properties = NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = 100);
NimBLEDescriptor* createDescriptor(NimBLEUUID uuid,
uint32_t properties = NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = 100);
NimBLEDescriptor* getDescriptorByUUID(const char* descriptorUUID);
NimBLEDescriptor* getDescriptorByUUID(NimBLEUUID descriptorUUID);
NimBLEUUID getUUID();
std::string getValue();
uint8_t* getData();
void indicate();
void notify(bool is_notification = true);
void setCallbacks(NimBLECharacteristicCallbacks* pCallbacks);
// Backward Compatibility - to be removed
void setBroadcastProperty(bool value);
void setIndicateProperty(bool value);
void setNotifyProperty(bool value);
void setReadProperty(bool value);
void setWriteProperty(bool value);
void setWriteNoResponseProperty(bool value);
//////////////////////////////////////////////////////
void setValue(uint8_t* data, size_t size);
void setValue(std::string value);
void setValue(uint16_t& data16);
void setValue(uint32_t& data32);
void setValue(int& data32);
void setValue(float& data32);
void setValue(double& data64);
std::string toString();
uint16_t getHandle();
// void setAccessPermissions(uint16_t perm);
// Backward Compatibility - to be removed
/* static const uint32_t PROPERTY_READ = 1<<0;
static const uint32_t PROPERTY_WRITE = 1<<1;
static const uint32_t PROPERTY_NOTIFY = 1<<2;
static const uint32_t PROPERTY_BROADCAST = 1<<3;
static const uint32_t PROPERTY_INDICATE = 1<<4;
static const uint32_t PROPERTY_WRITE_NR = 1<<5;
*/
//////////////////////////////////////////////////////
private:
friend class NimBLEServer;
friend class NimBLEService;
// friend class NimBLEDescriptor;
// friend class NimBLECharacteristicMap;
NimBLECharacteristic(const char* uuid, uint16_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE,
NimBLEService* pService = nullptr);
NimBLECharacteristic(NimBLEUUID uuid, uint16_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE,
NimBLEService* pService = nullptr);
virtual ~NimBLECharacteristic();
NimBLEUUID m_uuid;
NimBLEDescriptorMap m_descriptorMap;
uint16_t m_handle;
uint16_t m_properties;
NimBLECharacteristicCallbacks* m_pCallbacks;
NimBLEService* m_pService;
NimBLEValue m_value;
// uint16_t m_permissions;
void addDescriptor(NimBLEDescriptor* pDescriptor);
NimBLEService* getService();
uint8_t getProperties();
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);
FreeRTOS::Semaphore m_semaphoreConfEvt = FreeRTOS::Semaphore("ConfEvt");
}; // NimBLECharacteristic
/**
* @brief Callbacks that can be associated with a %BLE characteristic to inform of events.
*
* When a server application creates a %BLE characteristic, we may wish to be informed when there is either
* a read or write request to the characteristic's value. An application can register a
* sub-classed instance of this class and will be notified when such an event happens.
*/
class NimBLECharacteristicCallbacks {
public:
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 onWrite(NimBLECharacteristic* pCharacteristic);
virtual void onNotify(NimBLECharacteristic* pCharacteristic);
virtual void onStatus(NimBLECharacteristic* pCharacteristic, Status s, int code);
};
#endif /* CONFIG_BT_ENABLED */
#endif /*MAIN_NIMBLECHARACTERISTIC_H_*/

View File

@ -0,0 +1,128 @@
/*
* NimBLECharacteristicMap.cpp
*
* Created: on March 3, 2020
* Author H2zero
*
* BLECharacteristicMap.cpp
*
* Created on: Jun 22, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEService.h"
#include "NimBLELog.h"
/**
* @brief Return the characteristic by handle.
* @param [in] handle The handle to look up the characteristic.
* @return The characteristic.
*/
NimBLECharacteristic* NimBLECharacteristicMap::getByHandle(uint16_t handle) {
return m_handleMap.at(handle);
} // getByHandle
/**
* @brief Return the characteristic by UUID.
* @param [in] UUID The UUID to look up the characteristic.
* @return The characteristic.
*/
NimBLECharacteristic* NimBLECharacteristicMap::getByUUID(const char* uuid) {
return getByUUID(NimBLEUUID(uuid));
}
/**
* @brief Return the characteristic by UUID.
* @param [in] UUID The UUID to look up the characteristic.
* @return The characteristic.
*/
NimBLECharacteristic* NimBLECharacteristicMap::getByUUID(NimBLEUUID uuid) {
for (auto &myPair : m_uuidMap) {
if (myPair.first->getUUID().equals(uuid)) {
return myPair.first;
}
}
return nullptr;
} // getByUUID
/**
* @brief Get the number of characteristics in the map.
*/
uint8_t NimBLECharacteristicMap::getSize() {
return (uint8_t)m_uuidMap.size();
} // getSize
/**
* @brief Get the first characteristic in the map.
* @return The first characteristic in the map.
*/
NimBLECharacteristic* NimBLECharacteristicMap::getFirst() {
m_iterator = m_uuidMap.begin();
if (m_iterator == m_uuidMap.end()) return nullptr;
NimBLECharacteristic* pRet = m_iterator->first;
m_iterator++;
return pRet;
} // getFirst
/**
* @brief Get the next characteristic in the map.
* @return The next characteristic in the map.
*/
NimBLECharacteristic* NimBLECharacteristicMap::getNext() {
if (m_iterator == m_uuidMap.end()) return nullptr;
NimBLECharacteristic* pRet = m_iterator->first;
m_iterator++;
return pRet;
} // getNext
/**
* @brief Set the characteristic by handle.
* @param [in] handle The handle of the characteristic.
* @param [in] characteristic The characteristic to cache.
* @return N/A.
*/
void NimBLECharacteristicMap::setByHandle(uint16_t handle, NimBLECharacteristic* characteristic) {
m_handleMap.insert(std::pair<uint16_t, NimBLECharacteristic*>(handle, characteristic));
} // setByHandle
/**
* @brief Set the characteristic by UUID.
* @param [in] uuid The uuid of the characteristic.
* @param [in] characteristic The characteristic to cache.
* @return N/A.
*/
void NimBLECharacteristicMap::setByUUID(NimBLECharacteristic* pCharacteristic, NimBLEUUID uuid) {
m_uuidMap.insert(std::pair<NimBLECharacteristic*, std::string>(pCharacteristic, uuid.toString()));
} // setByUUID
/**
* @brief Return a string representation of the characteristic map.
* @return A string representation of the characteristic map.
*/
std::string NimBLECharacteristicMap::toString() {
std::string res;
int count = 0;
char hex[5];
for (auto &myPair: m_uuidMap) {
if (count > 0) {res += "\n";}
snprintf(hex, sizeof(hex), "%04x", myPair.first->getHandle());
count++;
res += "handle: 0x";
res += hex;
res += ", uuid: " + myPair.first->getUUID().toString();
}
return res;
} // toString
#endif /* CONFIG_BT_ENABLED */

900
src/NimBLEClient.cpp Normal file
View File

@ -0,0 +1,900 @@
/*
* NimBLEClient.cpp
*
* Created: on Jan 26 2020
* Author H2zero
*
* Originally:
* BLEClient.cpp
*
* Created on: Mar 22, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEClient.h"
#include "NimBLEUtils.h"
#include "NimBLEDevice.h"
#include "NimBLELog.h"
#include <string>
#include <unordered_set>
static const char* LOG_TAG = "NimBLEClient";
static NimBLEClientCallbacks defaultCallbacks;
/*
* Design
* ------
* When we perform a getService() request, we are asking the BLE server to return each of the services
* that it exposes. For each service, we receive a callback which contains details
* of the exposed service including its UUID.
*
* The objects we will invent for a NimBLEClient will be as follows:
* * NimBLERemoteService - A model of a remote service.
* * NimBLERemoteCharacteristic - A model of a remote characteristic
* * NimBLERemoteDescriptor - A model of a remote descriptor.
*
* Since there is a hierarchical relationship here, we will have the idea that from a NimBLERemoteService will own
* zero or more remote characteristics and a NimBLERemoteCharacteristic will own zero or more remote NimBLEDescriptors.
*
* We will assume that a NimBLERemoteService contains a map that maps NimBLEUUIDs to the set of owned characteristics
* and that a NimBLECharacteristic contains a map that maps NimBLEUUIDs to the set of owned descriptors.
*
*
*/
NimBLEClient::NimBLEClient()
{
m_pClientCallbacks = &defaultCallbacks;
m_conn_id = BLE_HS_CONN_HANDLE_NONE;
m_haveServices = false;
m_isConnected = false;
m_connectTimeout = 30000;
m_pConnParams = nullptr;
} // NimBLEClient
/**
* @brief Destructor, private - only callable by NimBLEDevice::deleteClient
* to ensure proper disconnect and removal from device list.
*/
NimBLEClient::~NimBLEClient() {
// We may have allocated service references associated with this client.
// Before we are finished with the client, we must release resources.
clearServices();
if(m_deleteCallbacks) {
delete m_pClientCallbacks;
}
if(m_pConnParams != nullptr) {
free(m_pConnParams);
}
} // ~NimBLEClient
/**
* @brief Clear any existing services.
*/
void NimBLEClient::clearServices() {
NIMBLE_LOGD(LOG_TAG, ">> clearServices");
// Delete all the services.
for (auto &myPair : m_servicesMap) {
delete myPair.second;
}
m_servicesMap.clear();
m_haveServices = false;
NIMBLE_LOGD(LOG_TAG, "<< clearServices");
} // clearServices
/**
* NOT NEEDED
*/
/*
void NimBLEClient::onHostReset() {
}
*/
/**
* Add overloaded function to ease connect to peer device with not public address
*/
bool NimBLEClient::connect(NimBLEAdvertisedDevice* device, bool refreshServices) {
NimBLEAddress address(device->getAddress());
uint8_t type = device->getAddressType();
return connect(address, type, refreshServices);
}
/**
* @brief Connect to the partner (BLE Server).
* @param [in] address The address of the partner.
* @return True on success.
*/
bool NimBLEClient::connect(NimBLEAddress address, uint8_t type, bool refreshServices) {
NIMBLE_LOGD(LOG_TAG, ">> connect(%s)", address.toString().c_str());
if(!NimBLEDevice::m_synced) {
NIMBLE_LOGC(LOG_TAG, "Host reset, wait for sync.");
return false;
}
if(ble_gap_conn_active()) {
NIMBLE_LOGE(LOG_TAG, "Connection in progress - must wait.");
return false;
}
int rc = 0;
m_peerAddress = address;
ble_addr_t peerAddrt;
memcpy(&peerAddrt.val, address.getNative(),6);
peerAddrt.type = type;
m_semaphoreOpenEvt.take("connect");
/** Try to connect the the advertiser. Allow 30 seconds (30000 ms) for
* timeout (default value of m_connectTimeout).
* Loop on BLE_HS_EBUSY if the scan hasn't stopped yet.
*/
do{
rc = ble_gap_connect(BLE_OWN_ADDR_PUBLIC, &peerAddrt, m_connectTimeout, m_pConnParams,
NimBLEClient::handleGapEvent, this);
}while(rc == BLE_HS_EBUSY);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Error: Failed to connect to device; addr_type=%d "
"addr=%s, rc=%d; %s",
type,
m_peerAddress.toString().c_str(),
rc, NimBLEUtils::returnCodeToString(BLE_HS_ATT_ERR(rc)));
m_semaphoreOpenEvt.give();
m_waitingToConnect = false;
return false;
}
m_waitingToConnect = true;
rc = m_semaphoreOpenEvt.wait("connect"); // Wait for the connection to complete.
if(rc != 0){
return false;
}
if(refreshServices) {
NIMBLE_LOGD(LOG_TAG, "Refreshing Services for: (%s)", address.toString().c_str());
clearServices();
}
if (!m_haveServices) {
if (!retrieveServices()) {
// error getting services, make sure we disconnect and release any resources before returning
disconnect();
clearServices();
return false;
}
else{
NIMBLE_LOGD(LOG_TAG, "Found %d services", getServices()->size());
}
}
m_pClientCallbacks->onConnect(this);
NIMBLE_LOGD(LOG_TAG, "<< connect()");
return true;
} // connect
/**
* @brief Called when a characteristic or descriptor requires encryption or authentication to access it.
* This will pair with the device and bond if enabled.
* @return True on success.
*/
bool NimBLEClient::secureConnection() {
m_semeaphoreSecEvt.take("secureConnection");
int rc = NimBLEDevice::startSecurity(m_conn_id);
if(rc != 0){
m_semeaphoreSecEvt.give();
return false;
}
rc = m_semeaphoreSecEvt.wait("secureConnection");
if(rc != 0){
return false;
}
return true;
}
/**
* @brief Disconnect from the peer.
* @return N/A.
*/
int NimBLEClient::disconnect(uint8_t reason) {
NIMBLE_LOGD(LOG_TAG, ">> disconnect()");
int rc = 0;
if(m_isConnected){
m_isConnected = false; // flag the disconnect now so no calls are performed after
rc = ble_gap_terminate(m_conn_id, reason);
if(rc != 0){
NIMBLE_LOGE(LOG_TAG, "ble_gap_terminate failed: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
}
}
return rc;
NIMBLE_LOGD(LOG_TAG, "<< disconnect()");
} // disconnect
/**
* @brief Set the connection paramaters to use when connecting to a server.
*/
void NimBLEClient::setConnectionParams(uint16_t minInterval, uint16_t maxInterval,
uint16_t latency, uint16_t timeout,
uint16_t minConnTime, uint16_t maxConnTime)
{
if(m_pConnParams == nullptr) {
m_pConnParams = (ble_gap_conn_params*)calloc(1, sizeof(ble_gap_conn_params));
if(m_pConnParams == nullptr) {
NIMBLE_LOGE(LOG_TAG, "setConnectionParams: Error No Mem");
return;
}
}else if(0 == (minInterval | maxInterval | latency | timeout)) {
free(m_pConnParams);
m_pConnParams = nullptr;
return;
}
m_pConnParams->scan_itvl = 16; // Scan interval in 0.625ms units (NimBLE Default)
m_pConnParams->scan_window = 16; // Scan window in 0.625ms units (NimBLE Default)
m_pConnParams->itvl_min = minInterval; // min_int = 0x10*1.25ms = 20ms
m_pConnParams->itvl_max = maxInterval; // max_int = 0x20*1.25ms = 40ms
m_pConnParams->latency = latency; // number of packets allowed to skip (extends max interval)
m_pConnParams->supervision_timeout = timeout; // timeout = 400*10ms = 4000ms
m_pConnParams->min_ce_len = minConnTime; // Minimum length of connection event in 0.625ms units
m_pConnParams->max_ce_len = maxConnTime; // Maximum length of connection event in 0.625ms units
int rc = NimBLEUtils::checkConnParams(m_pConnParams);
if(rc != 0) {
NIMBLE_LOGE(LOG_TAG,"setConnectionParams : %s", NimBLEUtils::returnCodeToString(rc));
free(m_pConnParams);
m_pConnParams = nullptr;
}
}
/**
* Update connection parameters can be called only after connection has been established
*/
void NimBLEClient::updateConnParams(uint16_t minInterval, uint16_t maxInterval,
uint16_t latency, uint16_t timeout,
uint16_t minConnTime, uint16_t maxConnTime)
{
if(m_pConnParams == nullptr) {
setConnectionParams(minInterval, maxInterval, latency, timeout, minConnTime, maxConnTime);
}
ble_gap_upd_params params;
params.latency = latency;
params.itvl_max = maxInterval;
params.itvl_min = minInterval;
params.supervision_timeout = timeout;
params.min_ce_len = minConnTime;
params.max_ce_len = maxConnTime;
int rc = ble_gap_update_params(m_conn_id, &params);
if(rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Update params error: %d, %s",
rc, NimBLEUtils::returnCodeToString(rc));
}
}
/**
* @brief Set the timeout to wait for connection attempt to complete
* @params[in] Time to wait in seconds.
*/
void NimBLEClient::setConnectTimeout(uint8_t time) {
m_connectTimeout = (uint32_t)(time * 1000);
}
/**
* @brief Get the connection id for this client.
* @return The connection id.
*/
uint16_t NimBLEClient::getConnId() {
return m_conn_id;
} // getConnId
/**
* @brief Retrieve the address of the peer.
*/
NimBLEAddress NimBLEClient::getPeerAddress() {
return m_peerAddress;
} // getAddress
/**
* @brief Ask the BLE server for the RSSI value.
* @return The RSSI value.
*/
int NimBLEClient::getRssi() {
NIMBLE_LOGD(LOG_TAG, ">> getRssi()");
if (!isConnected()) {
NIMBLE_LOGD(LOG_TAG, "<< getRssi(): Not connected");
return 0;
}
int8_t rssiValue = 0;
int rc = ble_gap_conn_rssi(m_conn_id, &rssiValue);
if(rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Failed to read RSSI error code: %d, %s",
rc, NimBLEUtils::returnCodeToString(rc));
return 0;
}
return rssiValue;
} // getRssi
/**
* @brief Get the service BLE Remote Service instance corresponding to the uuid.
* @param [in] uuid The UUID of the service being sought.
* @return A reference to the Service or nullptr if don't know about it.
*/
NimBLERemoteService* NimBLEClient::getService(const char* uuid) {
return getService(NimBLEUUID(uuid));
} // getService
/**
* @brief Get the service object corresponding to the uuid.
* @param [in] uuid The UUID of the service being sought.
* @return A reference to the Service or nullptr if don't know about it.
*/
NimBLERemoteService* NimBLEClient::getService(NimBLEUUID uuid) {
NIMBLE_LOGD(LOG_TAG, ">> getService: uuid: %s", uuid.toString().c_str());
if (!m_haveServices) {
return nullptr;
}
std::string uuidStr = uuid.toString();
for (auto &myPair : m_servicesMap) {
if (myPair.first == uuidStr) {
NIMBLE_LOGD(LOG_TAG, "<< getService: found the service with uuid: %s", uuid.toString().c_str());
return myPair.second;
}
}
NIMBLE_LOGD(LOG_TAG, "<< getService: not found");
return nullptr;
} // getService
/**
* @Get a pointer to the map of found services.
*/
std::map<std::string, NimBLERemoteService*>* NimBLEClient::getServices() {
return &m_servicesMap;
}
/**
* @brief Ask the remote %BLE server for its services.
* A %BLE Server exposes a set of services for its partners. Here we ask the server for its set of
* services and wait until we have received them all.
* We then ask for the characteristics for each service found and their desciptors.
* @return true on success otherwise false if an error occurred
*/
bool NimBLEClient::retrieveServices() {
/**
* Design
* ------
* We invoke ble_gattc_disc_all_svcs. This will request a list of the services exposed by the
* peer BLE partner to be returned in the callback function provided.
*/
NIMBLE_LOGD(LOG_TAG, ">> retrieveServices");
if(!m_isConnected){
NIMBLE_LOGE(LOG_TAG, "Disconnected, could not retrieve services -aborting");
return false;
}
m_semaphoreSearchCmplEvt.take("retrieveServices");
int rc = ble_gattc_disc_all_svcs(m_conn_id, NimBLEClient::serviceDiscoveredCB, this);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gattc_disc_all_svcs: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
m_haveServices = false;
m_semaphoreSearchCmplEvt.give();
return false;
}
// wait until we have all the services
// If sucessful, remember that we now have services.
m_haveServices = (m_semaphoreSearchCmplEvt.wait("retrieveServices") == 0);
if(m_haveServices){
for (auto &myPair : m_servicesMap) {
if(!m_isConnected || !myPair.second->retrieveCharacteristics()) {
NIMBLE_LOGE(LOG_TAG, "Disconnected, could not retrieve characteristics -aborting");
return false;
}
}
NIMBLE_LOGD(LOG_TAG, "<< retrieveServices");
return true;
}
else {
NIMBLE_LOGE(LOG_TAG, "Could not retrieve services");
return false;
}
} // getServices
/**
* @brief STATIC Callback for the service discovery API function.
* When a service is found or there is none left or there was an error
* the API will call this and report findings.
*/
int NimBLEClient::serviceDiscoveredCB(
uint16_t conn_handle,
const struct ble_gatt_error *error,
const struct ble_gatt_svc *service, void *arg)
{
NIMBLE_LOGD(LOG_TAG,"Service Discovered >> status: %d handle: %d", error->status, conn_handle);
NimBLEClient *peer = (NimBLEClient*)arg;
int rc=0;
// Make sure the service discovery is for this device
if(peer->getConnId() != conn_handle){
return 0;
}
switch (error->status) {
case 0: {
// Found a service - add it to the map
NimBLERemoteService* pRemoteService = new NimBLERemoteService(peer, service);
peer->m_servicesMap.insert(std::pair<std::string, NimBLERemoteService*>(pRemoteService->getUUID().toString(), pRemoteService));
break;
}
case BLE_HS_EDONE:{
// All services discovered; start discovering characteristics.
NIMBLE_LOGD(LOG_TAG,"Giving search semaphore - completed");
peer->m_semaphoreSearchCmplEvt.give(0);
rc = 0;
break;
}
default:
// Error; abort discovery.
rc = error->status;
break;
}
if (rc != 0) {
// pass non-zero to semaphore on error to indicate an error finding services
peer->m_semaphoreSearchCmplEvt.give(1);
}
NIMBLE_LOGD(LOG_TAG,"<< Service Discovered. status: %d", rc);
return rc;
}
/**
* @brief Get the value of a specific characteristic associated with a specific service.
* @param [in] serviceUUID The service that owns the characteristic.
* @param [in] characteristicUUID The characteristic whose value we wish to read.
* @returns characteristic value or an empty string if not found
*/
std::string NimBLEClient::getValue(NimBLEUUID serviceUUID, NimBLEUUID characteristicUUID) {
NIMBLE_LOGD(LOG_TAG, ">> getValue: serviceUUID: %s, characteristicUUID: %s", serviceUUID.toString().c_str(), characteristicUUID.toString().c_str());
std::string ret = "";
NimBLERemoteService* pService = getService(serviceUUID);
if(pService != nullptr) {
NimBLERemoteCharacteristic* pChar = pService->getCharacteristic(characteristicUUID);
if(pChar != nullptr) {
ret = pChar->readValue();
}
}
NIMBLE_LOGD(LOG_TAG, "<<getValue");
return ret;
} // getValue
/**
* @brief Set the value of a specific characteristic associated with a specific service.
* @param [in] serviceUUID The service that owns the characteristic.
* @param [in] characteristicUUID The characteristic whose value we wish to write.
* @returns true if successful otherwise false
*/
bool NimBLEClient::setValue(NimBLEUUID serviceUUID, NimBLEUUID characteristicUUID, std::string value) {
NIMBLE_LOGD(LOG_TAG, ">> setValue: serviceUUID: %s, characteristicUUID: %s", serviceUUID.toString().c_str(), characteristicUUID.toString().c_str());
bool ret = false;
NimBLERemoteService* pService = getService(serviceUUID);
if(pService != nullptr) {
NimBLERemoteCharacteristic* pChar = pService->getCharacteristic(characteristicUUID);
if(pChar != nullptr) {
ret = pChar->writeValue(value);
}
}
NIMBLE_LOGD(LOG_TAG, "<< setValue");
return ret;
} // setValue
/**
* @brief Get the current mtu of this connection.
*/
uint16_t NimBLEClient::getMTU() {
return ble_att_mtu(m_conn_id);
}
/**
* @brief Handle a received GAP event.
*
* @param [in] event
* @param [in] arg = pointer to the client instance
*/
/*STATIC*/ int NimBLEClient::handleGapEvent(struct ble_gap_event *event, void *arg) {
NimBLEClient* client = (NimBLEClient*)arg;
//struct ble_gap_conn_desc desc;
//struct ble_hs_adv_fields fields;
int rc;
NIMBLE_LOGD(LOG_TAG, "Got Client event %s", NimBLEUtils::gapEventToString(event->type));
// Execute handler code based on the type of event received.
switch(event->type) {
case BLE_GAP_EVENT_DISCONNECT: {
if(!client->m_isConnected)
return 0;
if(client->m_conn_id != event->disconnect.conn.conn_handle)
return 0;
client->m_isConnected = false;
client->m_waitingToConnect=false;
NIMBLE_LOGI(LOG_TAG, "disconnect; reason=%d, %s", event->disconnect.reason,
NimBLEUtils::returnCodeToString(event->disconnect.reason));
//print_conn_desc(&event->disconnect.conn);
//MODLOG_DFLT(INFO, "\n");
// If Host reset tell the device now before returning to prevent
// any errors caused by calling host functions before resyncing.
switch(event->disconnect.reason) {
case BLE_HS_ETIMEOUT_HCI:
case BLE_HS_EOS:
case BLE_HS_ECONTROLLER:
case BLE_HS_ENOTSYNCED:
NIMBLE_LOGC(LOG_TAG, "Disconnect - host reset, rc=%d", event->disconnect.reason);
NimBLEDevice::onReset(event->disconnect.reason);
break;
default:
break;
}
//client->m_conn_id = BLE_HS_CONN_HANDLE_NONE;
// Indicate a non-success return value to any semaphores waiting
client->m_semaphoreOpenEvt.give(1);
client->m_semaphoreSearchCmplEvt.give(1);
client->m_semeaphoreSecEvt.give(1);
// Remove the device from ignore list so we will scan it again
NimBLEDevice::removeIgnored(client->m_peerAddress);
client->m_pClientCallbacks->onDisconnect(client);
return 0;
} // BLE_GAP_EVENT_DISCONNECT
case BLE_GAP_EVENT_CONNECT: {
if(!client->m_waitingToConnect)
return 0;
//if(client->m_conn_id != BLE_HS_CONN_HANDLE_NONE)
// return 0;
client->m_waitingToConnect=false;
if (event->connect.status == 0) {
client->m_isConnected = true;
NIMBLE_LOGD(LOG_TAG, "Connection established");
client->m_conn_id = event->connect.conn_handle;
// rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
// assert(rc == 0);
// print_conn_desc(&desc);
// MODLOG_DFLT(INFO, "\n");
//client->m_pClientCallbacks->onConnect(client);
// In the case of a multiconnecting device we ignore this device when
// scanning since we are already connected to it
NimBLEDevice::addIgnored(client->m_peerAddress);
rc = ble_gattc_exchange_mtu(client->m_conn_id, NULL,NULL);
if(rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gattc_exchange_mtu: rc=%d %s",rc,
NimBLEUtils::returnCodeToString(rc));
// if error getting mtu indicate a connection error.
client->m_semaphoreOpenEvt.give(rc);
} /*else {
client->m_semaphoreOpenEvt.give(0);
}*/
} else {
// Connection attempt failed
NIMBLE_LOGE(LOG_TAG, "Error: Connection failed; status=%d %s",
event->connect.status,
NimBLEUtils::returnCodeToString(event->connect.status));
client->m_semaphoreOpenEvt.give(event->connect.status);
}
return 0;
} // BLE_GAP_EVENT_CONNECT
case BLE_GAP_EVENT_NOTIFY_RX: {
if(client->m_conn_id != event->notify_rx.conn_handle)
return 0;
NIMBLE_LOGD(LOG_TAG, "Notify Recieved for handle: %d",event->notify_rx.attr_handle);
if(!client->m_haveServices)
return 0;
for(auto &sPair : client->m_servicesMap){
// Dont waste cycles searching services without this handle in their range
if(sPair.second->getEndHandle() < event->notify_rx.attr_handle) {
continue;
}
auto cMap = sPair.second->getCharacteristicsByHandle();
NIMBLE_LOGD(LOG_TAG, "checking service %s for handle: %d", sPair.second->getUUID().toString().c_str(),event->notify_rx.attr_handle);
auto characteristic = cMap->find(event->notify_rx.attr_handle);
if(characteristic != cMap->end()) {
NIMBLE_LOGD(LOG_TAG, "Got Notification for characteristic %s", characteristic->second->toString().c_str());
if (characteristic->second->m_notifyCallback != nullptr) {
NIMBLE_LOGD(LOG_TAG, "Invoking callback for notification on characteristic %s", characteristic->second->toString().c_str());
characteristic->second->m_notifyCallback(characteristic->second, event->notify_rx.om->om_data, event->notify_rx.om->om_len, !event->notify_rx.indication);
}
break;
}
}
return 0;
} // BLE_GAP_EVENT_NOTIFY_RX
case BLE_GAP_EVENT_CONN_UPDATE_REQ:
case BLE_GAP_EVENT_L2CAP_UPDATE_REQ: {
if(client->m_conn_id != event->conn_update_req.conn_handle){
return 0; //BLE_HS_ENOTCONN BLE_ATT_ERR_INVALID_HANDLE
}
NIMBLE_LOGD(LOG_TAG, "Peer requesting to update connection parameters");
NIMBLE_LOGD(LOG_TAG, "MinInterval: %d, MaxInterval: %d, Latency: %d, Timeout: %d",
event->conn_update_req.peer_params->itvl_min,
event->conn_update_req.peer_params->itvl_max,
event->conn_update_req.peer_params->latency,
event->conn_update_req.peer_params->supervision_timeout);
rc = 0;
// if we set connection params and the peer is asking for new ones, reject them.
if(client->m_pConnParams != nullptr) {
if(event->conn_update_req.peer_params->itvl_min != client->m_pConnParams->itvl_min ||
event->conn_update_req.peer_params->itvl_max != client->m_pConnParams->itvl_max ||
event->conn_update_req.peer_params->latency != client->m_pConnParams->latency ||
event->conn_update_req.peer_params->supervision_timeout != client->m_pConnParams->supervision_timeout)
{
//event->conn_update_req.self_params->itvl_min = 6;//client->m_pConnParams->itvl_min;
rc = BLE_ERR_CONN_PARMS;
}
}
if(rc != 0) {
NIMBLE_LOGD(LOG_TAG, "Rejected peer params");
}
return rc;
} // BLE_GAP_EVENT_CONN_UPDATE_REQ, BLE_GAP_EVENT_L2CAP_UPDATE_REQ
case BLE_GAP_EVENT_CONN_UPDATE: {
if(client->m_conn_id != event->conn_update.conn_handle){
return 0; //BLE_HS_ENOTCONN BLE_ATT_ERR_INVALID_HANDLE
}
if(event->conn_update.status == 0) {
NIMBLE_LOGI(LOG_TAG, "Connection parameters updated.");
} else {
NIMBLE_LOGE(LOG_TAG, "Update connection parameters failed.");
}
return 0;
} // BLE_GAP_EVENT_CONN_UPDATE
case BLE_GAP_EVENT_ENC_CHANGE: {
if(client->m_conn_id != event->enc_change.conn_handle){
return 0; //BLE_HS_ENOTCONN BLE_ATT_ERR_INVALID_HANDLE
}
if(event->enc_change.status == 0) {
struct ble_gap_conn_desc desc;
rc = ble_gap_conn_find(event->conn_update.conn_handle, &desc);
assert(rc == 0);
if(NimBLEDevice::m_securityCallbacks != nullptr) {
NimBLEDevice::m_securityCallbacks->onAuthenticationComplete(&desc);
} else {
client->m_pClientCallbacks->onAuthenticationComplete(&desc);
}
}
client->m_semeaphoreSecEvt.give(event->enc_change.status);
return 0;
} //BLE_GAP_EVENT_ENC_CHANGE
case BLE_GAP_EVENT_MTU: {
if(client->m_conn_id != event->mtu.conn_handle){
return 0; //BLE_HS_ENOTCONN BLE_ATT_ERR_INVALID_HANDLE
}
NIMBLE_LOGI(LOG_TAG, "mtu update event; conn_handle=%d mtu=%d",
event->mtu.conn_handle,
event->mtu.value);
client->m_semaphoreOpenEvt.give(0);
//client->m_mtu = event->mtu.value;
return 0;
} // BLE_GAP_EVENT_MTU
case BLE_GAP_EVENT_PASSKEY_ACTION: {
struct ble_sm_io pkey = {0};
if(client->m_conn_id != event->passkey.conn_handle)
return 0;
if (event->passkey.params.action == BLE_SM_IOACT_DISP) {
pkey.action = event->passkey.params.action;
pkey.passkey = NimBLEDevice::m_passkey; // This is the passkey to be entered on peer
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
NIMBLE_LOGD(LOG_TAG, "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: %d", 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 = client->m_pClientCallbacks->onConfirmPIN(event->passkey.params.numcmp);
}
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
NIMBLE_LOGD(LOG_TAG, "ble_sm_inject_io result: %d", rc);
//TODO: Handle out of band pairing
} else if (event->passkey.params.action == BLE_SM_IOACT_OOB) {
static uint8_t tem_oob[16] = {0};
pkey.action = event->passkey.params.action;
for (int i = 0; i < 16; i++) {
pkey.oob[i] = tem_oob[i];
}
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
NIMBLE_LOGD(LOG_TAG, "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 {
client->m_pClientCallbacks->onPassKeyRequest();
}
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
NIMBLE_LOGD(LOG_TAG, "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");
}
return 0;
} // BLE_GAP_EVENT_PASSKEY_ACTION
default: {
return 0;
}
} // Switch
} // handleGapEvent
/**
* @brief Are we connected to a server?
* @return True if we are connected and false if we are not connected.
*/
bool NimBLEClient::isConnected() {
return m_isConnected;
} // isConnected
/**
* @brief Set the callbacks that will be invoked.
*/
void NimBLEClient::setClientCallbacks(NimBLEClientCallbacks* pClientCallbacks, bool deleteCallbacks) {
if (pClientCallbacks != nullptr){
m_pClientCallbacks = pClientCallbacks;
} else {
m_pClientCallbacks = &defaultCallbacks;
}
m_deleteCallbacks = deleteCallbacks;
} // setClientCallbacks
/**
* @brief Return a string representation of this client.
* @return A string representation of this client.
*/
std::string NimBLEClient::toString() {
std::string res = "peer address: " + m_peerAddress.toString();
res += "\nServices:\n";
for (auto &myPair : m_servicesMap) {
res += myPair.second->toString() + "\n";
}
return res;
} // toString
void NimBLEClientCallbacks::onConnect(NimBLEClient *pClient) {
NIMBLE_LOGD("NimBLEClientCallbacks", "onConnect: default");
}
void NimBLEClientCallbacks::onDisconnect(NimBLEClient *pClient) {
NIMBLE_LOGD("NimBLEClientCallbacks", "onDisconnect: default");
}
uint32_t NimBLEClientCallbacks::onPassKeyRequest(){
NIMBLE_LOGD("NimBLEClientCallbacks", "onPassKeyRequest: default: 123456");
return 123456;
}
void NimBLEClientCallbacks::onPassKeyNotify(uint32_t pass_key){
NIMBLE_LOGD("NimBLEClientCallbacks", "onPassKeyNotify: default: %d", pass_key);
}
bool NimBLEClientCallbacks::onSecurityRequest(){
NIMBLE_LOGD("NimBLEClientCallbacks", "onSecurityRequest: default: true");
return true;
}
void NimBLEClientCallbacks::onAuthenticationComplete(ble_gap_conn_desc*){
NIMBLE_LOGD("NimBLEClientCallbacks", "onAuthenticationComplete: default");
}
bool NimBLEClientCallbacks::onConfirmPIN(uint32_t pin){
NIMBLE_LOGD("NimBLEClientCallbacks", "onConfirmPIN: default: true");
return true;
}
#endif // CONFIG_BT_ENABLED

113
src/NimBLEClient.h Normal file
View File

@ -0,0 +1,113 @@
/*
* NimBLEClient.h
*
* Created: on Jan 26 2020
* Author H2zero
*
* Originally:
* BLEClient.h
*
* Created on: Mar 22, 2017
* Author: kolban
*/
#ifndef MAIN_NIMBLECLIENT_H_
#define MAIN_NIMBLECLIENT_H_
#if defined(CONFIG_BT_ENABLED)
#include "sdkconfig.h"
#include "NimBLEAddress.h"
#include "NimBLEAdvertisedDevice.h"
#include "NimBLERemoteService.h"
#include <map>
#include <string>
class NimBLERemoteService;
class NimBLEClientCallbacks;
class NimBLEAdvertisedDevice;
/**
* @brief A model of a %BLE client.
*/
class NimBLEClient {
public:
bool connect(NimBLEAdvertisedDevice* device, bool refreshServices = true);
bool connect(NimBLEAddress address, uint8_t type = BLE_ADDR_TYPE_PUBLIC, bool refreshServices = true); // Connect to the remote BLE Server
int disconnect(uint8_t reason = BLE_ERR_REM_USER_CONN_TERM); // Disconnect from the remote BLE Server
NimBLEAddress getPeerAddress(); // Get the address of the remote BLE Server
int getRssi(); // Get the RSSI of the remote BLE Server
std::map<std::string, NimBLERemoteService*>* getServices(); // Get a map of the services offered by the remote BLE Server
NimBLERemoteService* getService(const char* uuid); // Get a reference to a specified service offered by the remote BLE server.
NimBLERemoteService* getService(NimBLEUUID uuid); // Get a reference to a specified service offered by the remote BLE server.
std::string getValue(NimBLEUUID serviceUUID, NimBLEUUID characteristicUUID); // Get the value of a given characteristic at a given service.
bool setValue(NimBLEUUID serviceUUID, NimBLEUUID characteristicUUID, std::string value); // Set the value of a given characteristic at a given service.
bool isConnected(); // Return true if we are connected.
void setClientCallbacks(NimBLEClientCallbacks *pClientCallbacks, bool deleteCallbacks = true);
std::string toString(); // Return a string representation of this client.
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 minConnTime=16, uint16_t maxConnTime=768);
void updateConnParams(uint16_t minInterval, uint16_t maxInterval,
uint16_t latency, uint16_t timeout,
uint16_t minConnTime=16, uint16_t maxConnTime=768);
private:
NimBLEClient();
~NimBLEClient();
friend class NimBLEDevice;
friend class NimBLERemoteService;
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);
void clearServices(); // Clear any existing services.
bool retrieveServices(); //Retrieve services from the server
// void onHostReset();
NimBLEAddress m_peerAddress = NimBLEAddress("\0\0\0\0\0\0"); // The BD address of the remote server.
uint16_t m_conn_id;
bool m_haveServices = false; // Have we previously obtain the set of services from the remote server.
bool m_isConnected = false; // Are we currently connected.
bool m_waitingToConnect =false;
bool m_deleteCallbacks = true;
int32_t m_connectTimeout;
ble_gap_conn_params* m_pConnParams;
//uint16_t m_mtu = 23;
NimBLEClientCallbacks* m_pClientCallbacks = nullptr;
FreeRTOS::Semaphore m_semaphoreOpenEvt = FreeRTOS::Semaphore("OpenEvt");
FreeRTOS::Semaphore m_semaphoreSearchCmplEvt = FreeRTOS::Semaphore("SearchCmplEvt");
FreeRTOS::Semaphore m_semeaphoreSecEvt = FreeRTOS::Semaphore("Security");
std::map<std::string, NimBLERemoteService*> m_servicesMap;
}; // class NimBLEClient
/**
* @brief Callbacks associated with a %BLE client.
*/
class NimBLEClientCallbacks {
public:
virtual ~NimBLEClientCallbacks() {};
virtual void onConnect(NimBLEClient *pClient); // = 0;
virtual void onDisconnect(NimBLEClient *pClient); // = 0;
virtual uint32_t onPassKeyRequest(); //{return 0;}
virtual void onPassKeyNotify(uint32_t pass_key); //{}
virtual bool onSecurityRequest(); //{return false;}
virtual void onAuthenticationComplete(ble_gap_conn_desc*); //{};
virtual bool onConfirmPIN(uint32_t pin); //{return false;}
};
#endif // CONFIG_BT_ENABLED
#endif /* MAIN_NIMBLECLIENT_H_ */

248
src/NimBLEDescriptor.cpp Normal file
View File

@ -0,0 +1,248 @@
/*
* NimBLEDescriptor.cpp
*
* Created: on March 10, 2020
* Author H2zero
*
* Originally:
*
* BLEDescriptor.cpp
*
* Created on: Jun 22, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEService.h"
#include "NimBLEDescriptor.h"
#include "NimBLELog.h"
#include <string>
#define NULL_HANDLE (0xffff)
static const char* LOG_TAG = "NimBLEDescriptor";
static NimBLEDescriptorCallbacks defaultCallbacks;
/**
* @brief NimBLEDescriptor constructor.
*/
NimBLEDescriptor::NimBLEDescriptor(const char* uuid, uint16_t properties, uint16_t max_len,
NimBLECharacteristic* pCharacteristic)
: NimBLEDescriptor(NimBLEUUID(uuid), max_len, properties, pCharacteristic) {
}
/**
* @brief NimBLEDescriptor constructor.
*/
NimBLEDescriptor::NimBLEDescriptor(NimBLEUUID uuid, uint16_t properties, uint16_t max_len,
NimBLECharacteristic* pCharacteristic)
{
m_uuid = uuid;
m_value.attr_len = 0; // Initial length is 0.
m_value.attr_max_len = max_len; // Maximum length of the data.
m_handle = NULL_HANDLE; // Handle is initially unknown.
m_pCharacteristic = nullptr; // No initial characteristic.
m_pCallbacks = &defaultCallbacks; // No initial callback.
m_value.attr_value = (uint8_t*) calloc(max_len,1); // Allocate storage for the value.
m_properties = 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
/**
* @brief NimBLEDescriptor destructor.
*/
NimBLEDescriptor::~NimBLEDescriptor() {
free(m_value.attr_value); // Release the storage we created in the constructor.
} // ~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.attr_len;
} // getLength
/**
* @brief Get the UUID of the descriptor.
*/
NimBLEUUID NimBLEDescriptor::getUUID() {
return m_uuid;
} // getUUID
/**
* @brief Get the value of this descriptor.
* @return A pointer to the value of this descriptor.
*/
uint8_t* NimBLEDescriptor::getValue() {
return m_value.attr_value;
} // getValue
int NimBLEDescriptor::handleGapEvent(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt,
void *arg)
{
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: {
pDescriptor->m_pCallbacks->onRead(pDescriptor);
rc = os_mbuf_append(ctxt->om, pDescriptor->getValue(), pDescriptor->getLength());
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
case BLE_GATT_ACCESS_OP_WRITE_DSC: {
if (ctxt->om->om_len > BLE_ATT_ATTR_MAX_LEN) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
pDescriptor->setValue(ctxt->om->om_data, ctxt->om->om_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){
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(uint8_t* data, size_t length) {
if (length > BLE_ATT_ATTR_MAX_LEN) {
NIMBLE_LOGE(LOG_TAG, "Size %d too large, must be no bigger than %d", length, BLE_ATT_ATTR_MAX_LEN);
return;
}
m_value.attr_len = length;
memcpy(m_value.attr_value, data, length);
} // setValue
/**
* @brief Set the value of the descriptor.
* @param [in] value The value of the descriptor in string form.
*/
void NimBLEDescriptor::setValue(std::string value) {
setValue((uint8_t*) value.data(), value.length());
} // setValue
/*
void NimBLEDescriptor::setAccessPermissions(uint8_t perm) {
m_permissions = perm;
}
*/
/**
* @brief Return a string representation of the descriptor.
* @return A string representation of the descriptor.
*/
std::string NimBLEDescriptor::toString() {
char hex[5];
snprintf(hex, sizeof(hex), "%04x", m_handle);
std::string res = "UUID: " + m_uuid.toString() + ", handle: 0x" + hex;
return res;
} // toString
NimBLEDescriptorCallbacks::~NimBLEDescriptorCallbacks() {}
/**
* @brief Callback function to support a read request.
* @param [in] pDescriptor The descriptor that is the source of the event.
*/
void NimBLEDescriptorCallbacks::onRead(NimBLEDescriptor* pDescriptor) {
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.
*/
void NimBLEDescriptorCallbacks::onWrite(NimBLEDescriptor* pDescriptor) {
NIMBLE_LOGD("NimBLEDescriptorCallbacks", "onWrite: default");
} // onWrite
#endif /* CONFIG_BT_ENABLED */

101
src/NimBLEDescriptor.h Normal file
View File

@ -0,0 +1,101 @@
/*
* NimBLEDescriptor.h
*
* Created: on March 10, 2020
* Author H2zero
*
* Originally:
*
* BLEDescriptor.h
*
* Created on: Jun 22, 2017
* Author: kolban
*/
#ifndef MAIN_NIMBLEDESCRIPTOR_H_
#define MAIN_NIMBLEDESCRIPTOR_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLECharacteristic.h"
#include "NimBLEUUID.h"
#include "FreeRTOS.h"
#include <string>
typedef struct
{
uint16_t attr_max_len; /*!< attribute max value length */
uint16_t attr_len; /*!< attribute current value length */
uint8_t *attr_value; /*!< the pointer to attribute value */
} attr_value_t;
typedef attr_value_t esp_attr_value_t; /*!< compatibility for esp32 */
class NimBLEService;
class NimBLECharacteristic;
class NimBLEDescriptorCallbacks;
/**
* @brief A model of a %BLE descriptor.
*/
class NimBLEDescriptor {
public:
virtual ~NimBLEDescriptor();
uint16_t getHandle(); // Get the handle of the descriptor.
size_t getLength(); // Get the length of the value of the descriptor.
NimBLEUUID getUUID(); // Get the UUID of the descriptor.
uint8_t* getValue(); // Get a pointer to the value of the descriptor.
// void setAccessPermissions(uint8_t perm); // Set the permissions of the descriptor.
void setCallbacks(NimBLEDescriptorCallbacks* pCallbacks); // Set callbacks to be invoked for the descriptor.
void setValue(uint8_t* data, size_t size); // Set the value of the descriptor as a pointer to data.
void setValue(std::string value); // Set the value of the descriptor as a data buffer.
std::string toString(); // Convert the descriptor to a string representation.
private:
friend class NimBLEDescriptorMap;
friend class NimBLECharacteristic;
friend class NimBLEService;
friend class NimBLE2902;
friend class NimBLE2904;
NimBLEDescriptor(const char* uuid, uint16_t properties,
uint16_t max_len,
NimBLECharacteristic* pCharacteristic);
NimBLEDescriptor(NimBLEUUID uuid, uint16_t properties,
uint16_t max_len,
NimBLECharacteristic* pCharacteristic);
NimBLEUUID m_uuid;
uint16_t m_handle;
NimBLEDescriptorCallbacks* m_pCallbacks;
NimBLECharacteristic* m_pCharacteristic;
uint8_t m_properties;
attr_value_t m_value;
static int handleGapEvent(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg);
void setHandle(uint16_t handle);
}; // BLEDescriptor
/**
* @brief Callbacks that can be associated with a %BLE descriptors to inform of events.
*
* When a server application creates a %BLE descriptor, we may wish to be informed when there is either
* a read or write request to the descriptors value. An application can register a
* 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);
};
#endif /* CONFIG_BT_ENABLED */
#endif /* MAIN_NIMBLEDESCRIPTOR_H_ */

143
src/NimBLEDescriptorMap.cpp Normal file
View File

@ -0,0 +1,143 @@
/*
* NimBLEDescriptorMap.cpp
*
* Created: on March 10, 2020
* Author H2zero
*
* Originally:
*
* BLEDescriptorMap.cpp
*
* Created on: Jun 22, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLECharacteristic.h"
#include "NimBLEDescriptor.h"
/**
* @brief Return the descriptor by UUID.
* @param [in] UUID The UUID to look up the descriptor.
* @return The descriptor. If not present, then nullptr is returned.
*/
NimBLEDescriptor* NimBLEDescriptorMap::getByUUID(const char* uuid) {
return getByUUID(NimBLEUUID(uuid));
}
/**
* @brief Return the descriptor by UUID.
* @param [in] UUID The UUID to look up the descriptor.
* @return The descriptor. If not present, then nullptr is returned.
*/
NimBLEDescriptor* NimBLEDescriptorMap::getByUUID(NimBLEUUID uuid) {
for (auto &myPair : m_uuidMap) {
if (myPair.first->getUUID().equals(uuid)) {
return myPair.first;
}
}
return nullptr;
} // getByUUID
/**
* @brief Return the descriptor by handle.
* @param [in] handle The handle to look up the descriptor.
* @return The descriptor.
*/
/*
NimBLEDescriptor* NimBLEDescriptorMap::getByHandle(uint16_t handle) {
return m_handleMap.at(handle);
} // getByHandle
*/
/**
* @brief Set the descriptor by UUID.
* @param [in] uuid The uuid of the descriptor.
* @param [in] characteristic The descriptor to cache.
* @return N/A.
*/
void NimBLEDescriptorMap::setByUUID(const char* uuid, NimBLEDescriptor* pDescriptor){
m_uuidMap.insert(std::pair<NimBLEDescriptor*, std::string>(pDescriptor, uuid));
} // setByUUID
/**
* @brief Set the descriptor by UUID.
* @param [in] uuid The uuid of the descriptor.
* @param [in] characteristic The descriptor to cache.
* @return N/A.
*/
void NimBLEDescriptorMap::setByUUID(NimBLEUUID uuid, NimBLEDescriptor* pDescriptor) {
m_uuidMap.insert(std::pair<NimBLEDescriptor*, std::string>(pDescriptor, uuid.toString()));
} // setByUUID
/**
* @brief Set the descriptor by handle.
* @param [in] handle The handle of the descriptor.
* @param [in] descriptor The descriptor to cache.
* @return N/A.
*/
/*
void NimBLEDescriptorMap::setByHandle(uint16_t handle, NimBLEDescriptor* pDescriptor) {
m_handleMap.insert(std::pair<uint16_t, NimBLEDescriptor*>(handle, pDescriptor));
} // setByHandle
*/
/**
* @brief Get the number of descriptors in the map.
*/
uint8_t NimBLEDescriptorMap::getSize() {
return (uint8_t)m_uuidMap.size();
} // getSize
/**
* @brief Return a string representation of the descriptor map.
* @return A string representation of the descriptor map.
*/
std::string NimBLEDescriptorMap::toString() {
std::string res;
char hex[5];
int count = 0;
for (auto &myPair : m_uuidMap) {
if (count > 0) {res += "\n";}
snprintf(hex, sizeof(hex), "%04x", myPair.first->getHandle());
count++;
res += "handle: 0x";
res += hex;
res += ", uuid: " + myPair.first->getUUID().toString();
}
return res;
} // toString
/**
* @brief Get the first descriptor in the map.
* @return The first descriptor in the map.
*/
NimBLEDescriptor* NimBLEDescriptorMap::getFirst() {
m_iterator = m_uuidMap.begin();
if (m_iterator == m_uuidMap.end()) return nullptr;
NimBLEDescriptor* pRet = m_iterator->first;
m_iterator++;
return pRet;
} // getFirst
/**
* @brief Get the next descriptor in the map.
* @return The next descriptor in the map.
*/
NimBLEDescriptor* NimBLEDescriptorMap::getNext() {
if (m_iterator == m_uuidMap.end()) return nullptr;
NimBLEDescriptor* pRet = m_iterator->first;
m_iterator++;
return pRet;
} // getNext
#endif /* CONFIG_BT_ENABLED */

654
src/NimBLEDevice.cpp Normal file
View File

@ -0,0 +1,654 @@
/*
* NimBLEDevice.cpp
*
* Created: on Jan 24 2020
* Author H2zero
*
* Originally:
*
* BLEDevice.cpp
*
* Created on: Mar 16, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEDevice.h"
#include "NimBLEUtils.h"
#include "esp_err.h"
#include "esp_bt.h"
#include "nvs_flash.h"
#include "esp_nimble_hci.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/util/util.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-bt.h"
#endif
#include "NimBLELog.h"
static const char* LOG_TAG = "NimBLEDevice";
/**
* Singletons for the NimBLEDevice.
*/
bool initialized = false;
NimBLEScan* NimBLEDevice::m_pScan = nullptr;
NimBLEServer* NimBLEDevice::m_pServer = nullptr;
uint32_t NimBLEDevice::m_passkey = 123456;
bool NimBLEDevice::m_synced = false;
NimBLEAdvertising* NimBLEDevice::m_bleAdvertising = nullptr;
gap_event_handler NimBLEDevice::m_customGapHandler = nullptr;
ble_gap_event_listener NimBLEDevice::m_listener;
std::list <NimBLEClient*> NimBLEDevice::m_cList;
std::list <NimBLEAddress> NimBLEDevice::m_ignoreList;
NimBLESecurityCallbacks* NimBLEDevice::m_securityCallbacks = nullptr;
//std::map<uint16_t, conn_status_t> BLEDevice::m_connectedClientsMap;
//gattc_event_handler BLEDevice::m_customGattcHandler = nullptr;
//gatts_event_handler BLEDevice::m_customGattsHandler = nullptr;
/**
* @brief Create a new instance of a server.
* @return A new instance of the server.
*/
/* STATIC */ NimBLEServer* NimBLEDevice::createServer() {
/*#ifndef CONFIG_GATTS_ENABLE // Check that BLE GATTS is enabled in make menuconfig
NIMBLE_LOGE(LOG_TAG, "BLE GATTS is not enabled - CONFIG_GATTS_ENABLE not defined");
abort();
#endif // CONFIG_GATTS_ENABLE
*/
if(NimBLEDevice::m_pServer == nullptr) {
NimBLEDevice::m_pServer = new NimBLEServer();
ble_gatts_reset();
ble_svc_gap_init();
ble_svc_gatt_init();
}
return m_pServer;
} // createServer
NimBLEAdvertising* NimBLEDevice::getAdvertising() {
if(m_bleAdvertising == nullptr) {
m_bleAdvertising = new NimBLEAdvertising();
}
return m_bleAdvertising;
}
void NimBLEDevice::startAdvertising() {
getAdvertising()->start();
} // startAdvertising
void NimBLEDevice::stopAdvertising() {
getAdvertising()->stop();
} // stopAdvertising
/**
* @brief Retrieve the Scan object that we use for scanning.
* @return The scanning object reference. This is a singleton object. The caller should not
* try and release/delete it.
*/
/* STATIC */ NimBLEScan* NimBLEDevice::getScan() {
if (m_pScan == nullptr) {
m_pScan = new NimBLEScan();
}
return m_pScan;
} // getScan
/**
* @brief Creates a new client object and maintains a list of all client objects
* each client can connect to 1 peripheral device.
* @return A reference to the new client object.
*/
/* STATIC */ NimBLEClient* NimBLEDevice::createClient() {
if(m_cList.size() >= NIMBLE_MAX_CONNECTIONS) {
NIMBLE_LOGW("Number of clients exceeds Max connections. Max=(%d)",
NIMBLE_MAX_CONNECTIONS);
}
NimBLEClient* pClient = new NimBLEClient();
m_cList.push_back(pClient);
return pClient;
} // createClient
/**
* @brief Delete the client object and remove it from the list.
* Check if it is connected or trying to connect and close/stop it first.
* @param [in] Pointer to the client object.
*/
/* STATIC */ bool NimBLEDevice::deleteClient(NimBLEClient* pClient) {
if(pClient == nullptr) {
return false;
}
if(pClient->m_isConnected) {
if (pClient->disconnect() != 0) {
return false;
}
while(pClient->m_isConnected) {
vTaskDelay(1);
}
}
if(pClient->m_waitingToConnect) {
if(ble_gap_conn_cancel() != 0){
return false;
}
while(pClient->m_waitingToConnect) {
vTaskDelay(1);
}
}
m_cList.remove(pClient);
delete pClient;
return true;
} // deleteClient
/**
* @brief get the list of clients.
* @return a pointer to the list of clients.
*/
/* STATIC */std::list<NimBLEClient*>* NimBLEDevice::getClientList() {
return &m_cList;
} // getClientList
/**
* @brief get the size of the list of clients.
* @return a pointer to the list of clients.
*/
/* STATIC */size_t NimBLEDevice::getClientListSize() {
return m_cList.size();
} // getClientList
/**
* @brief Get a reference to a client by connection ID.
* @param [in] The client connection ID to search for.
* @return A reference pointer to the client with the spcified connection ID.
*/
/* STATIC */NimBLEClient* NimBLEDevice::getClientByID(uint16_t conn_id) {
for(auto it = m_cList.cbegin(); it != m_cList.cend(); ++it) {
if((*it)->getConnId() == conn_id) {
return (*it);
}
}
assert(0);
return nullptr;
} // getClientByID
/**
* @brief Get a reference to a client by peer address.
* @param [in] a NimBLEAddress of the peer to search for.
* @return A reference pointer to the client with the peer address.
*/
/* STATIC */NimBLEClient* NimBLEDevice::getClientByPeerAddress(NimBLEAddress peer_addr) {
for(auto it = m_cList.cbegin(); it != m_cList.cend(); ++it) {
if((*it)->getPeerAddress().equals(peer_addr)) {
return (*it);
}
}
return nullptr;
} // getClientPeerAddress
/**
* @brief Finds the first disconnected client in the list.
* @return A reference pointer to the first client that is not connected to a peer.
*/
/* STATIC */NimBLEClient* NimBLEDevice::getDisconnectedClient() {
for(auto it = m_cList.cbegin(); it != m_cList.cend(); ++it) {
if(!(*it)->isConnected()) {
return (*it);
}
}
return nullptr;
} // getDisconnectedClient
/**
* @brief Set the transmission power.
* The power level can be one of:
* * ESP_PWR_LVL_N12 = 0, !< Corresponding to -12dbm
* * ESP_PWR_LVL_N9 = 1, !< Corresponding to -9dbm
* * ESP_PWR_LVL_N6 = 2, !< Corresponding to -6dbm
* * ESP_PWR_LVL_N3 = 3, !< Corresponding to -3dbm
* * ESP_PWR_LVL_N0 = 4, !< Corresponding to 0dbm
* * ESP_PWR_LVL_P3 = 5, !< Corresponding to +3dbm
* * ESP_PWR_LVL_P6 = 6, !< Corresponding to +6dbm
* * ESP_PWR_LVL_P9 = 7, !< Corresponding to +9dbm
* @param [in] powerLevel.
*/
/* STATIC */ void NimBLEDevice::setPower(esp_power_level_t powerLevel) {
NIMBLE_LOGD(LOG_TAG, ">> setPower: %d", powerLevel);
esp_err_t errRc = ::esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, powerLevel);
if (errRc != ESP_OK) {
//NIMBLE_LOGE(LOG_TAG, "esp_ble_tx_power_set: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
NIMBLE_LOGE(LOG_TAG, "esp_ble_tx_power_set: rc=%d", errRc);
};
NIMBLE_LOGD(LOG_TAG, "<< setPower");
} // setPower
/**
* @brief Get our device address.
* @return A NimBLEAddress object of our public address if we have one,
* if not then our current random address.
*/
/* STATIC*/ NimBLEAddress NimBLEDevice::getAddress() {
ble_addr_t addr = {BLE_ADDR_PUBLIC, 0};
//ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, addr.val, NULL)
if(BLE_HS_ENOADDR == ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, addr.val, NULL)) {
NIMBLE_LOGD(LOG_TAG, "Public address not found, checking random");
addr.type = BLE_ADDR_RANDOM;
ble_hs_id_copy_addr(BLE_ADDR_RANDOM, addr.val, NULL);
}
return NimBLEAddress(addr);
} // getAddress
/**
* @brief Return a string representation of the address of this device.
* @return A string representation of this device address.
*/
/* STATIC */ std::string NimBLEDevice::toString() {
return getAddress().toString();
} // toString
/**
* @brief Setup local mtu that will be used to negotiate mtu during request from client peer
* @param [in] mtu Value to set local mtu, should be larger than 23 and lower or equal to
* BLE_ATT_MTU_MAX = 527
*/
/* STATIC */int NimBLEDevice::setMTU(uint16_t mtu) {
NIMBLE_LOGD(LOG_TAG, ">> setLocalMTU: %d", mtu);
int rc = ble_att_set_preferred_mtu(mtu);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Could not set local mtu value to: %d", mtu);
}
NIMBLE_LOGD(LOG_TAG, "<< setLocalMTU");
return rc;
} // setMTU
/**
* @brief Get local MTU value set.
*/
/* STATIC */uint16_t NimBLEDevice::getMTU() {
return ble_att_preferred_mtu();
}
/**
* @brief Host reset, we pass the message so we don't make calls until resynced.
*/
/* STATIC */ void NimBLEDevice::onReset(int reason)
{
if(!m_synced) {
return;
}
m_synced = false;
if(m_pScan != nullptr) {
m_pScan->onHostReset();
}
/* Not needed
if(m_pServer != nullptr) {
m_pServer->onHostReset();
}
for(auto it = m_cList.cbegin(); it != m_cList.cend(); ++it) {
(*it)->onHostReset();
}
if(m_bleAdvertising != nullptr) {
m_bleAdvertising->onHostReset();
}
*/
NIMBLE_LOGC(LOG_TAG, "Resetting state; reason=%d, %s", reason,
NimBLEUtils::returnCodeToString(reason));
} // onReset
/**
* @brief Host resynced with controller, all clear to make calls.
*/
/* STATIC */ void NimBLEDevice::onSync(void)
{
NIMBLE_LOGI(LOG_TAG, "NimBle host synced.");
// This check is needed due to potentially being called multiple times in succession
// If this happens, the call to scan start may get stuck or cause an advertising fault.
if(m_synced) {
return;
}
/* Make sure we have proper identity address set (public preferred) */
int rc = ble_hs_util_ensure_addr(0);
assert(rc == 0);
m_synced = true;
if(m_pScan != nullptr) {
// Restart scanning with the last values sent, allow to clear results.
m_pScan->start(m_pScan->m_duration, m_pScan->m_scanCompleteCB);
}
if(m_bleAdvertising != nullptr) {
// Restart advertisng, parameters should already be set.
m_bleAdvertising->start();
}
} // onSync
/**
* @brief The main host task.
*/
/* STATIC */ void NimBLEDevice::host_task(void *param)
{
NIMBLE_LOGI(LOG_TAG, "BLE Host Task Started");
/* This function will return only when nimble_port_stop() is executed */
nimble_port_run();
nimble_port_freertos_deinit();
} // host_task
/**
* @brief Initialize the %BLE environment.
* @param deviceName The device name of the device.
*/
/* STATIC */ void NimBLEDevice::init(std::string deviceName) {
if(!initialized){
initialized = true; // Set the initialization flag to ensure we are only initialized once.
int rc=0;
esp_err_t errRc = ESP_OK;
#ifdef ARDUINO_ARCH_ESP32
// make sure the linker includes esp32-hal-bt.c so ardruino init doesn't release BLE memory.
btStarted();
#endif
errRc = nvs_flash_init();
if (errRc == ESP_ERR_NVS_NO_FREE_PAGES || errRc == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
errRc = nvs_flash_init();
}
ESP_ERROR_CHECK(errRc);
ESP_ERROR_CHECK(esp_nimble_hci_and_controller_init());
nimble_port_init();
// Setup callbacks for host events
ble_hs_cfg.reset_cb = NimBLEDevice::onReset;
ble_hs_cfg.sync_cb = NimBLEDevice::onSync;
// Set initial security capabilities
ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_NO_IO;
ble_hs_cfg.sm_bonding = 0;
ble_hs_cfg.sm_mitm = 0;
ble_hs_cfg.sm_sc = 1;
ble_hs_cfg.sm_our_key_dist = 1;
ble_hs_cfg.sm_their_key_dist = 1;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr; /*TODO: Implement handler for this*/
// Set the device name.
rc = ble_svc_gap_device_name_set(deviceName.c_str());
assert(rc == 0);
ble_store_config_init();
nimble_port_freertos_init(NimBLEDevice::host_task);
}
// Wait for host and controller to sync before returning and accepting new tasks
while(!m_synced){
vTaskDelay(1 / portTICK_PERIOD_MS);
}
//vTaskDelay(200 / portTICK_PERIOD_MS); // Delay for 200 msecs as a workaround to an apparent Arduino environment issue.
} // init
/**
* @brief Shutdown the NimBLE stack/controller.
*/
/* STATIC */ void NimBLEDevice::deinit() {
int ret = nimble_port_stop();
if (ret == 0) {
nimble_port_deinit();
ret = esp_nimble_hci_and_controller_deinit();
if (ret != ESP_OK) {
NIMBLE_LOGE(LOG_TAG, "esp_nimble_hci_and_controller_deinit() failed with error: %d", ret);
}
initialized = false;
}
} // deinit
/**
* @brief Check if the initialization is complete.
*/
bool NimBLEDevice::getInitialized() {
return initialized;
} // getInitialized
/**
* @brief Set the authorization mode for this device.
* @param bonding, if true we allow bonding, false no bonding will be performed.
* @param mitm, if true we are capable of man in the middle protection, false if not.
* @param sc, if true we will perform secure connection pairing, false we will use legacy pairing.
*/
/*STATIC*/ void NimBLEDevice::setSecurityAuth(bool bonding, bool mitm, bool sc) {
NIMBLE_LOGD(LOG_TAG, "Setting bonding: %d, mitm: %d, sc: %d",bonding,mitm,sc);
ble_hs_cfg.sm_bonding = bonding;
ble_hs_cfg.sm_mitm = mitm;
ble_hs_cfg.sm_sc = sc;
} // setSecurityAuth
/**
* @brief Set the authorization mode for this device.
* @param A bitmap indicating what modes are supported.
* The bits are defined as follows:
** 0x01 BLE_SM_PAIR_AUTHREQ_BOND
** 0x04 BLE_SM_PAIR_AUTHREQ_MITM
** 0x08 BLE_SM_PAIR_AUTHREQ_SC
** 0x10 BLE_SM_PAIR_AUTHREQ_KEYPRESS - not yet supported.
** 0xe2 BLE_SM_PAIR_AUTHREQ_RESERVED - for reference only.
*/
/*STATIC*/void NimBLEDevice::setSecurityAuth(uint8_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);
} // setSecurityAuth
/**
* @brief Set the Input/Output capabilities of this device.
* @param One of the following:
** 0x00 BLE_HS_IO_DISPLAY_ONLY DisplayOnly IO capability
** 0x01 BLE_HS_IO_DISPLAY_YESNO DisplayYesNo IO capability
** 0x02 BLE_HS_IO_KEYBOARD_ONLY KeyboardOnly IO capability
** 0x03 BLE_HS_IO_NO_INPUT_OUTPUT NoInputNoOutput IO capability
** 0x04 BLE_HS_IO_KEYBOARD_DISPLAY KeyboardDisplay Only IO capability
*/
/*STATIC*/ void NimBLEDevice::setSecurityIOCap(uint8_t iocap) {
ble_hs_cfg.sm_io_cap = iocap;
} // setSecurityIOCap
/**
* @brief If we are the initiator of the security procedure this sets the keys we will distribute.
* @param A bitmap indicating which keys to distribute during pairing.
* The bits are defined as follows:
** 0x01: BLE_SM_PAIR_KEY_DIST_ENC - Distribute the encryption key.
** 0x02: BLE_SM_PAIR_KEY_DIST_ID - Distribute the ID key (IRK).
** 0x04: BLE_SM_PAIR_KEY_DIST_SIGN
** 0x08: BLE_SM_PAIR_KEY_DIST_LINK
*/
/*STATIC*/void NimBLEDevice::setSecurityInitKey(uint8_t init_key) {
ble_hs_cfg.sm_our_key_dist = init_key;
} // setsSecurityInitKey
/**
* @brief Set the keys we are willing to accept during pairing.
* @param A bitmap indicating which keys to accept during pairing.
* The bits are defined as follows:
** 0x01: BLE_SM_PAIR_KEY_DIST_ENC - Accept the encryption key.
** 0x02: BLE_SM_PAIR_KEY_DIST_ID - Accept the ID key (IRK).
** 0x04: BLE_SM_PAIR_KEY_DIST_SIGN
** 0x08: BLE_SM_PAIR_KEY_DIST_LINK
*/
/*STATIC*/void NimBLEDevice::setSecurityRespKey(uint8_t init_key) {
ble_hs_cfg.sm_their_key_dist = init_key;
} // setsSecurityRespKey
/**
* @brief Set the passkey for pairing.
*/
/*STATIC*/void NimBLEDevice::setSecurityPasskey(uint32_t pin) {
m_passkey = pin;
} // setSecurityPasskey
/**
* @brief Get the passkey for pairing.
*/
/*STATIC*/uint32_t NimBLEDevice::getSecurityPasskey() {
return m_passkey;
} // getSecurityPasskey
/**
* @brief Set callbacks that will be used to handle encryption negotiation events and authentication events
* @param [in] cllbacks Pointer to NimBLESecurityCallbacks class
*/
void NimBLEDevice::setSecurityCallbacks(NimBLESecurityCallbacks* callbacks) {
NimBLEDevice::m_securityCallbacks = callbacks;
} // setSecurityCallbacks
/**
* @brief Start the connection securing and authorization for this connection.
* @param Connection id of the client.
* @returns host return code 0 if success.
*/
/* STATIC */int NimBLEDevice::startSecurity(uint16_t conn_id) {
/* if(m_securityCallbacks != nullptr) {
m_securityCallbacks->onSecurityRequest();
}
*/
int rc = ble_gap_security_initiate(conn_id);
if(rc != 0){
NIMBLE_LOGE(LOG_TAG, "ble_gap_security_initiate: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
}
return rc;
} // startSecurity
/**
* @brief Check if the device address is on our ignore list.
* @return True if ignoring.
*/
/*STATIC*/ bool NimBLEDevice::isIgnored(NimBLEAddress address) {
for(auto &it : m_ignoreList) {
if(it.equals(address)){
return true;
}
}
return false;
}
/**
* @brief Add a device to the ignore list.
* @param Address of the device we want to ignore.
*/
/*STATIC*/ void NimBLEDevice::addIgnored(NimBLEAddress address) {
m_ignoreList.push_back(address);
}
/**
* @brief Remove a device from the ignore list.
* @param Address of the device we want to remove from the list.
*/
/*STATIC*/void NimBLEDevice::removeIgnored(NimBLEAddress address) {
for(auto it = m_ignoreList.begin(); it != m_ignoreList.end(); ++it) {
if((*it).equals(address)){
m_ignoreList.erase(it);
return;
}
}
}
/**
* @brief Set a custom callback for gap events.
*/
void NimBLEDevice::setCustomGapHandler(gap_event_handler handler) {
m_customGapHandler = handler;
int rc = ble_gap_event_listener_register(&m_listener, m_customGapHandler, NULL);
if(rc == BLE_HS_EALREADY){
NIMBLE_LOGI(LOG_TAG, "Already listening to GAP events.");
}
else{
assert(rc == 0);
}
} // setCustomGapHandler
/**
* @brief Backward compatibility for bluedroid gatt events.
* NimBLe does not send GATT events
*/
/*
void BLEDevice::setCustomGattcHandler(gattc_event_handler handler) {
setCustomGapHandler(handler);
}
void BLEDevice::setCustomGattsHandler(gatts_event_handler handler) {
setCustomGapHandler(handler);
}
*/
/**********************************************************/
#endif // CONFIG_BT_ENABLED

135
src/NimBLEDevice.h Normal file
View File

@ -0,0 +1,135 @@
/*
* NimBLEDevice.h
*
* Created: on Jan 24 2020
* Author H2zero
*
* Originally:
*
* BLEDevice.h
*
* Created on: Mar 16, 2017
* Author: kolban
*/
#ifndef MAIN_NIMBLEDEVICE_H_
#define MAIN_NIMBLEDEVICE_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimbleScan.h"
#include "NimBLEUtils.h"
#include "NimBLEClient.h"
#include "NimBLEServer.h"
#include "NimBLESecurity.h"
#include "esp_bt.h"
#include <map>
#include <string>
#include <list>
#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
#define NIMBLE_MAX_CONNECTIONS CONFIG_BT_NIMBLE_MAX_CONNECTIONS
/**
* @brief BLE functions.
*/
typedef int (*gap_event_handler)(ble_gap_event *event, void *arg);
//typedef void (*gattc_event_handler)(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* param);
//typedef void (*gatts_event_handler)(esp_gatts_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gatts_cb_param_t* param);
extern "C" void ble_store_config_init(void);
class NimBLEDevice {
public:
static void init(std::string deviceName); // Initialize the local BLE environment.
static void deinit();
static bool getInitialized();
static NimBLEAddress getAddress();
static std::string toString();
static NimBLEScan* getScan(); // Get the scan object
static NimBLEClient* createClient();
static NimBLEServer* createServer();
static bool deleteClient(NimBLEClient* pClient);
static void setPower(esp_power_level_t powerLevel);
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 setMTU(uint16_t mtu);
static uint16_t getMTU();
static bool isIgnored(NimBLEAddress address);
static void addIgnored(NimBLEAddress address);
static void removeIgnored(NimBLEAddress address);
static NimBLEAdvertising* getAdvertising();
static void startAdvertising();
static void stopAdvertising();
static NimBLEClient* getClientByID(uint16_t conn_id);
static NimBLEClient* getClientByPeerAddress(NimBLEAddress peer_addr);
static NimBLEClient* getDisconnectedClient();
static size_t getClientListSize();
static std::list<NimBLEClient*>* getClientList();
private:
friend class NimBLEServer;
friend class NimBLEClient;
friend class NimBLEScan;
friend class NimBLEAdvertising;
static void onReset(int reason);
static void onSync(void);
static void host_task(void *param);
static int startSecurity( uint16_t conn_id);
static bool m_synced;
static NimBLEScan* m_pScan;
static NimBLEServer* m_pServer;
static NimBLEAdvertising* m_bleAdvertising;
static ble_gap_event_listener m_listener;
static uint32_t m_passkey;
static std::list <NimBLEClient*> m_cList;
static std::list <NimBLEAddress> m_ignoreList;
static NimBLESecurityCallbacks* m_securityCallbacks;
public:
static gap_event_handler m_customGapHandler;
};
#endif // CONFIG_BT_ENABLED
#endif // MAIN_NIMBLEDEVICE_H_

152
src/NimBLEEddystoneTLM.cpp Normal file
View File

@ -0,0 +1,152 @@
/*
* NimBLEEddystoneTLM.cpp
*
* Created: on March 15 2020
* Author H2zero
*
* Originally:
*
* BLEEddystoneTLM.cpp
*
* Created on: Mar 12, 2018
* Author: pcbreflux
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEEddystoneTLM.h"
#include "NimBLELog.h"
#include <cstring>
#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00)>>8) + (((x)&0xFF)<<8))
#define ENDIAN_CHANGE_U32(x) ((((x)&0xFF000000)>>24) + (((x)&0x00FF0000)>>8)) + ((((x)&0xFF00)<<8) + (((x)&0xFF)<<24))
static const char LOG_TAG[] = "NimBLEEddystoneTLM";
NimBLEEddystoneTLM::NimBLEEddystoneTLM() {
beaconUUID = 0xFEAA;
m_eddystoneData.frameType = EDDYSTONE_TLM_FRAME_TYPE;
m_eddystoneData.version = 0;
m_eddystoneData.volt = 3300; // 3300mV = 3.3V
m_eddystoneData.temp = (uint16_t) ((float) 23.00 * 256); // 8.8 fixed format
m_eddystoneData.advCount = 0;
m_eddystoneData.tmil = 0;
} // NimBLEEddystoneTLM
std::string NimBLEEddystoneTLM::getData() {
return std::string((char*) &m_eddystoneData, sizeof(m_eddystoneData));
} // getData
NimBLEUUID NimBLEEddystoneTLM::getUUID() {
return NimBLEUUID(beaconUUID);
} // getUUID
uint8_t NimBLEEddystoneTLM::getVersion() {
return m_eddystoneData.version;
} // getVersion
uint16_t NimBLEEddystoneTLM::getVolt() {
return ENDIAN_CHANGE_U16(m_eddystoneData.volt);
} // getVolt
float NimBLEEddystoneTLM::getTemp() {
return ENDIAN_CHANGE_U16(m_eddystoneData.temp) / 256.0f;
} // getTemp
uint32_t NimBLEEddystoneTLM::getCount() {
return ENDIAN_CHANGE_U32(m_eddystoneData.advCount);
} // getCount
uint32_t NimBLEEddystoneTLM::getTime() {
return (ENDIAN_CHANGE_U32(m_eddystoneData.tmil)) / 10;
} // getTime
std::string NimBLEEddystoneTLM::toString() {
std::string out = "";
uint32_t rawsec = ENDIAN_CHANGE_U32(m_eddystoneData.tmil);
char val[12];
out += "Version "; // + std::string(m_eddystoneData.version);
snprintf(val, sizeof(val), "%d", m_eddystoneData.version);
out += val;
out += "\n";
out += "Battery Voltage "; // + ENDIAN_CHANGE_U16(m_eddystoneData.volt);
snprintf(val, sizeof(val), "%d", ENDIAN_CHANGE_U16(m_eddystoneData.volt));
out += val;
out += " mV\n";
out += "Temperature ";
snprintf(val, sizeof(val), "%.2f", ENDIAN_CHANGE_U16(m_eddystoneData.temp) / 256.0f);
out += val;
out += " C\n";
out += "Adv. Count ";
snprintf(val, sizeof(val), "%d", ENDIAN_CHANGE_U32(m_eddystoneData.advCount));
out += val;
out += "\n";
out += "Time in seconds ";
snprintf(val, sizeof(val), "%d", rawsec/10);
out += val;
out += "\n";
out += "Time ";
snprintf(val, sizeof(val), "%04d", rawsec / 864000);
out += val;
out += ".";
snprintf(val, sizeof(val), "%02d", (rawsec / 36000) % 24);
out += val;
out += ":";
snprintf(val, sizeof(val), "%02d", (rawsec / 600) % 60);
out += val;
out += ":";
snprintf(val, sizeof(val), "%02d", (rawsec / 10) % 60);
out += val;
out += "\n";
return out;
} // toString
/**
* Set the raw data for the beacon record.
*/
void NimBLEEddystoneTLM::setData(std::string data) {
if (data.length() != sizeof(m_eddystoneData)) {
NIMBLE_LOGE(LOG_TAG, "Unable to set the data ... length passed in was %d and expected %d",
data.length(), sizeof(m_eddystoneData));
return;
}
memcpy(&m_eddystoneData, data.data(), data.length());
} // setData
void NimBLEEddystoneTLM::setUUID(NimBLEUUID l_uuid) {
beaconUUID = l_uuid.getNative()->u16.value;
} // setUUID
void NimBLEEddystoneTLM::setVersion(uint8_t version) {
m_eddystoneData.version = version;
} // setVersion
void NimBLEEddystoneTLM::setVolt(uint16_t volt) {
m_eddystoneData.volt = volt;
} // setVolt
void NimBLEEddystoneTLM::setTemp(float temp) {
m_eddystoneData.temp = (uint16_t)temp;
} // setTemp
void NimBLEEddystoneTLM::setCount(uint32_t advCount) {
m_eddystoneData.advCount = advCount;
} // setCount
void NimBLEEddystoneTLM::setTime(uint32_t tmil) {
m_eddystoneData.tmil = tmil;
} // setTime
#endif

60
src/NimBLEEddystoneTLM.h Normal file
View File

@ -0,0 +1,60 @@
/*
* NimBLEEddystoneTLM.h
*
* Created: on March 15 2020
* Author H2zero
*
* Originally:
*
* BLEEddystoneTLM.h
*
* Created on: Mar 12, 2018
* Author: pcbreflux
*/
#ifndef _NimBLEEddystoneTLM_H_
#define _NimBLEEddystoneTLM_H_
#include "NimBLEUUID.h"
#include <string>
#define EDDYSTONE_TLM_FRAME_TYPE 0x20
/**
* @brief Representation of a beacon.
* See:
* * https://github.com/google/eddystone
*/
class NimBLEEddystoneTLM {
public:
NimBLEEddystoneTLM();
std::string getData();
NimBLEUUID getUUID();
uint8_t getVersion();
uint16_t getVolt();
float getTemp();
uint32_t getCount();
uint32_t getTime();
std::string toString();
void setData(std::string data);
void setUUID(NimBLEUUID l_uuid);
void setVersion(uint8_t version);
void setVolt(uint16_t volt);
void setTemp(float temp);
void setCount(uint32_t advCount);
void setTime(uint32_t tmil);
private:
uint16_t beaconUUID;
struct {
uint8_t frameType;
uint8_t version;
uint16_t volt;
uint16_t temp;
uint32_t advCount;
uint32_t tmil;
} __attribute__((packed)) m_eddystoneData;
}; // NimBLEEddystoneTLM
#endif /* _NimBLEEddystoneTLM_H_ */

159
src/NimBLEEddystoneURL.cpp Normal file
View File

@ -0,0 +1,159 @@
/*
* NimBLEEddystoneURL.cpp
*
* Created: on March 15 2020
* Author H2zero
*
* Originally:
*
* BLEEddystoneURL.cpp
*
* Created on: Mar 12, 2018
* Author: pcbreflux
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEEddystoneURL.h"
#include "NimBLELog.h"
#include <cstring>
static const char LOG_TAG[] = "NimBLEEddystoneURL";
NimBLEEddystoneURL::NimBLEEddystoneURL() {
beaconUUID = 0xFEAA;
lengthURL = 0;
m_eddystoneData.frameType = EDDYSTONE_URL_FRAME_TYPE;
m_eddystoneData.advertisedTxPower = 0;
memset(m_eddystoneData.url, 0, sizeof(m_eddystoneData.url));
} // BLEEddystoneURL
std::string NimBLEEddystoneURL::getData() {
return std::string((char*) &m_eddystoneData, sizeof(m_eddystoneData));
} // getData
NimBLEUUID NimBLEEddystoneURL::getUUID() {
return NimBLEUUID(beaconUUID);
} // getUUID
int8_t NimBLEEddystoneURL::getPower() {
return m_eddystoneData.advertisedTxPower;
} // getPower
std::string NimBLEEddystoneURL::getURL() {
return std::string((char*) &m_eddystoneData.url, sizeof(m_eddystoneData.url));
} // getURL
std::string NimBLEEddystoneURL::getDecodedURL() {
std::string decodedURL = "";
switch (m_eddystoneData.url[0]) {
case 0x00:
decodedURL += "http://www.";
break;
case 0x01:
decodedURL += "https://www.";
break;
case 0x02:
decodedURL += "http://";
break;
case 0x03:
decodedURL += "https://";
break;
default:
decodedURL += m_eddystoneData.url[0];
}
for (int i = 1; i < lengthURL; i++) {
if (m_eddystoneData.url[i] > 33 && m_eddystoneData.url[i] < 127) {
decodedURL += m_eddystoneData.url[i];
} else {
switch (m_eddystoneData.url[i]) {
case 0x00:
decodedURL += ".com/";
break;
case 0x01:
decodedURL += ".org/";
break;
case 0x02:
decodedURL += ".edu/";
break;
case 0x03:
decodedURL += ".net/";
break;
case 0x04:
decodedURL += ".info/";
break;
case 0x05:
decodedURL += ".biz/";
break;
case 0x06:
decodedURL += ".gov/";
break;
case 0x07:
decodedURL += ".com";
break;
case 0x08:
decodedURL += ".org";
break;
case 0x09:
decodedURL += ".edu";
break;
case 0x0A:
decodedURL += ".net";
break;
case 0x0B:
decodedURL += ".info";
break;
case 0x0C:
decodedURL += ".biz";
break;
case 0x0D:
decodedURL += ".gov";
break;
default:
break;
}
}
}
return decodedURL;
} // getDecodedURL
/**
* Set the raw data for the beacon record.
*/
void NimBLEEddystoneURL::setData(std::string data) {
if (data.length() > sizeof(m_eddystoneData)) {
NIMBLE_LOGE(LOG_TAG, "Unable to set the data ... length passed in was %d and max expected %d",
data.length(), sizeof(m_eddystoneData));
return;
}
memset(&m_eddystoneData, 0, sizeof(m_eddystoneData));
memcpy(&m_eddystoneData, data.data(), data.length());
lengthURL = data.length() - (sizeof(m_eddystoneData) - sizeof(m_eddystoneData.url));
} // setData
void NimBLEEddystoneURL::setUUID(NimBLEUUID l_uuid) {
beaconUUID = l_uuid.getNative()->u16.value;
} // setUUID
void NimBLEEddystoneURL::setPower(int8_t advertisedTxPower) {
m_eddystoneData.advertisedTxPower = advertisedTxPower;
} // setPower
void NimBLEEddystoneURL::setURL(std::string url) {
if (url.length() > sizeof(m_eddystoneData.url)) {
NIMBLE_LOGE(LOG_TAG, "Unable to set the url ... length passed in was %d and max expected %d",
url.length(), sizeof(m_eddystoneData.url));
return;
}
memset(m_eddystoneData.url, 0, sizeof(m_eddystoneData.url));
memcpy(m_eddystoneData.url, url.data(), url.length());
lengthURL = url.length();
} // setURL
#endif

52
src/NimBLEEddystoneURL.h Normal file
View File

@ -0,0 +1,52 @@
/*
* NimBLEEddystoneURL.h
*
* Created: on March 15 2020
* Author H2zero
*
* Originally:
*
* BLEEddystoneURL.h
*
* Created on: Mar 12, 2018
* Author: pcbreflux
*/
#ifndef _NIMBLEEddystoneURL_H_
#define _NIMBLEEddystoneURL_H_
#include "NimBLEUUID.h"
#include <string>
#define EDDYSTONE_URL_FRAME_TYPE 0x10
/**
* @brief Representation of a beacon.
* See:
* * https://github.com/google/eddystone
*/
class NimBLEEddystoneURL {
public:
NimBLEEddystoneURL();
std::string getData();
NimBLEUUID getUUID();
int8_t getPower();
std::string getURL();
std::string getDecodedURL();
void setData(std::string data);
void setUUID(NimBLEUUID l_uuid);
void setPower(int8_t advertisedTxPower);
void setURL(std::string url);
private:
uint16_t beaconUUID;
uint8_t lengthURL;
struct {
uint8_t frameType;
int8_t advertisedTxPower;
uint8_t url[16];
} __attribute__((packed)) m_eddystoneData;
}; // NIMBLEEddystoneURL
#endif /* _NIMBLEEddystoneURL_H_ */

57
src/NimBLELog.h Normal file
View File

@ -0,0 +1,57 @@
/*
* NimBLELog.h
*
* Created: on Feb 24 2020
* Author H2zero
*
*/
#ifndef MAIN_NIMBLELOG_H_
#define MAIN_NIMBLELOG_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "syscfg/syscfg.h"
#include "modlog/modlog.h"
// If Arduino is being used, strip out the colors and ignore log printing below ui setting.
// Note: because CONFIG_LOG_DEFAULT_LEVEL is set at ERROR in Arduino we must use MODLOG_DFLT(ERROR
// otherwise no messages will be printed above that level.
#ifdef ARDUINO_ARCH_ESP32
#if CORE_DEBUG_LEVEL >= 4
#define NIMBLE_LOGD( tag, format, ... ) MODLOG_DFLT(ERROR, "D %s: "#format"\n",tag,##__VA_ARGS__)
#else
#define NIMBLE_LOGD( tag, format, ... )
#endif
#if CORE_DEBUG_LEVEL >= 3
#define NIMBLE_LOGI( tag, format, ... ) MODLOG_DFLT(ERROR, "I %s: "#format"\n",tag,##__VA_ARGS__)
#else
#define NIMBLE_LOGI( tag, format, ... )
#endif
#if CORE_DEBUG_LEVEL >= 2
#define NIMBLE_LOGW( tag, format, ... ) MODLOG_DFLT(ERROR, "W %s: "#format"\n",tag,##__VA_ARGS__)
#else
#define NIMBLE_LOGW( tag, format, ... )
#endif
#if CORE_DEBUG_LEVEL >= 1
#define NIMBLE_LOGE( tag, format, ... ) MODLOG_DFLT(ERROR, "E %s: "#format"\n",tag,##__VA_ARGS__)
#else
#define NIMBLE_LOGE( tag, format, ... )
#endif
#define NIMBLE_LOGC( tag, format, ... ) MODLOG_DFLT(CRITICAL, "CRIT %s: "#format"\n",tag,##__VA_ARGS__)
#else
#define NIMBLE_LOGE( tag, format, ... ) MODLOG_DFLT(ERROR, "\033[0;31mE %s: "#format"\033[0m\n",tag,##__VA_ARGS__)
#define NIMBLE_LOGW( tag, format, ... ) MODLOG_DFLT(WARN, "\033[0;33mW %s: "#format"\033[0m\n",tag,##__VA_ARGS__)
#define NIMBLE_LOGI( tag, format, ... ) MODLOG_DFLT(INFO, "\033[0;32mI %s: "#format"\033[0m\n",tag,##__VA_ARGS__)
#define NIMBLE_LOGD( tag, format, ... ) MODLOG_DFLT(DEBUG, "D %s: "#format"\n",tag,##__VA_ARGS__)
#define NIMBLE_LOGC( tag, format, ... ) MODLOG_DFLT(CRITICAL, "\033[1;31mCRIT %s: "#format"\033[0m\n",tag,##__VA_ARGS__)
#endif /*ARDUINO_ARCH_ESP32*/
#endif /*CONFIG_BT_ENABLED*/
#endif /*MAIN_NIMBLELOG_H_*/

View File

@ -0,0 +1,611 @@
/*
* NimBLERemoteCharacteristic.cpp
*
* Created: on Jan 27 2020
* Author H2zero
*
* Originally:
*
* BLERemoteCharacteristic.cpp
*
* Created on: Mar 16, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLERemoteCharacteristic.h"
#include <esp_err.h>
#include "NimBLEUtils.h"
#include "NimBLELog.h"
static const char* LOG_TAG = "NimBLERemoteCharacteristic";
/**
* @brief Constructor.
* @param [in] reference to the service this characteristic belongs to.
* @param [in] ble_gatt_chr struct defined as:
* struct ble_gatt_chr {
* uint16_t def_handle;
* uint16_t val_handle;
* uint8_t properties;
* ble_uuid_any_t uuid;
* };
*/
NimBLERemoteCharacteristic::NimBLERemoteCharacteristic(NimBLERemoteService *pRemoteService, const struct ble_gatt_chr *chr) {
switch (chr->uuid.u.type) {
case BLE_UUID_TYPE_16:
m_uuid = NimBLEUUID(chr->uuid.u16.value);
break;
case BLE_UUID_TYPE_32:
m_uuid = NimBLEUUID(chr->uuid.u32.value);
break;
case BLE_UUID_TYPE_128:
m_uuid = NimBLEUUID(const_cast<ble_uuid128_t*>(&chr->uuid.u128));
break;
default:
m_uuid = nullptr;
break;
}
m_handle = chr->val_handle;
m_defHandle = chr->def_handle;
m_charProp = chr->properties;
m_pRemoteService = pRemoteService;
m_notifyCallback = nullptr;
} // NimBLERemoteCharacteristic
/**
*@brief Destructor.
*/
NimBLERemoteCharacteristic::~NimBLERemoteCharacteristic() {
removeDescriptors(); // Release resources for any descriptor information we may have allocated.
if(m_rawData != nullptr) free(m_rawData);
} // ~NimBLERemoteCharacteristic
/*
#define BLE_GATT_CHR_PROP_BROADCAST 0x01
#define BLE_GATT_CHR_PROP_READ 0x02
#define BLE_GATT_CHR_PROP_WRITE_NO_RSP 0x04
#define BLE_GATT_CHR_PROP_WRITE 0x08
#define BLE_GATT_CHR_PROP_NOTIFY 0x10
#define BLE_GATT_CHR_PROP_INDICATE 0x20
#define BLE_GATT_CHR_PROP_AUTH_SIGN_WRITE 0x40
#define BLE_GATT_CHR_PROP_EXTENDED 0x80
*/
/**
* @brief Does the characteristic support broadcasting?
* @return True if the characteristic supports broadcasting.
*/
bool NimBLERemoteCharacteristic::canBroadcast() {
return (m_charProp & BLE_GATT_CHR_PROP_BROADCAST) != 0;
} // canBroadcast
/**
* @brief Does the characteristic support indications?
* @return True if the characteristic supports indications.
*/
bool NimBLERemoteCharacteristic::canIndicate() {
return (m_charProp & BLE_GATT_CHR_PROP_INDICATE) != 0;
} // canIndicate
/**
* @brief Does the characteristic support notifications?
* @return True if the characteristic supports notifications.
*/
bool NimBLERemoteCharacteristic::canNotify() {
return (m_charProp & BLE_GATT_CHR_PROP_NOTIFY) != 0;
} // canNotify
/**
* @brief Does the characteristic support reading?
* @return True if the characteristic supports reading.
*/
bool NimBLERemoteCharacteristic::canRead() {
return (m_charProp & BLE_GATT_CHR_PROP_READ) != 0;
} // canRead
/**
* @brief Does the characteristic support writing?
* @return True if the characteristic supports writing.
*/
bool NimBLERemoteCharacteristic::canWrite() {
return (m_charProp & BLE_GATT_CHR_PROP_WRITE) != 0;
} // canWrite
/**
* @brief Does the characteristic support writing with no response?
* @return True if the characteristic supports writing with no response.
*/
bool NimBLERemoteCharacteristic::canWriteNoResponse() {
return (m_charProp & BLE_GATT_CHR_PROP_WRITE_NO_RSP) != 0;
} // canWriteNoResponse
/**
* @brief Callback used by the API when a descriptor is discovered or search complete.
*/
int NimBLERemoteCharacteristic::descriptorDiscCB(uint16_t conn_handle,
const struct ble_gatt_error *error,
uint16_t chr_val_handle,
const struct ble_gatt_dsc *dsc,
void *arg)
{
NIMBLE_LOGD(LOG_TAG,"Descriptor Discovered >> status: %d handle: %d", error->status, conn_handle);
NimBLERemoteCharacteristic *characteristic = (NimBLERemoteCharacteristic*)arg;
int rc=0;
// Make sure the discovery is for this device
if(characteristic->getRemoteService()->getClient()->getConnId() != conn_handle){
return 0;
}
switch (error->status) {
case 0: {
// Found a descriptor - add it to the map
NimBLERemoteDescriptor* pNewRemoteDescriptor = new NimBLERemoteDescriptor(characteristic, dsc);
characteristic->m_descriptorMap.insert(std::pair<std::string, NimBLERemoteDescriptor*>(pNewRemoteDescriptor->getUUID().toString(), pNewRemoteDescriptor));
break;
}
case BLE_HS_EDONE:{
/* All descriptors in this characteristic discovered; */
characteristic->m_semaphoreGetDescEvt.give(0);
rc = 0;
break;
}
default:
rc = error->status;
break;
}
if (rc != 0) {
/* Error; abort discovery. */
// pass non-zero to semaphore on error to indicate an error finding descriptors
characteristic->m_semaphoreGetDescEvt.give(1);
}
NIMBLE_LOGD(LOG_TAG,"<< Descriptor Discovered. status: %d", rc);
return rc;
}
/**
* @brief Populate the descriptors (if any) for this characteristic.
* @param [in] the end handle of the characteristic, or the service, whichever comes first.
*/
bool NimBLERemoteCharacteristic::retrieveDescriptors(uint16_t endHdl) {
NIMBLE_LOGD(LOG_TAG, ">> retrieveDescriptors() for characteristic: %s", getUUID().toString().c_str());
int rc = 0;
//removeDescriptors(); // Remove any existing descriptors.
m_semaphoreGetDescEvt.take("retrieveDescriptors");
rc = ble_gattc_disc_all_dscs(getRemoteService()->getClient()->getConnId(),
m_handle,
endHdl,
NimBLERemoteCharacteristic::descriptorDiscCB,
this);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gattc_disc_all_chrs: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
m_semaphoreGetDescEvt.give();
return false;
}
if(m_semaphoreGetDescEvt.wait("retrieveCharacteristics") != 0) {
// if there was an error release the resources
//removeDescriptors();
return false;
}
return true;
NIMBLE_LOGD(LOG_TAG, "<< retrieveDescriptors(): Found %d descriptors.", m_descriptorMap.size());
} // getDescriptors
/**
* @brief Retrieve the map of descriptors keyed by UUID.
*/
std::map<std::string, NimBLERemoteDescriptor*>* NimBLERemoteCharacteristic::getDescriptors() {
return &m_descriptorMap;
} // getDescriptors
/**
* @brief Get the handle for this characteristic.
* @return The handle for this characteristic.
*/
uint16_t NimBLERemoteCharacteristic::getHandle() {
return m_handle;
} // getHandle
/**
* @brief Get the handle for this characteristics definition.
* @return The handle for this characteristic definition.
*/
uint16_t NimBLERemoteCharacteristic::getDefHandle() {
return m_defHandle;
} // getDefHandle
/**
* @brief Get the descriptor instance with the given UUID that belongs to this characteristic.
* @param [in] uuid The UUID of the descriptor to find.
* @return The Remote descriptor (if present) or null if not present.
*/
NimBLERemoteDescriptor* NimBLERemoteCharacteristic::getDescriptor(NimBLEUUID uuid) {
NIMBLE_LOGD(LOG_TAG, ">> getDescriptor: uuid: %s", uuid.toString().c_str());
std::string v = uuid.toString();
for (auto &myPair : m_descriptorMap) {
if (myPair.first == v) {
NIMBLE_LOGD(LOG_TAG, "<< getDescriptor: found");
return myPair.second;
}
}
NIMBLE_LOGD(LOG_TAG, "<< getDescriptor: Not found");
return nullptr;
} // getDescriptor
/**
* @brief Get the remote service associated with this characteristic.
* @return The remote service associated with this characteristic.
*/
NimBLERemoteService* NimBLERemoteCharacteristic::getRemoteService() {
return m_pRemoteService;
} // getRemoteService
/**
* @brief Get the UUID for this characteristic.
* @return The UUID for this characteristic.
*/
NimBLEUUID NimBLERemoteCharacteristic::getUUID() {
return m_uuid;
} // getUUID
/**
* @brief Read an unsigned 16 bit value
* @return The unsigned 16 bit value.
*/
uint16_t NimBLERemoteCharacteristic::readUInt16() {
std::string value = readValue();
if (value.length() >= 2) {
return *(uint16_t*)(value.data());
}
return 0;
} // readUInt16
/**
* @brief Read an unsigned 32 bit value.
* @return the unsigned 32 bit value.
*/
uint32_t NimBLERemoteCharacteristic::readUInt32() {
std::string value = readValue();
if (value.length() >= 4) {
return *(uint32_t*)(value.data());
}
return 0;
} // readUInt32
/**
* @brief Read a byte value
* @return The value as a byte
*/
uint8_t NimBLERemoteCharacteristic::readUInt8() {
std::string value = readValue();
if (value.length() >= 1) {
return (uint8_t)value[0];
}
return 0;
} // readUInt8
/**
* @brief Read the value of the remote characteristic.
* @return The value of the remote characteristic.
*/
std::string NimBLERemoteCharacteristic::readValue() {
NIMBLE_LOGD(LOG_TAG, ">> readValue(): uuid: %s, handle: %d 0x%.2x", getUUID().toString().c_str(), getHandle(), getHandle());
int rc = 0;
int retryCount = 1;
NimBLEClient* pClient = getRemoteService()->getClient();
// Check to see that we are connected.
if (!pClient->isConnected()) {
NIMBLE_LOGE(LOG_TAG, "Disconnected");
return "";
}
do {
m_semaphoreReadCharEvt.take("readValue");
rc = ble_gattc_read(pClient->getConnId(), m_handle,
NimBLERemoteCharacteristic::onReadCB, this);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Error: Failed to read characteristic; rc=%d", rc);
//goto err;
m_semaphoreReadCharEvt.give();
return "";
}
rc = m_semaphoreReadCharEvt.wait("readValue");
switch(rc){
case 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;
default:
return "";
}
} while(rc != 0 && retryCount--);
NIMBLE_LOGD(LOG_TAG, "<< readValue(): length: %d", m_value.length());
return (rc == 0) ? m_value : "";
} // readValue
/**
* @brief Callback for characteristic read operation.
* @return 0 or error code.
*/
int NimBLERemoteCharacteristic::onReadCB(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg)
{
NimBLERemoteCharacteristic* characteristic = (NimBLERemoteCharacteristic*)arg;
// Make sure the discovery is for this device
if(characteristic->getRemoteService()->getClient()->getConnId() != conn_handle){
return 0;
}
NIMBLE_LOGI(LOG_TAG, "Read complete; status=%d conn_handle=%d", error->status, conn_handle);
if(characteristic->m_rawData != nullptr) {
free(characteristic->m_rawData);
}
if (error->status == 0) {
characteristic->m_value = std::string((char*) attr->om->om_data, attr->om->om_len);
characteristic->m_semaphoreReadCharEvt.give(0);
characteristic->m_rawData = (uint8_t*) calloc(attr->om->om_len, sizeof(uint8_t));
memcpy(characteristic->m_rawData, attr->om->om_data, attr->om->om_len);
} else {
characteristic->m_rawData = nullptr;
characteristic->m_value = "";
characteristic->m_semaphoreReadCharEvt.give(error->status);
}
return 0;
}
/**
* @brief Register for notifications.
* @param [in] notifyCallback A callback to be invoked for a notification. If NULL is provided then we are
* unregistering for notifications.
* @param [in] bool if true, register for notifications, false register for indications.
* @param [in] bool if true, require a write response from the descriptor write operation.
* @return true if successful.
*/
bool NimBLERemoteCharacteristic::registerForNotify(notify_callback notifyCallback, bool notifications, bool response) {
NIMBLE_LOGD(LOG_TAG, ">> registerForNotify(): %s", toString().c_str());
m_notifyCallback = notifyCallback; // Save the notification callback.
uint8_t val[] = {0x01, 0x00};
NimBLERemoteDescriptor* desc = getDescriptor(NimBLEUUID((uint16_t)0x2902));
if(desc == nullptr)
return false;
if(notifyCallback != nullptr){
if(!notifications){
val[0] = 0x02;
}
}
else if (notifyCallback == nullptr){
val[0] = 0x00;
}
NIMBLE_LOGD(LOG_TAG, "<< registerForNotify()");
return desc->writeValue(val, 2, response);
} // registerForNotify
/**
* @brief Delete the descriptors in the descriptor map.
* We maintain a map called m_descriptorMap that contains pointers to BLERemoteDescriptors
* object references. Since we allocated these in this class, we are also responsible for deleteing
* them. This method does just that.
* @return N/A.
*/
void NimBLERemoteCharacteristic::removeDescriptors() {
// Iterate through all the descriptors releasing their storage and erasing them from the map.
for (auto &myPair : m_descriptorMap) {
m_descriptorMap.erase(myPair.first);
delete myPair.second;
}
m_descriptorMap.clear(); // Technically not neeeded, but just to be sure.
} // removeCharacteristics
/**
* @brief Convert a BLERemoteCharacteristic to a string representation;
* @return a String representation.
*/
std::string NimBLERemoteCharacteristic::toString() {
std::string res = "Characteristic: uuid: " + m_uuid.toString();
char val[6];
res += ", handle: ";
snprintf(val, sizeof(val), "%d", getHandle());
res += val;
res += " 0x";
snprintf(val, sizeof(val), "%04x", getHandle());
res += val;
res += ", props: ";
res += " 0x";
snprintf(val, sizeof(val), "%02x", m_charProp);
res += val;
for (auto &myPair : m_descriptorMap) {
res += "\n" + myPair.second->toString();
}
return res;
} // toString
/**
* @brief Write the new value for the characteristic.
* @param [in] newValue The new value to write.
* @param [in] response Do we expect a response?
* @return false if not connected or cant perform write for some reason.
*/
bool NimBLERemoteCharacteristic::writeValue(std::string newValue, bool response) {
return writeValue((uint8_t*)newValue.c_str(), strlen(newValue.c_str()), response);
} // writeValue
/**
* @brief Write the new value for the characteristic.
*
* This is a convenience function. Many BLE characteristics are a single byte of data.
* @param [in] newValue The new byte value to write.
* @param [in] response Whether we require a response from the write.
* @return false if not connected or cant perform write for some reason.
*/
bool NimBLERemoteCharacteristic::writeValue(uint8_t newValue, bool response) {
return writeValue(&newValue, 1, response);
} // writeValue
/**
* @brief Write the new value for the 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 cant perform write for some reason.
*/
bool NimBLERemoteCharacteristic::writeValue(uint8_t* data, size_t length, bool response) {
NIMBLE_LOGD(LOG_TAG, ">> writeValue(), length: %d", length);
NimBLEClient* pClient = getRemoteService()->getClient();
int rc = 0;
int retryCount = 1;
// Check to see that we are connected.
if (!pClient->isConnected()) {
NIMBLE_LOGE(LOG_TAG, "Disconnected");
return false;
}
if(!response) {
rc = ble_gattc_write_no_rsp_flat(pClient->getConnId(), m_handle, data, length);
return (rc==0);
}
do {
m_semaphoreWriteCharEvt.take("writeValue");
rc = ble_gattc_write_flat(pClient->getConnId(), m_handle,
data, length,
NimBLERemoteCharacteristic::onWriteCB,
this);;
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Error: Failed to write characteristic; rc=%d", rc);
m_semaphoreWriteCharEvt.give();
return false;
}
rc = m_semaphoreWriteCharEvt.wait("writeValue");
switch(rc){
case 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;
default:
return false;
}
} while(rc != 0 && retryCount--);
NIMBLE_LOGD(LOG_TAG, "<< writeValue, rc: %d",rc);
return (rc == 0); //true;
} // writeValue
/**
* @brief Callback for characteristic write operation.
* @return 0 or error code.
*/
int NimBLERemoteCharacteristic::onWriteCB(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg)
{
NimBLERemoteCharacteristic* characteristic = (NimBLERemoteCharacteristic*)arg;
// Make sure the discovery is for this device
if(characteristic->getRemoteService()->getClient()->getConnId() != conn_handle){
return 0;
}
NIMBLE_LOGI(LOG_TAG, "Write complete; status=%d conn_handle=%d", error->status, conn_handle);
if (error->status == 0) {
characteristic->m_semaphoreWriteCharEvt.give(0);
} else {
characteristic->m_semaphoreWriteCharEvt.give(error->status);
}
return error->status;
}
/**
* @brief Read raw data from remote characteristic as hex bytes
* @return return pointer data read
*/
uint8_t* NimBLERemoteCharacteristic::readRawData() {
return m_rawData;
}
void NimBLERemoteCharacteristic::releaseSemaphores() {
for (auto &dPair : m_descriptorMap) {
dPair.second->releaseSemaphores();
}
m_semaphoreWriteCharEvt.give(1);
m_semaphoreGetDescEvt.give(1);
m_semaphoreReadCharEvt.give(1);
}
#endif /* CONFIG_BT_ENABLED */

View File

@ -0,0 +1,100 @@
/*
* NimBLERemoteCharacteristic.h
*
* Created: on Jan 27 2020
* Author H2zero
*
* Originally:
*
* BLERemoteCharacteristic.h
*
* Created on: Jul 8, 2017
* Author: kolban
*/
#ifndef COMPONENTS_NIMBLEREMOTECHARACTERISTIC_H_
#define COMPONENTS_NIMBLEREMOTECHARACTERISTIC_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
//#include "NimBLEUUID.h"
//#include "FreeRTOS.h"
#include "NimBLERemoteService.h"
#include "NimBLERemoteDescriptor.h"
//#include <string>
#include <map>
class NimBLERemoteService;
class NimBLERemoteDescriptor;
typedef void (*notify_callback)(NimBLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify);
/**
* @brief A model of a remote %BLE characteristic.
*/
class NimBLERemoteCharacteristic {
public:
~NimBLERemoteCharacteristic();
// Public member functions
bool canBroadcast();
bool canIndicate();
bool canNotify();
bool canRead();
bool canWrite();
bool canWriteNoResponse();
NimBLERemoteDescriptor* getDescriptor(NimBLEUUID uuid);
std::map<std::string, NimBLERemoteDescriptor*>* getDescriptors();
uint16_t getHandle();
uint16_t getDefHandle();
NimBLEUUID getUUID();
std::string readValue();
uint8_t readUInt8();
uint16_t readUInt16();
uint32_t readUInt32();
bool registerForNotify(notify_callback _callback, bool notifications = true, bool response = true);
bool writeValue(uint8_t* data, size_t length, bool response = false);
bool writeValue(std::string newValue, bool response = false);
bool writeValue(uint8_t newValue, bool response = false);
std::string toString();
uint8_t* readRawData();
NimBLERemoteService* getRemoteService();
private:
NimBLERemoteCharacteristic(NimBLERemoteService *pRemoteservice, const struct ble_gatt_chr *chr);
friend class NimBLEClient;
friend class NimBLERemoteService;
friend class NimBLERemoteDescriptor;
// Private member functions
void removeDescriptors();
bool retrieveDescriptors(uint16_t endHdl);
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);
void releaseSemaphores();
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);
// Private properties
NimBLEUUID m_uuid;
uint8_t m_charProp;
uint16_t m_handle;
uint16_t m_defHandle;
NimBLERemoteService* m_pRemoteService;
FreeRTOS::Semaphore m_semaphoreGetDescEvt = FreeRTOS::Semaphore("GetDescEvt");
FreeRTOS::Semaphore m_semaphoreReadCharEvt = FreeRTOS::Semaphore("ReadCharEvt");
FreeRTOS::Semaphore m_semaphoreWriteCharEvt = FreeRTOS::Semaphore("WriteCharEvt");
std::string m_value;
uint8_t* m_rawData = nullptr;
notify_callback m_notifyCallback;
// We maintain a map of descriptors owned by this characteristic keyed by a string representation of the UUID.
std::map<std::string, NimBLERemoteDescriptor*> m_descriptorMap;
}; // BLERemoteCharacteristic
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_NIMBLEREMOTECHARACTERISTIC_H_ */

View File

@ -0,0 +1,312 @@
/*
* NimBLERemoteDescriptor.cpp
*
* Created: on Jan 27 2020
* Author H2zero
*
* Originally:
*
* BLERemoteDescriptor.cpp
*
* Created on: Jul 8, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLERemoteDescriptor.h"
#include "NimBLELog.h"
static const char* LOG_TAG = "NimBLERemoteDescriptor";
/**
* @brief Remote descriptor constructor.
* @param [in] Reference to the Characteristic that this belongs to.
* @param [in] Reference to the struct that contains the descriptor information.
*/
NimBLERemoteDescriptor::NimBLERemoteDescriptor(NimBLERemoteCharacteristic* pRemoteCharacteristic,
const struct ble_gatt_dsc *dsc)
{
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:
m_uuid = nullptr;
break;
}
m_handle = dsc->handle;
m_pRemoteCharacteristic = pRemoteCharacteristic;
}
/**
* @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
/**
* @brief Get the characteristic that owns this descriptor.
* @return The characteristic that owns this descriptor.
*/
NimBLERemoteCharacteristic* NimBLERemoteDescriptor::getRemoteCharacteristic() {
return 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 Callback for Descriptor read operation.
* @return 0 or error code.
*/
int NimBLERemoteDescriptor::onReadCB(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg)
{
NimBLERemoteDescriptor* desc = (NimBLERemoteDescriptor*)arg;
// Make sure the discovery is for this device
if(desc->getRemoteCharacteristic()->getRemoteService()->getClient()->getConnId() != conn_handle){
return 0;
}
NIMBLE_LOGD(LOG_TAG, "Read complete; status=%d conn_handle=%d", error->status, conn_handle);
if (error->status == 0) {
desc->m_value = std::string((char*) attr->om->om_data, attr->om->om_len);
desc->m_semaphoreReadDescrEvt.give(0);
} else {
desc->m_value = "";
desc->m_semaphoreReadDescrEvt.give(error->status);
}
return 0;
}
std::string NimBLERemoteDescriptor::readValue() {
NIMBLE_LOGD(LOG_TAG, ">> Descriptor readValue: %s", toString().c_str());
NimBLEClient* pClient = getRemoteCharacteristic()->getRemoteService()->getClient();
int rc = 0;
int retryCount = 1;
// Check to see that we are connected.
if (!pClient->isConnected()) {
NIMBLE_LOGE(LOG_TAG, "Disconnected");
return "";
}
do {
m_semaphoreReadDescrEvt.take("ReadDescriptor");
rc = ble_gattc_read(pClient->getConnId(), m_handle,
NimBLERemoteDescriptor::onReadCB, this);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Descriptor read failed, code: %d", rc);
m_semaphoreReadDescrEvt.give();
return "";
}
rc = m_semaphoreReadDescrEvt.wait("ReadDescriptor");
switch(rc){
case 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;
default:
return "";
}
} while(rc != 0 && retryCount--);
NIMBLE_LOGD(LOG_TAG, "<< Descriptor readValue(): length: %d, rc: %d", m_value.length(), rc);
return (rc == 0) ? m_value : "";
} // readValue
uint8_t NimBLERemoteDescriptor::readUInt8() {
std::string value = readValue();
if (value.length() >= 1) {
return (uint8_t) value[0];
}
return 0;
} // readUInt8
uint16_t NimBLERemoteDescriptor::readUInt16() {
std::string value = readValue();
if (value.length() >= 2) {
return *(uint16_t*) value.data();
}
return 0;
} // readUInt16
uint32_t NimBLERemoteDescriptor::readUInt32() {
std::string value = readValue();
if (value.length() >= 4) {
return *(uint32_t*) value.data();
}
return 0;
} // readUInt32
/**
* @brief Return a string representation of this BLE Remote Descriptor.
* @retun A string representation of this BLE Remote Descriptor.
*/
std::string NimBLERemoteDescriptor::toString() {
std::string res = "Descriptor: uuid: " + getUUID().toString();
char val[6];
res += ", handle: ";
snprintf(val, sizeof(val), "%d", getHandle());
res += val;
return res;
} // toString
/**
* @brief Callback for descriptor write operation.
* @return 0 or error code.
*/
int NimBLERemoteDescriptor::onWriteCB(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg)
{
NimBLERemoteDescriptor* descriptor = (NimBLERemoteDescriptor*)arg;
// Make sure the discovery is for this device
if(descriptor->getRemoteCharacteristic()->getRemoteService()->getClient()->getConnId() != conn_handle){
return 0;
}
NIMBLE_LOGD(LOG_TAG, "Write complete; status=%d conn_handle=%d", error->status, conn_handle);
if (error->status == 0) {
descriptor->m_semaphoreDescWrite.give(0);
} else {
descriptor->m_semaphoreDescWrite.give(error->status);
}
return 0;
}
/**
* @brief Write data to the BLE 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 response.
*/
bool NimBLERemoteDescriptor::writeValue(uint8_t* data, size_t length, bool response) {
NIMBLE_LOGD(LOG_TAG, ">> Descriptor writeValue: %s", toString().c_str());
NimBLEClient* pClient = getRemoteCharacteristic()->getRemoteService()->getClient();
int rc = 0;
int retryCount = 1;
// Check to see that we are connected.
if (!pClient->isConnected()) {
NIMBLE_LOGE(LOG_TAG, "Disconnected");
return false;
}
if(!response) {
rc = ble_gattc_write_no_rsp_flat(pClient->getConnId(), m_handle, data, length);
return (rc==0);
}
do {
m_semaphoreDescWrite.take("WriteDescriptor");
rc = ble_gattc_write_flat(pClient->getConnId(), m_handle,
data, length,
NimBLERemoteDescriptor::onWriteCB,
this);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Error: Failed to write descriptor; rc=%d", rc);
m_semaphoreDescWrite.give();
return false;
}
rc = m_semaphoreDescWrite.wait("WriteDescriptor");
switch(rc){
case 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;
default:
return false;
}
} while(rc != 0 && retryCount--);
NIMBLE_LOGD(LOG_TAG, "<< Descriptor writeValue, rc: %d",rc);
return (rc == 0); //true;
} // writeValue
/**
* @brief Write data represented as a string to the BLE Remote Descriptor.
* @param [in] newValue The data to send to the remote descriptor.
* @param [in] response True if we expect a response.
*/
bool NimBLERemoteDescriptor::writeValue(std::string newValue, bool response) {
return writeValue((uint8_t*) newValue.data(), newValue.length(), response);
} // writeValue
/**
* @brief Write a byte value to the Descriptor.
* @param [in] The single byte to write.
* @param [in] True if we expect a response.
*/
bool NimBLERemoteDescriptor::writeValue(uint8_t newValue, bool response) {
return writeValue(&newValue, 1, response);
} // writeValue
/**
* @brief In the event of an error this is called to make sure we don't block.
*/
void NimBLERemoteDescriptor::releaseSemaphores() {
m_semaphoreDescWrite.give(1);
m_semaphoreReadDescrEvt.give(1);
}
#endif /* CONFIG_BT_ENABLED */

View File

@ -0,0 +1,58 @@
/*
* NimBLERemoteDescriptor.h
*
* Created: on Jan 27 2020
* Author H2zero
*
* Originally:
*
* BLERemoteDescriptor.h
*
* Created on: Jul 8, 2017
* Author: kolban
*/
#ifndef COMPONENTS_NIMBLEREMOTEDESCRIPTOR_H_
#define COMPONENTS_NIMBLEREMOTEDESCRIPTOR_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLERemoteCharacteristic.h"
class NimBLERemoteCharacteristic;
/**
* @brief A model of remote %BLE descriptor.
*/
class NimBLERemoteDescriptor {
public:
uint16_t getHandle();
NimBLERemoteCharacteristic* getRemoteCharacteristic();
NimBLEUUID getUUID();
std::string readValue(void);
uint8_t readUInt8(void);
uint16_t readUInt16(void);
uint32_t readUInt32(void);
std::string toString(void);
bool writeValue(uint8_t* data, size_t length, bool response = false);
bool writeValue(std::string newValue, bool response = false);
bool writeValue(uint8_t newValue, bool response = false);
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);
void releaseSemaphores();
uint16_t m_handle; // Server handle of this descriptor.
NimBLEUUID m_uuid; // UUID of this descriptor.
std::string m_value; // Last received value of the descriptor.
NimBLERemoteCharacteristic* m_pRemoteCharacteristic; // Reference to the Remote characteristic of which this descriptor is associated.
FreeRTOS::Semaphore m_semaphoreReadDescrEvt = FreeRTOS::Semaphore("ReadDescrEvt");
FreeRTOS::Semaphore m_semaphoreDescWrite = FreeRTOS::Semaphore("WriteDescEvt");
};
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_NIMBLEREMOTEDESCRIPTOR_H_ */

357
src/NimBLERemoteService.cpp Normal file
View File

@ -0,0 +1,357 @@
/*
* NimBLERemoteService.cpp
*
* Created: on Jan 27 2020
* Author H2zero
*
* Originally:
*
* BLERemoteService.cpp
*
* Created on: Jul 8, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLERemoteService.h"
#include "NimBLEUtils.h"
#include "NimBLEDevice.h"
#include "NimBLELog.h"
static const char* LOG_TAG = "NimBLERemoteService";
/**
* @brief Remote Service constructor.
* @param [in] Reference to the client this belongs to.
* @param [in] Refernce to the structure with the services' information.
*/
NimBLERemoteService::NimBLERemoteService(NimBLEClient* pClient, const struct ble_gatt_svc* service) {
NIMBLE_LOGD(LOG_TAG, ">> BLERemoteService()");
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:
m_uuid = nullptr;
break;
}
m_startHandle = service->start_handle;
m_endHandle = service->end_handle;
m_haveCharacteristics = false;
NIMBLE_LOGD(LOG_TAG, "<< BLERemoteService()");
}
/**
* @brief When deleting the service make sure we delete all characteristics and descriptors.
* Also release any semaphores they may be holding.
*/
NimBLERemoteService::~NimBLERemoteService() {
removeCharacteristics();
}
/**
* @brief Get the remote characteristic object for the characteristic UUID.
* @param [in] uuid Remote characteristic uuid.
* @return Reference to the remote characteristic object.
*/
NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const char* uuid) {
return getCharacteristic(NimBLEUUID(uuid));
} // getCharacteristic
/**
* @brief Get the characteristic object for the UUID.
* @param [in] uuid Characteristic uuid.
* @return Reference to the characteristic object, or nullptr if not found.
*/
NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(NimBLEUUID uuid) {
if (m_haveCharacteristics) {
std::string v = uuid.toString();
for (auto &myPair : m_characteristicMap) {
if (myPair.first == v) {
return myPair.second;
}
}
}
return nullptr;
} // getCharacteristic
/**
* @brief Callback for Characterisic discovery.
*/
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, conn_handle);
NimBLERemoteService *service = (NimBLERemoteService*)arg;
int rc=0;
// Make sure the discovery is for this device
if(service->getClient()->getConnId() != conn_handle){
return 0;
}
switch (error->status) {
case 0: {
// Found a service - add it to the map
NimBLERemoteCharacteristic* pRemoteCharacteristic = new NimBLERemoteCharacteristic(service, chr);
service->m_characteristicMap.insert(std::pair<std::string, NimBLERemoteCharacteristic*>(pRemoteCharacteristic->getUUID().toString(), pRemoteCharacteristic));
service->m_characteristicMapByHandle.insert(std::pair<uint16_t, NimBLERemoteCharacteristic*>(chr->val_handle, pRemoteCharacteristic));
break;
}
case BLE_HS_EDONE:{
/** All characteristics in this service discovered; start discovering
* characteristics in the next service.
*/
service->m_semaphoreGetCharEvt.give(0);
rc = 0;
break;
}
default:
rc = error->status;
break;
}
if (rc != 0) {
/* Error; abort discovery. */
// pass non-zero to semaphore on error to indicate an error finding characteristics
// release memory from any characteristics we created
//service->removeCharacteristics(); --this will now be done when we clear services on returning with error
NIMBLE_LOGE(LOG_TAG, "characteristicDiscCB() rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
service->m_semaphoreGetCharEvt.give(1);
}
NIMBLE_LOGD(LOG_TAG,"<< Characteristic Discovered. status: %d", rc);
return rc;
}
/**
* @brief Retrieve all the characteristics for this service.
* This function will not return until we have all the characteristics.
* @return N/A
*/
bool NimBLERemoteService::retrieveCharacteristics() {
NIMBLE_LOGD(LOG_TAG, ">> retrieveCharacteristics() for service: %s", getUUID().toString().c_str());
int rc = 0;
//removeCharacteristics(); // Forget any previous characteristics.
m_semaphoreGetCharEvt.take("retrieveCharacteristics");
rc = ble_gattc_disc_all_chrs(m_pClient->getConnId(),
m_startHandle,
m_endHandle,
NimBLERemoteService::characteristicDiscCB,
this);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gattc_disc_all_chrs: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
m_haveCharacteristics = false;
m_semaphoreGetCharEvt.give();
return false;
}
m_haveCharacteristics = (m_semaphoreGetCharEvt.wait("retrieveCharacteristics") == 0);
if(m_haveCharacteristics){
uint16_t endHdl = 0xFFFF;
NIMBLE_LOGD(LOG_TAG, "Found %d Characteristics", m_characteristicMapByHandle.size());
for (auto it = m_characteristicMapByHandle.cbegin(); it != m_characteristicMapByHandle.cend(); ++it) {
NIMBLE_LOGD(LOG_TAG, "Found UUID: %s Handle: %d Def Handle: %d", (*it).second->getUUID().toString().c_str(), (*it).second->getHandle(), (*it).second->getDefHandle());
// The descriptor handle is between this characteristic val_handle and the next ones def_handle
// so make the end of the scan at the handle before the next characteristic def_handle
// Make sure we don't go past the service end handle
if(++it != m_characteristicMapByHandle.cend()){
NIMBLE_LOGD(LOG_TAG, "Next UUID: %s Handle: %d Def Handle: %d", (*it).second->getUUID().toString().c_str(), (*it).second->getHandle(),(*it).second->getDefHandle());
endHdl = (*it).second->getDefHandle()-1;
}
else{
NIMBLE_LOGD(LOG_TAG, "END CHARS");
endHdl = m_endHandle;
}
--it;
//If there is no handles between this characteristic and the next there is no descriptor so skip to the next
if((*it).second->getHandle() != endHdl){
if(!m_pClient->m_isConnected || !(*it).second->retrieveDescriptors(endHdl)) {
return false;
}
}
//NIMBLE_LOGD(LOG_TAG, "Found %d Characteristics in service UUID: %s", chars->size(), myPair.first.c_str());
}
NIMBLE_LOGD(LOG_TAG, "<< retrieveCharacteristics()");
return true;
}
NIMBLE_LOGE(LOG_TAG, "Could not retrieve characteristics");
return false;
} // retrieveCharacteristics
/**
* @brief Retrieve a map of all the characteristics of this service.
* @return A map of all the characteristics of this service.
*/
std::map<std::string, NimBLERemoteCharacteristic*>* NimBLERemoteService::getCharacteristics() {
return &m_characteristicMap;
} // getCharacteristics
/**
* @brief Retrieve a map of all the characteristics of this service.
* @return A map of all the characteristics of this service.
*/
std::map<uint16_t, NimBLERemoteCharacteristic*>* NimBLERemoteService::getCharacteristicsByHandle() {
return &m_characteristicMapByHandle;
} // getCharacteristicsByHandle
/**
* @brief Get the client associated with this service.
* @return A reference to the client associated with this service.
*/
NimBLEClient* NimBLERemoteService::getClient() {
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.
* @returns a string containing the value or an empty string if not found or error.
*/
std::string NimBLERemoteService::getValue(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();
}
NIMBLE_LOGD(LOG_TAG, "<< readValue");
return ret;
} // readValue
/**
* @brief Set the value of a characteristic.
* @param [in] characteristicUuid The characteristic to set.
* @param [in] value The value to set.
* @returns true on success, false if not found or error
*/
bool NimBLERemoteService::setValue(NimBLEUUID characteristicUuid, 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);
}
NIMBLE_LOGD(LOG_TAG, "<< setValue");
return ret;
} // setValue
/**
* @brief Delete the characteristics in the characteristics map.
* We maintain a map called m_characteristicsMap that contains pointers to BLERemoteCharacteristic
* object references. Since we allocated these in this class, we are also responsible for deleteing
* them. This method does just that.
* @return N/A.
*/
void NimBLERemoteService::removeCharacteristics() {
m_characteristicMap.clear(); // Clear the map
for (auto &myPair : m_characteristicMapByHandle) {
delete myPair.second;
}
m_characteristicMapByHandle.clear(); // Clear the map
} // removeCharacteristics
/**
* @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);
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 += val;
for (auto &myPair : m_characteristicMap) {
res += "\n" + myPair.second->toString();
}
return res;
} // toString
/**
* @brief called when an error occurrs and we need to release the semaphores to resume operations.
* Will release all characteristic and subsequently all descriptor semaphores for this service.
*/
void NimBLERemoteService::releaseSemaphores() {
for (auto &cPair : m_characteristicMapByHandle) {
cPair.second->releaseSemaphores();
}
m_semaphoreGetCharEvt.give(1);
}
#endif /* CONFIG_BT_ENABLED */

89
src/NimBLERemoteService.h Normal file
View File

@ -0,0 +1,89 @@
/*
* NimBLERemoteService.h
*
* Created: on Jan 27 2020
* Author H2zero
*
* Originally:
*
* BLERemoteService.h
*
* Created on: Jul 8, 2017
* Author: kolban
*/
#ifndef COMPONENTS_NIMBLEREMOTESERVICE_H_
#define COMPONENTS_NIMBLEREMOTESERVICE_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEClient.h"
#include "NimBLEUUID.h"
#include "FreeRTOS.h"
#include "NimBLERemoteCharacteristic.h"
#include <map>
class NimBLEClient;
class NimBLERemoteCharacteristic;
/**
* @brief A model of a remote %BLE service.
*/
class NimBLERemoteService {
public:
virtual ~NimBLERemoteService();
// Public methods
NimBLERemoteCharacteristic* getCharacteristic(const char* uuid); // Get the specified characteristic reference.
NimBLERemoteCharacteristic* getCharacteristic(NimBLEUUID uuid); // Get the specified characteristic reference.
// BLERemoteCharacteristic* getCharacteristic(uint16_t uuid); // Get the specified characteristic reference.
std::map<std::string, NimBLERemoteCharacteristic*>* getCharacteristics();
std::map<uint16_t, NimBLERemoteCharacteristic*>* getCharacteristicsByHandle(); // Get the characteristics map.
// void getCharacteristics(std::map<uint16_t, BLERemoteCharacteristic*>* pCharacteristicMap);
NimBLEClient* getClient(void); // Get a reference to the client associated with this service.
uint16_t getHandle(); // Get the handle of this service.
NimBLEUUID getUUID(void); // Get the UUID of this service.
std::string getValue(NimBLEUUID characteristicUuid); // Get the value of a characteristic.
bool setValue(NimBLEUUID characteristicUuid, std::string value); // Set the value of a characteristic.
std::string toString(void);
private:
// Private constructor ... never meant to be created by a user application.
NimBLERemoteService(NimBLEClient* pClient, const struct ble_gatt_svc *service);
// Friends
friend class NimBLEClient;
friend class NimBLERemoteCharacteristic;
// Private methods
bool retrieveCharacteristics(void); // Retrieve the characteristics from the BLE Server.
static int characteristicDiscCB(uint16_t conn_handle,
const struct ble_gatt_error *error,
const struct ble_gatt_chr *chr, void *arg);
uint16_t getStartHandle(); // Get the start handle for this service.
uint16_t getEndHandle(); // Get the end handle for this service.
void releaseSemaphores();
void removeCharacteristics();
// Properties
// We maintain a map of characteristics owned by this service keyed by a string representation of the UUID.
std::map<std::string, NimBLERemoteCharacteristic*> m_characteristicMap;
// We maintain a map of characteristics owned by this service keyed by a handle.
std::map<uint16_t, NimBLERemoteCharacteristic*> m_characteristicMapByHandle;
bool m_haveCharacteristics; // Have we previously obtained the characteristics.
NimBLEClient* m_pClient;
FreeRTOS::Semaphore m_semaphoreGetCharEvt = FreeRTOS::Semaphore("GetCharEvt");
NimBLEUUID m_uuid; // The UUID of this service.
uint16_t m_startHandle; // The starting handle of this service.
uint16_t m_endHandle; // The ending handle of this service.
}; // BLERemoteService
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_NIMBLEREMOTESERVICE_H_ */

399
src/NimBLEScan.cpp Normal file
View File

@ -0,0 +1,399 @@
/*
* NimBLEScan.cpp
*
* Created: on Jan 24 2020
* Author H2zero
*
* Originally:
*
* BLEScan.cpp
*
* Created on: Jul 1, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEScan.h"
#include "NimBLEUtils.h"
#include "NimBLEDevice.h"
#include "NimBLELog.h"
#include <string>
static const char* LOG_TAG = "NimBLEScan";
/*
* Scanning filter policy
* NO_WL:
* Scanner processes all advertising packets (white list not used) except
* directed, connectable advertising packets not sent to the scanner.
* USE_WL:
* Scanner processes advertisements from white list only. A connectable,
* directed advertisment is ignored unless it contains scanners address.
* NO_WL_INITA:
* Scanner process all advertising packets (white list not used). A
* connectable, directed advertisement shall not be ignored if the InitA
* is a resolvable private address.
* USE_WL_INITA:
* Scanner process advertisements from white list only. A connectable,
* directed advertisement shall not be ignored if the InitA is a
* resolvable private address.
*/
//#define BLE_HCI_SCAN_FILT_NO_WL (0)
//#define BLE_HCI_SCAN_FILT_USE_WL (1)
//#define BLE_HCI_SCAN_FILT_NO_WL_INITA (2)
//#define BLE_HCI_SCAN_FILT_USE_WL_INITA (3)
//#define BLE_HCI_SCAN_FILT_MAX (3)
/**
* @brief Scan constuctor.
*/
NimBLEScan::NimBLEScan() {
uint8_t own_addr_type;
if(ble_hs_id_infer_auto(0, &own_addr_type) !=0){
NIMBLE_LOGE(LOG_TAG, "error determining address type\n");
return;
}
m_own_addr_type = own_addr_type;
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 = 1; // If set, the controller ignores all but the first advertisement from each device.
m_pAdvertisedDeviceCallbacks = nullptr;
m_stopped = true;
m_wantDuplicates = false;
}
/**
* @brief Handle GAP events related to scans.
* @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) {
NimBLEScan* pScan = (NimBLEScan*)arg;
struct ble_hs_adv_fields fields;
int rc = 0;
switch(event->type) {
case BLE_GAP_EVENT_DISC: {
if(pScan->m_stopped) {
NIMBLE_LOGE(LOG_TAG, "Scan stop called, ignoring results.");
return 0;
}
rc = ble_hs_adv_parse_fields(&fields, event->disc.data,
event->disc.length_data);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Gap Event Parse ERROR.");
return 0;
}
NimBLEAddress advertisedAddress(event->disc.addr);
// Print advertisement data
// print_adv_fields(&fields);
// If we are not scanning, nothing to do with the extra results.
if (pScan->m_stopped) {
return 0;
}
// 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)) {
NIMBLE_LOGI(LOG_TAG, "Ignoring device: address: %s", advertisedAddress.toString().c_str());
return 0;
}
NimBLEAdvertisedDevice* advertisedDevice = nullptr;
// If we've seen this device before get a pointer to it from the map
auto it = pScan->m_scanResults.m_advertisedDevicesMap.find(advertisedAddress.toString());
if(it != pScan->m_scanResults.m_advertisedDevicesMap.cend()) {
advertisedDevice = (*it).second;
}
// If we haven't seen this device before; create a new instance and insert it in the map.
// Otherwise just update the relevant parameters of the already known device.
if(advertisedDevice == nullptr){
advertisedDevice = new NimBLEAdvertisedDevice();
advertisedDevice->setAddressType(event->disc.addr.type);
advertisedDevice->setAddress(advertisedAddress);
//NIMBLE_LOGE(LOG_TAG, "advertisement type: %d, %s",event->disc.event_type, NimBLEUtils::advTypeToString(event->disc.event_type));
advertisedDevice->setAdvType(event->disc.event_type);
pScan->m_scanResults.m_advertisedDevicesMap.insert(std::pair<std::string, NimBLEAdvertisedDevice*>(advertisedAddress.toString(), advertisedDevice));
NIMBLE_LOGI(LOG_TAG, "NEW DEVICE FOUND: %s", advertisedAddress.toString().c_str());
}
else{
NIMBLE_LOGI(LOG_TAG, "UPDATING PREVIOUSLY FOUND DEVICE: %s", advertisedAddress.toString().c_str());
}
advertisedDevice->setRSSI(event->disc.rssi);
advertisedDevice->parseAdvertisement(&fields);
advertisedDevice->setScan(pScan);
advertisedDevice->setAdvertisementResult(event->disc.data, event->disc.length_data);
if (pScan->m_pAdvertisedDeviceCallbacks) {
// If not active scanning report the result to the listener.
if(pScan->m_scan_params.passive) {
pScan->m_pAdvertisedDeviceCallbacks->onResult(advertisedDevice);
// Otherwise wait for the scan response so we can report all of the data at once.
} else if (event->disc.event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP) {
pScan->m_pAdvertisedDeviceCallbacks->onResult(advertisedDevice);
}
//m_pAdvertisedDeviceCallbacks->onResult(*advertisedDevice);
}
return 0;
}
case BLE_GAP_EVENT_DISC_COMPLETE: {
NIMBLE_LOGD(LOG_TAG, "discovery complete; reason=%d",
event->disc_complete.reason);
if (pScan->m_scanCompleteCB != nullptr) {
pScan->m_scanCompleteCB(pScan->m_scanResults);
}
pScan->m_stopped = true;
pScan->m_semaphoreScanEnd.give();
return 0;
}
default:
return 0;
}
} // gapEventHandler
/**
* @brief Should we perform an active or passive scan?
* The default is a passive scan. An active scan means that we will wish a scan response.
* @param [in] active If true, we perform an active scan otherwise a passive scan.
* @return N/A.
*/
void NimBLEScan::setActiveScan(bool active) {
if (active) {
m_scan_params.passive = 0;
} else {
m_scan_params.passive = 1;
}
} // setActiveScan
/**
* @brief Set the call backs to be invoked.
* @param [in] pAdvertisedDeviceCallbacks 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*/) {
//m_wantDuplicates = wantDuplicates;
m_pAdvertisedDeviceCallbacks = pAdvertisedDeviceCallbacks;
} // setAdvertisedDeviceCallbacks
/**
* @brief Set the interval to scan.
* @param [in] The interval in msecs.
*/
void NimBLEScan::setInterval(uint16_t intervalMSecs) {
m_scan_params.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;
} // setWindow
/**
* @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] are we continue scan (true) or we want to clear stored devices (false)
* @return True if scan started or false if there was an error.
*/
bool NimBLEScan::start(uint32_t duration, void (*scanCompleteCB)(NimBLEScanResults), bool is_continue) {
NIMBLE_LOGD(LOG_TAG, ">> start(duration=%d)", duration);
// If Host is not synced we cannot start scanning.
if(!NimBLEDevice::m_synced) {
NIMBLE_LOGC(LOG_TAG, "Host reset, wait for sync.");
return false;
}
// If we are already scanning don't start again or we will get stuck on the semaphore.
if(!m_stopped || ble_gap_disc_active()) { // double check - can cause host reset.
NIMBLE_LOGE(LOG_TAG, "Scan already in progress");
return false;
}
m_stopped = false;
m_semaphoreScanEnd.take("start");
// Save the callback to be invoked when the scan completes.
m_scanCompleteCB = scanCompleteCB;
// Save the duration in the case that the host is reset so we can reuse it.
m_duration = duration;
// If 0 duration specified then we assume a continuous scan is desired.
if(duration == 0){
duration = BLE_HS_FOREVER;
}
else{
duration = duration*1000; // convert duration to milliseconds
}
// if we are connecting to devices that are advertising even after being connected, multiconnecting peripherals
// then we should not clear map or we will connect the same device few times
if(!is_continue) {
clearResults();
}
int rc = 0;
do{
rc = ble_gap_disc(m_own_addr_type, duration, &m_scan_params,
NimBLEScan::handleGapEvent, this);
}while(rc == BLE_HS_EBUSY);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Error initiating GAP discovery procedure; rc=%d, %s",
rc, NimBLEUtils::returnCodeToString(rc));
m_stopped = true;
m_semaphoreScanEnd.give();
return false;
}
// m_stopped = false;
NIMBLE_LOGD(LOG_TAG, "<< start()");
return true;
} // start
/**
* @brief Start scanning and block until scanning has been completed.
* @param [in] duration The duration in seconds for which to scan.
* @return The BLEScanResults.
*/
NimBLEScanResults NimBLEScan::start(uint32_t duration, bool is_continue) {
if(start(duration, nullptr, is_continue)) {
m_semaphoreScanEnd.wait("start"); // Wait for the semaphore to release.
}
return m_scanResults;
} // start
/**
* @brief Stop an in progress scan.
* @return N/A.
*/
void NimBLEScan::stop() {
NIMBLE_LOGD(LOG_TAG, ">> stop()");
int rc = ble_gap_disc_cancel();
if (rc != 0 && rc != BLE_HS_EALREADY) {
NIMBLE_LOGE(LOG_TAG, "Failed to cancel scan; rc=%d\n", rc);
return;
}
m_stopped = true;
if (m_scanCompleteCB != nullptr) {
m_scanCompleteCB(m_scanResults);
}
m_semaphoreScanEnd.give();
NIMBLE_LOGD(LOG_TAG, "<< stop()");
} // stop
// delete peer device from cache after disconnecting, it is required in case we are connecting to devices with not public address
void NimBLEScan::erase(NimBLEAddress address) {
NIMBLE_LOGI(LOG_TAG, "erase device: %s", address.toString().c_str());
NimBLEAdvertisedDevice *advertisedDevice = m_scanResults.m_advertisedDevicesMap.find(address.toString())->second;
m_scanResults.m_advertisedDevicesMap.erase(address.toString());
delete advertisedDevice;
}
/**
* @brief If the host reset the scan will have stopped so we should flag it and release the semaphore.
* @return N/A.
*/
void NimBLEScan::onHostReset() {
m_stopped = true;
m_semaphoreScanEnd.give();
}
/**
* @brief Get the results of the scan.
* @return NimBLEScanResults object.
*/
NimBLEScanResults NimBLEScan::getResults() {
return m_scanResults;
}
/**
* @brief Clear the results of the scan.
*/
void NimBLEScan::clearResults() {
for(auto _dev : m_scanResults.m_advertisedDevicesMap){
delete _dev.second;
}
m_scanResults.m_advertisedDevicesMap.clear();
}
/**
* @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++) {
NIMBLE_LOGI(LOG_TAG, "- %s", getDevice(i).toString().c_str());
}
} // dump
/**
* @brief Return the count of devices found in the last scan.
* @return The number of devices found in the last scan.
*/
int NimBLEScanResults::getCount() {
return m_advertisedDevicesMap.size();
} // getCount
/**
* @brief Return the specified device at the given index.
* The index should be between 0 and getCount()-1.
* @param [in] i The index of the device.
* @return The device at the specified index.
*/
NimBLEAdvertisedDevice NimBLEScanResults::getDevice(uint32_t i) {
uint32_t x = 0;
NimBLEAdvertisedDevice dev = *m_advertisedDevicesMap.begin()->second;
for (auto it = m_advertisedDevicesMap.begin(); it != m_advertisedDevicesMap.end(); it++) {
dev = *it->second;
if (x==i) break;
x++;
}
return dev;
}
#endif /* CONFIG_BT_ENABLED */

87
src/NimBLEScan.h Normal file
View File

@ -0,0 +1,87 @@
/*
* NimBLEScan.h
*
* Created: on Jan 24 2020
* Author H2zero
*
* Originally:
*
* BLEScan.h
*
* Created on: Jul 1, 2017
* Author: kolban
*/
#ifndef COMPONENTS_NIMBLE_SCAN_H_
#define COMPONENTS_NIMBLE_SCAN_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEAdvertisedDevice.h"
#include "FreeRTOS.h"
#include "host/ble_gap.h"
#include <map>
class NimBLEDevice;
class NimBLEScan;
class NimBLEAdvertisedDevice;
class NimBLEAdvertisedDeviceCallbacks;
/**
* @brief The result of having performed a scan.
* When a scan completes, we have a set of found devices. Each device is described
* by a BLEAdvertisedDevice object. The number of items in the set is given by
* getCount(). We can retrieve a device by calling getDevice() passing in the
* index (starting at 0) of the desired device.
*/
class NimBLEScanResults {
public:
void dump();
int getCount();
NimBLEAdvertisedDevice getDevice(uint32_t i);
private:
friend NimBLEScan;
std::map<std::string, NimBLEAdvertisedDevice*> m_advertisedDevicesMap;
};
/**
* @brief Perform and manage %BLE scans.
*
* 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);
void setAdvertisedDeviceCallbacks(NimBLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks/*, bool wantDuplicates = false*/);
void setActiveScan(bool active);
void setInterval(uint16_t intervalMSecs);
void setWindow(uint16_t windowMSecs);
void stop();
void clearResults();
NimBLEScanResults getResults();
void erase(NimBLEAddress address);
private:
NimBLEScan();
friend class NimBLEDevice;
static int handleGapEvent(ble_gap_event* event, void* arg);
void onHostReset();
NimBLEAdvertisedDeviceCallbacks* m_pAdvertisedDeviceCallbacks = nullptr;
void (*m_scanCompleteCB)(NimBLEScanResults scanResults);
ble_gap_disc_params m_scan_params;
uint8_t m_own_addr_type;
bool m_stopped;
bool m_wantDuplicates;
NimBLEScanResults m_scanResults;
FreeRTOS::Semaphore m_semaphoreScanEnd = FreeRTOS::Semaphore("ScanEnd");
uint32_t m_duration;
};
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_NIMBLE_SCAN_H_ */

138
src/NimBLESecurity.cpp Normal file
View File

@ -0,0 +1,138 @@
/*
* NimBLESecurity.cpp
*
* Created: on Feb 22 2020
* Author H2zero
*
* Originally:
*
* BLESecurity.cpp
*
* Created on: Dec 17, 2017
* Author: chegewara
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLESecurity.h"
#include "NimBLEDevice.h"
/**
* @brief This class is for backward compatibility with the bluedroid based library.
* Use the new security functions in NimBLEDevice instead.
* New callback functions in NimBLEServer and NimBLEClient.
*/
NimBLESecurity::NimBLESecurity() {
}
NimBLESecurity::~NimBLESecurity() {
}
/**
* @brief Set requested authentication mode
*/
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-digits pin code
*/
void NimBLESecurity::setCapability(esp_ble_io_cap_t iocap) {
NimBLEDevice::setSecurityIOCap(iocap);
} // setCapability
/**
* @brief Init encryption key by server
* @param key_size is value between 7 and 16
*/
void NimBLESecurity::setInitEncryptionKey(uint8_t init_key) {
NimBLEDevice::setSecurityInitKey(init_key);
} // setInitEncryptionKey
/**
* @brief Init encryption key by client
* @param key_size is value between 7 and 16
*/
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
/**
* Setup for static PIN connection.
*/
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

117
src/NimBLESecurity.h Normal file
View File

@ -0,0 +1,117 @@
/*
* NimBLESecurity.h
*
* Created: on Feb 22 2020
* Author H2zero
*
* Originally:
*
* BLESecurity.h
*
* Created on: Dec 17, 2017
* Author: chegewara
*/
/** This class exists for backward compatibility - Should not be used in new code
* See the security functions in NimBLEDevice and callbacks in NimBLEServer / NimBLEClient
*/
#ifndef COMPONENTS_NIMBLESECURITY_H_
#define COMPONENTS_NIMBLESECURITY_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "host/ble_gap.h"
/**** 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 */
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
*/
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
*/
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 ;
/**
* Provide us information when authentication process is completed
*/
virtual void onAuthenticationComplete(ble_gap_conn_desc*) = 0;
virtual bool onConfirmPIN(uint32_t pin) = 0;
}; // BLESecurityCallbacks
#endif // CONFIG_BT_ENABLED
#endif // COMPONENTS_NIMBLESECURITY_H_

591
src/NimBLEServer.cpp Normal file
View File

@ -0,0 +1,591 @@
/*
* NimBLEServer.cpp
*
* Created: on March 2, 2020
* Author H2zero
*
* Originally:
*
* BLEServer.cpp
*
* Created on: Apr 16, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEServer.h"
#include "NimBLE2902.h"
#include "NimBLEUtils.h"
#include "NimBLEDevice.h"
#include "NimBLELog.h"
static const char* LOG_TAG = "NimBLEServer";
static NimBLEServerCallbacks defaultCallbacks;
/**
* @brief Construct a %BLE Server
*
* This class is not designed to be individually instantiated. Instead one should create a server by asking
* the BLEDevice class.
*/
NimBLEServer::NimBLEServer() {
m_connId = BLE_HS_CONN_HANDLE_NONE;
m_svcChgChrHdl = 0xffff;
m_pServerCallbacks = &defaultCallbacks;
m_gattsStarted = false;
} // BLEServer
/**
* @brief Create a %BLE Service.
*
* With a %BLE server, we can host one or more services. Invoking this function causes the creation of a definition
* of a new service. Every service must have a unique UUID.
* @param [in] uuid The UUID of the new service.
* @return A reference to the new service object.
*/
NimBLEService* NimBLEServer::createService(const char* uuid) {
return createService(NimBLEUUID(uuid));
}
/**
* @brief Create a %BLE Service.
*
* With a %BLE server, we can host one or more services. Invoking this function causes the creation of a definition
* of a new service. Every service must have a unique UUID.
* @param [in] uuid The UUID of the new service.
* @param [in] numHandles The maximum number of handles associated with this service.
* @param [in] inst_id With multiple services with the same UUID we need to provide inst_id value different for each service.
* @return A reference to the new service object.
*/
NimBLEService* NimBLEServer::createService(NimBLEUUID uuid, uint32_t numHandles, uint8_t inst_id) {
NIMBLE_LOGD(LOG_TAG, ">> createService - %s", uuid.toString().c_str());
// Check that a service with the supplied UUID does not already exist.
if (m_serviceMap.getByUUID(uuid) != nullptr) {
NIMBLE_LOGW(LOG_TAG, "<< Attempt to create a new service with uuid %s but a service with that UUID already exists.",
uuid.toString().c_str());
}
NimBLEService* pService = new NimBLEService(uuid, numHandles, this);
pService->m_instId = inst_id;
m_serviceMap.setByUUID(uuid, pService); // Save a reference to this service being on this server.
NIMBLE_LOGD(LOG_TAG, "<< createService");
return pService;
} // createService
/**
* @brief Get a %BLE Service by its UUID
* @param [in] uuid The UUID of the new service.
* @return A reference to the service object.
*/
NimBLEService* NimBLEServer::getServiceByUUID(const char* uuid) {
return m_serviceMap.getByUUID(uuid);
}
/**
* @brief Get a %BLE Service by its UUID
* @param [in] uuid The UUID of the new service.
* @return A reference to the service object.
*/
NimBLEService* NimBLEServer::getServiceByUUID(NimBLEUUID uuid) {
return m_serviceMap.getByUUID(uuid);
}
/**
* @brief Retrieve the advertising object that can be used to advertise the existence of the server.
*
* @return An advertising object.
*/
NimBLEAdvertising* NimBLEServer::getAdvertising() {
return BLEDevice::getAdvertising();
}
/**
* @brief Retrieve the connection id of the last connected client.
* @todo Not very useful, should refactor or remove.
* @return Client connection id.
*/
uint16_t NimBLEServer::getConnId() {
return m_connId;
}
/**
* @brief Start the GATT server. Required to be called after setup of all
* services and characteristics / descriptors for the NimBLE host to register them.
*/
void NimBLEServer::start() {
if(m_gattsStarted) {
NIMBLE_LOGW(LOG_TAG, "Gatt server already started");
return;
}
int rc = ble_gatts_start();
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gatts_start; rc=%d, %s", rc,
NimBLEUtils::returnCodeToString(rc));
abort();
}
#if CONFIG_LOG_DEFAULT_LEVEL > 3 || ARDUHAL_LOG_LEVEL_INFO > 3
ble_gatts_show_local();
#endif
ble_uuid16_t svc = {BLE_UUID_TYPE_16, 0x1801};
ble_uuid16_t chr = {BLE_UUID_TYPE_16, 0x2a05};
rc = ble_gatts_find_chr(&svc.u, &chr.u, NULL, &m_svcChgChrHdl);
if(rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gatts_find_chr: rc=%d, %s", rc,
NimBLEUtils::returnCodeToString(rc));
abort();
}
NIMBLE_LOGI(LOG_TAG, "Service changed characterisic handle: %d", m_svcChgChrHdl);
// Build a map of characteristics with Notify / Indicate capabilities for event handling
uint8_t numSvcs = m_serviceMap.getRegisteredServiceCount();
NimBLEService* pService = m_serviceMap.getFirst();
for(int i = 0; i < numSvcs; i++) {
uint8_t numChrs = pService->m_characteristicMap.getSize();
NimBLECharacteristic* pChr = pService->m_characteristicMap.getFirst();
if(pChr != nullptr) {
for( int d = 0; d < numChrs; d++) {
// if Notify / Indicate is enabled but we didn't create the descriptor
// we do it now.
if((pChr->m_properties & BLE_GATT_CHR_F_INDICATE) ||
(pChr->m_properties & BLE_GATT_CHR_F_NOTIFY)) {
if(nullptr == pChr->getDescriptorByUUID("2902")) {
pChr->createDescriptor("2902");
}
m_notifyChrMap.insert(std::pair<uint16_t, NimBLECharacteristic*>
(pChr->getHandle(), pChr));
}
pChr = pService->m_characteristicMap.getNext();
}
}
pService = m_serviceMap.getNext();
}
m_gattsStarted = true;
}
/**
* @brief Disconnect the specified client with optional reason.
* @param [in] Connection Id of the client to disconnect.
* @param [in] Reason code for disconnecting.
* @return NimBLE host return code.
*/
int NimBLEServer::disconnect(uint16_t connId, uint8_t reason) {
NIMBLE_LOGD(LOG_TAG, ">> disconnect()");
int rc = ble_gap_terminate(connId, reason);
if(rc != 0){
NIMBLE_LOGE(LOG_TAG, "ble_gap_terminate failed: rc=%d %s", rc,
NimBLEUtils::returnCodeToString(rc));
}
return rc;
NIMBLE_LOGD(LOG_TAG, "<< disconnect()");
}
/**
* @brief Return the number of connected clients.
* @return The number of connected clients.
*/
uint32_t NimBLEServer::getConnectedCount() {
return m_connectedServersMap.size();
} // getConnectedCount
/**
* @brief Handle a GATT Server Event.
*
* @param [in] event
* @param [in] gatts_if
* @param [in] param
*
*/
/*STATIC*/int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) {
NimBLEServer* server = (NimBLEServer*)arg;
NIMBLE_LOGD(LOG_TAG, ">> handleGapEvent: %s",
NimBLEUtils::gapEventToString(event->type));
int rc = 0;
switch(event->type) {
case BLE_GAP_EVENT_CONNECT: {
if (event->connect.status != 0) {
/* Connection failed; resume advertising */
NIMBLE_LOGC(LOG_TAG, "Connection failed");
NimBLEDevice::startAdvertising();
server->m_connId = BLE_HS_CONN_HANDLE_NONE;
}
else {
server->m_connId = event->connect.conn_handle;
server->addPeerDevice((void*)server, false, server->m_connId);
ble_gap_conn_desc desc;
rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
assert(rc == 0);
server->m_pServerCallbacks->onConnect(server);
server->m_pServerCallbacks->onConnect(server, &desc);
}
return 0;
} // BLE_GAP_EVENT_CONNECT
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.
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);
NimBLEDevice::onReset(event->disconnect.reason);
break;
default:
break;
}
server->removePeerDevice(event->disconnect.conn.conn_handle, false);
server->m_connId = BLE_HS_CONN_HANDLE_NONE;
server->m_pServerCallbacks->onDisconnect(server);
return 0;
} // BLE_GAP_EVENT_DISCONNECT
case BLE_GAP_EVENT_SUBSCRIBE: {
NIMBLE_LOGI(LOG_TAG, "subscribe event; cur_notify=%d\n value handle; "
"val_handle=%d\n",
event->subscribe.cur_notify, event->subscribe.attr_handle);
auto it = server->m_notifyChrMap.find(event->subscribe.attr_handle);
if(it != server->m_notifyChrMap.cend()) {
(*it).second->setSubscribe(event);
}
return 0;
} // BLE_GAP_EVENT_SUBSCRIBE
case BLE_GAP_EVENT_MTU: {
NIMBLE_LOGI(LOG_TAG, "mtu update event; conn_handle=%d mtu=%d",
event->mtu.conn_handle,
event->mtu.value);
server->updatePeerMTU(event->mtu.conn_handle, event->mtu.value);
return 0;
} // BLE_GAP_EVENT_MTU
case BLE_GAP_EVENT_NOTIFY_TX: {
if(event->notify_tx.indication && event->notify_tx.status != 0) {
auto it = server->m_notifyChrMap.find(event->notify_tx.attr_handle);
if(it != server->m_notifyChrMap.cend()) {
(*it).second->m_semaphoreConfEvt.give(event->notify_tx.status);
}
}
return 0;
} // BLE_GAP_EVENT_NOTIFY_TX
case BLE_GAP_EVENT_CONN_UPDATE: {
NIMBLE_LOGD(LOG_TAG, "Connection parameters updated.");
return 0;
} // BLE_GAP_EVENT_CONN_UPDATE
case BLE_GAP_EVENT_ENC_CHANGE: {
struct ble_gap_conn_desc desc;
int rc = ble_gap_conn_find(event->conn_update.conn_handle, &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);
}
return 0;
} // BLE_GAP_EVENT_ENC_CHANGE
case BLE_GAP_EVENT_PASSKEY_ACTION: {
struct ble_sm_io pkey = {0};
if (event->passkey.params.action == BLE_SM_IOACT_DISP) {
pkey.action = event->passkey.params.action;
// backward compatibility
pkey.passkey = NimBLEDevice::getSecurityPasskey(); // This is the passkey to be entered on peer
// 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();
}
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: %d", 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_sm_inject_io(event->passkey.conn_handle, &pkey);
NIMBLE_LOGD(LOG_TAG, "BLE_SM_IOACT_NUMCMP; ble_sm_inject_io result: %d", rc);
//TODO: Handle out of band pairing
} else if (event->passkey.params.action == BLE_SM_IOACT_OOB) {
static uint8_t tem_oob[16] = {0};
pkey.action = event->passkey.params.action;
for (int i = 0; i < 16; i++) {
pkey.oob[i] = tem_oob[i];
}
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");
}
NIMBLE_LOGD(LOG_TAG, "<< handleGATTServerEvent");
return 0;
} // BLE_GAP_EVENT_PASSKEY_ACTION
default:
break;
}
NIMBLE_LOGD(LOG_TAG, "<< handleGATTServerEvent");
return 0;
} // handleGATTServerEvent
/**
* @brief Set the server callbacks.
*
* As a %BLE server operates, it will generate server level events such as a new client connecting or a previous client
* disconnecting. This function can be called to register a callback handler that will be invoked when these
* events are detected.
*
* @param [in] pCallbacks The callbacks to be invoked.
*/
void NimBLEServer::setCallbacks(NimBLEServerCallbacks* pCallbacks) {
if (pCallbacks != nullptr){
m_pServerCallbacks = pCallbacks;
} else {
m_pServerCallbacks = &defaultCallbacks;
}
} // setCallbacks
/*
* Remove service
*/
/*
void BLEServer::removeService(BLEService* service) {
service->stop();
service->executeDelete();
m_serviceMap.removeService(service);
}
*/
/**
* @brief Start advertising.
*
* Start the server advertising its existence. This is a convenience function and is equivalent to
* retrieving the advertising object and invoking start upon it.
*/
void NimBLEServer::startAdvertising() {
NIMBLE_LOGD(LOG_TAG, ">> startAdvertising");
NimBLEDevice::startAdvertising();
NIMBLE_LOGD(LOG_TAG, "<< startAdvertising");
} // startAdvertising
/**
* @brief Stop advertising.
*/
void NimBLEServer::stopAdvertising() {
NIMBLE_LOGD(LOG_TAG, ">> stopAdvertising");
NimBLEDevice::stopAdvertising();
NIMBLE_LOGD(LOG_TAG, "<< stopAdvertising");
} // startAdvertising
/**
* Allow to connect GATT server to peer device
* Probably can be used in ANCS for iPhone
*/
/*
bool BLEServer::connect(BLEAddress address) {
esp_bd_addr_t addr;
memcpy(&addr, address.getNative(), 6);
// Perform the open connection request against the target BLE Server.
m_semaphoreOpenEvt.take("connect");
esp_err_t errRc = ::esp_ble_gatts_open(
getGattsIf(),
addr, // address
1 // direct connection
);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gattc_open: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return false;
}
uint32_t rc = m_semaphoreOpenEvt.wait("connect"); // Wait for the connection to complete.
ESP_LOGD(LOG_TAG, "<< connect(), rc=%d", rc==ESP_GATT_OK);
return rc == ESP_GATT_OK;
} // connect
*/
void NimBLEServerCallbacks::onConnect(NimBLEServer* pServer) {
NIMBLE_LOGD("NimBLEServerCallbacks", "onConnect(): Default");
} // onConnect
void NimBLEServerCallbacks::onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) {
NIMBLE_LOGD("NimBLEServerCallbacks", "onConnect(): Default");
} // onConnect
void NimBLEServerCallbacks::onDisconnect(NimBLEServer* pServer) {
NIMBLE_LOGD("NimBLEServerCallbacks", "onDisconnect(): Default");
} // onDisconnect
uint32_t NimBLEServerCallbacks::onPassKeyRequest(){
NIMBLE_LOGD("NimBLEServerCallbacks", "onPassKeyRequest: default: 123456");
return 123456;
}
void NimBLEServerCallbacks::onPassKeyNotify(uint32_t pass_key){
NIMBLE_LOGD("NimBLEServerCallbacks", "onPassKeyNotify: default: %d", pass_key);
}
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){
NIMBLE_LOGD("NimBLEServerCallbacks", "onConfirmPIN: default: true");
return true;
}
/* multi connect support */
void NimBLEServer::updatePeerMTU(uint16_t conn_id, uint16_t mtu) {
const std::map<uint16_t, conn_status_t>::iterator it = m_connectedServersMap.find(conn_id);
if (it != m_connectedServersMap.end()) {
it->second.mtu = mtu;
}
}
std::map<uint16_t, conn_status_t> NimBLEServer::getPeerDevices() {
return m_connectedServersMap;
}
/**
* @brief Get the MTU of the client.
* @returns The client MTU or 0 if not found/connected.
*/
uint16_t NimBLEServer::getPeerMTU(uint16_t conn_id) {
auto it = m_connectedServersMap.find(conn_id);
if(it != m_connectedServersMap.cend()) {
return (*it).second.mtu;
} else {
return 0;
}
}
void NimBLEServer::addPeerDevice(void* peer, bool _client, uint16_t conn_id) {
conn_status_t status = {
.peer_device = peer,
.connected = true,
.mtu = 23
};
m_connectedServersMap.insert(std::pair<uint16_t, conn_status_t>(conn_id, status));
}
void NimBLEServer::removePeerDevice(uint16_t conn_id, bool _client) {
m_connectedServersMap.erase(conn_id);
}
/* multi connect support */
/**
* Update connection parameters can be called only after connection has been established
*/
void NimBLEServer::updateConnParams(uint16_t conn_handle,
uint16_t minInterval, uint16_t maxInterval,
uint16_t latency, uint16_t timeout,
uint16_t minConnTime, uint16_t maxConnTime)
{
ble_gap_upd_params params;
params.latency = latency;
params.itvl_max = maxInterval; // max_int = 0x20*1.25ms = 40ms
params.itvl_min = minInterval; // min_int = 0x10*1.25ms = 20ms
params.supervision_timeout = timeout; // timeout = 400*10ms = 4000ms
params.min_ce_len = minConnTime; // Minimum length of connection event in 0.625ms units
params.max_ce_len = maxConnTime; // Maximum length of connection event in 0.625ms units
int rc = ble_gap_update_params(conn_handle, &params);
if(rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Update params error: %d, %s", rc, NimBLEUtils::returnCodeToString(rc));
}
}
/* Don't think this is needed
void NimBLEServer::onHostReset() {
for(auto it = m_notifyChrMap.cbegin(); it != m_notifyChrMap.cend(); ++it) {
(*it).second->m_semaphoreConfEvt.give(0);
}
}
*/
#endif // CONFIG_BT_ENABLED

154
src/NimBLEServer.h Normal file
View File

@ -0,0 +1,154 @@
/*
* NimBLEServer.h
*
* Created: on March 2, 2020
* Author H2zero
*
* Originally:
*
* BLEServer.h
*
* Created on: Apr 16, 2017
* Author: kolban
*/
#ifndef MAIN_NIMBLESERVER_H_
#define MAIN_NIMBLESERVER_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEAddress.h"
#include "NimBLEUUID.h"
#include "NimBLEAdvertising.h"
#include "NimBLEService.h"
#include "NimBLESecurity.h"
#include "FreeRTOS.h"
#include <map>
class NimBLEService;
class NimBLECharacteristic;
class NimBLEServerCallbacks;
/* TODO possibly refactor this struct */
typedef struct {
void *peer_device; // peer device BLEClient or BLEServer - maybe its better to have 2 structures or union here
bool connected; // do we need it?
uint16_t mtu; // every peer device negotiate own mtu
} conn_status_t;
/**
* @brief A data structure that manages the %BLE servers owned by a BLE server.
*/
class NimBLEServiceMap {
public:
// NimBLEService* getByHandle(uint16_t handle);
NimBLEService* getByUUID(const char* uuid);
NimBLEService* getByUUID(NimBLEUUID uuid, uint8_t inst_id = 0);
// void setByHandle(uint16_t handle, NimBLEService* service);
void setByUUID(const char* uuid, NimBLEService* service);
void setByUUID(NimBLEUUID uuid, NimBLEService* service);
std::string toString();
NimBLEService* getFirst();
NimBLEService* getNext();
void removeService(NimBLEService *service);
int getRegisteredServiceCount();
private:
// std::map<uint16_t, NimBLEService*> m_handleMap;
std::map<NimBLEService*, std::string> m_uuidMap;
std::map<NimBLEService*, std::string>::iterator m_iterator;
};
/**
* @brief The model of a %BLE server.
*/
class NimBLEServer {
public:
uint32_t getConnectedCount();
NimBLEService* createService(const char* uuid);
NimBLEService* createService(NimBLEUUID uuid, uint32_t numHandles=15, uint8_t inst_id=0);
NimBLEAdvertising* getAdvertising();
void setCallbacks(NimBLEServerCallbacks* pCallbacks);
void startAdvertising();
void stopAdvertising();
void start();
// void removeService(BLEService* service);
NimBLEService* getServiceByUUID(const char* uuid);
NimBLEService* getServiceByUUID(NimBLEUUID uuid);
int disconnect(uint16_t connID, uint8_t reason = BLE_ERR_REM_USER_CONN_TERM);
// bool connect(BLEAddress address);
void updateConnParams(uint16_t conn_handle,
uint16_t minInterval, uint16_t maxInterval,
uint16_t latency, uint16_t timeout,
uint16_t minConnTime=0, uint16_t maxConnTime=0);
/* multi connection support */
std::map<uint16_t, conn_status_t> getPeerDevices();
void addPeerDevice(void* peer, bool is_client, uint16_t conn_id);
void removePeerDevice(uint16_t conn_id, bool client);
NimBLEServer* getServerByConnId(uint16_t conn_id);
void updatePeerMTU(uint16_t connId, uint16_t mtu);
uint16_t getPeerMTU(uint16_t conn_id);
uint16_t getConnId();
private:
NimBLEServer();
//friend class BLEService;
friend class NimBLECharacteristic;
friend class NimBLEDevice;
friend class NimBLEAdvertising;
// void onHostReset();
// BLEAdvertising m_bleAdvertising;
uint16_t m_connId;
uint16_t m_svcChgChrHdl;
bool m_gattsStarted;
std::map<uint16_t, conn_status_t> m_connectedServersMap;
std::map<uint16_t, NimBLECharacteristic*> m_notifyChrMap;
NimBLEServiceMap m_serviceMap;
NimBLEServerCallbacks* m_pServerCallbacks;
static int handleGapEvent(struct ble_gap_event *event, void *arg);
}; // NimBLEServer
/**
* @brief Callbacks associated with the operation of a %BLE server.
*/
class NimBLEServerCallbacks {
public:
virtual ~NimBLEServerCallbacks() {};
/**
* @brief Handle a new client connection.
*
* When a new client connects, we are invoked.
*
* @param [in] pServer A reference to the %BLE server that received the client connection.
*/
virtual void onConnect(NimBLEServer* pServer);
virtual void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc);
/**
* @brief Handle an existing client disconnection.
*
* When an existing client disconnects, we are invoked.
*
* @param [in] pServer A reference to the %BLE server that received the existing client disconnection.
*/
virtual void onDisconnect(NimBLEServer* pServer);
virtual uint32_t onPassKeyRequest(); //{return 0;}
virtual void onPassKeyNotify(uint32_t pass_key); //{}
virtual bool onSecurityRequest(); //{return true;}
virtual void onAuthenticationComplete(ble_gap_conn_desc* desc);//{};
virtual bool onConfirmPIN(uint32_t pin);//{return true;}
}; // BLEServerCallbacks
#endif /* CONFIG_BT_ENABLED */
#endif /* MAIN_NIMBLESERVER_H_ */

293
src/NimBLEService.cpp Normal file
View File

@ -0,0 +1,293 @@
/*
* NimBLEService.cpp
*
* Created: on March 2, 2020
* Author H2zero
*
* Originally:
*
* BLEService.cpp
*
* Created on: Mar 25, 2017
* Author: kolban
*/
// A service is identified by a UUID. A service is also the container for one or more characteristics.
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEService.h"
#include "NimBLEUtils.h"
#include "NimBLELog.h"
#include <string>
static const char* LOG_TAG = "NimBLEService"; // Tag for logging.
#define NULL_HANDLE (0xffff)
/**
* @brief Construct an instance of the BLEService
* @param [in] uuid The UUID of the service.
* @param [in] numHandles The maximum number of handles associated with the service.
*/
NimBLEService::NimBLEService(const char* uuid, uint16_t numHandles, NimBLEServer* pServer)
: NimBLEService(NimBLEUUID(uuid), numHandles, pServer) {
}
/**
* @brief Construct an instance of the BLEService
* @param [in] uuid The UUID of the service.
* @param [in] numHandles The maximum number of handles associated with the service.
*/
NimBLEService::NimBLEService(NimBLEUUID uuid, uint16_t numHandles, NimBLEServer* pServer) {
m_uuid = uuid;
m_handle = NULL_HANDLE;
m_pServer = pServer;
m_numHandles = numHandles;
} // 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);
NIMBLE_LOGD(LOG_TAG, "Characteristics:\n%s", m_characteristicMap.toString().c_str());
} // dump
/**
* @brief Get the UUID of the service.
* @return the UUID of the service.
*/
NimBLEUUID NimBLEService::getUUID() {
return m_uuid;
} // getUUID
/**
* @brief Start the service.
* Here we wish to start the service which means that we will respond to partner requests about it.
* Starting a service also means that we can create the corresponding characteristics.
* @return Start the service.
*/
bool NimBLEService::start() {
NIMBLE_LOGD(LOG_TAG, ">> start(): Starting service: %s", toString().c_str());
int rc = 0;
// 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;
uint8_t numChrs = m_characteristicMap.getSize();
NIMBLE_LOGD(LOG_TAG,"Adding %d characteristics for service %s", numChrs, toString().c_str());
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];
NimBLECharacteristic* pCharacteristic = m_characteristicMap.getFirst();
for(uint8_t i=0; i < numChrs; i++) {
uint8_t numDscs = pCharacteristic->m_descriptorMap.getSize();
if(numDscs) {
// skip 2902 as it's automatically created by NimBLE
// if Indicate or Notify flags are set
if((pCharacteristic->m_properties & BLE_GATT_CHR_F_INDICATE) ||
(pCharacteristic->m_properties & BLE_GATT_CHR_F_NOTIFY)) {
numDscs--;
}
}
if(!numDscs){
pChr_a[i].descriptors = NULL;
} else {
// Must have last descriptor uuid = 0 so we have to create 1 extra
//NIMBLE_LOGD(LOG_TAG, "Adding %d descriptors", numDscs);
pDsc_a = new ble_gatt_dsc_def[numDscs+1];
NimBLEDescriptor* pDescriptor = pCharacteristic->m_descriptorMap.getFirst();
for(uint8_t d=0; d < numDscs;) {
// skip 2902
if(pDescriptor->m_uuid.equals(NimBLEUUID((uint16_t)0x2902))) {
//NIMBLE_LOGD(LOG_TAG, "Skipped 0x2902");
pDescriptor = pCharacteristic->m_descriptorMap.getNext();
continue;
}
pDsc_a[d].uuid = &pDescriptor->m_uuid.getNative()->u;
pDsc_a[d].att_flags = pDescriptor->m_properties;
pDsc_a[d].min_key_size = 0;
pDsc_a[d].access_cb = NimBLEDescriptor::handleGapEvent;
pDsc_a[d].arg = pDescriptor;
pDescriptor = pCharacteristic->m_descriptorMap.getNext();
d++;
}
pDsc_a[numDscs].uuid = NULL;
pChr_a[i].descriptors = pDsc_a;
}
pChr_a[i].uuid = &pCharacteristic->m_uuid.getNative()->u;
pChr_a[i].access_cb = NimBLECharacteristic::handleGapEvent;
pChr_a[i].arg = pCharacteristic;
pChr_a[i].flags = pCharacteristic->m_properties;
pChr_a[i].min_key_size = 0;
pChr_a[i].val_handle = &pCharacteristic->m_handle;
pCharacteristic = m_characteristicMap.getNext();
}
pChr_a[numChrs].uuid = NULL;
svc[0].characteristics = pChr_a;
}
// end of services must indicate to api with type = 0
svc[1].type = 0;
rc = ble_gatts_count_cfg((const ble_gatt_svc_def*)svc);
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*)svc);
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 Set the handle associated with this service.
* @param [in] handle The handle associated with the service.
*/
void NimBLEService::setHandle(uint16_t handle) {
NIMBLE_LOGD(LOG_TAG, ">> setHandle - Handle=0x%.2x, service UUID=%s)", handle, getUUID().toString().c_str());
if (m_handle != NULL_HANDLE) {
NIMBLE_LOGE(LOG_TAG, "!!! Handle is already set %.2x", m_handle);
return;
}
m_handle = handle;
NIMBLE_LOGD(LOG_TAG, "<< setHandle");
} // setHandle
/**
* @brief Get the handle associated with this service.
* @return The handle associated with this service.
*/
uint16_t NimBLEService::getHandle() {
return m_handle;
} // getHandle
/**
* @brief Add a characteristic to the service.
* @param [in] pCharacteristic A pointer to the characteristic to be added.
*/
void NimBLEService::addCharacteristic(NimBLECharacteristic* pCharacteristic) {
// We maintain a mapping of characteristics owned by this service. These are managed by the
// BLECharacteristicMap class instance found in m_characteristicMap. We add the characteristic
// to the map and then ask the service to add the characteristic at the BLE level (ESP-IDF).
NIMBLE_LOGD(LOG_TAG, ">> addCharacteristic()");
NIMBLE_LOGD(LOG_TAG, "Adding characteristic: uuid=%s to service: %s",
pCharacteristic->getUUID().toString().c_str(),
toString().c_str());
// Check that we don't add the same characteristic twice.
if (m_characteristicMap.getByUUID(pCharacteristic->getUUID()) != nullptr) {
NIMBLE_LOGW(LOG_TAG, "<< Adding a new characteristic with the same UUID as a previous one");
//return;
}
// Remember this characteristic in our map of characteristics. At this point, we can lookup by UUID
// but not by handle. The handle is allocated to us on the ESP_GATTS_ADD_CHAR_EVT.
m_characteristicMap.setByUUID(pCharacteristic, pCharacteristic->getUUID());
NIMBLE_LOGD(LOG_TAG, "<< addCharacteristic()");
} // addCharacteristic
/**
* @brief Create a new BLE Characteristic associated with this service.
* @param [in] uuid - The UUID of the characteristic.
* @param [in] properties - The properties of the characteristic.
* @return The new BLE characteristic.
*/
NimBLECharacteristic* NimBLEService::createCharacteristic(const char* uuid, uint32_t properties) {
return createCharacteristic(NimBLEUUID(uuid), properties);
}
/**
* @brief Create a new BLE Characteristic associated with this service.
* @param [in] uuid - The UUID of the characteristic.
* @param [in] properties - The properties of the characteristic.
* @return The new BLE characteristic.
*/
NimBLECharacteristic* NimBLEService::createCharacteristic(NimBLEUUID uuid, uint32_t properties) {
NimBLECharacteristic* pCharacteristic = new NimBLECharacteristic(uuid, properties, this);
addCharacteristic(pCharacteristic);
//pCharacteristic->executeCreate(this);
return pCharacteristic;
} // createCharacteristic
NimBLECharacteristic* NimBLEService::getCharacteristic(const char* uuid) {
return getCharacteristic(NimBLEUUID(uuid));
}
NimBLECharacteristic* NimBLEService::getCharacteristic(NimBLEUUID uuid) {
return m_characteristicMap.getByUUID(uuid);
}
/**
* @brief Return a string representation of this service.
* A service is defined by:
* * Its UUID
* * Its handle
* @return A string representation of this service.
*/
std::string NimBLEService::toString() {
std::string res = "UUID: " + getUUID().toString();
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() {
return m_pServer;
} // getServer
#endif // CONFIG_BT_ENABLED

96
src/NimBLEService.h Normal file
View File

@ -0,0 +1,96 @@
/*
* NimBLEService.h
*
* Created: on March 2, 2020
* Author H2zero
*
* Originally:
*
* BLEService.h
*
* Created on: Mar 25, 2017
* Author: kolban
*/
#ifndef MAIN_NIMBLESERVICE_H_
#define MAIN_NIMBLESERVICE_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLECharacteristic.h"
#include "NimBLEServer.h"
#include "NimBLEUUID.h"
#include "FreeRTOS.h"
class NimBLEServer;
class NimBLECharacteristic;
/**
* @brief A data mapping used to manage the set of %BLE characteristics known to the server.
*/
class NimBLECharacteristicMap {
public:
void setByUUID(NimBLECharacteristic* pCharacteristic, const char* uuid);
void setByUUID(NimBLECharacteristic* pCharacteristic, NimBLEUUID uuid);
void setByHandle(uint16_t handle, NimBLECharacteristic* pCharacteristic);
NimBLECharacteristic* getByUUID(const char* uuid);
NimBLECharacteristic* getByUUID(NimBLEUUID uuid);
NimBLECharacteristic* getByHandle(uint16_t handle);
NimBLECharacteristic* getFirst();
NimBLECharacteristic* getNext();
uint8_t getSize();
std::string toString();
private:
std::map<NimBLECharacteristic*, std::string> m_uuidMap;
std::map<uint16_t, NimBLECharacteristic*> m_handleMap;
std::map<NimBLECharacteristic*, std::string>::iterator m_iterator;
};
/**
* @brief The model of a %BLE service.
*
*/
class NimBLEService {
public:
NimBLECharacteristic* createCharacteristic(const char* uuid,
uint32_t properties = NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE);
NimBLECharacteristic* createCharacteristic(NimBLEUUID uuid,
uint32_t properties = NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE);
void dump();
NimBLECharacteristic* getCharacteristic(const char* uuid);
NimBLECharacteristic* getCharacteristic(NimBLEUUID uuid);
NimBLEUUID getUUID();
NimBLEServer* getServer();
bool start();
// void stop();
std::string toString();
uint16_t getHandle();
uint8_t m_instId = 0;
private:
NimBLEService(const char* uuid, uint16_t numHandles, NimBLEServer* pServer);
NimBLEService(NimBLEUUID uuid, uint16_t numHandles, NimBLEServer* pServer);
friend class NimBLEServer;
friend class NimBLEDevice;
void addCharacteristic(NimBLECharacteristic* pCharacteristic);
NimBLECharacteristicMap m_characteristicMap;
uint16_t m_handle;
NimBLEServer* m_pServer = nullptr;
NimBLEUUID m_uuid;
uint16_t m_numHandles;
void setHandle(uint16_t handle);
}; // BLEService
#endif // CONFIG_BT_ENABLED
#endif /* MAIN_NIMBLESERVICE_H_ */

137
src/NimBLEServiceMap.cpp Normal file
View File

@ -0,0 +1,137 @@
/*
* NimBLEService.cpp
*
* Created: on March 7, 2020
* Author H2zero
*
* Originally:
*
* BLEServiceMap.cpp
*
* Created on: Jun 22, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEService.h"
/**
* @brief Return the service by UUID.
* @param [in] UUID The UUID to look up the service.
* @return The characteristic.
*/
NimBLEService* NimBLEServiceMap::getByUUID(const char* uuid) {
return getByUUID(NimBLEUUID(uuid));
}
/**
* @brief Return the service by UUID.
* @param [in] UUID The UUID to look up the service.
* @return The characteristic.
*/
NimBLEService* NimBLEServiceMap::getByUUID(NimBLEUUID uuid, uint8_t inst_id) {
for (auto &myPair : m_uuidMap) {
if (myPair.first->getUUID().equals(uuid)) {
return myPair.first;
}
}
//return m_uuidMap.at(uuid.toString());
return nullptr;
} // getByUUID
/**
* @brief Return the service by handle.
* @param [in] handle The handle to look up the service.
* @return The service.
*/
/*
NimBLEService* NimBLEServiceMap::getByHandle(uint16_t handle) {
return m_handleMap.at(handle);
} // getByHandle
*/
/**
* @brief Set the service by UUID.
* @param [in] uuid The uuid of the service.
* @param [in] characteristic The service to cache.
* @return N/A.
*/
void NimBLEServiceMap::setByUUID(NimBLEUUID uuid, NimBLEService* service) {
m_uuidMap.insert(std::pair<NimBLEService*, std::string>(service, uuid.toString()));
} // setByUUID
/**
* @brief Set the service by handle.
* @param [in] handle The handle of the service.
* @param [in] service The service to cache.
* @return N/A.
*/
/*
void NimBLEServiceMap::setByHandle(uint16_t handle, NimBLEService* service) {
m_handleMap.insert(std::pair<uint16_t, NimBLEService*>(handle, service));
} // setByHandle
*/
/**
* @brief Return a string representation of the service map.
* @return A string representation of the service map.
*/
std::string NimBLEServiceMap::toString() {
std::string res;
//char hex[5];
for (auto &myPair: m_uuidMap) {
// res += "handle: 0x";
// snprintf(hex, sizeof(hex), "%04x", myPair.first);
// res += hex;
res += ", uuid: " + myPair.second + "\n";
}
return res;
} // toString
/**
* @brief Get the first service in the map.
* @return The first service in the map.
*/
NimBLEService* NimBLEServiceMap::getFirst() {
m_iterator = m_uuidMap.begin();
if (m_iterator == m_uuidMap.end()) return nullptr;
NimBLEService* pRet = m_iterator->first;
m_iterator++;
return pRet;
} // getFirst
/**
* @brief Get the next service in the map.
* @return The next service in the map.
*/
NimBLEService* NimBLEServiceMap::getNext() {
if (m_iterator == m_uuidMap.end()) return nullptr;
NimBLEService* pRet = m_iterator->first;
m_iterator++;
return pRet;
} // getNext
/**
* @brief Removes service from maps.
* @return N/A.
*/
void NimBLEServiceMap::removeService(NimBLEService* service) {
//m_handleMap.erase(service->getHandle());
m_uuidMap.erase(service);
} // removeService
/**
* @brief Returns the amount of registered services
* @return amount of registered services
*/
int NimBLEServiceMap::getRegisteredServiceCount(){
//return m_handleMap.size();
return m_uuidMap.size();
}
#endif /* CONFIG_BT_ENABLED */

301
src/NimBLEUUID.cpp Normal file
View File

@ -0,0 +1,301 @@
/*
* NimBLEUUID.cpp
*
* Created: on Jan 24 2020
* Author H2zero
*
* Originally:
*
* BLEUUID.cpp
*
* Created on: Jun 21, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEUtils.h"
#include "NimBLEUUID.h"
#include "NimBLELog.h"
static const char* LOG_TAG = "NimBLEUUID";
/**
* @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:
*
* ```
* "beb5483e-36e1-4688-b7f5-ea07361b26a8"
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
* 12345678-90ab-cdef-1234-567890abcdef
* ```
*
* This has a length of 36 characters. We need to parse this into 16 bytes.
*
* @param [in] value The string to build a UUID from.
*/
NimBLEUUID::NimBLEUUID(std::string value) {
m_valueSet = true;
if (value.length() == 4) {
m_uuid.u.type = BLE_UUID_TYPE_16;
m_uuid.u16.value = 0;
for(int i=0;i<value.length();){
uint8_t MSB = value.c_str()[i];
uint8_t LSB = value.c_str()[i+1];
if(MSB > '9') MSB -= 7;
if(LSB > '9') LSB -= 7;
m_uuid.u16.value += (((MSB&0x0F) <<4) | (LSB & 0x0F))<<(2-i)*4;
i+=2;
}
}
else if (value.length() == 8) {
m_uuid.u.type = BLE_UUID_TYPE_32;
m_uuid.u32.value = 0;
for(int i=0;i<value.length();){
uint8_t MSB = value.c_str()[i];
uint8_t LSB = value.c_str()[i+1];
if(MSB > '9') MSB -= 7;
if(LSB > '9') LSB -= 7;
m_uuid.u32.value += (((MSB&0x0F) <<4) | (LSB & 0x0F))<<(6-i)*4;
i+=2;
}
}
else if (value.length() == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be investigated (lack of time)
m_uuid.u.type = BLE_UUID_TYPE_128;
NimBLEUtils::memrcpy(m_uuid.u128.value, (uint8_t*)value.data(), 16);
}
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.
m_uuid.u.type = BLE_UUID_TYPE_128;
int n = 0;
for(int i=0;i<value.length();){
if(value.c_str()[i] == '-')
i++;
uint8_t MSB = value.c_str()[i];
uint8_t LSB = value.c_str()[i+1];
if(MSB > '9') MSB -= 7;
if(LSB > '9') LSB -= 7;
m_uuid.u128.value[15-n++] = ((MSB&0x0F) <<4) | (LSB & 0x0F);
i+=2;
}
}
else {
NIMBLE_LOGE(LOG_TAG,"ERROR: UUID value not 2, 4, 16 or 36 bytes");
m_valueSet = false;
}
} // NimBLEUUID(std::string)
/**
* @brief Create a UUID from 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(uint8_t* pData, size_t size, bool msbFirst) {
/*** TODO: change this to use the Nimble function for various lenght UUIDs:
int ble_uuid_init_from_buf(ble_uuid_any_t *uuid, const void *buf, size_t len);
***/
if (size != 16) {
NIMBLE_LOGE(LOG_TAG,"ERROR: UUID length not 16 bytes");
return;
}
m_uuid.u.type = BLE_UUID_TYPE_128;
if (msbFirst) {
NimBLEUtils::memrcpy(m_uuid.u128.value, pData, 16);
} else {
memcpy(m_uuid.u128.value, pData, 16);
}
m_valueSet = true;
} // NimBLEUUID
/**
* @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
/**
* @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
/**
* @brief Create a UUID from the native UUID.
*
* @param [in] uuid The native UUID.
*/
NimBLEUUID::NimBLEUUID(ble_uuid128_t* uuid) {
m_uuid.u.type = BLE_UUID_TYPE_128;
memcpy(m_uuid.u128.value, uuid->value, 16);
m_valueSet = true;
} // NimBLEUUID
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.
*/
uint8_t NimBLEUUID::bitSize() {
if (!m_valueSet) return 0;
return m_uuid.u.type;
} // bitSize
/**
* @brief Compare a UUID against this UUID.
*
* @param [in] uuid The UUID to compare against.
* @return True if the UUIDs are equal and false otherwise.
*/
bool NimBLEUUID::equals(NimBLEUUID uuid) {
if(ble_uuid_cmp(&m_uuid.u, &uuid.getNative()->u) == 0){
return true;
}
return false;
}
/**
* Create a BLEUUID from a string of the form:
* 0xNNNN
* 0xNNNNNNNN
* 0x<UUID>
* NNNN
* NNNNNNNN
* <UUID>
*/
NimBLEUUID NimBLEUUID::fromString(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) {
uint16_t x = strtoul(_uuid.substr(start, len).c_str(), NULL, 16);
return NimBLEUUID(x);
} else if (len == 8) {
uint32_t x = strtoul(_uuid.substr(start, len).c_str(), NULL, 16);
return NimBLEUUID(x);
} else if (len == 36) {
return NimBLEUUID(_uuid);
}
return NimBLEUUID();
} // fromString
/**
* @brief Get the native UUID value.
*
* @return The native UUID value or NULL if not set.
*/
ble_uuid_any_t* NimBLEUUID::getNative() {
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.
*
* A UUID can be internally represented as 16bit, 32bit or the full 128bit. This method
* will convert 16 or 32 bit representations to the full 128bit.
*/
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 4 bytes of the variable part of the UUID.
if (m_uuid.u.type == BLE_UUID_TYPE_16) {
uint16_t temp = m_uuid.u16.value;
m_uuid.u128.value[15] = 0;
m_uuid.u128.value[14] = 0;
m_uuid.u128.value[13] = (temp >> 8) & 0xff;
m_uuid.u128.value[12] = temp & 0xff;
}
else if (m_uuid.u.type == BLE_UUID_TYPE_32) {
uint32_t temp = m_uuid.u32.value;
m_uuid.u128.value[15] = (temp >> 24) & 0xff;
m_uuid.u128.value[14] = (temp >> 16) & 0xff;
m_uuid.u128.value[13] = (temp >> 8) & 0xff;
m_uuid.u128.value[12] = temp & 0xff;
}
// Set the fixed parts of the UUID.
m_uuid.u128.value[11] = 0x00;
m_uuid.u128.value[10] = 0x00;
m_uuid.u128.value[9] = 0x10;
m_uuid.u128.value[8] = 0x00;
m_uuid.u128.value[7] = 0x80;
m_uuid.u128.value[6] = 0x00;
m_uuid.u128.value[5] = 0x00;
m_uuid.u128.value[4] = 0x80;
m_uuid.u128.value[3] = 0x5f;
m_uuid.u128.value[2] = 0x9b;
m_uuid.u128.value[1] = 0x34;
m_uuid.u128.value[0] = 0xfb;
m_uuid.u.type = BLE_UUID_TYPE_128;
return *this;
} // to128
/**
* @brief Get a string representation of the UUID.
*
* The format of a string is:
* 01234567 8901 2345 6789 012345678901
* 0000180d-0000-1000-8000-00805f9b34fb
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
*
* @return A string representation of the UUID.
*/
std::string NimBLEUUID::toString() {
if (!m_valueSet) return "<NULL>"; // If we have no value, nothing to format.
char buf[BLE_UUID_STR_LEN];
return ble_uuid_to_str(&m_uuid.u, buf);
} // toString
#endif /* CONFIG_BT_ENABLED */

47
src/NimBLEUUID.h Normal file
View File

@ -0,0 +1,47 @@
/*
* NimBLEUUID.h
*
* Created: on Jan 24 2020
* Author H2zero
*
* Originally:
*
* BLEUUID.h
*
* Created on: Jun 21, 2017
* Author: kolban
*/
#ifndef COMPONENTS_NIMBLEUUID_H_
#define COMPONENTS_NIMBLEUUID_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "host/ble_uuid.h"
#include <string>
/**
* @brief A model of a %BLE UUID.
*/
class NimBLEUUID {
public:
NimBLEUUID(std::string uuid);
NimBLEUUID(uint16_t uuid);
NimBLEUUID(uint32_t uuid);
NimBLEUUID(ble_uuid128_t* uuid);
NimBLEUUID(uint8_t* pData, size_t size, bool msbFirst);
NimBLEUUID();
uint8_t bitSize(); // Get the number of bits in this uuid.
bool equals(NimBLEUUID uuid);
ble_uuid_any_t* getNative();
NimBLEUUID to128();
std::string toString();
static NimBLEUUID fromString(std::string uuid); // Create a NimBLEUUID from a string
private:
ble_uuid_any_t m_uuid; // The underlying UUID structure that this class wraps.
bool m_valueSet = false; // Is there a value set for this instance.
}; // NimBLEUUID
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_NIMBLEUUID_H_ */

701
src/NimBLEUtils.cpp Normal file
View File

@ -0,0 +1,701 @@
/*
* NimBLEUtils.cpp
*
* Created: on Jan 25 2020
* Author H2zero
*
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEUtils.h"
#include "NimBLELog.h"
static const char* LOG_TAG = "NimBLEUtils";
/**
* @brief Copy memory from source to target but in reverse order.
*
* When we move memory from one location it is normally:
*
* ```
* [0][1][2]...[n] -> [0][1][2]...[n]
* ```
*
* with this function, it is:
*
* ```
* [0][1][2]...[n] -> [n][n-1][n-2]...[0]
* ```
*
* @param [in] target The target of the copy
* @param [in] source The source of the copy
* @param [in] size The number of bytes to copy
*/
void NimBLEUtils::memrcpy(uint8_t* target, uint8_t* source, uint32_t size) {
assert(size > 0);
target += (size - 1); // Point target to the last byte of the target data
while (size > 0) {
*target = *source;
target--;
source++;
size--;
}
} // memrcpy
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;
}
/* 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;
}
const char* NimBLEUtils::returnCodeToString(int rc) {
switch(rc) {
case 0:
return "SUCCESS";
case BLE_HS_EAGAIN:
return "Temporary failure; try again.";
case BLE_HS_EALREADY:
return "Operation already in progress or completed.";
case BLE_HS_EINVAL:
return "One or more arguments are invalid.";
case BLE_HS_EMSGSIZE:
return "The provided buffer is too small.";
case BLE_HS_ENOENT:
return "No entry matching the specified criteria.";
case BLE_HS_ENOMEM:
return "Operation failed due to resource exhaustion.";
case BLE_HS_ENOTCONN:
return "No open connection with the specified handle.";
case BLE_HS_ENOTSUP:
return "Operation disabled at compile time.";
case BLE_HS_EAPP:
return "Application callback behaved unexpectedly.";
case BLE_HS_EBADDATA:
return "Command from peer is invalid.";
case BLE_HS_EOS:
return "Mynewt OS error.";
case BLE_HS_ECONTROLLER:
return "Event from controller is invalid.";
case BLE_HS_ETIMEOUT:
return "Operation timed out.";
case BLE_HS_EDONE:
return "Operation completed successfully.";
case BLE_HS_EBUSY:
return "Operation cannot be performed until procedure completes.";
case BLE_HS_EREJECT:
return "Peer rejected a connection parameter update request.";
case BLE_HS_EUNKNOWN:
return "Unexpected failure; catch all.";
case BLE_HS_EROLE:
return "Operation requires different role (e.g., central vs. peripheral).";
case BLE_HS_ETIMEOUT_HCI:
return "HCI request timed out; controller unresponsive.";
case BLE_HS_ENOMEM_EVT:
return "Controller failed to send event due to memory exhaustion (combined host-controller only).";
case BLE_HS_ENOADDR:
return "Operation requires an identity address but none configured.";
case BLE_HS_ENOTSYNCED:
return "Attempt to use the host before it is synced with controller.";
case BLE_HS_EAUTHEN:
return "Insufficient authentication.";
case BLE_HS_EAUTHOR:
return "Insufficient authorization.";
case BLE_HS_EENCRYPT:
return "Insufficient encryption level.";
case BLE_HS_EENCRYPT_KEY_SZ:
return "Insufficient key size.";
case BLE_HS_ESTORE_CAP:
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 "Unknown HCI Command";
case (0x0200+BLE_ERR_UNK_CONN_ID ):
return "Unknown Connection Identifier";
case (0x0200+BLE_ERR_HW_FAIL ):
return "Hardware Failure";
case (0x0200+BLE_ERR_PAGE_TMO ):
return "Page Timeout";
case (0x0200+BLE_ERR_AUTH_FAIL ):
return "Authentication Failure";
case (0x0200+BLE_ERR_PINKEY_MISSING ):
return "PIN or Key Missing";
case (0x0200+BLE_ERR_MEM_CAPACITY ):
return "Memory Capacity Exceeded";
case (0x0200+BLE_ERR_CONN_SPVN_TMO ):
return "Connection Timeout";
case (0x0200+BLE_ERR_CONN_LIMIT ):
return "Connection Limit Exceeded";
case (0x0200+BLE_ERR_SYNCH_CONN_LIMIT ):
return "Synchronous Connection Limit To A Device Exceeded";
case (0x0200+BLE_ERR_ACL_CONN_EXISTS ):
return "ACL Connection Already Exists";
case (0x0200+BLE_ERR_CMD_DISALLOWED ):
return "Command Disallowed";
case (0x0200+BLE_ERR_CONN_REJ_RESOURCES ):
return "Connection Rejected due to Limited Resources";
case (0x0200+BLE_ERR_CONN_REJ_SECURITY ):
return "Connection Rejected Due To Security Reasons";
case (0x0200+BLE_ERR_CONN_REJ_BD_ADDR ):
return "Connection Rejected due to Unacceptable BD_ADDR";
case (0x0200+BLE_ERR_CONN_ACCEPT_TMO ):
return "Connection Accept Timeout Exceeded";
case (0x0200+BLE_ERR_UNSUPPORTED ):
return "Unsupported Feature or Parameter Value";
case (0x0200+BLE_ERR_INV_HCI_CMD_PARMS ):
return "Invalid HCI Command Parameters";
case (0x0200+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 ):
return "Connection Terminated By Local Host";
case (0x0200+BLE_ERR_REPEATED_ATTEMPTS ):
return "Repeated Attempts";
case (0x0200+BLE_ERR_NO_PAIRING ):
return "Pairing Not Allowed";
case (0x0200+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 ):
return "SCO Offset Rejected";
case (0x0200+BLE_ERR_SCO_ITVL ):
return "SCO Interval Rejected";
case (0x0200+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 ):
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 ):
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 ):
return "LMP Error Transaction Collision";
case (0x0200+BLE_ERR_LMP_PDU ):
return "LMP PDU Not Allowed";
case (0x0200+BLE_ERR_ENCRYPTION_MODE ):
return "Encryption Mode Not Acceptable";
case (0x0200+BLE_ERR_LINK_KEY_CHANGE ):
return "Link Key cannot be Changed";
case (0x0200+BLE_ERR_UNSUPP_QOS ):
return "Requested QoS Not Supported";
case (0x0200+BLE_ERR_INSTANT_PASSED ):
return "Instant Passed";
case (0x0200+BLE_ERR_UNIT_KEY_PAIRING ):
return "Pairing With Unit Key Not Supported";
case (0x0200+BLE_ERR_DIFF_TRANS_COLL ):
return "Different Transaction Collision";
case (0x0200+BLE_ERR_QOS_PARM ):
return "QoS Unacceptable Parameter";
case (0x0200+BLE_ERR_QOS_REJECTED ):
return "QoS Rejected";
case (0x0200+BLE_ERR_CHAN_CLASS ):
return "Channel Classification Not Supported";
case (0x0200+BLE_ERR_INSUFFICIENT_SEC ):
return "Insufficient Security";
case (0x0200+BLE_ERR_PARM_OUT_OF_RANGE ):
return "Parameter Out Of Mandatory Range";
case (0x0200+BLE_ERR_PENDING_ROLE_SW ):
return "Role Switch Pending";
case (0x0200+BLE_ERR_RESERVED_SLOT ):
return "Reserved Slot Violation";
case (0x0200+BLE_ERR_ROLE_SW_FAIL ):
return "Role Switch Failed";
case (0x0200+BLE_ERR_INQ_RSP_TOO_BIG ):
return "Extended Inquiry Response Too Large";
case (0x0200+BLE_ERR_SEC_SIMPLE_PAIR ):
return "Secure Simple Pairing Not Supported By Host";
case (0x0200+BLE_ERR_HOST_BUSY_PAIR ):
return "Host Busy - Pairing";
case (0x0200+BLE_ERR_CONN_REJ_CHANNEL ):
return "Connection Rejected, No Suitable Channel Found";
case (0x0200+BLE_ERR_CTLR_BUSY ):
return "Controller Busy";
case (0x0200+BLE_ERR_CONN_PARMS ):
return "Unacceptable Connection Parameters";
case (0x0200+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 ):
return "Connection Failed to be Established";
case (0x0200+BLE_ERR_MAC_CONN_FAIL ):
return "MAC Connection Failed";
case (0x0200+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.";
default:
return "Unknown";
}
}
/**
* @brief Convert the BLE Advertising Data flags to a string.
* @param adFlags The flags to convert
* @return std::string A string representation of the advertising flags.
*/
const char* NimBLEUtils::advTypeToString(uint8_t advType) {
switch(advType) {
case BLE_HCI_ADV_TYPE_ADV_IND : //0
return "Undirected - Connectable / Scannable";
case BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_HD: //1
return "Directed High Duty - Connectable";
case BLE_HCI_ADV_TYPE_ADV_SCAN_IND: //2
return "Non-Connectable - Scan Response Available";
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
return "Directed Low Duty - Connectable";
default:
return "Unknown flag";
}
} // 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, 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
void NimBLEUtils::dumpGapEvent(ble_gap_event *event, void *arg){
NIMBLE_LOGD(LOG_TAG, "Received a GAP event: %s", gapEventToString(event->type));
}
/**
* @brief Convert a BT 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) {
switch (eventType) {
case BLE_GAP_EVENT_CONNECT : //0
return "BLE_GAP_EVENT_CONNECT ";
case BLE_GAP_EVENT_DISCONNECT: //1
return "BLE_GAP_EVENT_DISCONNECT";
case BLE_GAP_EVENT_CONN_UPDATE: //3
return "BLE_GAP_EVENT_CONN_UPDATE";
case BLE_GAP_EVENT_CONN_UPDATE_REQ: //4
return "BLE_GAP_EVENT_CONN_UPDATE_REQ";
case BLE_GAP_EVENT_L2CAP_UPDATE_REQ: //5
return "BLE_GAP_EVENT_L2CAP_UPDATE_REQ";
case BLE_GAP_EVENT_TERM_FAILURE: //6
return "BLE_GAP_EVENT_TERM_FAILURE";
case BLE_GAP_EVENT_DISC: //7
return "BLE_GAP_EVENT_DISC";
case BLE_GAP_EVENT_DISC_COMPLETE: //8
return "BLE_GAP_EVENT_DISC_COMPLETE";
case BLE_GAP_EVENT_ADV_COMPLETE: //9
return "BLE_GAP_EVENT_ADV_COMPLETE";
case BLE_GAP_EVENT_ENC_CHANGE: //10
return "BLE_GAP_EVENT_ENC_CHANGE";
case BLE_GAP_EVENT_PASSKEY_ACTION : //11
return "BLE_GAP_EVENT_PASSKEY_ACTION";
case BLE_GAP_EVENT_NOTIFY_RX: //12
return "BLE_GAP_EVENT_NOTIFY_RX";
case BLE_GAP_EVENT_NOTIFY_TX : //13
return "BLE_GAP_EVENT_NOTIFY_TX";
case BLE_GAP_EVENT_SUBSCRIBE : //14
return "BLE_GAP_EVENT_SUBSCRIBE";
case BLE_GAP_EVENT_MTU: //15
return "BLE_GAP_EVENT_MTU";
case BLE_GAP_EVENT_IDENTITY_RESOLVED: //16
return "BLE_GAP_EVENT_IDENTITY_RESOLVED";
case BLE_GAP_EVENT_REPEAT_PAIRING: //17
return "BLE_GAP_EVENT_REPEAT_PAIRING";
case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: //18
return "BLE_GAP_EVENT_PHY_UPDATE_COMPLETE";
case BLE_GAP_EVENT_EXT_DISC: //19
return "BLE_GAP_EVENT_EXT_DISC";
case BLE_GAP_EVENT_PERIODIC_SYNC: //20
return "BLE_GAP_EVENT_PERIODIC_SYNC";
case BLE_GAP_EVENT_PERIODIC_REPORT: //21
return "BLE_GAP_EVENT_PERIODIC_REPORT";
case BLE_GAP_EVENT_PERIODIC_SYNC_LOST: //22
return "BLE_GAP_EVENT_PERIODIC_SYNC_LOST";
case BLE_GAP_EVENT_SCAN_REQ_RCVD: //23
return "BLE_GAP_EVENT_SCAN_REQ_RCVD";
default:
NIMBLE_LOGD(LOG_TAG, "gapEventToString: Unknown event type %d 0x%.2x", eventType, eventType);
return "Unknown event type";
}
} // gapEventToString
/**
* Utility function to log an array of bytes.
*/
void print_bytes(const uint8_t *bytes, int len)
{
int i;
for (i = 0; i < len; i++) {
MODLOG_DFLT(DEBUG, "%s0x%02x", i != 0 ? ":" : "", bytes[i]);
}
}
void print_mbuf(const struct os_mbuf *om)
{
int colon;
colon = 0;
while (om != NULL) {
if (colon) {
MODLOG_DFLT(DEBUG, ":");
} else {
colon = 1;
}
print_bytes(om->om_data, om->om_len);
om = SLIST_NEXT(om, om_next);
}
}
char *addr_str(const void *addr)
{
static char buf[6 * 2 + 5 + 1];
const uint8_t *u8p;
u8p = (const uint8_t*)addr;
sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x",
u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]);
return buf;
}
void print_uuid(const ble_uuid_t *uuid)
{
char buf[BLE_UUID_STR_LEN];
MODLOG_DFLT(DEBUG, "%s", ble_uuid_to_str(uuid, buf));
}
void print_adv_fields(const struct ble_hs_adv_fields *fields)
{
char s[BLE_HS_ADV_MAX_SZ];
const uint8_t *u8p;
int i;
if (fields->flags != 0) {
MODLOG_DFLT(DEBUG, " flags=0x%02x\n", fields->flags);
}
if (fields->uuids16 != NULL) {
MODLOG_DFLT(DEBUG, " uuids16(%scomplete)=",
fields->uuids16_is_complete ? "" : "in");
for (i = 0; i < fields->num_uuids16; i++) {
print_uuid(&fields->uuids16[i].u);
MODLOG_DFLT(DEBUG, " ");
}
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->uuids32 != NULL) {
MODLOG_DFLT(DEBUG, " uuids32(%scomplete)=",
fields->uuids32_is_complete ? "" : "in");
for (i = 0; i < fields->num_uuids32; i++) {
print_uuid(&fields->uuids32[i].u);
MODLOG_DFLT(DEBUG, " ");
}
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->uuids128 != NULL) {
MODLOG_DFLT(DEBUG, " uuids128(%scomplete)=",
fields->uuids128_is_complete ? "" : "in");
for (i = 0; i < fields->num_uuids128; i++) {
print_uuid(&fields->uuids128[i].u);
MODLOG_DFLT(DEBUG, " ");
}
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->name != NULL) {
assert(fields->name_len < sizeof s - 1);
memcpy(s, fields->name, fields->name_len);
s[fields->name_len] = '\0';
MODLOG_DFLT(DEBUG, " name(%scomplete)=%s\n",
fields->name_is_complete ? "" : "in", s);
}
if (fields->tx_pwr_lvl_is_present) {
MODLOG_DFLT(DEBUG, " tx_pwr_lvl=%d\n", fields->tx_pwr_lvl);
}
if (fields->slave_itvl_range != NULL) {
MODLOG_DFLT(DEBUG, " slave_itvl_range=");
print_bytes(fields->slave_itvl_range, BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN);
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->svc_data_uuid16 != NULL) {
MODLOG_DFLT(DEBUG, " svc_data_uuid16=");
print_bytes(fields->svc_data_uuid16, fields->svc_data_uuid16_len);
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->public_tgt_addr != NULL) {
MODLOG_DFLT(DEBUG, " public_tgt_addr=");
u8p = fields->public_tgt_addr;
for (i = 0; i < fields->num_public_tgt_addrs; i++) {
MODLOG_DFLT(DEBUG, "public_tgt_addr=%s ", addr_str(u8p));
u8p += BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN;
}
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->appearance_is_present) {
MODLOG_DFLT(DEBUG, " appearance=0x%04x\n", fields->appearance);
}
if (fields->adv_itvl_is_present) {
MODLOG_DFLT(DEBUG, " adv_itvl=0x%04x\n", fields->adv_itvl);
}
if (fields->svc_data_uuid32 != NULL) {
MODLOG_DFLT(DEBUG, " svc_data_uuid32=");
print_bytes(fields->svc_data_uuid32, fields->svc_data_uuid32_len);
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->svc_data_uuid128 != NULL) {
MODLOG_DFLT(DEBUG, " svc_data_uuid128=");
print_bytes(fields->svc_data_uuid128, fields->svc_data_uuid128_len);
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->uri != NULL) {
MODLOG_DFLT(DEBUG, " uri=");
print_bytes(fields->uri, fields->uri_len);
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->mfg_data != NULL) {
MODLOG_DFLT(DEBUG, " mfg_data=");
print_bytes(fields->mfg_data, fields->mfg_data_len);
MODLOG_DFLT(DEBUG, "\n");
}
}
/**
* Logs information about a connection to the console.
*/
void print_conn_desc(const struct ble_gap_conn_desc *desc)
{
MODLOG_DFLT(DEBUG, "handle=%d our_ota_addr_type=%d our_ota_addr=%s ",
desc->conn_handle, desc->our_ota_addr.type,
addr_str(desc->our_ota_addr.val));
MODLOG_DFLT(DEBUG, "our_id_addr_type=%d our_id_addr=%s ",
desc->our_id_addr.type, addr_str(desc->our_id_addr.val));
MODLOG_DFLT(DEBUG, "peer_ota_addr_type=%d peer_ota_addr=%s ",
desc->peer_ota_addr.type, addr_str(desc->peer_ota_addr.val));
MODLOG_DFLT(DEBUG, "peer_id_addr_type=%d peer_id_addr=%s ",
desc->peer_id_addr.type, addr_str(desc->peer_id_addr.val));
MODLOG_DFLT(DEBUG, "conn_itvl=%d conn_latency=%d supervision_timeout=%d "
"encrypted=%d authenticated=%d bonded=%d",
desc->conn_itvl, desc->conn_latency,
desc->supervision_timeout,
desc->sec_state.encrypted,
desc->sec_state.authenticated,
desc->sec_state.bonded);
}
void print_addr(const void *addr)
{
const uint8_t *u8p;
u8p = (uint8_t*)addr;
MODLOG_DFLT(INFO, "%02x:%02x:%02x:%02x:%02x:%02x",
u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]);
}
#endif //CONFIG_BT_ENABLED

37
src/NimBLEUtils.h Normal file
View File

@ -0,0 +1,37 @@
/*
* NimBLEUtils.h
*
* Created: on Jan 25 2020
* Author H2zero
*
*/
#ifndef COMPONENTS_NIMBLEUTILS_H_
#define COMPONENTS_NIMBLEUTILS_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "host/ble_gap.h"
extern "C"{
char *addr_str(const void *addr);
void print_conn_desc(const struct ble_gap_conn_desc *desc);
void print_adv_fields(const struct ble_hs_adv_fields *fields);
void print_addr(const void *addr);
void print_bytes(const uint8_t *bytes, int len);
}
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, uint8_t* source, uint8_t length);
static const char* advTypeToString(uint8_t advType);
static const char* returnCodeToString(int rc);
static void memrcpy(uint8_t* target, uint8_t* source, uint32_t size);
static int checkConnParams(ble_gap_conn_params* params);
};
#endif // CONFIG_BT_ENABLED
#endif // COMPONENTS_NIMBLEUTILS_H_

140
src/NimBLEValue.cpp Normal file
View File

@ -0,0 +1,140 @@
/*
* NimNimBLEValue.cpp
*
* Created: on March 6, 2020
* Author H2zero
*
* Originally:
*
* BLEValue.cpp
*
* Created on: Jul 17, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEValue.h"
#include "NimBLELog.h"
static const char* LOG_TAG="NimBLEValue";
NimBLEValue::NimBLEValue() {
m_accumulation = "";
m_value = "";
m_readOffset = 0;
} // NimBLEValue
/**
* @brief Add a message part to the accumulation.
* The accumulation is a growing set of data that is added to until a commit or cancel.
* @param [in] part A message part being added.
*/
void NimBLEValue::addPart(std::string part) {
NIMBLE_LOGD(LOG_TAG, ">> addPart: length=%d", part.length());
m_accumulation += part;
} // addPart
/**
* @brief Add a message part to the accumulation.
* The accumulation is a growing set of data that is added to until a commit or cancel.
* @param [in] pData A message part being added.
* @param [in] length The number of bytes being added.
*/
void NimBLEValue::addPart(uint8_t* pData, size_t length) {
NIMBLE_LOGD(LOG_TAG, ">> addPart: length=%d", length);
m_accumulation += std::string((char*) pData, length);
} // addPart
/**
* @brief Cancel the current accumulation.
*/
void NimBLEValue::cancel() {
NIMBLE_LOGD(LOG_TAG, ">> cancel");
m_accumulation = "";
m_readOffset = 0;
} // cancel
/**
* @brief Commit the current accumulation.
* When writing a value, we may find that we write it in "parts" meaning that the writes come in in pieces
* of the overall message. After the last part has been received, we may perform a commit which means that
* we now have the complete message and commit the change as a unit.
*/
void NimBLEValue::commit() {
NIMBLE_LOGD(LOG_TAG, ">> commit");
// If there is nothing to commit, do nothing.
if (m_accumulation.length() == 0) return;
setValue(m_accumulation);
m_accumulation = "";
m_readOffset = 0;
} // commit
/**
* @brief Get a pointer to the data.
* @return A pointer to the data.
*/
uint8_t* NimBLEValue::getData() {
return (uint8_t*) m_value.data();
}
/**
* @brief Get the length of the data in bytes.
* @return The length of the data in bytes.
*/
size_t NimBLEValue::getLength() {
return m_value.length();
} // getLength
/**
* @brief Get the read offset.
* @return The read offset into the read.
*/
uint16_t NimBLEValue::getReadOffset() {
return m_readOffset;
} // getReadOffset
/**
* @brief Get the current value.
*/
std::string NimBLEValue::getValue() {
return m_value;
} // getValue
/**
* @brief Set the read offset
* @param [in] readOffset The offset into the read.
*/
void NimBLEValue::setReadOffset(uint16_t readOffset) {
m_readOffset = readOffset;
} // setReadOffset
/**
* @brief Set the current value.
*/
void NimBLEValue::setValue(std::string value) {
m_value = value;
} // setValue
/**
* @brief Set the current value.
* @param [in] pData The data for the current value.
* @param [in] The length of the new current value.
*/
void NimBLEValue::setValue(uint8_t* pData, size_t length) {
m_value = std::string((char*) pData, length);
} // setValue
#endif // CONFIG_BT_ENABLED

46
src/NimBLEValue.h Normal file
View File

@ -0,0 +1,46 @@
/*
* NimBLEValue.h
*
* Created: on March 6, 2020
* Author H2zero
*
* Originally:
*
* BLEValue.h
*
* Created on: Jul 17, 2017
* Author: kolban
*/
#ifndef MAIN_BLEVALUE_H_
#define MAIN_BLEVALUE_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <string>
/**
* @brief The model of a %BLE value.
*/
class NimBLEValue {
public:
NimBLEValue();
void addPart(std::string part);
void addPart(uint8_t* pData, size_t length);
void cancel();
void commit();
uint8_t* getData();
size_t getLength();
uint16_t getReadOffset();
std::string getValue();
void setReadOffset(uint16_t readOffset);
void setValue(std::string value);
void setValue(uint8_t* pData, size_t length);
private:
std::string m_accumulation;
uint16_t m_readOffset;
std::string m_value;
};
#endif // CONFIG_BT_ENABLED
#endif /* MAIN_BLEVALUE_H_ */