diff --git a/API_DIFFERENCES.md b/API_DIFFERENCES.md new file mode 100644 index 0000000..f47f8d0 --- /dev/null +++ b/API_DIFFERENCES.md @@ -0,0 +1,207 @@ +# Server API differnces: + +### Characteristics: +When creating a characteristic the properties are now set with `NIMBLE_PROPERTY::XXXX` instead of `BLECharacteristic::XXXX`. + +#### Previous: +``` +BLECharacteristic::PROPERTY_READ | +BLECharacteristic::PROPERTY_WRITE +``` + +#### Changed to: +``` +NIMBLE_PROPERTY::READ | +NIMBLE_PROPERTY::WRITE +``` + +#### The full list of properties: +``` +NIMBLE_PROPERTY::READ +NIMBLE_PROPERTY::READ_ENC +NIMBLE_PROPERTY::READ_AUTHEN +NIMBLE_PROPERTY::READ_AUTHOR +NIMBLE_PROPERTY::WRITE +NIMBLE_PROPERTY::WRITE_NR +NIMBLE_PROPERTY::WRITE_ENC +NIMBLE_PROPERTY::WRITE_AUTHEN +NIMBLE_PROPERTY::WRITE_AUTHOR +NIMBLE_PROPERTY::BROADCAST +NIMBLE_PROPERTY::NOTIFY +NIMBLE_PROPERTY::INDICATE +``` + +### Descriptors: +Descriptors are now created using the NimBLEcharacteristic method `createDescriptor()`. + +The previous method `addDescriptor()` is now a private function in the library. + +This was done because the NimBLE host automatically creates a 0x2902 descriptor if a characteristic has notify or indicate properties applied. +Due to this fact, this library also creates one automatically for your application. +The only reason to manually create this descriptor now is to assign callback functions. +If you do not require this functionality you can safely exclude the manual creation of that descriptor. + + +For any other descriptor, (except 0x2904, see below) it should now be created just as characteristics are +by invoking the `NimBLECharacteristic::createDescriptor` methods. +Which are defined as: +``` +NimBLEDescriptor* createDescriptor(const char* uuid, + uint32_t properties = NIMBLE_PROPERTY::READ | + NIMBLE_PROPERTY::WRITE, + uint16_t max_len = 100); + +NimBLEDescriptor* createDescriptor(NimBLEUUID uuid, + uint32_t properties = NIMBLE_PROPERTY::READ | + NIMBLE_PROPERTY::WRITE, + uint16_t max_len = 100); +``` +##### Example: +``` +pDescriptor = pCharacteristic->createDescriptor("ABCD", + NIMBLE_PROPERTY::READ | + NIMBLE_PROPERTY::WRITE | + NIMBLE_PROPERTY::WRITE_ENC, + 25);` +``` +Would create a descriptor with the UUID 0xABCD, publicly readable but only writable if paired/bonded (encrypted) and has a max value length of 25 bytes. + +For the 0x2904 descriptor, there is a special class that is created when you call `createDescriptor("2904")`. + +The pointer returned is of the base class `NimBLEDescriptor` but the call will create the derived class of `NimBLE2904` so you must cast the returned pointer to `NimBLE2904*` to access the specific class methods. + +##### Example: +``` +p2904 = (NimBLE2904*)pCharacteristic->createDescriptor("2904"); +``` + +#### Server Security: +Security is set on the characteristic or descriptor properties by applying one of the following: +``` +NIMBLE_PROPERTY::READ_ENC +NIMBLE_PROPERTY::READ_AUTHEN +NIMBLE_PROPERTY::READ_AUTHOR +NIMBLE_PROPERTY::WRITE_ENC +NIMBLE_PROPERTY::WRITE_AUTHEN +NIMBLE_PROPERTY::WRITE_AUTHOR +``` +When a peer wants to read or write a characteristic or descriptor with any of these properties applied +it will trigger the pairing process. By default the "just-works" pairing will be performed automatically. +This can be changed to use passkey authentication or numeric confirmation. See below for details. + + +# Client API Differences: +The `BLEAdvertisedDeviceCallbacks` class `onResult()` method now receives a pointer to the +`NimBLEAdvertisedDevice` object instead of a copy. + +`NimBLEClient::connect()` now takes an extra parameter to indicate if the client should download the services + database from the peripheral, default value is true. + +Defined as: +``` +bool connect(NimBLEAdvertisedDevice* device, bool refreshServices = true); +bool connect(NimBLEAddress address, uint8_t type = BLE_ADDR_TYPE_PUBLIC, bool refreshServices = true); +``` +If set to false the client will use the services database it retrieved from the peripheral last time it connected. +This allows for faster connections and power saving if the devices just dropped connection and want to reconnect. + +``` +NimBLERemoteCharacteristic::writeValue(); +NimBLERemoteCharacteristic::registerForNotify(); +``` +Now return true or false to indicate success or failure so you can choose to disconnect or try again. + +#### Client Security: +The client will automatically initiate security when the peripheral responds that it's required. +The default configuration will use "just-works" pairing with no bonding, if you wish to enable bonding see below. + + +# Security: +Security callback functions are now incorporated in the client/server Callbacks class. +However backward compatibility with the `BLESecurity` class is retained to minimize app code changes. + +The relevent server callbacks are defined as: +``` +bool onConfirmPIN(uint32_t pin); // accept or reject the passkey +void onAuthenticationComplete(ble_gap_conn_desc* desc); // auth complete - details in desc +bool onPassKeyNotify(uint32_t pass_key); // receive the passkey sent by the client, accept or reject +``` +The relevent client callbacks are defined as: +``` +bool onConfirmPIN(uint32_t pin); // accept or reject the passkey +void onAuthenticationComplete(ble_gap_conn_desc* desc); // auth complete - details in desc +uint32_t onPassKeyRequest(); // return the passkey to send to the server +``` + +Security settings and IO capabilities are now set by the corresponding method of `NimBLEDevice::`. +``` +static void setSecurityAuth(bool bonding, bool mitm, bool sc); +static void setSecurityAuth(uint8_t auth_req); +static void setSecurityIOCap(uint8_t iocap); +static void setSecurityInitKey(uint8_t init_key); +static void setSecurityRespKey(uint8_t init_key); + + +/** + * @brief Set the authorization mode for this device. + * @param bonding, if true we allow bonding, false no bonding will be performed. + * @param mitm, if true we are capable of man in the middle protection, false if not. + * @param sc, if true we will perform secure connection pairing, false we will use legacy pairing. + */ +void NimBLEDevice::setSecuityAuth(bool bonding, bool mitm, bool sc) + + + +/** + * @brief Set the authorization mode for this device. + * @param A bitmap indicating what modes are supported. + * The bits are defined as follows: + ** 0x01 BLE_SM_PAIR_AUTHREQ_BOND + ** 0x04 BLE_SM_PAIR_AUTHREQ_MITM + ** 0x08 BLE_SM_PAIR_AUTHREQ_SC + ** 0x10 BLE_SM_PAIR_AUTHREQ_KEYPRESS - not yet supported. + ** 0xe2 BLE_SM_PAIR_AUTHREQ_RESERVED - for reference only. + */ +void NimBLEDevice::setSecuityAuth(uint8_t auth_req) + + + +/** + * @brief Set the Input/Output capabilities of this device. + * @param One of the following: + ** 0x00 BLE_HS_IO_DISPLAY_ONLY DisplayOnly IO capability + ** 0x01 BLE_HS_IO_DISPLAY_YESNO DisplayYesNo IO capability + ** 0x02 BLE_HS_IO_KEYBOARD_ONLY KeyboardOnly IO capability + ** 0x03 BLE_HS_IO_NO_INPUT_OUTPUT NoInputNoOutput IO capability + ** 0x04 BLE_HS_IO_KEYBOARD_DISPLAY KeyboardDisplay Only IO capability + */ +void NimBLEDevice::setSecurityIOCap(uint8_t iocap) + + + +/** + * @brief If we are the initiator of the security procedure this sets the keys we will distribute. + * @param A bitmap indicating which keys to distribute during pairing. + * The bits are defined as follows: + ** 0x01: BLE_SM_PAIR_KEY_DIST_ENC - Distribute the encryption key. + ** 0x02: BLE_SM_PAIR_KEY_DIST_ID - Distribute the ID key (IRK). + ** 0x04: BLE_SM_PAIR_KEY_DIST_SIGN + ** 0x08: BLE_SM_PAIR_KEY_DIST_LINK + */ +void NimBLEDevice::setSecurityInitKey(uint8_t init_key) + + +/** + * @brief Set the keys we are willing to accept during pairing. + * @param A bitmap indicating which keys to accept during pairing. + * The bits are defined as follows: + ** 0x01: BLE_SM_PAIR_KEY_DIST_ENC - Accept the encryption key. + ** 0x02: BLE_SM_PAIR_KEY_DIST_ID - Accept the ID key (IRK). + ** 0x04: BLE_SM_PAIR_KEY_DIST_SIGN + ** 0x08: BLE_SM_PAIR_KEY_DIST_LINK + */ +void NimBLEDevice::setSecurityRespKey(uint8_t init_key) +``` + + I'm sure there are more things I have forgotten but this is all the majors. + I will update this document as necessary. \ No newline at end of file diff --git a/LICENSE b/LICENSE index 261eeb9..ff16276 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ - Apache License + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -178,7 +178,7 @@ APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" + boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright {2020} {Ryan Powell} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -199,3 +199,5 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + + This product partly derives from esp32-snippets; Copyright 2017 Neil Kolban. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..529a367 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# *** UPDATE *** + +This library is now ready with (mostly)all original BLE library compatiblity. +Check the examples and API_DIFFERENCES document for details of using this library. + +3 simultaneous connections tested stable so far on both client and server. + + +# esp-nimble-cpp +A fork of the NimBLE stack restructured for compilation in the Ardruino IDE with a CPP library for use with ESP32. + +Why? Because the Bluedroid library is too bulky. + +Initial client code testing has resulted in code size reduction of ~115k and reduced ram consumption of ~37k. + + +# Installation: + +Download as .zip and extract to components folder in your esp-idf project. + +`#include "NimBLEDevice.h"` in main.cpp. + + +# Usage: + +This library is intended to be compatible with the original ESP32 BLE functions and types with minor changes. + + +# Acknowledgments: + +* @nkolban and @chegewara for the [original esp32 BLE library](https://github.com/nkolban/esp32-snippets) this project was derived from. +* @beegee-tokyo for contributing your time to test/debug and contributing the beacon examples. + + +# Todo: + +1. Code cleanup. +2. Create documentation. +3. Expose more NimBLE features. +4. Add BLE Mesh code. + diff --git a/src/FreeRTOS.cpp b/src/FreeRTOS.cpp new file mode 100644 index 0000000..6435590 --- /dev/null +++ b/src/FreeRTOS.cpp @@ -0,0 +1,305 @@ +/* + * FreeRTOS.cpp + * + * Created on: Feb 24, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#include "FreeRTOS.h" +#include "NimBLELog.h" + +#include // Include the base FreeRTOS definitions +#include // Include the task definitions +#include // Include the semaphore definitions +#include + +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(""); + 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(""); + + 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 + + diff --git a/src/FreeRTOS.h b/src/FreeRTOS.h new file mode 100644 index 0000000..efec5be --- /dev/null +++ b/src/FreeRTOS.h @@ -0,0 +1,74 @@ +/* + * FreeRTOS.h + * + * Created on: Feb 24, 2017 + * Author: kolban + */ + +#ifndef MAIN_FREERTOS_H_ +#define MAIN_FREERTOS_H_ + +#include // Include the base FreeRTOS definitions. +#include // Include the task definitions. +#include // Include the semaphore definitions. +#include // Include the ringbuffer definitions. + +#include +#include +#include + + +/** + * @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 = ""); + ~Semaphore(); + void give(); + void give(uint32_t value); + void giveFromISR(); + void setName(std::string name); + bool take(std::string owner = ""); + bool take(uint32_t timeoutMs, std::string owner = ""); + std::string toString(); + bool timedWait(std::string owner = "", uint32_t timeoutMs = portMAX_DELAY); + uint32_t wait(std::string owner = ""); + 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_ */ diff --git a/src/HIDKeyboardTypes.h b/src/HIDKeyboardTypes.h new file mode 100644 index 0000000..4e221d5 --- /dev/null +++ b/src/HIDKeyboardTypes.h @@ -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 diff --git a/src/HIDTypes.h b/src/HIDTypes.h new file mode 100644 index 0000000..64850ef --- /dev/null +++ b/src/HIDTypes.h @@ -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 + +/* */ +#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 diff --git a/src/NimBLE2902.cpp b/src/NimBLE2902.cpp new file mode 100644 index 0000000..d2f5589 --- /dev/null +++ b/src/NimBLE2902.cpp @@ -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 \ No newline at end of file diff --git a/src/NimBLE2902.h b/src/NimBLE2902.h new file mode 100644 index 0000000..dcecbaa --- /dev/null +++ b/src/NimBLE2902.h @@ -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 + +#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 m_subscribedMap; + +}; // NimBLE2902 + +#endif /* CONFIG_BT_ENABLED */ +#endif /* MAIN_NIMBLE2902_H_ */ \ No newline at end of file diff --git a/src/NimBLE2904.cpp b/src/NimBLE2904.cpp new file mode 100644 index 0000000..59ef005 --- /dev/null +++ b/src/NimBLE2904.cpp @@ -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 \ No newline at end of file diff --git a/src/NimBLE2904.h b/src/NimBLE2904.h new file mode 100644 index 0000000..b2aafb0 --- /dev/null +++ b/src/NimBLE2904.h @@ -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_ */ \ No newline at end of file diff --git a/src/NimBLEAddress.cpp b/src/NimBLEAddress.cpp new file mode 100644 index 0000000..074ce7e --- /dev/null +++ b/src/NimBLEAddress.cpp @@ -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 diff --git a/src/NimBLEAddress.h b/src/NimBLEAddress.h new file mode 100644 index 0000000..ac1f26c --- /dev/null +++ b/src/NimBLEAddress.h @@ -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 + +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_ */ diff --git a/src/NimBLEAdvertisedDevice.cpp b/src/NimBLEAdvertisedDevice.cpp new file mode 100644 index 0000000..a0c2e30 --- /dev/null +++ b/src/NimBLEAdvertisedDevice.cpp @@ -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(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(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(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(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(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 */ + diff --git a/src/NimBLEAdvertisedDevice.h b/src/NimBLEAdvertisedDevice.h new file mode 100644 index 0000000..b6b2f70 --- /dev/null +++ b/src/NimBLEAdvertisedDevice.h @@ -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 +#include + + +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 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_ */ diff --git a/src/NimBLEAdvertising.cpp b/src/NimBLEAdvertising.cpp new file mode 100644 index 0000000..fe44be1 --- /dev/null +++ b/src/NimBLEAdvertising.cpp @@ -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 */ \ No newline at end of file diff --git a/src/NimBLEAdvertising.h b/src/NimBLEAdvertising.h new file mode 100644 index 0000000..cfb8adc --- /dev/null +++ b/src/NimBLEAdvertising.h @@ -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 + +/* 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 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_ */ \ No newline at end of file diff --git a/src/NimBLEBeacon.cpp b/src/NimBLEBeacon.cpp new file mode 100644 index 0000000..d9f32ae --- /dev/null +++ b/src/NimBLEBeacon.cpp @@ -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 +#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 diff --git a/src/NimBLEBeacon.h b/src/NimBLEBeacon.h new file mode 100644 index 0000000..c0c15c8 --- /dev/null +++ b/src/NimBLEBeacon.h @@ -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_ */ diff --git a/src/NimBLECharacteristic.cpp b/src/NimBLECharacteristic.cpp new file mode 100644 index 0000000..e58e712 --- /dev/null +++ b/src/NimBLECharacteristic.cpp @@ -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 + +#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(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 */ \ No newline at end of file diff --git a/src/NimBLECharacteristic.h b/src/NimBLECharacteristic.h new file mode 100644 index 0000000..743178a --- /dev/null +++ b/src/NimBLECharacteristic.h @@ -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 +#include + + +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 m_uuidMap; +// std::map m_handleMap; + std::map::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_*/ \ No newline at end of file diff --git a/src/NimBLECharacteristicMap.cpp b/src/NimBLECharacteristicMap.cpp new file mode 100644 index 0000000..9ee741b --- /dev/null +++ b/src/NimBLECharacteristicMap.cpp @@ -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(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(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 */ \ No newline at end of file diff --git a/src/NimBLEClient.cpp b/src/NimBLEClient.cpp new file mode 100644 index 0000000..9422ef7 --- /dev/null +++ b/src/NimBLEClient.cpp @@ -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 +#include + +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, ¶ms); + 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* 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(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, "<> 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 diff --git a/src/NimBLEClient.h b/src/NimBLEClient.h new file mode 100644 index 0000000..b8219bc --- /dev/null +++ b/src/NimBLEClient.h @@ -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 +#include + +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* 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 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_ */ diff --git a/src/NimBLEDescriptor.cpp b/src/NimBLEDescriptor.cpp new file mode 100644 index 0000000..8283345 --- /dev/null +++ b/src/NimBLEDescriptor.cpp @@ -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 + +#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 */ \ No newline at end of file diff --git a/src/NimBLEDescriptor.h b/src/NimBLEDescriptor.h new file mode 100644 index 0000000..4f376a9 --- /dev/null +++ b/src/NimBLEDescriptor.h @@ -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 + + +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_ */ \ No newline at end of file diff --git a/src/NimBLEDescriptorMap.cpp b/src/NimBLEDescriptorMap.cpp new file mode 100644 index 0000000..8d0a13d --- /dev/null +++ b/src/NimBLEDescriptorMap.cpp @@ -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(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(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(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 */ \ No newline at end of file diff --git a/src/NimBLEDevice.cpp b/src/NimBLEDevice.cpp new file mode 100644 index 0000000..84dce51 --- /dev/null +++ b/src/NimBLEDevice.cpp @@ -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 NimBLEDevice::m_cList; +std::list NimBLEDevice::m_ignoreList; +NimBLESecurityCallbacks* NimBLEDevice::m_securityCallbacks = nullptr; + +//std::map 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* 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 diff --git a/src/NimBLEDevice.h b/src/NimBLEDevice.h new file mode 100644 index 0000000..954e996 --- /dev/null +++ b/src/NimBLEDevice.h @@ -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 +#include +#include + +#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* 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 m_cList; + static std::list m_ignoreList; + static NimBLESecurityCallbacks* m_securityCallbacks; + +public: + static gap_event_handler m_customGapHandler; +}; + + +#endif // CONFIG_BT_ENABLED +#endif // MAIN_NIMBLEDEVICE_H_ diff --git a/src/NimBLEEddystoneTLM.cpp b/src/NimBLEEddystoneTLM.cpp new file mode 100644 index 0000000..b601d8d --- /dev/null +++ b/src/NimBLEEddystoneTLM.cpp @@ -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 + +#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 diff --git a/src/NimBLEEddystoneTLM.h b/src/NimBLEEddystoneTLM.h new file mode 100644 index 0000000..3c7219e --- /dev/null +++ b/src/NimBLEEddystoneTLM.h @@ -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 + +#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_ */ diff --git a/src/NimBLEEddystoneURL.cpp b/src/NimBLEEddystoneURL.cpp new file mode 100644 index 0000000..c1d2aff --- /dev/null +++ b/src/NimBLEEddystoneURL.cpp @@ -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 + +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 diff --git a/src/NimBLEEddystoneURL.h b/src/NimBLEEddystoneURL.h new file mode 100644 index 0000000..2e13588 --- /dev/null +++ b/src/NimBLEEddystoneURL.h @@ -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 + +#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_ */ diff --git a/src/NimBLELog.h b/src/NimBLELog.h new file mode 100644 index 0000000..348da13 --- /dev/null +++ b/src/NimBLELog.h @@ -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_*/ \ No newline at end of file diff --git a/src/NimBLERemoteCharacteristic.cpp b/src/NimBLERemoteCharacteristic.cpp new file mode 100644 index 0000000..f054c00 --- /dev/null +++ b/src/NimBLERemoteCharacteristic.cpp @@ -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 +#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(&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(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* 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 */ diff --git a/src/NimBLERemoteCharacteristic.h b/src/NimBLERemoteCharacteristic.h new file mode 100644 index 0000000..c107be7 --- /dev/null +++ b/src/NimBLERemoteCharacteristic.h @@ -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 +#include + +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* 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 m_descriptorMap; +}; // BLERemoteCharacteristic +#endif /* CONFIG_BT_ENABLED */ +#endif /* COMPONENTS_NIMBLEREMOTECHARACTERISTIC_H_ */ diff --git a/src/NimBLERemoteDescriptor.cpp b/src/NimBLERemoteDescriptor.cpp new file mode 100644 index 0000000..7b61cc6 --- /dev/null +++ b/src/NimBLERemoteDescriptor.cpp @@ -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(&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 */ diff --git a/src/NimBLERemoteDescriptor.h b/src/NimBLERemoteDescriptor.h new file mode 100644 index 0000000..004d897 --- /dev/null +++ b/src/NimBLERemoteDescriptor.h @@ -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_ */ diff --git a/src/NimBLERemoteService.cpp b/src/NimBLERemoteService.cpp new file mode 100644 index 0000000..28c2cc3 --- /dev/null +++ b/src/NimBLERemoteService.cpp @@ -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(&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(pRemoteCharacteristic->getUUID().toString(), pRemoteCharacteristic)); + service->m_characteristicMapByHandle.insert(std::pair(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* 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* 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 */ diff --git a/src/NimBLERemoteService.h b/src/NimBLERemoteService.h new file mode 100644 index 0000000..bef8f0b --- /dev/null +++ b/src/NimBLERemoteService.h @@ -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 + +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* getCharacteristics(); + std::map* getCharacteristicsByHandle(); // Get the characteristics map. +// void getCharacteristics(std::map* 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 m_characteristicMap; + + // We maintain a map of characteristics owned by this service keyed by a handle. + std::map 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_ */ diff --git a/src/NimBLEScan.cpp b/src/NimBLEScan.cpp new file mode 100644 index 0000000..634a953 --- /dev/null +++ b/src/NimBLEScan.cpp @@ -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 + +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(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; isecond; + 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 */ diff --git a/src/NimBLEScan.h b/src/NimBLEScan.h new file mode 100644 index 0000000..a19a3da --- /dev/null +++ b/src/NimBLEScan.h @@ -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 + +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 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_ */ diff --git a/src/NimBLESecurity.cpp b/src/NimBLESecurity.cpp new file mode 100644 index 0000000..0651858 --- /dev/null +++ b/src/NimBLESecurity.cpp @@ -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 diff --git a/src/NimBLESecurity.h b/src/NimBLESecurity.h new file mode 100644 index 0000000..60e4f44 --- /dev/null +++ b/src/NimBLESecurity.h @@ -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 + +#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_ diff --git a/src/NimBLEServer.cpp b/src/NimBLEServer.cpp new file mode 100644 index 0000000..3978dae --- /dev/null +++ b/src/NimBLEServer.cpp @@ -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 + (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::iterator it = m_connectedServersMap.find(conn_id); + if (it != m_connectedServersMap.end()) { + it->second.mtu = mtu; + } +} + +std::map 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(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, ¶ms); + 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 \ No newline at end of file diff --git a/src/NimBLEServer.h b/src/NimBLEServer.h new file mode 100644 index 0000000..436f203 --- /dev/null +++ b/src/NimBLEServer.h @@ -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 + +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 m_handleMap; + std::map m_uuidMap; + std::map::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 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 m_connectedServersMap; + std::map 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_ */ \ No newline at end of file diff --git a/src/NimBLEService.cpp b/src/NimBLEService.cpp new file mode 100644 index 0000000..4bf6f34 --- /dev/null +++ b/src/NimBLEService.cpp @@ -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 + +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 \ No newline at end of file diff --git a/src/NimBLEService.h b/src/NimBLEService.h new file mode 100644 index 0000000..70fed3b --- /dev/null +++ b/src/NimBLEService.h @@ -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 m_uuidMap; + std::map m_handleMap; + std::map::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_ */ \ No newline at end of file diff --git a/src/NimBLEServiceMap.cpp b/src/NimBLEServiceMap.cpp new file mode 100644 index 0000000..779c49a --- /dev/null +++ b/src/NimBLEServiceMap.cpp @@ -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(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(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 */ \ No newline at end of file diff --git a/src/NimBLEUUID.cpp b/src/NimBLEUUID.cpp new file mode 100644 index 0000000..0002d19 --- /dev/null +++ b/src/NimBLEUUID.cpp @@ -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 '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 '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 '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 + * NNNN + * NNNNNNNN + * + */ + +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 ""; // 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 */ diff --git a/src/NimBLEUUID.h b/src/NimBLEUUID.h new file mode 100644 index 0000000..df36a52 --- /dev/null +++ b/src/NimBLEUUID.h @@ -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 + +/** + * @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_ */ diff --git a/src/NimBLEUtils.cpp b/src/NimBLEUtils.cpp new file mode 100644 index 0000000..1ac609d --- /dev/null +++ b/src/NimBLEUtils.cpp @@ -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 \ No newline at end of file diff --git a/src/NimBLEUtils.h b/src/NimBLEUtils.h new file mode 100644 index 0000000..3c7021d --- /dev/null +++ b/src/NimBLEUtils.h @@ -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_ \ No newline at end of file diff --git a/src/NimBLEValue.cpp b/src/NimBLEValue.cpp new file mode 100644 index 0000000..808812b --- /dev/null +++ b/src/NimBLEValue.cpp @@ -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 \ No newline at end of file diff --git a/src/NimBLEValue.h b/src/NimBLEValue.h new file mode 100644 index 0000000..2fa1fcb --- /dev/null +++ b/src/NimBLEValue.h @@ -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 + +/** + * @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_ */ \ No newline at end of file