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
+305
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
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
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
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
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
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
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
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
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
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_ */
+546
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 */
+133
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
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
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
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
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_ */
+615
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
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_*/
+128
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
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
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
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
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
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
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
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
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
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
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
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
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_*/
+611
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 */
+100
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_ */
+312
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 */
+58
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
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
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
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, don’t send scan requests to advertisers (i.e., don’t request additional advertising data).
m_scan_params.itvl = 0; // This is defined as the time interval from when the Controller started its last LE scan until it begins the subsequent LE scan. (units=0.625 msec)
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
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
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
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
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
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
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
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
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
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
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
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 doesn’t 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
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
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
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_ */