Add and remove libs and components for Arduino Core 3 (#400)

* Add and remove libs and components for Arduino Core 3

* Add back NimBLE-Arduino in resources
This commit is contained in:
iranl
2024-06-20 18:34:49 +02:00
committed by GitHub
parent 90d13068c9
commit b673fb4d5c
1217 changed files with 118233 additions and 140 deletions

View File

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

View File

@@ -0,0 +1,91 @@
/* Copyright (c) 2010-2011 mbed.org, MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef USBCLASS_HID_TYPES
#define USBCLASS_HID_TYPES
#include <stdint.h>
/* */
#define HID_VERSION_1_11 (0x0111)
/* HID Class */
#define HID_CLASS (3)
#define HID_SUBCLASS_NONE (0)
#define HID_PROTOCOL_NONE (0)
/* Descriptors */
#define HID_DESCRIPTOR (33)
#define HID_DESCRIPTOR_LENGTH (0x09)
#define REPORT_DESCRIPTOR (34)
/* Class requests */
#define GET_REPORT (0x1)
#define GET_IDLE (0x2)
#define SET_REPORT (0x9)
#define SET_IDLE (0xa)
/* HID Class Report Descriptor */
/* Short items: size is 0, 1, 2 or 3 specifying 0, 1, 2 or 4 (four) bytes */
/* of data as per HID Class standard */
/* Main items */
#define HIDINPUT(size) (0x80 | size)
#define HIDOUTPUT(size) (0x90 | size)
#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

View File

@@ -0,0 +1,82 @@
/*
* NimBLE2904.cpp
*
* Created: on March 13, 2020
* Author H2zero
*
* Originally:
*
* BLE2904.cpp
*
* Created on: Dec 23, 2017
* Author: kolban
*/
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#include "NimBLE2904.h"
NimBLE2904::NimBLE2904(NimBLECharacteristic* pCharacteristic)
: NimBLEDescriptor(NimBLEUUID((uint16_t) 0x2904),
BLE_GATT_CHR_F_READ,
sizeof(BLE2904_Data),
pCharacteristic)
{
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));
} // BLE2904
/**
* @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 /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */

View File

@@ -0,0 +1,80 @@
/*
* 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 "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#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.
*/
class NimBLE2904: public NimBLEDescriptor {
public:
NimBLE2904(NimBLECharacteristic* pCharacterisitic = nullptr);
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:
friend class NimBLECharacteristic;
BLE2904_Data m_data;
}; // BLE2904
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */
#endif /* MAIN_NIMBLE2904_H_ */

View File

@@ -0,0 +1,206 @@
/*
* NimBLEAddress.cpp
*
* Created: on Jan 24 2020
* Author H2zero
*
* Originally:
*
* BLEAddress.cpp
*
* Created on: Jul 2, 2017
* Author: kolban
*/
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <algorithm>
#include "NimBLEAddress.h"
#include "NimBLEUtils.h"
#include "NimBLELog.h"
static const char* LOG_TAG = "NimBLEAddress";
/*************************************************
* NOTE: NimBLE address bytes are in INVERSE ORDER!
* We will accomodate that fact in these methods.
*************************************************/
/**
* @brief Create an address from the native NimBLE representation.
* @param [in] address The native NimBLE address.
*/
NimBLEAddress::NimBLEAddress(ble_addr_t address) {
memcpy(m_address, address.val, 6);
m_addrType = address.type;
} // NimBLEAddress
/**
* @brief Create a blank address, i.e. 00:00:00:00:00:00, type 0.
*/
NimBLEAddress::NimBLEAddress() {
NimBLEAddress("");
} // NimBLEAddress
/**
* @brief Create an address from a hex string
*
* A hex string is of the format:
* ```
* 00:00:00:00:00:00
* ```
* which is 17 characters in length.
*
* @param [in] stringAddress The hex string representation of the address.
* @param [in] type The type of the address.
*/
NimBLEAddress::NimBLEAddress(const std::string &stringAddress, uint8_t type) {
m_addrType = type;
if (stringAddress.length() == 0) {
memset(m_address, 0, 6);
return;
}
if (stringAddress.length() == 6) {
std::reverse_copy(stringAddress.data(), stringAddress.data() + 6, m_address);
return;
}
if (stringAddress.length() != 17) {
memset(m_address, 0, sizeof m_address); // "00:00:00:00:00:00" represents an invalid address
NIMBLE_LOGD(LOG_TAG, "Invalid address '%s'", stringAddress.c_str());
return;
}
int data[6];
if(sscanf(stringAddress.c_str(), "%x:%x:%x:%x:%x:%x", &data[5], &data[4], &data[3], &data[2], &data[1], &data[0]) != 6) {
memset(m_address, 0, sizeof m_address); // "00:00:00:00:00:00" represents an invalid address
NIMBLE_LOGD(LOG_TAG, "Invalid address '%s'", stringAddress.c_str());
}
for(size_t index = 0; index < sizeof m_address; index++) {
m_address[index] = data[index];
}
} // NimBLEAddress
/**
* @brief Constructor for compatibility with bluedroid esp library using native ESP representation.
* @param [in] address A uint8_t[6] or esp_bd_addr_t containing the address.
* @param [in] type The type of the address.
*/
NimBLEAddress::NimBLEAddress(uint8_t address[6], uint8_t type) {
std::reverse_copy(address, address + sizeof m_address, m_address);
m_addrType = type;
} // NimBLEAddress
/**
* @brief Constructor for address using a hex value.\n
* Use the same byte order, so use 0xa4c1385def16 for "a4:c1:38:5d:ef:16"
* @param [in] address uint64_t containing the address.
* @param [in] type The type of the address.
*/
NimBLEAddress::NimBLEAddress(const uint64_t &address, uint8_t type) {
memcpy(m_address, &address, sizeof m_address);
m_addrType = type;
} // NimBLEAddress
/**
* @brief Determine if this address equals another.
* @param [in] otherAddress The other address to compare against.
* @return True if the addresses are equal.
*/
bool NimBLEAddress::equals(const NimBLEAddress &otherAddress) const {
return *this == otherAddress;
} // equals
/**
* @brief Get the native representation of the address.
* @return a pointer to the uint8_t[6] array of the address.
*/
const uint8_t *NimBLEAddress::getNative() const {
return m_address;
} // getNative
/**
* @brief Get the address type.
* @return The address type.
*/
uint8_t NimBLEAddress::getType() const {
return m_addrType;
} // getType
/**
* @brief Determine if this address is a Resolvable Private Address.
* @return True if the address is a RPA.
*/
bool NimBLEAddress::isRpa() const {
return (m_addrType && ((m_address[5] & 0xc0) == 0x40));
} // isRpa
/**
* @brief Convert a BLE address to a string.
*
* A string representation of an address is in the format:
*
* ```
* xx:xx:xx:xx:xx:xx
* ```
*
* @return The string representation of the address.
* @deprecated Use std::string() operator instead.
*/
std::string NimBLEAddress::toString() const {
return std::string(*this);
} // toString
/**
* @brief Convenience operator to check if this address is equal to another.
*/
bool NimBLEAddress::operator ==(const NimBLEAddress & rhs) const {
return memcmp(rhs.m_address, m_address, sizeof m_address) == 0;
} // operator ==
/**
* @brief Convenience operator to check if this address is not equal to another.
*/
bool NimBLEAddress::operator !=(const NimBLEAddress & rhs) const {
return !this->operator==(rhs);
} // operator !=
/**
* @brief Convienience operator to convert this address to string representation.
* @details This allows passing NimBLEAddress to functions
* that accept std::string and/or or it's methods as a parameter.
*/
NimBLEAddress::operator std::string() const {
char buffer[18];
snprintf(buffer, sizeof(buffer), "%02x:%02x:%02x:%02x:%02x:%02x",
m_address[5], m_address[4], m_address[3],
m_address[2], m_address[1], m_address[0]);
return std::string(buffer);
} // operator std::string
/**
* @brief Convenience operator to convert the native address representation to uint_64.
*/
NimBLEAddress::operator uint64_t() const {
uint64_t address = 0;
memcpy(&address, m_address, sizeof m_address);
return address;
} // operator uint64_t
#endif

View File

@@ -0,0 +1,63 @@
/*
* NimBLEAddress.h
*
* Created: on Jan 24 2020
* Author H2zero
*
* Originally:
*
* BLEAddress.h
*
* Created on: Jul 2, 2017
* Author: kolban
*/
#ifndef COMPONENTS_NIMBLEADDRESS_H_
#define COMPONENTS_NIMBLEADDRESS_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
#if defined(CONFIG_NIMBLE_CPP_IDF)
#include "nimble/ble.h"
#else
#include "nimble/nimble/include/nimble/ble.h"
#endif
/**** FIX COMPILATION ****/
#undef min
#undef max
/**************************/
#include <string>
#include <algorithm>
/**
* @brief A %BLE device address.
*
* Every %BLE device has a unique address which can be used to identify it and form connections.
*/
class NimBLEAddress {
public:
NimBLEAddress();
NimBLEAddress(ble_addr_t address);
NimBLEAddress(uint8_t address[6], uint8_t type = BLE_ADDR_PUBLIC);
NimBLEAddress(const std::string &stringAddress, uint8_t type = BLE_ADDR_PUBLIC);
NimBLEAddress(const uint64_t &address, uint8_t type = BLE_ADDR_PUBLIC);
bool isRpa() const;
bool equals(const NimBLEAddress &otherAddress) const;
const uint8_t* getNative() const;
std::string toString() const;
uint8_t getType() const;
bool operator ==(const NimBLEAddress & rhs) const;
bool operator !=(const NimBLEAddress & rhs) const;
operator std::string() const;
operator uint64_t() const;
private:
uint8_t m_address[6];
uint8_t m_addrType;
};
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_NIMBLEADDRESS_H_ */

View File

@@ -0,0 +1,920 @@
/*
* NimBLEAdvertisedDevice.cpp
*
* Created: on Jan 24 2020
* Author H2zero
*
* Originally:
*
* BLEAdvertisedDevice.cpp
*
* Created on: Jul 3, 2017
* Author: kolban
*/
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
#include "NimBLEDevice.h"
#include "NimBLEAdvertisedDevice.h"
#include "NimBLEUtils.h"
#include "NimBLELog.h"
#include <climits>
static const char* LOG_TAG = "NimBLEAdvertisedDevice";
/**
* @brief Constructor
*/
NimBLEAdvertisedDevice::NimBLEAdvertisedDevice() :
m_payload(62,0)
{
m_advType = 0;
m_rssi = -9999;
m_callbackSent = 0;
m_timestamp = 0;
m_advLength = 0;
} // NimBLEAdvertisedDevice
/**
* @brief Get the address of the advertising device.
* @return The address of the advertised device.
*/
NimBLEAddress NimBLEAdvertisedDevice::getAddress() {
return m_address;
} // getAddress
/**
* @brief Get the advertisement type.
* @return The advertising type the device is reporting:
* * BLE_HCI_ADV_TYPE_ADV_IND (0) - indirect advertising
* * BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_HD (1) - direct advertising - high duty cycle
* * BLE_HCI_ADV_TYPE_ADV_SCAN_IND (2) - indirect scan response
* * BLE_HCI_ADV_TYPE_ADV_NONCONN_IND (3) - indirect advertising - not connectable
* * BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_LD (4) - direct advertising - low duty cycle
*/
uint8_t NimBLEAdvertisedDevice::getAdvType() {
return m_advType;
} // getAdvType
/**
* @brief Get the advertisement flags.
* @return The advertisement flags, a bitmask of:
* BLE_HS_ADV_F_DISC_LTD (0x01) - limited discoverability
* BLE_HS_ADV_F_DISC_GEN (0x02) - general discoverability
* BLE_HS_ADV_F_BREDR_UNSUP - BR/EDR not supported
*/
uint8_t NimBLEAdvertisedDevice::getAdvFlags() {
size_t data_loc = 0;
if(findAdvField(BLE_HS_ADV_TYPE_FLAGS, 0, &data_loc) > 0) {
ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc];
if(field->length == BLE_HS_ADV_FLAGS_LEN + 1) {
return *field->value;
}
}
return 0;
} // getAdvFlags
/**
* @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
* typically in the form of an icon.
*
* @return The appearance of the advertised device.
*/
uint16_t NimBLEAdvertisedDevice::getAppearance() {
size_t data_loc = 0;
if(findAdvField(BLE_HS_ADV_TYPE_APPEARANCE, 0, &data_loc) > 0) {
ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc];
if(field->length == BLE_HS_ADV_APPEARANCE_LEN + 1) {
return *field->value | *(field->value + 1) << 8;
}
}
return 0;
} // getAppearance
/**
* @brief Get the advertisement interval.
* @return The advertisement interval in 0.625ms units.
*/
uint16_t NimBLEAdvertisedDevice::getAdvInterval() {
size_t data_loc = 0;
if(findAdvField(BLE_HS_ADV_TYPE_ADV_ITVL, 0, &data_loc) > 0) {
ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc];
if(field->length == BLE_HS_ADV_ADV_ITVL_LEN + 1) {
return *field->value | *(field->value + 1) << 8;
}
}
return 0;
} // getAdvInterval
/**
* @brief Get the preferred min connection interval.
* @return The preferred min connection interval in 1.25ms units.
*/
uint16_t NimBLEAdvertisedDevice::getMinInterval() {
size_t data_loc = 0;
if(findAdvField(BLE_HS_ADV_TYPE_SLAVE_ITVL_RANGE, 0, &data_loc) > 0) {
ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc];
if(field->length == BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN + 1) {
return *field->value | *(field->value + 1) << 8;
}
}
return 0;
} // getMinInterval
/**
* @brief Get the preferred max connection interval.
* @return The preferred max connection interval in 1.25ms units.
*/
uint16_t NimBLEAdvertisedDevice::getMaxInterval() {
size_t data_loc = 0;
if(findAdvField(BLE_HS_ADV_TYPE_SLAVE_ITVL_RANGE, 0, &data_loc) > 0) {
ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc];
if(field->length == BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN + 1) {
return *(field->value + 2) | *(field->value + 3) << 8;
}
}
return 0;
} // getMaxInterval
/**
* @brief Get the manufacturer data.
* @param [in] index The index of the of the manufacturer data set to get.
* @return The manufacturer data.
*/
std::string NimBLEAdvertisedDevice::getManufacturerData(uint8_t index) {
size_t data_loc = 0;
index++;
if(findAdvField(BLE_HS_ADV_TYPE_MFG_DATA, index, &data_loc) > 0) {
ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc];
if(field->length > 1) {
return std::string((char*)field->value, field->length - 1);
}
}
return "";
} // getManufacturerData
/**
* @brief Get the count of manufacturer data sets.
* @return The number of manufacturer data sets.
*/
uint8_t NimBLEAdvertisedDevice::getManufacturerDataCount() {
return findAdvField(BLE_HS_ADV_TYPE_MFG_DATA);
} // getManufacturerDataCount
/**
* @brief Get the URI from the advertisement.
* @return The URI data.
*/
std::string NimBLEAdvertisedDevice::getURI() {
size_t data_loc = 0;
if(findAdvField(BLE_HS_ADV_TYPE_URI, 0, &data_loc) > 0) {
ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc];
if(field->length > 1) {
return std::string((char*)field->value, field->length - 1);
}
}
return "";
} // getURI
/**
* @brief Get the data from any type available in the advertisement
* @param [in] type The advertised data type BLE_HS_ADV_TYPE
* @return The data available under the type `type`
*/
std::string NimBLEAdvertisedDevice::getPayloadByType(uint16_t type) {
size_t data_loc = 0;
if(findAdvField(type, 0, &data_loc) > 0) {
ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc];
if(field->length > 1) {
return std::string((char*)field->value, field->length - 1);
}
}
return "";
} // getPayloadByType
/**
* @brief Get the advertised name.
* @return The name of the advertised device.
*/
std::string NimBLEAdvertisedDevice::getName() {
size_t data_loc = 0;
if(findAdvField(BLE_HS_ADV_TYPE_COMP_NAME, 0, &data_loc) > 0 ||
findAdvField(BLE_HS_ADV_TYPE_INCOMP_NAME, 0, &data_loc) > 0)
{
ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc];
if(field->length > 1) {
return std::string((char*)field->value, field->length - 1);
}
}
return "";
} // 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 advertised device.
* @return The scan object.
*/
NimBLEScan* NimBLEAdvertisedDevice::getScan() {
return NimBLEDevice::getScan();
} // getScan
/**
* @brief Get the number of target addresses.
* @return The number of addresses.
*/
uint8_t NimBLEAdvertisedDevice::getTargetAddressCount() {
uint8_t count = 0;
count = findAdvField(BLE_HS_ADV_TYPE_PUBLIC_TGT_ADDR);
count += findAdvField(BLE_HS_ADV_TYPE_RANDOM_TGT_ADDR);
return count;
}
/**
* @brief Get the target address at the index.
* @param [in] index The index of the target address.
* @return The target address.
*/
NimBLEAddress NimBLEAdvertisedDevice::getTargetAddress(uint8_t index) {
ble_hs_adv_field *field = nullptr;
uint8_t count = 0;
size_t data_loc = ULONG_MAX;
index++;
count = findAdvField(BLE_HS_ADV_TYPE_PUBLIC_TGT_ADDR, index, &data_loc);
if (count < index) {
index -= count;
count = findAdvField(BLE_HS_ADV_TYPE_RANDOM_TGT_ADDR, index, &data_loc);
}
if(count > 0 && data_loc != ULONG_MAX) {
field = (ble_hs_adv_field *)&m_payload[data_loc];
if(field->length < index * BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN) {
index -= count - field->length / BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN;
}
if(field->length > index * BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN) {
return NimBLEAddress(field->value + (index - 1) * BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN);
}
}
return NimBLEAddress("");
}
/**
* @brief Get the service data.
* @param [in] index The index of the service data requested.
* @return The advertised service data or empty string if no data.
*/
std::string NimBLEAdvertisedDevice::getServiceData(uint8_t index) {
ble_hs_adv_field *field = nullptr;
uint8_t bytes;
size_t data_loc = findServiceData(index, &bytes);
if(data_loc != ULONG_MAX) {
field = (ble_hs_adv_field *)&m_payload[data_loc];
if(field->length > bytes) {
return std::string((char*)(field->value + bytes), field->length - bytes - 1);
}
}
return "";
} //getServiceData
/**
* @brief Get the service data.
* @param [in] uuid The uuid of the service data requested.
* @return The advertised service data or empty string if no data.
*/
std::string NimBLEAdvertisedDevice::getServiceData(const NimBLEUUID &uuid) {
ble_hs_adv_field *field = nullptr;
uint8_t bytes;
uint8_t index = 0;
size_t data_loc = findServiceData(index, &bytes);
size_t plSize = m_payload.size() - 2;
uint8_t uuidBytes = uuid.bitSize() / 8;
while(data_loc < plSize) {
field = (ble_hs_adv_field *)&m_payload[data_loc];
if(bytes == uuidBytes && NimBLEUUID(field->value, bytes, false) == uuid) {
return std::string((char*)(field->value + bytes), field->length - bytes - 1);
}
index++;
data_loc = findServiceData(index, &bytes);
}
NIMBLE_LOGI(LOG_TAG, "No service data found");
return "";
} //getServiceData
/**
* @brief Get the UUID of the service data at the index.
* @param [in] index The index of the service data UUID requested.
* @return The advertised service data UUID or an empty UUID if not found.
*/
NimBLEUUID NimBLEAdvertisedDevice::getServiceDataUUID(uint8_t index) {
ble_hs_adv_field *field = nullptr;
uint8_t bytes;
size_t data_loc = findServiceData(index, &bytes);
if(data_loc != ULONG_MAX) {
field = (ble_hs_adv_field *)&m_payload[data_loc];
if(field->length >= bytes) {
return NimBLEUUID(field->value, bytes, false);
}
}
return NimBLEUUID("");
} // getServiceDataUUID
/**
* @brief Find the service data at the index.
* @param [in] index The index of the service data to find.
* @param [in] bytes A pointer to storage for the number of the bytes in the UUID.
* @return The index in the vector where the data is located, ULONG_MAX if not found.
*/
size_t NimBLEAdvertisedDevice::findServiceData(uint8_t index, uint8_t *bytes) {
size_t data_loc = 0;
uint8_t found = 0;
*bytes = 0;
index++;
found = findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID16, index, &data_loc);
if(found == index) {
*bytes = 2;
return data_loc;
}
index -= found;
found = findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID32, index, &data_loc);
if(found == index) {
*bytes = 4;
return data_loc;
}
index -= found;
found = findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID128, index, &data_loc);
if(found == index) {
*bytes = 16;
return data_loc;
}
return ULONG_MAX;
}
/**
* @brief Get the count of advertised service data UUIDS
* @return The number of service data UUIDS in the vector.
*/
uint8_t NimBLEAdvertisedDevice::getServiceDataCount() {
uint8_t count = 0;
count += findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID16);
count += findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID32);
count += findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID128);
return count;
} // getServiceDataCount
/**
* @brief Get the Service UUID.
* @param [in] index The index of the service UUID requested.
* @return The Service UUID of the advertised service, or an empty UUID if not found.
*/
NimBLEUUID NimBLEAdvertisedDevice::getServiceUUID(uint8_t index) {
uint8_t count = 0;
size_t data_loc = 0;
uint8_t uuidBytes = 0;
uint8_t type = BLE_HS_ADV_TYPE_INCOMP_UUIDS16;
ble_hs_adv_field *field = nullptr;
index++;
do {
count = findAdvField(type, index, &data_loc);
if(count >= index) {
if(type < BLE_HS_ADV_TYPE_INCOMP_UUIDS32) {
uuidBytes = 2;
} else if(type < BLE_HS_ADV_TYPE_INCOMP_UUIDS128) {
uuidBytes = 4;
} else {
uuidBytes = 16;
}
break;
} else {
type++;
index -= count;
}
} while(type <= BLE_HS_ADV_TYPE_COMP_UUIDS128);
if(uuidBytes > 0) {
field = (ble_hs_adv_field *)&m_payload[data_loc];
// In the case of more than one field of service uuid's we need to adjust
// the index to account for the uuids of the previous fields.
if(field->length < index * uuidBytes) {
index -= count - field->length / uuidBytes;
}
if(field->length > uuidBytes * index) {
return NimBLEUUID(field->value + uuidBytes * (index - 1), uuidBytes, false);
}
}
return NimBLEUUID("");
} // getServiceUUID
/**
* @brief Get the number of services advertised
* @return The count of services in the advertising packet.
*/
uint8_t NimBLEAdvertisedDevice::getServiceUUIDCount() {
uint8_t count = 0;
count += findAdvField(BLE_HS_ADV_TYPE_INCOMP_UUIDS16);
count += findAdvField(BLE_HS_ADV_TYPE_COMP_UUIDS16);
count += findAdvField(BLE_HS_ADV_TYPE_INCOMP_UUIDS32);
count += findAdvField(BLE_HS_ADV_TYPE_COMP_UUIDS32);
count += findAdvField(BLE_HS_ADV_TYPE_INCOMP_UUIDS128);
count += findAdvField(BLE_HS_ADV_TYPE_COMP_UUIDS128);
return count;
} // getServiceUUIDCount
/**
* @brief Check advertised services for existence of the required UUID
* @param [in] uuid The service uuid to look for in the advertisement.
* @return Return true if service is advertised
*/
bool NimBLEAdvertisedDevice::isAdvertisingService(const NimBLEUUID &uuid) {
size_t count = getServiceUUIDCount();
for(size_t i = 0; i < count; i++) {
if(uuid == getServiceUUID(i)) {
return true;
}
}
return false;
} // isAdvertisingService
/**
* @brief Get the TX Power.
* @return The TX Power of the advertised device.
*/
int8_t NimBLEAdvertisedDevice::getTXPower() {
size_t data_loc = 0;
if(findAdvField(BLE_HS_ADV_TYPE_TX_PWR_LVL, 0, &data_loc) > 0) {
ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc];
if(field->length == BLE_HS_ADV_TX_PWR_LVL_LEN + 1) {
return *(int8_t*)field->value;
}
}
return -99;
} // getTXPower
/**
* @brief Does this advertisement have preferred connection parameters?
* @return True if connection parameters are present.
*/
bool NimBLEAdvertisedDevice::haveConnParams() {
return findAdvField(BLE_HS_ADV_TYPE_SLAVE_ITVL_RANGE) > 0;
} // haveConnParams
/**
* @brief Does this advertisement have have the advertising interval?
* @return True if the advertisement interval is present.
*/
bool NimBLEAdvertisedDevice::haveAdvInterval() {
return findAdvField(BLE_HS_ADV_TYPE_ADV_ITVL) > 0;
} // haveAdvInterval
/**
* @brief Does this advertisement have an appearance value?
* @return True if there is an appearance value present.
*/
bool NimBLEAdvertisedDevice::haveAppearance() {
return findAdvField(BLE_HS_ADV_TYPE_APPEARANCE) > 0;
} // haveAppearance
/**
* @brief Does this advertisement have manufacturer data?
* @return True if there is manufacturer data present.
*/
bool NimBLEAdvertisedDevice::haveManufacturerData() {
return findAdvField(BLE_HS_ADV_TYPE_MFG_DATA) > 0;
} // haveManufacturerData
/**
* @brief Does this advertisement have a URI?
* @return True if there is a URI present.
*/
bool NimBLEAdvertisedDevice::haveURI() {
return findAdvField(BLE_HS_ADV_TYPE_URI) > 0;
} // haveURI
/**
* @brief Does this advertisement have a adv type `type`?
* @return True if there is a `type` present.
*/
bool NimBLEAdvertisedDevice::haveType(uint16_t type) {
return findAdvField(type) > 0;
}
/**
* @brief Does the advertisement contain a target address?
* @return True if an address is present.
*/
bool NimBLEAdvertisedDevice::haveTargetAddress() {
return findAdvField(BLE_HS_ADV_TYPE_PUBLIC_TGT_ADDR) > 0 ||
findAdvField(BLE_HS_ADV_TYPE_RANDOM_TGT_ADDR) > 0;
}
/**
* @brief Does this advertisement have a name value?
* @return True if there is a name value present.
*/
bool NimBLEAdvertisedDevice::haveName() {
return findAdvField(BLE_HS_ADV_TYPE_COMP_NAME) > 0 ||
findAdvField(BLE_HS_ADV_TYPE_INCOMP_NAME) > 0;
} // 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_rssi != -9999;
} // haveRSSI
/**
* @brief Does this advertisement have a service data value?
* @return True if there is a service data value present.
*/
bool NimBLEAdvertisedDevice::haveServiceData() {
return getServiceDataCount() > 0;
} // haveServiceData
/**
* @brief Does this advertisement have a service UUID value?
* @return True if there is a service UUID value present.
*/
bool NimBLEAdvertisedDevice::haveServiceUUID() {
return getServiceUUIDCount() > 0;
} // haveServiceUUID
/**
* @brief Does this advertisement have a transmission power value?
* @return True if there is a transmission power value present.
*/
bool NimBLEAdvertisedDevice::haveTXPower() {
return findAdvField(BLE_HS_ADV_TYPE_TX_PWR_LVL) > 0;
} // haveTXPower
#if CONFIG_BT_NIMBLE_EXT_ADV
/**
* @brief Get the set ID of the extended advertisement.
* @return The set ID.
*/
uint8_t NimBLEAdvertisedDevice::getSetId() {
return m_sid;
} // getSetId
/**
* @brief Get the primary PHY used by this advertisement.
* @return The PHY type, one of:
* * BLE_HCI_LE_PHY_1M
* * BLE_HCI_LE_PHY_CODED
*/
uint8_t NimBLEAdvertisedDevice::getPrimaryPhy() {
return m_primPhy;
} // getPrimaryPhy
/**
* @brief Get the primary PHY used by this advertisement.
* @return The PHY type, one of:
* * BLE_HCI_LE_PHY_1M
* * BLE_HCI_LE_PHY_2M
* * BLE_HCI_LE_PHY_CODED
*/
uint8_t NimBLEAdvertisedDevice::getSecondaryPhy() {
return m_secPhy;
} // getSecondaryPhy
/**
* @brief Get the periodic interval of the advertisement.
* @return The periodic advertising interval, 0 if not periodic advertising.
*/
uint16_t NimBLEAdvertisedDevice::getPeriodicInterval() {
return m_periodicItvl;
} // getPeriodicInterval
#endif
uint8_t NimBLEAdvertisedDevice::findAdvField(uint8_t type, uint8_t index, size_t * data_loc) {
ble_hs_adv_field *field = nullptr;
size_t length = m_payload.size();
size_t data = 0;
uint8_t count = 0;
if (length < 3) {
return count;
}
while (length > 2) {
field = (ble_hs_adv_field*)&m_payload[data];
if (field->length >= length) {
return count;
}
if (field->type == type) {
switch (type) {
case BLE_HS_ADV_TYPE_INCOMP_UUIDS16:
case BLE_HS_ADV_TYPE_COMP_UUIDS16:
count += field->length / 2;
break;
case BLE_HS_ADV_TYPE_INCOMP_UUIDS32:
case BLE_HS_ADV_TYPE_COMP_UUIDS32:
count += field->length / 4;
break;
case BLE_HS_ADV_TYPE_INCOMP_UUIDS128:
case BLE_HS_ADV_TYPE_COMP_UUIDS128:
count += field->length / 16;
break;
case BLE_HS_ADV_TYPE_PUBLIC_TGT_ADDR:
case BLE_HS_ADV_TYPE_RANDOM_TGT_ADDR:
count += field->length / 6;
break;
default:
count++;
break;
}
if (data_loc != nullptr) {
if (index == 0 || count >= index) {
break;
}
}
}
length -= 1 + field->length;
data += 1 + field->length;
}
if (data_loc != nullptr && field != nullptr) {
*data_loc = data;
}
return count;
}
/**
* @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] advType The advertisement flag data from the advertisement.
*/
void NimBLEAdvertisedDevice::setAdvType(uint8_t advType, bool isLegacyAdv) {
m_advType = advType;
#if CONFIG_BT_NIMBLE_EXT_ADV
m_isLegacyAdv = isLegacyAdv;
#else
(void)isLegacyAdv;
#endif
} // setAdvType
/**
* @brief Set the RSSI for this device.
* @param [in] rssi The RSSI of the discovered device.
*/
void NimBLEAdvertisedDevice::setRSSI(int rssi) {
m_rssi = rssi;
} // setRSSI
/**
* @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[5];
snprintf(val, sizeof(val), "%d", getTXPower());
res += ", txPower: ";
res += val;
}
if (haveServiceData()) {
uint8_t count = getServiceDataCount();
res += "\nService Data:";
for(uint8_t i = 0; i < count; i++) {
res += "\nUUID: " + std::string(getServiceDataUUID(i));
res += ", Data: " + getServiceData(i);
}
}
return res;
} // toString
/**
* @brief Get the payload advertised by the device.
* @return The advertisement payload.
*/
uint8_t* NimBLEAdvertisedDevice::getPayload() {
return &m_payload[0];
} // getPayload
/**
* @brief Stores the payload of the advertised device in a vector.
* @param [in] payload The advertisement payload.
* @param [in] length The length of the payload in bytes.
* @param [in] append Indicates if the the data should be appended (scan response).
*/
void NimBLEAdvertisedDevice::setPayload(const uint8_t *payload, uint8_t length, bool append) {
if(!append) {
m_advLength = length;
m_payload.assign(payload, payload + length);
} else {
m_payload.insert(m_payload.end(), payload, payload + length);
}
}
/**
* @brief Get the length of the advertisement data in the payload.
* @return The number of bytes in the payload that is from the advertisement.
*/
uint8_t NimBLEAdvertisedDevice::getAdvLength() {
return m_advLength;
}
/**
* @brief Get the advertised device address type.
* @return The device address type:
* * BLE_ADDR_PUBLIC (0x00)
* * BLE_ADDR_RANDOM (0x01)
* * BLE_ADDR_PUBLIC_ID (0x02)
* * BLE_ADDR_RANDOM_ID (0x03)
*/
uint8_t NimBLEAdvertisedDevice::getAddressType() {
return m_address.getType();
} // getAddressType
/**
* @brief Get the timeStamp of when the device last advertised.
* @return The timeStamp of when the device was last seen.
*/
time_t NimBLEAdvertisedDevice::getTimestamp() {
return m_timestamp;
} // getTimestamp
/**
* @brief Get the length of the payload advertised by the device.
* @return The size of the payload in bytes.
*/
size_t NimBLEAdvertisedDevice::getPayloadLength() {
return m_payload.size();
} // getPayloadLength
/**
* @brief Check if this device is advertising as connectable.
* @return True if the device is connectable.
*/
bool NimBLEAdvertisedDevice::isConnectable() {
#if CONFIG_BT_NIMBLE_EXT_ADV
if (m_isLegacyAdv) {
return m_advType == BLE_HCI_ADV_RPT_EVTYPE_ADV_IND ||
m_advType == BLE_HCI_ADV_RPT_EVTYPE_DIR_IND;
}
#endif
return (m_advType & BLE_HCI_ADV_CONN_MASK) ||
(m_advType & BLE_HCI_ADV_DIRECT_MASK);
} // isConnectable
/**
* @brief Check if this advertisement is a legacy or extended type
* @return True if legacy (Bluetooth 4.x), false if extended (bluetooth 5.x).
*/
bool NimBLEAdvertisedDevice::isLegacyAdvertisement() {
#if CONFIG_BT_NIMBLE_EXT_ADV
return m_isLegacyAdv;
# else
return true;
#endif
} // isLegacyAdvertisement
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */

View File

@@ -0,0 +1,183 @@
/*
* 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 "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
#include "NimBLEAddress.h"
#include "NimBLEScan.h"
#include "NimBLEUUID.h"
#if defined(CONFIG_NIMBLE_CPP_IDF)
#include "host/ble_hs_adv.h"
#else
#include "nimble/nimble/host/include/host/ble_hs_adv.h"
#endif
#include <map>
#include <vector>
#include <time.h>
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();
uint8_t getAdvType();
uint8_t getAdvFlags();
uint16_t getAppearance();
uint16_t getAdvInterval();
uint16_t getMinInterval();
uint16_t getMaxInterval();
uint8_t getManufacturerDataCount();
std::string getManufacturerData(uint8_t index = 0);
std::string getURI();
std::string getPayloadByType(uint16_t type);
/**
* @brief A template to convert the service data to <type\>.
* @tparam T The type to convert the data to.
* @param [in] skipSizeCheck If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is
* less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>getManufacturerData<type>(skipSizeCheck);</tt>
*/
template<typename T>
T getManufacturerData(bool skipSizeCheck = false) {
std::string data = getManufacturerData();
if(!skipSizeCheck && data.size() < sizeof(T)) return T();
const char *pData = data.data();
return *((T *)pData);
}
std::string getName();
int getRSSI();
NimBLEScan* getScan();
uint8_t getServiceDataCount();
std::string getServiceData(uint8_t index = 0);
std::string getServiceData(const NimBLEUUID &uuid);
/**
* @brief A template to convert the service data to <tt><type\></tt>.
* @tparam T The type to convert the data to.
* @param [in] index The vector index of the service data requested.
* @param [in] skipSizeCheck If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is
* less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>getServiceData<type>(skipSizeCheck);</tt>
*/
template<typename T>
T getServiceData(uint8_t index = 0, bool skipSizeCheck = false) {
std::string data = getServiceData(index);
if(!skipSizeCheck && data.size() < sizeof(T)) return T();
const char *pData = data.data();
return *((T *)pData);
}
/**
* @brief A template to convert the service data to <tt><type\></tt>.
* @tparam T The type to convert the data to.
* @param [in] uuid The uuid of the service data requested.
* @param [in] skipSizeCheck If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is
* less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>getServiceData<type>(skipSizeCheck);</tt>
*/
template<typename T>
T getServiceData(const NimBLEUUID &uuid, bool skipSizeCheck = false) {
std::string data = getServiceData(uuid);
if(!skipSizeCheck && data.size() < sizeof(T)) return T();
const char *pData = data.data();
return *((T *)pData);
}
NimBLEUUID getServiceDataUUID(uint8_t index = 0);
NimBLEUUID getServiceUUID(uint8_t index = 0);
uint8_t getServiceUUIDCount();
NimBLEAddress getTargetAddress(uint8_t index = 0);
uint8_t getTargetAddressCount();
int8_t getTXPower();
uint8_t* getPayload();
uint8_t getAdvLength();
size_t getPayloadLength();
uint8_t getAddressType();
time_t getTimestamp();
bool isAdvertisingService(const NimBLEUUID &uuid);
bool haveAppearance();
bool haveManufacturerData();
bool haveName();
bool haveRSSI();
bool haveServiceData();
bool haveServiceUUID();
bool haveTXPower();
bool haveConnParams();
bool haveAdvInterval();
bool haveTargetAddress();
bool haveURI();
bool haveType(uint16_t type);
std::string toString();
bool isConnectable();
bool isLegacyAdvertisement();
#if CONFIG_BT_NIMBLE_EXT_ADV
uint8_t getSetId();
uint8_t getPrimaryPhy();
uint8_t getSecondaryPhy();
uint16_t getPeriodicInterval();
#endif
private:
friend class NimBLEScan;
void setAddress(NimBLEAddress address);
void setAdvType(uint8_t advType, bool isLegacyAdv);
void setPayload(const uint8_t *payload, uint8_t length, bool append);
void setRSSI(int rssi);
#if CONFIG_BT_NIMBLE_EXT_ADV
void setSetId(uint8_t sid) { m_sid = sid; }
void setPrimaryPhy(uint8_t phy) { m_primPhy = phy; }
void setSecondaryPhy(uint8_t phy) { m_secPhy = phy; }
void setPeriodicInterval(uint16_t itvl) { m_periodicItvl = itvl; }
#endif
uint8_t findAdvField(uint8_t type, uint8_t index = 0, size_t * data_loc = nullptr);
size_t findServiceData(uint8_t index, uint8_t* bytes);
NimBLEAddress m_address = NimBLEAddress("");
uint8_t m_advType;
int m_rssi;
time_t m_timestamp;
uint8_t m_callbackSent;
uint8_t m_advLength;
#if CONFIG_BT_NIMBLE_EXT_ADV
bool m_isLegacyAdv;
uint8_t m_sid;
uint8_t m_primPhy;
uint8_t m_secPhy;
uint16_t m_periodicItvl;
#endif
std::vector<uint8_t> m_payload;
};
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_OBSERVER */
#endif /* COMPONENTS_NIMBLEADVERTISEDDEVICE_H_ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,150 @@
/*
* 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 "nimconfig.h"
#if (defined(CONFIG_BT_ENABLED) && \
defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) && \
!CONFIG_BT_NIMBLE_EXT_ADV) || defined(_DOXYGEN_)
#if defined(CONFIG_NIMBLE_CPP_IDF)
#include "host/ble_gap.h"
#else
#include "nimble/nimble/host/include/host/ble_gap.h"
#endif
/**** FIX COMPILATION ****/
#undef min
#undef max
/**************************/
#include "NimBLEUUID.h"
#include "NimBLEAddress.h"
#include <functional>
#include <vector>
/* COMPATIBILITY - DO NOT USE */
#define ESP_BLE_ADV_FLAG_LIMIT_DISC (0x01 << 0)
#define ESP_BLE_ADV_FLAG_GEN_DISC (0x01 << 1)
#define ESP_BLE_ADV_FLAG_BREDR_NOT_SPT (0x01 << 2)
#define ESP_BLE_ADV_FLAG_DMT_CONTROLLER_SPT (0x01 << 3)
#define ESP_BLE_ADV_FLAG_DMT_HOST_SPT (0x01 << 4)
#define ESP_BLE_ADV_FLAG_NON_LIMIT_DISC (0x00 )
/* ************************* */
class NimBLEAdvertising;
typedef std::function<void(NimBLEAdvertising*)> advCompleteCB_t;
/**
* @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(const NimBLEUUID &uuid);
void setCompleteServices16(const std::vector<NimBLEUUID> &v_uuid);
void setCompleteServices32(const std::vector<NimBLEUUID> &v_uuid);
void setFlags(uint8_t);
void setManufacturerData(const std::string &data);
void setManufacturerData(const std::vector<uint8_t> &data);
void setURI(const std::string &uri);
void setName(const std::string &name);
void setPartialServices(const NimBLEUUID &uuid);
void setPartialServices16(const std::vector<NimBLEUUID> &v_uuid);
void setPartialServices32(const std::vector<NimBLEUUID> &v_uuid);
void setServiceData(const NimBLEUUID &uuid, const std::string &data);
void setShortName(const std::string &name);
void addData(const std::string &data); // Add data to the payload.
void addData(char * data, size_t length);
void addTxPower();
void setPreferredParams(uint16_t min, uint16_t max);
std::string getPayload(); // Retrieve the current advert payload.
void clearData(); // Clear the advertisement data.
private:
friend class NimBLEAdvertising;
void setServices(const bool complete, const uint8_t size,
const std::vector<NimBLEUUID> &v_uuid);
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(const NimBLEUUID &serviceUUID);
void addServiceUUID(const char* serviceUUID);
void removeServiceUUID(const NimBLEUUID &serviceUUID);
bool start(uint32_t duration = 0, advCompleteCB_t advCompleteCB = nullptr, NimBLEAddress* dirAddr = nullptr);
void removeServices();
bool stop();
void setAppearance(uint16_t appearance);
void setName(const std::string &name);
void setManufacturerData(const std::string &data);
void setManufacturerData(const std::vector<uint8_t> &data);
void setURI(const std::string &uri);
void setServiceData(const NimBLEUUID &uuid, const std::string &data);
void setAdvertisementType(uint8_t adv_type);
void setMaxInterval(uint16_t maxinterval);
void setMinInterval(uint16_t mininterval);
void setAdvertisementData(NimBLEAdvertisementData& advertisementData);
void setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly);
void setScanResponseData(NimBLEAdvertisementData& advertisementData);
void setScanResponse(bool);
void setMinPreferred(uint16_t);
void setMaxPreferred(uint16_t);
void addTxPower();
void reset();
void advCompleteCB();
bool isAdvertising();
private:
friend class NimBLEDevice;
friend class NimBLEServer;
void onHostSync();
static int handleGapEvent(struct ble_gap_event *event, void *arg);
ble_hs_adv_fields m_advData;
ble_hs_adv_fields m_scanData;
ble_gap_adv_params m_advParams;
std::vector<NimBLEUUID> m_serviceUUIDs;
bool m_customAdvData;
bool m_customScanResponseData;
bool m_scanResp;
bool m_advDataSet;
advCompleteCB_t m_advCompCB{nullptr};
uint8_t m_slaveItvl[4];
uint32_t m_duration;
std::vector<uint8_t> m_svcData16;
std::vector<uint8_t> m_svcData32;
std::vector<uint8_t> m_svcData128;
std::vector<uint8_t> m_name;
std::vector<uint8_t> m_mfgData;
std::vector<uint8_t> m_uri;
};
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_BROADCASTER && !CONFIG_BT_NIMBLE_EXT_ADV */
#endif /* MAIN_BLEADVERTISING_H_ */

View File

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

View File

@@ -0,0 +1,157 @@
/*
* NimBLEBeacon2.cpp
*
* Created: on March 15 2020
* Author H2zero
*
* Originally:
*
* BLEBeacon.cpp
*
* Created on: Jan 4, 2018
* Author: kolban
*/
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <string.h>
#include <algorithm>
#include "NimBLEBeacon.h"
#include "NimBLELog.h"
#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00)>>8) + (((x)&0xFF)<<8))
static const char* LOG_TAG = "NimBLEBeacon";
/**
* @brief Construct a default beacon object.
*/
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
/**
* @brief Retrieve the data that is being advertised.
* @return The advertised data.
*/
std::string NimBLEBeacon::getData() {
return std::string((char*) &m_beaconData, sizeof(m_beaconData));
} // getData
/**
* @brief Get the major value being advertised.
* @return The major value advertised.
*/
uint16_t NimBLEBeacon::getMajor() {
return m_beaconData.major;
}
/**
* @brief Get the manufacturer ID being advertised.
* @return The manufacturer ID value advertised.
*/
uint16_t NimBLEBeacon::getManufacturerId() {
return m_beaconData.manufacturerId;
}
/**
* @brief Get the minor value being advertised.
* @return minor value advertised.
*/
uint16_t NimBLEBeacon::getMinor() {
return m_beaconData.minor;
}
/**
* @brief Get the proximity UUID being advertised.
* @return The UUID advertised.
*/
NimBLEUUID NimBLEBeacon::getProximityUUID() {
return NimBLEUUID(m_beaconData.proximityUUID, 16, true);
}
/**
* @brief Get the signal power being advertised.
* @return signal power level advertised.
*/
int8_t NimBLEBeacon::getSignalPower() {
return m_beaconData.signalPower;
}
/**
* @brief Set the raw data for the beacon record.
* @param [in] data The raw beacon data.
*/
void NimBLEBeacon::setData(const 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
/**
* @brief Set the major value.
* @param [in] major The major value.
*/
void NimBLEBeacon::setMajor(uint16_t major) {
m_beaconData.major = ENDIAN_CHANGE_U16(major);
} // setMajor
/**
* @brief Set the manufacturer ID.
* @param [in] manufacturerId The manufacturer ID value.
*/
void NimBLEBeacon::setManufacturerId(uint16_t manufacturerId) {
m_beaconData.manufacturerId = ENDIAN_CHANGE_U16(manufacturerId);
} // setManufacturerId
/**
* @brief Set the minor value.
* @param [in] minor The minor value.
*/
void NimBLEBeacon::setMinor(uint16_t minor) {
m_beaconData.minor = ENDIAN_CHANGE_U16(minor);
} // setMinor
/**
* @brief Set the proximity UUID.
* @param [in] uuid The proximity UUID.
*/
void NimBLEBeacon::setProximityUUID(const NimBLEUUID &uuid) {
NimBLEUUID temp_uuid = uuid;
temp_uuid.to128();
std::reverse_copy(temp_uuid.getNative()->u128.value,
temp_uuid.getNative()->u128.value + 16,
m_beaconData.proximityUUID);
} // setProximityUUID
/**
* @brief Set the signal power.
* @param [in] signalPower The signal power value.
*/
void NimBLEBeacon::setSignalPower(int8_t signalPower) {
m_beaconData.signalPower = signalPower;
} // setSignalPower
#endif

View File

@@ -0,0 +1,51 @@
/*
* 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(const std::string &data);
void setMajor(uint16_t major);
void setMinor(uint16_t minor);
void setManufacturerId(uint16_t manufacturerId);
void setProximityUUID(const NimBLEUUID &uuid);
void setSignalPower(int8_t signalPower);
}; // NimBLEBeacon
#endif /* MAIN_NIMBLEBEACON_H_ */

View File

@@ -0,0 +1,657 @@
/*
* NimBLECharacteristic.cpp
*
* Created: on March 3, 2020
* Author H2zero
*
* BLECharacteristic.cpp
*
* Created on: Jun 22, 2017
* Author: kolban
*/
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#include "NimBLECharacteristic.h"
#include "NimBLE2904.h"
#include "NimBLEDevice.h"
#include "NimBLELog.h"
#define NULL_HANDLE (0xffff)
#define NIMBLE_SUB_NOTIFY 0x0001
#define NIMBLE_SUB_INDICATE 0x0002
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.
* @param [in] max_len - The maximum length in bytes that the characteristic value can hold. (Default: 512 bytes for esp32, 20 for all others).
* @param [in] pService - pointer to the service instance this characteristic belongs to.
*/
NimBLECharacteristic::NimBLECharacteristic(const char* uuid, uint16_t properties,
uint16_t max_len, NimBLEService* pService)
: NimBLECharacteristic(NimBLEUUID(uuid), properties, max_len, pService) {
}
/**
* @brief Construct a characteristic
* @param [in] uuid - UUID for the characteristic.
* @param [in] properties - Properties for the characteristic.
* @param [in] max_len - The maximum length in bytes that the characteristic value can hold. (Default: 512 bytes for esp32, 20 for all others).
* @param [in] pService - pointer to the service instance this characteristic belongs to.
*/
NimBLECharacteristic::NimBLECharacteristic(const NimBLEUUID &uuid, uint16_t properties,
uint16_t max_len, NimBLEService* pService)
: m_value(std::min(CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH , (int)max_len), max_len) {
m_uuid = uuid;
m_handle = NULL_HANDLE;
m_properties = properties;
m_pCallbacks = &defaultCallback;
m_pService = pService;
m_removed = 0;
} // NimBLECharacteristic
/**
* @brief Destructor.
*/
NimBLECharacteristic::~NimBLECharacteristic() {
for(auto &it : m_dscVec) {
delete it;
}
} // ~NimBLECharacteristic
/**
* @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.
* @param [in] max_len - The max length in bytes of the descriptor value.
* @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.
* @param [in] max_len - The max length in bytes of the descriptor value.
* @return The new BLE descriptor.
*/
NimBLEDescriptor* NimBLECharacteristic::createDescriptor(const NimBLEUUID &uuid, uint32_t properties, uint16_t max_len) {
NimBLEDescriptor* pDescriptor = nullptr;
if(uuid == NimBLEUUID(uint16_t(0x2902))) {
assert(0 && "0x2902 descriptors cannot be manually created");
} else if (uuid == NimBLEUUID(uint16_t(0x2904))) {
pDescriptor = new NimBLE2904(this);
} else {
pDescriptor = new NimBLEDescriptor(uuid, properties, max_len, this);
}
addDescriptor(pDescriptor);
return pDescriptor;
} // createDescriptor
/**
* @brief Add a descriptor to the characteristic.
* @param [in] pDescriptor A pointer to the descriptor to add.
*/
void NimBLECharacteristic::addDescriptor(NimBLEDescriptor *pDescriptor) {
bool foundRemoved = false;
if(pDescriptor->m_removed > 0) {
for(auto& it : m_dscVec) {
if(it == pDescriptor) {
foundRemoved = true;
pDescriptor->m_removed = 0;
}
}
}
if(!foundRemoved) {
m_dscVec.push_back(pDescriptor);
}
pDescriptor->setCharacteristic(this);
NimBLEDevice::getServer()->serviceChanged();
}
/**
* @brief Remove a descriptor from the characteristic.
* @param[in] pDescriptor A pointer to the descriptor instance to remove from the characteristic.
* @param[in] deleteDsc If true it will delete the descriptor instance and free it's resources.
*/
void NimBLECharacteristic::removeDescriptor(NimBLEDescriptor *pDescriptor, bool deleteDsc) {
// Check if the descriptor was already removed and if so, check if this
// is being called to delete the object and do so if requested.
// Otherwise, ignore the call and return.
if(pDescriptor->m_removed > 0) {
if(deleteDsc) {
for(auto it = m_dscVec.begin(); it != m_dscVec.end(); ++it) {
if ((*it) == pDescriptor) {
delete *it;
m_dscVec.erase(it);
break;
}
}
}
return;
}
pDescriptor->m_removed = deleteDsc ? NIMBLE_ATT_REMOVE_DELETE : NIMBLE_ATT_REMOVE_HIDE;
NimBLEDevice::getServer()->serviceChanged();
} // removeDescriptor
/**
* @brief Return the BLE Descriptor for the given UUID.
* @param [in] uuid The UUID of the descriptor.
* @return A pointer to the descriptor object or nullptr if not found.
*/
NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const char* uuid) {
return getDescriptorByUUID(NimBLEUUID(uuid));
} // getDescriptorByUUID
/**
* @brief Return the BLE Descriptor for the given UUID.
* @param [in] uuid The UUID of the descriptor.
* @return A pointer to the descriptor object or nullptr if not found.
*/
NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const NimBLEUUID &uuid) {
for (auto &it : m_dscVec) {
if (it->getUUID() == uuid) {
return it;
}
}
return nullptr;
} // getDescriptorByUUID
/**
* @brief Return the BLE Descriptor for the given handle.
* @param [in] handle The handle of the descriptor.
* @return A pointer to the descriptor object or nullptr if not found.
*/
NimBLEDescriptor *NimBLECharacteristic::getDescriptorByHandle(uint16_t handle) {
for (auto &it : m_dscVec) {
if (it->getHandle() == handle) {
return it;
}
}
return nullptr;
}
/**
* @brief Get the handle of the characteristic.
* @return The handle of the characteristic.
*/
uint16_t NimBLECharacteristic::getHandle() {
return m_handle;
} // getHandle
/**
* @brief Get the properties of the characteristic.
* @return The properties of the characteristic.
*/
uint16_t NimBLECharacteristic::getProperties() {
return m_properties;
} // getProperties
/**
* @brief Get the service associated with this characteristic.
*/
NimBLEService* NimBLECharacteristic::getService() {
return m_pService;
} // getService
void NimBLECharacteristic::setService(NimBLEService *pService) {
m_pService = pService;
}
/**
* @brief Get the UUID of the characteristic.
* @return The UUID of the characteristic.
*/
NimBLEUUID NimBLECharacteristic::getUUID() {
return m_uuid;
} // getUUID
/**
* @brief Retrieve the current value of the characteristic.
* @return The NimBLEAttValue containing the current characteristic value.
*/
NimBLEAttValue NimBLECharacteristic::getValue(time_t *timestamp) {
if(timestamp != nullptr) {
m_value.getValue(timestamp);
}
return m_value;
} // getValue
/**
* @brief Retrieve the the current data length of the characteristic.
* @return The length of the current characteristic data.
*/
size_t NimBLECharacteristic::getDataLength() {
return m_value.size();
}
/**
* @brief STATIC callback to handle events from the NimBLE stack.
*/
int NimBLECharacteristic::handleGapEvent(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt,
void *arg)
{
if (conn_handle > BLE_HCI_LE_CONN_HANDLE_MAX)
{
NIMBLE_LOGW(LOG_TAG, "Conn_handle (%d) is above the maximum value (%d)", conn_handle, BLE_HCI_LE_CONN_HANDLE_MAX);
return BLE_ATT_ERR_INVALID_HANDLE;
}
const ble_uuid_t *uuid;
int rc;
NimBLEConnInfo peerInfo;
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: {
ble_gap_conn_find(conn_handle, &peerInfo.m_desc);
// If the packet header is only 8 bytes this is a follow up of a long read
// so we don't want to call the onRead() callback again.
if(ctxt->om->om_pkthdr_len > 8 ||
pCharacteristic->m_value.size() <= (ble_att_mtu(peerInfo.m_desc.conn_handle) - 3)) {
pCharacteristic->m_pCallbacks->onRead(pCharacteristic, peerInfo);
}
ble_npl_hw_enter_critical();
rc = os_mbuf_append(ctxt->om, pCharacteristic->m_value.data(), pCharacteristic->m_value.size());
ble_npl_hw_exit_critical(0);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
case BLE_GATT_ACCESS_OP_WRITE_CHR: {
uint16_t att_max_len = pCharacteristic->m_value.max_size();
if (ctxt->om->om_len > att_max_len) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
uint8_t buf[att_max_len];
size_t len = ctxt->om->om_len;
memcpy(buf, ctxt->om->om_data,len);
os_mbuf *next;
next = SLIST_NEXT(ctxt->om, om_next);
while(next != NULL){
if((len + next->om_len) > att_max_len) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
memcpy(&buf[len], next->om_data, next->om_len);
len += next->om_len;
next = SLIST_NEXT(next, om_next);
}
ble_gap_conn_find(conn_handle, &peerInfo.m_desc);
pCharacteristic->setValue(buf, len);
pCharacteristic->m_pCallbacks->onWrite(pCharacteristic, peerInfo);
return 0;
}
default:
break;
}
}
return BLE_ATT_ERR_UNLIKELY;
}
/**
* @brief Get the number of clients subscribed to the characteristic.
* @returns Number of clients subscribed to notifications / indications.
*/
size_t NimBLECharacteristic::getSubscribedCount() {
return m_subscribedVec.size();
}
/**
* @brief Set the subscribe status for this characteristic.\n
* This will maintain a vector of subscribed clients and their indicate/notify status.
*/
void NimBLECharacteristic::setSubscribe(struct ble_gap_event *event) {
NimBLEConnInfo peerInfo;
if(ble_gap_conn_find(event->subscribe.conn_handle, &peerInfo.m_desc) != 0) {
return;
}
uint16_t subVal = 0;
if(event->subscribe.cur_notify > 0 && (m_properties & NIMBLE_PROPERTY::NOTIFY)) {
subVal |= NIMBLE_SUB_NOTIFY;
}
if(event->subscribe.cur_indicate && (m_properties & NIMBLE_PROPERTY::INDICATE)) {
subVal |= NIMBLE_SUB_INDICATE;
}
NIMBLE_LOGI(LOG_TAG, "New subscribe value for conn: %d val: %d",
event->subscribe.conn_handle, subVal);
if(!event->subscribe.cur_indicate && event->subscribe.prev_indicate) {
NimBLEDevice::getServer()->clearIndicateWait(event->subscribe.conn_handle);
}
auto it = m_subscribedVec.begin();
for(;it != m_subscribedVec.end(); ++it) {
if((*it).first == event->subscribe.conn_handle) {
break;
}
}
if(subVal > 0) {
if(it == m_subscribedVec.end()) {
m_subscribedVec.push_back({event->subscribe.conn_handle, subVal});
} else {
(*it).second = subVal;
}
} else if(it != m_subscribedVec.end()) {
m_subscribedVec.erase(it);
}
m_pCallbacks->onSubscribe(this, peerInfo, subVal);
}
/**
* @brief Send an indication.
*/
void NimBLECharacteristic::indicate() {
notify(false);
} // indicate
/**
* @brief Send an indication.
* @param[in] value A pointer to the data to send.
* @param[in] length The length of the data to send.
*/
void NimBLECharacteristic::indicate(const uint8_t* value, size_t length) {
notify(value, length, false);
} // indicate
/**
* @brief Send an indication.
* @param[in] value A std::vector<uint8_t> containing the value to send as the notification value.
*/
void NimBLECharacteristic::indicate(const std::vector<uint8_t>& value) {
notify(value.data(), value.size(), false);
} // indicate
/**
* @brief Send a notification or indication.
* @param[in] is_notification if true sends a notification, false sends an indication.
* @param[in] conn_handle Connection handle to send individual notification, or BLE_HCI_LE_CONN_HANDLE_MAX + 1 to send notification to all subscribed clients.
*/
void NimBLECharacteristic::notify(bool is_notification, uint16_t conn_handle) {
notify(m_value.data(), m_value.length(), is_notification, conn_handle);
} // notify
/**
* @brief Send a notification or indication.
* @param[in] value A std::vector<uint8_t> containing the value to send as the notification value.
* @param[in] is_notification if true sends a notification, false sends an indication.
* @param[in] conn_handle Connection handle to send individual notification, or BLE_HCI_LE_CONN_HANDLE_MAX + 1 to send notification to all subscribed clients.
*/
void NimBLECharacteristic::notify(const std::vector<uint8_t>& value, bool is_notification, uint16_t conn_handle) {
notify(value.data(), value.size(), is_notification, conn_handle);
} // notify
/**
* @brief Send a notification or indication.
* @param[in] value A pointer to the data to send.
* @param[in] length The length of the data to send.
* @param[in] is_notification if true sends a notification, false sends an indication.
* @param[in] conn_handle Connection handle to send individual notification, or BLE_HCI_LE_CONN_HANDLE_MAX + 1 to send notification to all subscribed clients.
*/
void NimBLECharacteristic::notify(const uint8_t* value, size_t length, bool is_notification, uint16_t conn_handle) {
NIMBLE_LOGD(LOG_TAG, ">> notify: length: %d", length);
if(!(m_properties & NIMBLE_PROPERTY::NOTIFY) &&
!(m_properties & NIMBLE_PROPERTY::INDICATE))
{
NIMBLE_LOGE(LOG_TAG,
"<< notify-Error; Notify/indicate not enabled for characteristic: %s",
std::string(getUUID()).c_str());
}
if (m_subscribedVec.size() == 0) {
NIMBLE_LOGD(LOG_TAG, "<< notify: No clients subscribed.");
return;
}
m_pCallbacks->onNotify(this);
bool reqSec = (m_properties & BLE_GATT_CHR_F_READ_AUTHEN) ||
(m_properties & BLE_GATT_CHR_F_READ_AUTHOR) ||
(m_properties & BLE_GATT_CHR_F_READ_ENC);
int rc = 0;
for (auto &it : m_subscribedVec) {
// check if need a specific client
if ((conn_handle <= BLE_HCI_LE_CONN_HANDLE_MAX) && (it.first != conn_handle)) {
continue;
}
uint16_t _mtu = getService()->getServer()->getPeerMTU(it.first) - 3;
// check if connected and subscribed
if(_mtu == 0 || it.second == 0) {
continue;
}
// check if security requirements are satisfied
if(reqSec) {
struct ble_gap_conn_desc desc;
rc = ble_gap_conn_find(it.first, &desc);
if(rc != 0 || !desc.sec_state.encrypted) {
continue;
}
}
if (length > _mtu) {
NIMBLE_LOGW(LOG_TAG, "- Truncating to %d bytes (maximum notify size)", _mtu);
}
if(is_notification && (!(it.second & NIMBLE_SUB_NOTIFY))) {
NIMBLE_LOGW(LOG_TAG,
"Sending notification to client subscribed to indications, sending indication instead");
is_notification = false;
}
if(!is_notification && (!(it.second & NIMBLE_SUB_INDICATE))) {
NIMBLE_LOGW(LOG_TAG,
"Sending indication to client subscribed to notification, sending notification instead");
is_notification = true;
}
// don't create the m_buf until we are sure to send the data or else
// we could be allocating a buffer that doesn't get released.
// We also must create it in each loop iteration because it is consumed with each host call.
os_mbuf *om = ble_hs_mbuf_from_flat(value, length);
if(!is_notification && (m_properties & NIMBLE_PROPERTY::INDICATE)) {
if(!NimBLEDevice::getServer()->setIndicateWait(it.first)) {
NIMBLE_LOGE(LOG_TAG, "prior Indication in progress");
os_mbuf_free_chain(om);
return;
}
rc = ble_gattc_indicate_custom(it.first, m_handle, om);
if(rc != 0){
NimBLEDevice::getServer()->clearIndicateWait(it.first);
}
} else {
ble_gattc_notify_custom(it.first, m_handle, om);
}
}
NIMBLE_LOGD(LOG_TAG, "<< notify");
} // Notify
/**
* @brief Set the callback handlers for this characteristic.
* @param [in] pCallbacks An instance of a NimBLECharacteristicCallbacks class\n
* used to define any callbacks for the characteristic.
*/
void NimBLECharacteristic::setCallbacks(NimBLECharacteristicCallbacks* pCallbacks) {
if (pCallbacks != nullptr){
m_pCallbacks = pCallbacks;
} else {
m_pCallbacks = &defaultCallback;
}
} // setCallbacks
/**
* @brief Get the callback handlers for this characteristic.
*/
NimBLECharacteristicCallbacks* NimBLECharacteristic::getCallbacks() {
return m_pCallbacks;
} //getCallbacks
/**
* @brief Set the value of the characteristic from a data buffer .
* @param [in] data The data buffer to set for the characteristic.
* @param [in] length The number of bytes in the data buffer.
*/
void NimBLECharacteristic::setValue(const uint8_t* data, size_t length) {
#if CONFIG_NIMBLE_CPP_LOG_LEVEL >= 4
char* pHex = NimBLEUtils::buildHexData(nullptr, data, length);
NIMBLE_LOGD(LOG_TAG, ">> setValue: length=%d, data=%s, characteristic UUID=%s",
length, pHex, getUUID().toString().c_str());
free(pHex);
#endif
m_value.setValue(data, length);
NIMBLE_LOGD(LOG_TAG, "<< setValue");
} // setValue
/**
* @brief Set the value of the characteristic from a `std::vector<uint8_t>`.\n
* @param [in] vec The std::vector<uint8_t> reference to set the characteristic value from.
*/
void NimBLECharacteristic::setValue(const std::vector<uint8_t>& vec) {
return setValue((uint8_t*)&vec[0], vec.size());
}// setValue
/**
* @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
/**
* @brief Callback function to support a read request.
* @param [in] pCharacteristic The characteristic that is the source of the event.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
*/
void NimBLECharacteristicCallbacks::onRead(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) {
NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onRead: default");
} // onRead
/**
* @brief Callback function to support a write request.
* @param [in] pCharacteristic The characteristic that is the source of the event.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
*/
void NimBLECharacteristicCallbacks::onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) {
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] code Status return code from the NimBLE stack.
* @details The status code for success is 0 for notifications and BLE_HS_EDONE for indications,
* any other value is an error.
*/
void NimBLECharacteristicCallbacks::onStatus(NimBLECharacteristic* pCharacteristic, int code) {
NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onStatus: default");
} // onStatus
/**
* @brief Callback function called when a client changes subscription status.
* @param [in] pCharacteristic The characteristic that is the source of the event.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
* @param [in] subValue The subscription status:
* * 0 = Un-Subscribed
* * 1 = Notifications
* * 2 = Indications
* * 3 = Notifications and Indications
*/
void NimBLECharacteristicCallbacks::onSubscribe(NimBLECharacteristic* pCharacteristic,
NimBLEConnInfo& connInfo,
uint16_t subValue)
{
NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onSubscribe: default");
}
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */

View File

@@ -0,0 +1,213 @@
/*
* 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 "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#if defined(CONFIG_NIMBLE_CPP_IDF)
#include "host/ble_hs.h"
#else
#include "nimble/nimble/host/include/host/ble_hs.h"
#endif
/**** FIX COMPILATION ****/
#undef min
#undef max
/**************************/
typedef enum {
READ = BLE_GATT_CHR_F_READ,
READ_ENC = BLE_GATT_CHR_F_READ_ENC,
READ_AUTHEN = BLE_GATT_CHR_F_READ_AUTHEN,
READ_AUTHOR = BLE_GATT_CHR_F_READ_AUTHOR,
WRITE = BLE_GATT_CHR_F_WRITE,
WRITE_NR = BLE_GATT_CHR_F_WRITE_NO_RSP,
WRITE_ENC = BLE_GATT_CHR_F_WRITE_ENC,
WRITE_AUTHEN = BLE_GATT_CHR_F_WRITE_AUTHEN,
WRITE_AUTHOR = BLE_GATT_CHR_F_WRITE_AUTHOR,
BROADCAST = BLE_GATT_CHR_F_BROADCAST,
NOTIFY = BLE_GATT_CHR_F_NOTIFY,
INDICATE = BLE_GATT_CHR_F_INDICATE
} NIMBLE_PROPERTY;
#include "NimBLEService.h"
#include "NimBLEDescriptor.h"
#include "NimBLEAttValue.h"
#include "NimBLEConnInfo.h"
#include <string>
#include <vector>
class NimBLEService;
class NimBLEDescriptor;
class NimBLECharacteristicCallbacks;
/**
* @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:
NimBLECharacteristic(const char* uuid,
uint16_t properties =
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN,
NimBLEService* pService = nullptr);
NimBLECharacteristic(const NimBLEUUID &uuid,
uint16_t properties =
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN,
NimBLEService* pService = nullptr);
~NimBLECharacteristic();
uint16_t getHandle();
NimBLEUUID getUUID();
std::string toString();
void indicate();
void indicate(const uint8_t* value, size_t length);
void indicate(const std::vector<uint8_t>& value);
void notify(bool is_notification = true, uint16_t conn_handle = BLE_HCI_LE_CONN_HANDLE_MAX + 1);
void notify(const uint8_t* value, size_t length, bool is_notification = true, uint16_t conn_handle = BLE_HCI_LE_CONN_HANDLE_MAX + 1);
void notify(const std::vector<uint8_t>& value, bool is_notification = true, uint16_t conn_handle = BLE_HCI_LE_CONN_HANDLE_MAX + 1);
size_t getSubscribedCount();
void addDescriptor(NimBLEDescriptor *pDescriptor);
NimBLEDescriptor* getDescriptorByUUID(const char* uuid);
NimBLEDescriptor* getDescriptorByUUID(const NimBLEUUID &uuid);
NimBLEDescriptor* getDescriptorByHandle(uint16_t handle);
void removeDescriptor(NimBLEDescriptor *pDescriptor, bool deleteDsc = false);
NimBLEService* getService();
uint16_t getProperties();
NimBLEAttValue getValue(time_t *timestamp = nullptr);
size_t getDataLength();
void setValue(const uint8_t* data, size_t size);
void setValue(const std::vector<uint8_t>& vec);
void setCallbacks(NimBLECharacteristicCallbacks* pCallbacks);
NimBLEDescriptor* createDescriptor(const char* uuid,
uint32_t properties =
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);;
NimBLEDescriptor* createDescriptor(const NimBLEUUID &uuid,
uint32_t properties =
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);
NimBLECharacteristicCallbacks* getCallbacks();
/*********************** Template Functions ************************/
/**
* @brief Template to set the characteristic value to <type\>val.
* @param [in] s The value to set.
*/
template<typename T>
void setValue(const T &s) { m_value.setValue<T>(s); }
/**
* @brief Template to convert the characteristic data to <type\>.
* @tparam T The type to convert the data to.
* @param [in] timestamp (Optional) A pointer to a time_t struct to store the time the value was read.
* @param [in] skipSizeCheck (Optional) If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>getValue<type>(&timestamp, skipSizeCheck);</tt>
*/
template<typename T>
T getValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) {
return m_value.getValue<T>(timestamp, skipSizeCheck);
}
/**
* @brief Template to send a notification from a class type that has a c_str() and length() method.
* @tparam T The a reference to a class containing the data to send.
* @param[in] value The <type\>value to set.
* @param[in] is_notification if true sends a notification, false sends an indication.
* @details Only used if the <type\> has a `c_str()` method.
*/
template<typename T>
#ifdef _DOXYGEN_
void
#else
typename std::enable_if<Has_c_str_len<T>::value, void>::type
#endif
notify(const T& value, bool is_notification = true) {
notify((uint8_t*)value.c_str(), value.length(), is_notification);
}
/**
* @brief Template to send an indication from a class type that has a c_str() and length() method.
* @tparam T The a reference to a class containing the data to send.
* @param[in] value The <type\>value to set.
* @details Only used if the <type\> has a `c_str()` method.
*/
template<typename T>
#ifdef _DOXYGEN_
void
#else
typename std::enable_if<Has_c_str_len<T>::value, void>::type
#endif
indicate(const T& value) {
indicate((uint8_t*)value.c_str(), value.length());
}
private:
friend class NimBLEServer;
friend class NimBLEService;
void setService(NimBLEService *pService);
void setSubscribe(struct ble_gap_event *event);
static int handleGapEvent(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg);
NimBLEUUID m_uuid;
uint16_t m_handle;
uint16_t m_properties;
NimBLECharacteristicCallbacks* m_pCallbacks;
NimBLEService* m_pService;
NimBLEAttValue m_value;
std::vector<NimBLEDescriptor*> m_dscVec;
uint8_t m_removed;
std::vector<std::pair<uint16_t, uint16_t>> m_subscribedVec;
}; // 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:
virtual ~NimBLECharacteristicCallbacks(){}
virtual void onRead(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo);
virtual void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo);
virtual void onNotify(NimBLECharacteristic* pCharacteristic);
virtual void onStatus(NimBLECharacteristic* pCharacteristic, int code);
virtual void onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue);
};
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */
#endif /*MAIN_NIMBLECHARACTERISTIC_H_*/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,176 @@
/*
* 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_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
#include "NimBLEAddress.h"
#include "NimBLEUUID.h"
#include "NimBLEUtils.h"
#include "NimBLEConnInfo.h"
#include "NimBLEAttValue.h"
#include "NimBLEAdvertisedDevice.h"
#include "NimBLERemoteService.h"
#include <vector>
#include <string>
class NimBLERemoteService;
class NimBLERemoteCharacteristic;
class NimBLEClientCallbacks;
class NimBLEAdvertisedDevice;
/**
* @brief A model of a %BLE client.
*/
class NimBLEClient {
public:
bool connect(NimBLEAdvertisedDevice* device, bool deleteAttributes = true);
bool connect(const NimBLEAddress &address, bool deleteAttributes = true);
bool connect(bool deleteAttributes = true);
int disconnect(uint8_t reason = BLE_ERR_REM_USER_CONN_TERM);
NimBLEAddress getPeerAddress();
void setPeerAddress(const NimBLEAddress &address);
int getRssi();
std::vector<NimBLERemoteService*>* getServices(bool refresh = false);
std::vector<NimBLERemoteService*>::iterator begin();
std::vector<NimBLERemoteService*>::iterator end();
NimBLERemoteService* getService(const char* uuid);
NimBLERemoteService* getService(const NimBLEUUID &uuid);
void deleteServices();
size_t deleteService(const NimBLEUUID &uuid);
NimBLEAttValue getValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID);
bool setValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID,
const NimBLEAttValue &value, bool response = false);
NimBLERemoteCharacteristic* getCharacteristic(const uint16_t handle);
bool isConnected();
void setClientCallbacks(NimBLEClientCallbacks *pClientCallbacks,
bool deleteCallbacks = true);
std::string toString();
uint16_t getConnId();
void clearConnection();
bool setConnection(NimBLEConnInfo &conn_info);
bool setConnection(uint16_t conn_id);
uint16_t getMTU();
bool secureConnection();
void setConnectTimeout(uint32_t timeout);
void setConnectionParams(uint16_t minInterval, uint16_t maxInterval,
uint16_t latency, uint16_t timeout,
uint16_t scanInterval=16, uint16_t scanWindow=16);
void updateConnParams(uint16_t minInterval, uint16_t maxInterval,
uint16_t latency, uint16_t timeout);
void setDataLen(uint16_t tx_octets);
bool discoverAttributes();
NimBLEConnInfo getConnInfo();
int getLastError();
#if CONFIG_BT_NIMBLE_EXT_ADV
void setConnectPhy(uint8_t mask);
#endif
private:
NimBLEClient(const NimBLEAddress &peerAddress);
~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);
static void dcTimerCb(ble_npl_event *event);
bool retrieveServices(const NimBLEUUID *uuid_filter = nullptr);
NimBLEAddress m_peerAddress;
int m_lastErr;
uint16_t m_conn_id;
bool m_connEstablished;
bool m_deleteCallbacks;
int32_t m_connectTimeout;
NimBLEClientCallbacks* m_pClientCallbacks;
ble_task_data_t* m_pTaskData;
ble_npl_callout m_dcTimer;
#if CONFIG_BT_NIMBLE_EXT_ADV
uint8_t m_phyMask;
#endif
std::vector<NimBLERemoteService*> m_servicesVector;
private:
friend class NimBLEClientCallbacks;
ble_gap_conn_params m_pConnParams;
}; // class NimBLEClient
/**
* @brief Callbacks associated with a %BLE client.
*/
class NimBLEClientCallbacks {
public:
virtual ~NimBLEClientCallbacks() {};
/**
* @brief Called after client connects.
* @param [in] pClient A pointer to the calling client object.
*/
virtual void onConnect(NimBLEClient* pClient);
/**
* @brief Called when disconnected from the server.
* @param [in] pClient A pointer to the calling client object.
* @param [in] reason Contains the reason code for the disconnection.
*/
virtual void onDisconnect(NimBLEClient* pClient, int reason);
/**
* @brief Called when server requests to update the connection parameters.
* @param [in] pClient A pointer to the calling client object.
* @param [in] params A pointer to the struct containing the connection parameters requested.
* @return True to accept the parameters.
*/
virtual bool onConnParamsUpdateRequest(NimBLEClient* pClient, const ble_gap_upd_params* params);
/**
* @brief Called when server requests a passkey for pairing.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
*/
virtual void onPassKeyEntry(const NimBLEConnInfo& connInfo);
/**
* @brief Called when the pairing procedure is complete.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.\n
* This can be used to check the status of the connection encryption/pairing.
*/
virtual void onAuthenticationComplete(const NimBLEConnInfo& connInfo);
/**
* @brief Called when using numeric comparision for pairing.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
* @param [in] pin The pin to compare with the server.
*/
virtual void onConfirmPIN(const NimBLEConnInfo& connInfo, uint32_t pin);
/**
* @brief Called when the peer identity address is resolved.
* @param [in] connInfo A reference to a NimBLEConnInfo instance with information
*/
virtual void onIdentity(const NimBLEConnInfo& connInfo);
};
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */
#endif /* MAIN_NIMBLECLIENT_H_ */

View File

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

View File

@@ -0,0 +1,307 @@
/*
* NimBLEDescriptor.cpp
*
* Created: on March 10, 2020
* Author H2zero
*
* Originally:
*
* BLEDescriptor.cpp
*
* Created on: Jun 22, 2017
* Author: kolban
*/
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#include "NimBLEService.h"
#include "NimBLEDescriptor.h"
#include "NimBLELog.h"
#include <string>
#define NULL_HANDLE (0xffff)
static const char* LOG_TAG = "NimBLEDescriptor";
static NimBLEDescriptorCallbacks defaultCallbacks;
/**
* @brief Construct a descriptor
* @param [in] uuid - UUID (const char*) for the descriptor.
* @param [in] properties - Properties for the descriptor.
* @param [in] max_len - The maximum length in bytes that the descriptor value can hold. (Default: 512 bytes for esp32, 20 for all others).
* @param [in] pCharacteristic - pointer to the characteristic instance this descriptor belongs to.
*/
NimBLEDescriptor::NimBLEDescriptor(const char* uuid, uint16_t properties, uint16_t max_len,
NimBLECharacteristic* pCharacteristic)
: NimBLEDescriptor(NimBLEUUID(uuid), properties, max_len, pCharacteristic) {
}
/**
* @brief Construct a descriptor
* @param [in] uuid - UUID (const char*) for the descriptor.
* @param [in] properties - Properties for the descriptor.
* @param [in] max_len - The maximum length in bytes that the descriptor value can hold. (Default: 512 bytes for esp32, 20 for all others).
* @param [in] pCharacteristic - pointer to the characteristic instance this descriptor belongs to.
*/
NimBLEDescriptor::NimBLEDescriptor(NimBLEUUID uuid, uint16_t properties, uint16_t max_len,
NimBLECharacteristic* pCharacteristic)
: m_value(std::min(CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH , (int)max_len), max_len) {
m_uuid = uuid;
m_handle = NULL_HANDLE; // Handle is initially unknown.
m_pCharacteristic = pCharacteristic;
m_pCallbacks = &defaultCallbacks; // No initial callback.
m_properties = 0;
m_removed = 0;
if (properties & BLE_GATT_CHR_F_READ) { // convert uint16_t properties to uint8_t
m_properties |= BLE_ATT_F_READ;
}
if (properties & (BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_WRITE)) {
m_properties |= BLE_ATT_F_WRITE;
}
if (properties & BLE_GATT_CHR_F_READ_ENC) {
m_properties |= BLE_ATT_F_READ_ENC;
}
if (properties & BLE_GATT_CHR_F_READ_AUTHEN) {
m_properties |= BLE_ATT_F_READ_AUTHEN;
}
if (properties & BLE_GATT_CHR_F_READ_AUTHOR) {
m_properties |= BLE_ATT_F_READ_AUTHOR;
}
if (properties & BLE_GATT_CHR_F_WRITE_ENC) {
m_properties |= BLE_ATT_F_WRITE_ENC;
}
if (properties & BLE_GATT_CHR_F_WRITE_AUTHEN) {
m_properties |= BLE_ATT_F_WRITE_AUTHEN;
}
if (properties & BLE_GATT_CHR_F_WRITE_AUTHOR) {
m_properties |= BLE_ATT_F_WRITE_AUTHOR;
}
} // NimBLEDescriptor
/**
* @brief NimBLEDescriptor destructor.
*/
NimBLEDescriptor::~NimBLEDescriptor() {
} // ~NimBLEDescriptor
/**
* @brief Get the BLE handle for this descriptor.
* @return The handle for this descriptor.
*/
uint16_t NimBLEDescriptor::getHandle() {
return m_handle;
} // getHandle
/**
* @brief Get the length of the value of this descriptor.
* @return The length (in bytes) of the value of this descriptor.
*/
size_t NimBLEDescriptor::getLength() {
return m_value.size();
} // getLength
/**
* @brief Get the UUID of the descriptor.
*/
NimBLEUUID NimBLEDescriptor::getUUID() {
return m_uuid;
} // getUUID
/**
* @brief Get the value of this descriptor.
* @return The NimBLEAttValue of this descriptor.
*/
NimBLEAttValue NimBLEDescriptor::getValue(time_t *timestamp) {
if (timestamp != nullptr) {
m_value.getValue(timestamp);
}
return m_value;
} // getValue
/**
* @brief Get the value of this descriptor as a string.
* @return A std::string instance containing a copy of the descriptor's value.
*/
std::string NimBLEDescriptor::getStringValue() {
return std::string(m_value);
}
/**
* @brief Get the characteristic this descriptor belongs to.
* @return A pointer to the characteristic this descriptor belongs to.
*/
NimBLECharacteristic* NimBLEDescriptor::getCharacteristic() {
return m_pCharacteristic;
} // getCharacteristic
int NimBLEDescriptor::handleGapEvent(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg) {
(void)conn_handle;
(void)attr_handle;
const ble_uuid_t *uuid;
int rc;
NimBLEConnInfo peerInfo;
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: {
rc = ble_gap_conn_find(conn_handle, &peerInfo.m_desc);
assert(rc == 0);
// If the packet header is only 8 bytes this is a follow up of a long read
// so we don't want to call the onRead() callback again.
if(ctxt->om->om_pkthdr_len > 8 ||
pDescriptor->m_value.size() <= (ble_att_mtu(peerInfo.getConnHandle()) - 3)) {
pDescriptor->m_pCallbacks->onRead(pDescriptor, peerInfo);
}
ble_npl_hw_enter_critical();
rc = os_mbuf_append(ctxt->om, pDescriptor->m_value.data(), pDescriptor->m_value.size());
ble_npl_hw_exit_critical(0);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
case BLE_GATT_ACCESS_OP_WRITE_DSC: {
rc = ble_gap_conn_find(conn_handle, &peerInfo.m_desc);
assert(rc == 0);
uint16_t att_max_len = pDescriptor->m_value.max_size();
if (ctxt->om->om_len > att_max_len) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
uint8_t buf[att_max_len];
size_t len = ctxt->om->om_len;
memcpy(buf, ctxt->om->om_data,len);
os_mbuf *next;
next = SLIST_NEXT(ctxt->om, om_next);
while(next != NULL){
if((len + next->om_len) > att_max_len) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
memcpy(&buf[len], next->om_data, next->om_len);
len += next->om_len;
next = SLIST_NEXT(next, om_next);
}
pDescriptor->setValue(buf, len);
pDescriptor->m_pCallbacks->onWrite(pDescriptor, peerInfo);
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(const uint8_t* data, size_t length) {
m_value.setValue(data, length);
} // setValue
/**
* @brief Set the value of the descriptor from a `std::vector<uint8_t>`.\n
* @param [in] vec The std::vector<uint8_t> reference to set the descriptor value from.
*/
void NimBLEDescriptor::setValue(const std::vector<uint8_t>& vec) {
return setValue((uint8_t*)&vec[0], vec.size());
} // setValue
/**
* @brief Set the characteristic this descriptor belongs to.
* @param [in] pChar A pointer to the characteristic this descriptor belongs to.
*/
void NimBLEDescriptor::setCharacteristic(NimBLECharacteristic* pChar) {
m_pCharacteristic = pChar;
} // setCharacteristic
/**
* @brief Return a string representation of the descriptor.
* @return A string representation of the descriptor.
*/
std::string NimBLEDescriptor::toString() {
char hex[5];
snprintf(hex, sizeof(hex), "%04x", m_handle);
std::string res = "UUID: " + m_uuid.toString() + ", handle: 0x" + hex;
return res;
} // toString
/**
* @brief Callback function to support a read request.
* @param [in] pDescriptor The descriptor that is the source of the event.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
*/
void NimBLEDescriptorCallbacks::onRead(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo) {
(void)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.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
*/
void NimBLEDescriptorCallbacks::onWrite(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo) {
(void)pDescriptor;
NIMBLE_LOGD("NimBLEDescriptorCallbacks", "onWrite: default");
} // onWrite
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */

View File

@@ -0,0 +1,120 @@
/*
* 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 "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#include "NimBLECharacteristic.h"
#include "NimBLEUUID.h"
#include "NimBLEAttValue.h"
#include "NimBLEConnInfo.h"
#include <string>
class NimBLEService;
class NimBLECharacteristic;
class NimBLEDescriptorCallbacks;
/**
* @brief A model of a %BLE descriptor.
*/
class NimBLEDescriptor {
public:
NimBLEDescriptor(const char* uuid, uint16_t properties,
uint16_t max_len,
NimBLECharacteristic* pCharacteristic = nullptr);
NimBLEDescriptor(NimBLEUUID uuid, uint16_t properties,
uint16_t max_len,
NimBLECharacteristic* pCharacteristic = nullptr);
~NimBLEDescriptor();
uint16_t getHandle();
NimBLEUUID getUUID();
std::string toString();
void setCallbacks(NimBLEDescriptorCallbacks* pCallbacks);
NimBLECharacteristic* getCharacteristic();
size_t getLength();
NimBLEAttValue getValue(time_t *timestamp = nullptr);
std::string getStringValue();
void setValue(const uint8_t* data, size_t size);
void setValue(const std::vector<uint8_t>& vec);
/*********************** Template Functions ************************/
/**
* @brief Template to set the characteristic value to <type\>val.
* @param [in] s The value to set.
*/
template<typename T>
void setValue(const T &s) { m_value.setValue<T>(s); }
/**
* @brief Template to convert the descriptor data to <type\>.
* @tparam T The type to convert the data to.
* @param [in] timestamp (Optional) A pointer to a time_t struct to store the time the value was read.
* @param [in] skipSizeCheck (Optional) If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>getValue<type>(&timestamp, skipSizeCheck);</tt>
*/
template<typename T>
T getValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) {
return m_value.getValue<T>(timestamp, skipSizeCheck);
}
private:
friend class NimBLECharacteristic;
friend class NimBLEService;
friend class NimBLE2904;
static int handleGapEvent(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg);
void setHandle(uint16_t handle);
void setCharacteristic(NimBLECharacteristic* pChar);
NimBLEUUID m_uuid;
uint16_t m_handle;
NimBLEDescriptorCallbacks* m_pCallbacks;
NimBLECharacteristic* m_pCharacteristic;
uint8_t m_properties;
NimBLEAttValue m_value;
uint8_t m_removed;
}; // NimBLEDescriptor
/**
* @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, NimBLEConnInfo& connInfo);
virtual void onWrite(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo);
};
#include "NimBLE2904.h"
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */
#endif /* MAIN_NIMBLEDESCRIPTOR_H_ */

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,227 @@
/*
* NimBLEEddystoneTLM.cpp
*
* Created: on March 15 2020
* Author H2zero
*
* Originally:
*
* BLEEddystoneTLM.cpp
*
* Created on: Mar 12, 2018
* Author: pcbreflux
*/
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEEddystoneTLM.h"
#include "NimBLELog.h"
#include <stdio.h>
#include <cstring>
#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00)>>8) + (((x)&0xFF)<<8))
#define ENDIAN_CHANGE_U32(x) ((((x)&0xFF000000)>>24) + (((x)&0x00FF0000)>>8)) + ((((x)&0xFF00)<<8) + (((x)&0xFF)<<24))
static const char LOG_TAG[] = "NimBLEEddystoneTLM";
/**
* @brief Construct a default EddystoneTLM beacon object.
*/
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
/**
* @brief Retrieve the data that is being advertised.
* @return The advertised data.
*/
std::string NimBLEEddystoneTLM::getData() {
return std::string((char*) &m_eddystoneData, sizeof(m_eddystoneData));
} // getData
/**
* @brief Get the UUID being advertised.
* @return The UUID advertised.
*/
NimBLEUUID NimBLEEddystoneTLM::getUUID() {
return NimBLEUUID(beaconUUID);
} // getUUID
/**
* @brief Get the version being advertised.
* @return The version number.
*/
uint8_t NimBLEEddystoneTLM::getVersion() {
return m_eddystoneData.version;
} // getVersion
/**
* @brief Get the battery voltage.
* @return The battery voltage.
*/
uint16_t NimBLEEddystoneTLM::getVolt() {
return ENDIAN_CHANGE_U16(m_eddystoneData.volt);
} // getVolt
/**
* @brief Get the temperature being advertised.
* @return The temperature value.
*/
float NimBLEEddystoneTLM::getTemp() {
return (int16_t)ENDIAN_CHANGE_U16(m_eddystoneData.temp) / 256.0f;
} // getTemp
/**
* @brief Get the count of advertisements sent.
* @return The number of advertisements.
*/
uint32_t NimBLEEddystoneTLM::getCount() {
return ENDIAN_CHANGE_U32(m_eddystoneData.advCount);
} // getCount
/**
* @brief Get the advertisement time.
* @return The advertisement time.
*/
uint32_t NimBLEEddystoneTLM::getTime() {
return (ENDIAN_CHANGE_U32(m_eddystoneData.tmil)) / 10;
} // getTime
/**
* @brief Get a string representation of the beacon.
* @return The string representation.
*/
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), "%" PRIu32, ENDIAN_CHANGE_U32(m_eddystoneData.advCount));
out += val;
out += "\n";
out += "Time in seconds ";
snprintf(val, sizeof(val), "%" PRIu32, rawsec/10);
out += val;
out += "\n";
out += "Time ";
snprintf(val, sizeof(val), "%04" PRIu32, rawsec / 864000);
out += val;
out += ".";
snprintf(val, sizeof(val), "%02" PRIu32, (rawsec / 36000) % 24);
out += val;
out += ":";
snprintf(val, sizeof(val), "%02" PRIu32, (rawsec / 600) % 60);
out += val;
out += ":";
snprintf(val, sizeof(val), "%02" PRIu32, (rawsec / 10) % 60);
out += val;
out += "\n";
return out;
} // toString
/**
* @brief Set the raw data for the beacon advertisement.
* @param [in] data The raw data to advertise.
*/
void NimBLEEddystoneTLM::setData(const 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
/**
* @brief Set the UUID to advertise.
* @param [in] l_uuid The UUID.
*/
void NimBLEEddystoneTLM::setUUID(const NimBLEUUID &l_uuid) {
beaconUUID = l_uuid.getNative()->u16.value;
} // setUUID
/**
* @brief Set the version to advertise.
* @param [in] version The version number.
*/
void NimBLEEddystoneTLM::setVersion(uint8_t version) {
m_eddystoneData.version = version;
} // setVersion
/**
* @brief Set the battery voltage to advertise.
* @param [in] volt The voltage in millivolts.
*/
void NimBLEEddystoneTLM::setVolt(uint16_t volt) {
m_eddystoneData.volt = volt;
} // setVolt
/**
* @brief Set the temperature to advertise.
* @param [in] temp The temperature value.
*/
void NimBLEEddystoneTLM::setTemp(float temp) {
m_eddystoneData.temp = ENDIAN_CHANGE_U16((int16_t)(temp * 256.0f));
} // setTemp
/**
* @brief Set the advertisement count.
* @param [in] advCount The advertisement number.
*/
void NimBLEEddystoneTLM::setCount(uint32_t advCount) {
m_eddystoneData.advCount = advCount;
} // setCount
/**
* @brief Set the advertisement time.
* @param [in] tmil The advertisement time in milliseconds.
*/
void NimBLEEddystoneTLM::setTime(uint32_t tmil) {
m_eddystoneData.tmil = tmil;
} // setTime
#endif

View File

@@ -0,0 +1,61 @@
/*
* NimBLEEddystoneTLM.h
*
* Created: on March 15 2020
* Author H2zero
*
* Originally:
*
* BLEEddystoneTLM.h
*
* Created on: Mar 12, 2018
* Author: pcbreflux
*/
#ifndef _NimBLEEddystoneTLM_H_
#define _NimBLEEddystoneTLM_H_
#include "NimBLEUUID.h"
#include <string>
#define EDDYSTONE_TLM_FRAME_TYPE 0x20
/**
* @brief Representation of a beacon.
* See:
* * https://github.com/google/eddystone
*/
class NimBLEEddystoneTLM {
public:
NimBLEEddystoneTLM();
std::string getData();
NimBLEUUID getUUID();
uint8_t getVersion();
uint16_t getVolt();
float getTemp();
uint32_t getCount();
uint32_t getTime();
std::string toString();
void setData(const std::string &data);
void setUUID(const 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_ */

View File

@@ -0,0 +1,204 @@
/*
* NimBLEEddystoneURL.cpp
*
* Created: on March 15 2020
* Author H2zero
*
* Originally:
*
* BLEEddystoneURL.cpp
*
* Created on: Mar 12, 2018
* Author: pcbreflux
*/
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEEddystoneURL.h"
#include "NimBLELog.h"
#include <cstring>
static const char LOG_TAG[] = "NimBLEEddystoneURL";
/**
* @brief Construct a default EddystoneURL beacon object.
*/
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
/**
* @brief Retrieve the data that is being advertised.
* @return The advertised data.
*/
std::string NimBLEEddystoneURL::getData() {
return std::string((char*) &m_eddystoneData, sizeof(m_eddystoneData));
} // getData
/**
* @brief Get the UUID being advertised.
* @return The UUID advertised.
*/
NimBLEUUID NimBLEEddystoneURL::getUUID() {
return NimBLEUUID(beaconUUID);
} // getUUID
/**
* @brief Get the transmit power being advertised.
* @return The transmit power.
*/
int8_t NimBLEEddystoneURL::getPower() {
return m_eddystoneData.advertisedTxPower;
} // getPower
/**
* @brief Get the raw URL being advertised.
* @return The raw URL.
*/
std::string NimBLEEddystoneURL::getURL() {
return std::string((char*) &m_eddystoneData.url, sizeof(m_eddystoneData.url));
} // getURL
/**
* @brief Get the full URL being advertised.
* @return The full URL.
*/
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
/**
* @brief Set the raw data for the beacon advertisement.
* @param [in] data The raw data to advertise.
*/
void NimBLEEddystoneURL::setData(const 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
/**
* @brief Set the UUID to advertise.
* @param [in] l_uuid The UUID.
*/
void NimBLEEddystoneURL::setUUID(const NimBLEUUID &l_uuid) {
beaconUUID = l_uuid.getNative()->u16.value;
} // setUUID
/**
* @brief Set the transmit power to advertise.
* @param [in] advertisedTxPower The transmit power level.
*/
void NimBLEEddystoneURL::setPower(int8_t advertisedTxPower) {
m_eddystoneData.advertisedTxPower = advertisedTxPower;
} // setPower
/**
* @brief Set the URL to advertise.
* @param [in] url The URL.
*/
void NimBLEEddystoneURL::setURL(const 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

View File

@@ -0,0 +1,52 @@
/*
* NimBLEEddystoneURL.h
*
* Created: on March 15 2020
* Author H2zero
*
* Originally:
*
* BLEEddystoneURL.h
*
* Created on: Mar 12, 2018
* Author: pcbreflux
*/
#ifndef _NIMBLEEddystoneURL_H_
#define _NIMBLEEddystoneURL_H_
#include "NimBLEUUID.h"
#include <string>
#define EDDYSTONE_URL_FRAME_TYPE 0x10
/**
* @brief Representation of a beacon.
* See:
* * https://github.com/google/eddystone
*/
class NimBLEEddystoneURL {
public:
NimBLEEddystoneURL();
std::string getData();
NimBLEUUID getUUID();
int8_t getPower();
std::string getURL();
std::string getDecodedURL();
void setData(const std::string &data);
void setUUID(const NimBLEUUID &l_uuid);
void setPower(int8_t advertisedTxPower);
void setURL(const 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_ */

View File

@@ -0,0 +1,867 @@
/*
* NimBLEExtAdvertising.cpp
*
* Created: on February 6, 2022
* Author H2zero
*/
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && \
defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) && \
CONFIG_BT_NIMBLE_EXT_ADV
#if defined(CONFIG_NIMBLE_CPP_IDF)
#include "services/gap/ble_svc_gap.h"
#else
#include "nimble/nimble/host/services/gap/include/services/gap/ble_svc_gap.h"
#endif
#include "NimBLEExtAdvertising.h"
#include "NimBLEDevice.h"
#include "NimBLEServer.h"
#include "NimBLEUtils.h"
#include "NimBLELog.h"
static NimBLEExtAdvertisingCallbacks defaultCallbacks;
static const char* LOG_TAG = "NimBLEExtAdvertising";
/**
* @brief Destructor: deletes callback instances if requested.
*/
NimBLEExtAdvertising::~NimBLEExtAdvertising() {
if(m_deleteCallbacks && m_pCallbacks != &defaultCallbacks) {
delete m_pCallbacks;
}
}
/**
* @brief Register the extended advertisement data.
* @param [in] inst_id The extended advertisement instance ID to assign to this data.
* @param [in] adv The extended advertisement instance with the data to set.
* @return True if advertising started successfully.
*/
bool NimBLEExtAdvertising::setInstanceData(uint8_t inst_id, NimBLEExtAdvertisement& adv) {
adv.m_params.sid = inst_id;
// Legacy advertising as connectable requires the scannable flag also.
if (adv.m_params.legacy_pdu && adv.m_params.connectable) {
adv.m_params.scannable = true;
}
// If connectable or not scannable disable the callback for scan response requests
if (adv.m_params.connectable || !adv.m_params.scannable) {
adv.m_params.scan_req_notif = false;
}
#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
NimBLEServer* pServer = NimBLEDevice::getServer();
if (pServer != nullptr) {
if (!pServer->m_gattsStarted) {
pServer->start();
}
}
int rc = ble_gap_ext_adv_configure(inst_id,
&adv.m_params,
NULL,
(pServer != nullptr) ? NimBLEServer::handleGapEvent :
NimBLEExtAdvertising::handleGapEvent,
NULL);
#else
int rc = ble_gap_ext_adv_configure(inst_id,
&data.m_params,
NULL,
NimBLEExtAdvertising::handleGapEvent,
NULL);
#endif
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Advertising config error: rc = %d", rc);
} else {
os_mbuf *buf;
buf = os_msys_get_pkthdr(adv.m_payload.size(), 0);
if (!buf) {
NIMBLE_LOGE(LOG_TAG, "Data buffer allocation failed");
return false;
}
rc = os_mbuf_append(buf, &adv.m_payload[0], adv.m_payload.size());
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Unable to copy data: rc = %d", rc);
return false;
} else {
if (adv.m_params.scannable && !adv.m_params.legacy_pdu) {
rc = ble_gap_ext_adv_rsp_set_data(inst_id, buf);
} else {
rc = ble_gap_ext_adv_set_data(inst_id, buf);
}
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Invalid advertisement data: rc = %d", rc);
} else {
if (adv.m_advAddress != NimBLEAddress("")) {
ble_addr_t addr;
memcpy(&addr.val, adv.m_advAddress.getNative(), 6);
// Custom advertising address must be random.
addr.type = BLE_OWN_ADDR_RANDOM;
rc = ble_gap_ext_adv_set_addr(inst_id, &addr);
}
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Error setting advertisement address: rc = %d", rc);
return false;
}
}
}
}
return (rc == 0);
}
/**
* @brief Set the scan response data for a legacy advertisement.
* @param [in] inst_id The extended advertisement instance ID to assign to this data.
* @param [in] lsr A reference to a NimBLEExtAdvertisement that contains the data.
*/
bool NimBLEExtAdvertising::setScanResponseData(uint8_t inst_id, NimBLEExtAdvertisement & lsr) {
os_mbuf *buf = os_msys_get_pkthdr(lsr.m_payload.size(), 0);
if (!buf) {
NIMBLE_LOGE(LOG_TAG, "Data buffer allocation failed");
return false;
}
int rc = os_mbuf_append(buf, &lsr.m_payload[0], lsr.m_payload.size());
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Unable to copy scan data: rc = %d", rc);
return false;
} else {
rc = ble_gap_ext_adv_rsp_set_data(inst_id, buf);
}
return (rc == 0);
}
/**
* @brief Start extended advertising.
* @param [in] inst_id The extended advertisement instance ID to start.
* @param [in] duration How long to advertise for in milliseconds, 0 = forever (default).
* @param [in] max_events Maximum number of advertisement events to send, 0 = no limit (default).
* @return True if advertising started successfully.
*/
bool NimBLEExtAdvertising::start(uint8_t inst_id, int duration, int max_events) {
NIMBLE_LOGD(LOG_TAG, ">> Extended Advertising start");
// If Host is not synced we cannot start advertising.
if(!NimBLEDevice::m_synced) {
NIMBLE_LOGE(LOG_TAG, "Host reset, wait for sync.");
return false;
}
int rc = ble_gap_ext_adv_start(inst_id, duration / 10, max_events);
switch (rc) {
case 0:
m_advStatus[inst_id] = true;
break;
case BLE_HS_EINVAL:
NIMBLE_LOGE(LOG_TAG, "Unable to advertise - Value Error");
break;
case BLE_HS_EALREADY:
NIMBLE_LOGI(LOG_TAG, "Advertisement Already active");
break;
case BLE_HS_ETIMEOUT_HCI:
case BLE_HS_EOS:
case BLE_HS_ECONTROLLER:
case BLE_HS_ENOTSYNCED:
NIMBLE_LOGE(LOG_TAG, "Unable to advertise - Host Reset");
break;
default:
NIMBLE_LOGE(LOG_TAG, "Error enabling advertising; rc=%d, %s",
rc, NimBLEUtils::returnCodeToString(rc));
break;
}
NIMBLE_LOGD(LOG_TAG, "<< Extended Advertising start");
return (rc == 0 || rc == BLE_HS_EALREADY);
} // start
/**
* @brief Stop and remove this instance data from the advertisement set.
* @param [in] inst_id The extended advertisement instance to stop advertising.
* @return True if successful.
*/
bool NimBLEExtAdvertising::removeInstance(uint8_t inst_id) {
if (stop(inst_id)) {
int rc = ble_gap_ext_adv_remove(inst_id);
if (rc != 0 && rc != BLE_HS_EALREADY) {
NIMBLE_LOGE(LOG_TAG, "ble_gap_ext_adv_remove rc = %d %s",
rc, NimBLEUtils::returnCodeToString(rc));
return false;
}
return true;
}
return false;
} // removeInstance
/**
* @brief Stop and remove all advertising instance data.
* @return True if successful.
*/
bool NimBLEExtAdvertising::removeAll() {
if (stop()) {
int rc = ble_gap_ext_adv_clear();
if (rc == 0 || rc == BLE_HS_EALREADY) {
return true;
} else {
NIMBLE_LOGE(LOG_TAG, "ble_gap_ext_adv_clear rc = %d %s",
rc, NimBLEUtils::returnCodeToString(rc));
}
}
return false;
} // removeAll
/**
* @brief Stop advertising this instance data.
* @param [in] inst_id The extended advertisement instance to stop advertising.
* @return True if successful.
*/
bool NimBLEExtAdvertising::stop(uint8_t inst_id) {
int rc = ble_gap_ext_adv_stop(inst_id);
if (rc != 0 && rc != BLE_HS_EALREADY) {
NIMBLE_LOGE(LOG_TAG, "ble_gap_ext_adv_stop rc = %d %s",
rc, NimBLEUtils::returnCodeToString(rc));
return false;
}
m_advStatus[inst_id] = false;
return true;
} // stop
/**
* @brief Stop all advertisements.
* @return True if successful.
*/
bool NimBLEExtAdvertising::stop() {
int rc = ble_gap_ext_adv_clear();
if (rc != 0 && rc != BLE_HS_EALREADY) {
NIMBLE_LOGE(LOG_TAG, "ble_gap_ext_adv_stop rc = %d %s",
rc, NimBLEUtils::returnCodeToString(rc));
return false;
}
for(auto it : m_advStatus) {
it = false;
}
return true;
} // stop
/**
* @brief Set a callback to call when the advertisement stops.
* @param [in] pCallbacks A pointer to a callback to be invoked when an advertisement stops.
* @param [in] deleteCallbacks if true callback class will be deleted when advertising is destructed.
*/
void NimBLEExtAdvertising::setCallbacks(NimBLEExtAdvertisingCallbacks* pCallbacks,
bool deleteCallbacks) {
if (pCallbacks != nullptr){
m_pCallbacks = pCallbacks;
m_deleteCallbacks = deleteCallbacks;
} else {
m_pCallbacks = &defaultCallbacks;
}
} // setCallbacks
/**
* @brief Check if currently advertising.
* @param [in] inst_id The instance ID of the advertised data to get the status of.
* @return True if advertising is active.
*/
bool NimBLEExtAdvertising::isActive(uint8_t inst_id) {
return m_advStatus[inst_id];
} // isAdvertising
/**
* @brief Check if any instances are currently advertising.
* @return True if any instance is active.
*/
bool NimBLEExtAdvertising::isAdvertising() {
for (auto it : m_advStatus) {
if (it) {
return true;
}
}
return false;
} // isAdvertising
/*
* Host reset seems to clear advertising data,
* we need clear the flag so it reloads it.
*/
void NimBLEExtAdvertising::onHostSync() {
NIMBLE_LOGD(LOG_TAG, "Host re-synced");
for(auto it : m_advStatus) {
it = false;
}
} // onHostSync
/**
* @brief Handler for gap events when not using peripheral role.
* @param [in] event the event data.
* @param [in] arg pointer to the advertising instance.
*/
/*STATIC*/
int NimBLEExtAdvertising::handleGapEvent(struct ble_gap_event *event, void *arg) {
(void)arg;
NimBLEExtAdvertising* pAdv = NimBLEDevice::getAdvertising();
switch (event->type) {
case BLE_GAP_EVENT_ADV_COMPLETE: {
switch (event->adv_complete.reason) {
// Don't call the callback if host reset, we want to
// preserve the active flag until re-sync to restart advertising.
case BLE_HS_ETIMEOUT_HCI:
case BLE_HS_EOS:
case BLE_HS_ECONTROLLER:
case BLE_HS_ENOTSYNCED:
NIMBLE_LOGC(LOG_TAG, "host reset, rc = %d", event->adv_complete.reason);
NimBLEDevice::onReset(event->adv_complete.reason);
return 0;
default:
break;
}
pAdv->m_advStatus[event->adv_complete.instance] = false;
pAdv->m_pCallbacks->onStopped(pAdv, event->adv_complete.reason,
event->adv_complete.instance);
break;
}
case BLE_GAP_EVENT_SCAN_REQ_RCVD: {
pAdv->m_pCallbacks->onScanRequest(pAdv, event->scan_req_rcvd.instance,
NimBLEAddress(event->scan_req_rcvd.scan_addr));
break;
}
}
return 0;
} // handleGapEvent
/** Default callback handlers */
void NimBLEExtAdvertisingCallbacks::onStopped(NimBLEExtAdvertising *pAdv,
int reason, uint8_t inst_id) {
NIMBLE_LOGD("NimBLEExtAdvertisingCallbacks", "onStopped: Default");
} // onStopped
void NimBLEExtAdvertisingCallbacks::onScanRequest(NimBLEExtAdvertising *pAdv,
uint8_t inst_id, NimBLEAddress addr) {
NIMBLE_LOGD("NimBLEExtAdvertisingCallbacks", "onScanRequest: Default");
} // onScanRequest
/**
* @brief Construct a BLE extended advertisement.
* @param [in] priPhy The primary Phy to advertise on, can be one of:
* * BLE_HCI_LE_PHY_1M
* * BLE_HCI_LE_PHY_CODED
* @param [in] secPhy The secondary Phy to advertise on, can be one of:
* * BLE_HCI_LE_PHY_1M
* * BLE_HCI_LE_PHY_2M
* * BLE_HCI_LE_PHY_CODED
*/
NimBLEExtAdvertisement::NimBLEExtAdvertisement(uint8_t priPhy, uint8_t secPhy)
: m_advAddress("")
{
memset (&m_params, 0, sizeof(m_params));
m_params.own_addr_type = NimBLEDevice::m_own_addr_type;
m_params.primary_phy = priPhy;
m_params.secondary_phy = secPhy;
m_params.tx_power = 127;
} // NimBLEExtAdvertisement
/**
* @brief Sets wether the advertisement should use legacy (BLE 4.0, 31 bytes max) advertising.
* @param [in] val true = using legacy advertising.
*/
void NimBLEExtAdvertisement::setLegacyAdvertising(bool val) {
m_params.legacy_pdu = val;
} // setLegacyAdvertising
/**
* @brief Sets wether the advertisement has scan response data available.
* @param [in] val true = scan response is available.
*/
void NimBLEExtAdvertisement::setScannable(bool val) {
m_params.scannable = val;
} // setScannable
/**
* @brief Sets the transmission power level for this advertisement.
* @param [in] dbm the transmission power to use in dbm.
* @details The allowable value range depends on device hardware. \n
* The ESP32C3 and ESP32S3 have a range of -27 to +18.
*/
void NimBLEExtAdvertisement::setTxPower(int8_t dbm) {
m_params.tx_power = dbm;
}
/**
* @brief Sets wether this advertisement should advertise as a connectable device.
* @param [in] val True = connectable.
*/
void NimBLEExtAdvertisement::setConnectable(bool val) {
#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
m_params.connectable = val;
#endif
} // setConnectable
/**
* @brief Set the address to use for this advertisement.
* @param [in] addr The address to use.
*/
void NimBLEExtAdvertisement::setAddress(const NimBLEAddress & addr) {
m_advAddress = addr;
// Must use random address type.
m_params.own_addr_type = BLE_OWN_ADDR_RANDOM;
}
/**
* @brief Sets The primary channels to advertise on.
* @param [in] ch37 Advertise on channel 37.
* @param [in] ch38 Advertise on channel 38.
* @param [in] ch39 Advertise on channel 39.
* @details This will set a bitmask using the input parameters to allow different \n
* combinations. If all inputs are false then all 3 channels will be used.
*/
void NimBLEExtAdvertisement::setPrimaryChannels(bool ch37, bool ch38, bool ch39) {
m_params.channel_map = (ch37 | (ch38 << 1) | (ch39 << 2));
} // setPrimaryChannels
/**
* @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 NimBLEExtAdvertisement::setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly) {
if (!scanRequestWhitelistOnly && !connectWhitelistOnly) {
m_params.filter_policy = BLE_HCI_ADV_FILT_NONE;
return;
}
if (scanRequestWhitelistOnly && !connectWhitelistOnly) {
m_params.filter_policy = BLE_HCI_ADV_FILT_SCAN;
return;
}
if (!scanRequestWhitelistOnly && connectWhitelistOnly) {
m_params.filter_policy = BLE_HCI_ADV_FILT_CONN;
return;
}
if (scanRequestWhitelistOnly && connectWhitelistOnly) {
m_params.filter_policy = BLE_HCI_ADV_FILT_BOTH;
return;
}
} // setScanFilter
/**
* @brief Sets the peer to directly advertise to.
* @param [in] addr The address of the peer to direct the advertisements.
*/
void NimBLEExtAdvertisement::setDirectedPeer(const NimBLEAddress & addr) {
ble_addr_t peerAddr;
memcpy(&peerAddr.val, addr.getNative(), 6);
peerAddr.type = addr.getType();
m_params.peer = peerAddr;
} // setDirectedPeer
/**
* @brief Enable or disable direct advertisements to the peer set with `NimBLEExtAdvertisement::setDirectedPeer`
* @param [in] val true = send directed advertisements to peer.
* @param [in] high_duty true = use fast advertising rate, default - true.
*/
void NimBLEExtAdvertisement::setDirected(bool val, bool high_duty) {
m_params.directed = val;
m_params.high_duty_directed = high_duty;
} // setDirected
/**
* @brief Set the minimum advertising interval.
* @param [in] mininterval Minimum value for advertising interval in 0.625ms units, 0 = use default.
*/
void NimBLEExtAdvertisement::setMinInterval(uint32_t mininterval) {
m_params.itvl_min = mininterval;
} // setMinInterval
/**
* @brief Set the maximum advertising interval.
* @param [in] maxinterval Maximum value for advertising interval in 0.625ms units, 0 = use default.
*/
void NimBLEExtAdvertisement::setMaxInterval(uint32_t maxinterval) {
m_params.itvl_max = maxinterval;
} // setMaxInterval
/**
* @brief Set the primary advertising PHY to use
* @param [in] phy Can be one of following constants:
* * BLE_HCI_LE_PHY_1M
* * BLE_HCI_LE_PHY_CODED
*/
void NimBLEExtAdvertisement::setPrimaryPhy(uint8_t phy) {
m_params.primary_phy = phy;
} // setPrimaryPhy
/**
* @brief Set the secondary advertising PHY to use
* @param [in] phy Can be one of following constants:
* * BLE_HCI_LE_PHY_1M
* * BLE_HCI_LE_PHY_2M
* * BLE_HCI_LE_PHY_CODED
*/
void NimBLEExtAdvertisement::setSecondaryPhy(uint8_t phy) {
m_params.secondary_phy = phy;
} // setSecondaryPhy
/**
* @brief Sets whether the advertisement should be anonymous.
* @param [in] val Set to true to enable anonymous advertising.
*
* @details Anonymous advertising omits the device's address from the advertisement.
*/
void NimBLEExtAdvertisement::setAnonymous(bool val) {
m_params.anonymous = val;
} // setAnonymous
/**
* @brief Sets whether the scan response request callback should be called.
* @param [in] enable If true the scan response request callback will be called for this advertisement.
*/
void NimBLEExtAdvertisement::enableScanRequestCallback(bool enable) {
m_params.scan_req_notif = enable;
} // enableScanRequestCallback
/**
* @brief Clears the data stored in this instance, does not change settings.
* @details This will clear all data but preserves advertising parameter settings.
*/
void NimBLEExtAdvertisement::clearData() {
std::vector<uint8_t> swap;
std::swap(m_payload, swap);
}
/**
* @brief Get the size of the current data.
*/
size_t NimBLEExtAdvertisement::getDataSize() {
return m_payload.size();
} // getDataSize
/**
* @brief Set the advertisement data.
* @param [in] data The data to be set as the payload.
* @param [in] length The size of data.
* @details This will completely replace any data that was previously set.
*/
void NimBLEExtAdvertisement::setData(const uint8_t * data, size_t length) {
m_payload.assign(data, data + length);
} // setData
/**
* @brief Add data to the payload to be advertised.
* @param [in] data The data to be added to the payload.
*/
void NimBLEExtAdvertisement::addData(const std::string &data) {
addData((uint8_t*)data.data(), data.length());
} // addData
/**
* @brief Add data to the payload to be advertised.
* @param [in] data The data to be added to the payload.
* @param [in] length The size of data to be added to the payload.
*/
void NimBLEExtAdvertisement::addData(const uint8_t * data, size_t length) {
m_payload.insert(m_payload.end(), data, data + length);
} // addData
/**
* @brief Set the appearance.
* @param [in] appearance The appearance code value.
*/
void NimBLEExtAdvertisement::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 advertisement flags.
* @param [in] flag The flags to be set in the advertisement.
* * BLE_HS_ADV_F_DISC_LTD
* * BLE_HS_ADV_F_DISC_GEN
* * BLE_HS_ADV_F_BREDR_UNSUP - must always use with NimBLE
*/
void NimBLEExtAdvertisement::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));
} // setFlags
/**
* @brief Set manufacturer specific data.
* @param [in] data The manufacturer data to advertise.
*/
void NimBLEExtAdvertisement::setManufacturerData(const std::string &data) {
char cdata[2];
cdata[0] = data.length() + 1;
cdata[1] = BLE_HS_ADV_TYPE_MFG_DATA ; // 0xff
addData(std::string(cdata, 2) + data);
} // setManufacturerData
/**
* @brief Set the URI to advertise.
* @param [in] uri The uri to advertise.
*/
void NimBLEExtAdvertisement::setURI(const std::string &uri) {
char cdata[2];
cdata[0] = uri.length() + 1;
cdata[1] = BLE_HS_ADV_TYPE_URI;
addData(std::string(cdata, 2) + uri);
} // setURI
/**
* @brief Set the complete name of this device.
* @param [in] name The name to advertise.
*/
void NimBLEExtAdvertisement::setName(const std::string &name) {
char cdata[2];
cdata[0] = name.length() + 1;
cdata[1] = BLE_HS_ADV_TYPE_COMP_NAME; // 0x09
addData(std::string(cdata, 2) + name);
} // setName
/**
* @brief Set a single service to advertise as a complete list of services.
* @param [in] uuid The service to advertise.
*/
void NimBLEExtAdvertisement::setCompleteServices(const NimBLEUUID &uuid) {
setServices(true, uuid.bitSize(), {uuid});
} // setCompleteServices
/**
* @brief Set the complete list of 16 bit services to advertise.
* @param [in] v_uuid A vector of 16 bit UUID's to advertise.
*/
void NimBLEExtAdvertisement::setCompleteServices16(const std::vector<NimBLEUUID>& v_uuid) {
setServices(true, 16, v_uuid);
} // setCompleteServices16
/**
* @brief Set the complete list of 32 bit services to advertise.
* @param [in] v_uuid A vector of 32 bit UUID's to advertise.
*/
void NimBLEExtAdvertisement::setCompleteServices32(const std::vector<NimBLEUUID>& v_uuid) {
setServices(true, 32, v_uuid);
} // setCompleteServices32
/**
* @brief Set a single service to advertise as a partial list of services.
* @param [in] uuid The service to advertise.
*/
void NimBLEExtAdvertisement::setPartialServices(const NimBLEUUID &uuid) {
setServices(false, uuid.bitSize(), {uuid});
} // setPartialServices
/**
* @brief Set the partial list of services to advertise.
* @param [in] v_uuid A vector of 16 bit UUID's to advertise.
*/
void NimBLEExtAdvertisement::setPartialServices16(const std::vector<NimBLEUUID>& v_uuid) {
setServices(false, 16, v_uuid);
} // setPartialServices16
/**
* @brief Set the partial list of services to advertise.
* @param [in] v_uuid A vector of 32 bit UUID's to advertise.
*/
void NimBLEExtAdvertisement::setPartialServices32(const std::vector<NimBLEUUID>& v_uuid) {
setServices(false, 32, v_uuid);
} // setPartialServices32
/**
* @brief Utility function to create the list of service UUID's from a vector.
* @param [in] complete If true the vector is the complete set of services.
* @param [in] size The bit size of the UUID's in the vector. (16, 32, or 128).
* @param [in] v_uuid The vector of service UUID's to advertise.
*/
void NimBLEExtAdvertisement::setServices(const bool complete, const uint8_t size,
const std::vector<NimBLEUUID> &v_uuid)
{
char cdata[2];
cdata[0] = (size / 8) * v_uuid.size() + 1;
switch(size) {
case 16:
cdata[1] = complete ? BLE_HS_ADV_TYPE_COMP_UUIDS16 : BLE_HS_ADV_TYPE_INCOMP_UUIDS16;
break;
case 32:
cdata[1] = complete ? BLE_HS_ADV_TYPE_COMP_UUIDS32 : BLE_HS_ADV_TYPE_INCOMP_UUIDS32;
break;
case 128:
cdata[1] = complete ? BLE_HS_ADV_TYPE_COMP_UUIDS128 : BLE_HS_ADV_TYPE_INCOMP_UUIDS128;
break;
default:
return;
}
std::string uuids;
for(auto &it : v_uuid){
if(it.bitSize() != size) {
NIMBLE_LOGE(LOG_TAG, "Service UUID(%d) invalid", size);
return;
} else {
switch(size) {
case 16:
uuids += std::string((char*)&it.getNative()->u16.value, 2);
break;
case 32:
uuids += std::string((char*)&it.getNative()->u32.value, 4);
break;
case 128:
uuids += std::string((char*)&it.getNative()->u128.value, 16);
break;
default:
return;
}
}
}
addData(std::string(cdata, 2) + uuids);
} // setServices
/**
* @brief Set the service data (UUID + data)
* @param [in] uuid The UUID to set with the service data.
* @param [in] data The data to be associated with the service data advertised.
*/
void NimBLEExtAdvertisement::setServiceData(const NimBLEUUID &uuid, const std::string &data) {
char cdata[2];
switch (uuid.bitSize()) {
case 16: {
// [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] name The short name of the device.
*/
void NimBLEExtAdvertisement::setShortName(const std::string &name) {
char cdata[2];
cdata[0] = name.length() + 1;
cdata[1] = BLE_HS_ADV_TYPE_INCOMP_NAME; // 0x08
addData(std::string(cdata, 2) + name);
} // setShortName
/**
* @brief Adds Tx power level to the advertisement data.
*/
void NimBLEExtAdvertisement::addTxPower() {
m_params.include_tx_power = 1;
} // addTxPower
/**
* @brief Set the preferred connection interval parameters.
* @param [in] min The minimum interval desired.
* @param [in] max The maximum interval desired.
*/
void NimBLEExtAdvertisement::setPreferredParams(uint16_t min, uint16_t max) {
uint8_t data[6];
data[0] = BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN + 1;
data[1] = BLE_HS_ADV_TYPE_SLAVE_ITVL_RANGE;
data[2] = min;
data[3] = min >> 8;
data[4] = max;
data[5] = max >> 8;
addData(data, 6);
} // setPreferredParams
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_BROADCASTER && CONFIG_BT_NIMBLE_EXT_ADV */

View File

@@ -0,0 +1,152 @@
/*
* NimBLEExtAdvertising.h
*
* Created: on February 6, 2022
* Author H2zero
*/
#ifndef MAIN_BLEEXTADVERTISING_H_
#define MAIN_BLEEXTADVERTISING_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && \
defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) && \
CONFIG_BT_NIMBLE_EXT_ADV
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_gap.h"
# else
# include "nimble/nimble/host/include/host/ble_gap.h"
# endif
/**** FIX COMPILATION ****/
#undef min
#undef max
/**************************/
#include "NimBLEAddress.h"
#include "NimBLEUUID.h"
#include <vector>
class NimBLEExtAdvertisingCallbacks;
/**
* @brief Extended advertisement data
*/
class NimBLEExtAdvertisement {
public:
NimBLEExtAdvertisement(uint8_t priPhy = BLE_HCI_LE_PHY_1M,
uint8_t secPhy = BLE_HCI_LE_PHY_1M);
void setAppearance(uint16_t appearance);
void setCompleteServices(const NimBLEUUID &uuid);
void setCompleteServices16(const std::vector<NimBLEUUID> &v_uuid);
void setCompleteServices32(const std::vector<NimBLEUUID> &v_uuid);
void setFlags(uint8_t flag);
void setManufacturerData(const std::string &data);
void setURI(const std::string &uri);
void setName(const std::string &name);
void setPartialServices(const NimBLEUUID &uuid);
void setPartialServices16(const std::vector<NimBLEUUID> &v_uuid);
void setPartialServices32(const std::vector<NimBLEUUID> &v_uuid);
void setServiceData(const NimBLEUUID &uuid, const std::string &data);
void setShortName(const std::string &name);
void setData(const uint8_t * data, size_t length);
void addData(const std::string &data);
void addData(const uint8_t * data, size_t length);
void addTxPower();
void setPreferredParams(uint16_t min, uint16_t max);
void setLegacyAdvertising(bool val);
void setConnectable(bool val);
void setScannable(bool val);
void setMinInterval(uint32_t mininterval);
void setMaxInterval(uint32_t maxinterval);
void setPrimaryPhy(uint8_t phy);
void setSecondaryPhy(uint8_t phy);
void setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly);
void setDirectedPeer(const NimBLEAddress & addr);
void setDirected(bool val, bool high_duty = true);
void setAnonymous(bool val);
void setPrimaryChannels(bool ch37, bool ch38, bool ch39);
void setTxPower(int8_t dbm);
void setAddress(const NimBLEAddress & addr);
void enableScanRequestCallback(bool enable);
void clearData();
size_t getDataSize();
private:
friend class NimBLEExtAdvertising;
void setServices(const bool complete, const uint8_t size,
const std::vector<NimBLEUUID> &v_uuid);
std::vector<uint8_t> m_payload;
ble_gap_ext_adv_params m_params;
NimBLEAddress m_advAddress;
}; // NimBLEExtAdvertisement
/**
* @brief Extended advertising class.
*/
class NimBLEExtAdvertising {
public:
/**
* @brief Construct an extended advertising object.
*/
NimBLEExtAdvertising() :m_advStatus(CONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES + 1, false) {}
~NimBLEExtAdvertising();
bool start(uint8_t inst_id, int duration = 0, int max_events = 0);
bool setInstanceData(uint8_t inst_id, NimBLEExtAdvertisement& adv);
bool setScanResponseData(uint8_t inst_id, NimBLEExtAdvertisement & data);
bool removeInstance(uint8_t inst_id);
bool removeAll();
bool stop(uint8_t inst_id);
bool stop();
bool isActive(uint8_t inst_id);
bool isAdvertising();
void setCallbacks(NimBLEExtAdvertisingCallbacks* callbacks,
bool deleteCallbacks = true);
private:
friend class NimBLEDevice;
friend class NimBLEServer;
void onHostSync();
static int handleGapEvent(struct ble_gap_event *event, void *arg);
bool m_scanResp;
bool m_deleteCallbacks;
NimBLEExtAdvertisingCallbacks* m_pCallbacks;
ble_gap_ext_adv_params m_advParams;
std::vector<bool> m_advStatus;
};
/**
* @brief Callbacks associated with NimBLEExtAdvertising class.
*/
class NimBLEExtAdvertisingCallbacks {
public:
virtual ~NimBLEExtAdvertisingCallbacks() {};
/**
* @brief Handle an advertising stop event.
* @param [in] pAdv A convenience pointer to the extended advertising interface.
* @param [in] reason The reason code for stopping the advertising.
* @param [in] inst_id The instance ID of the advertisement that was stopped.
*/
virtual void onStopped(NimBLEExtAdvertising *pAdv, int reason, uint8_t inst_id);
/**
* @brief Handle a scan response request.
* This is called when a scanning device requests a scan response.
* @param [in] pAdv A convenience pointer to the extended advertising interface.
* @param [in] inst_id The instance ID of the advertisement that the scan response request was made.
* @param [in] addr The address of the device making the request.
*/
virtual void onScanRequest(NimBLEExtAdvertising *pAdv, uint8_t inst_id, NimBLEAddress addr);
}; // NimBLEExtAdvertisingCallbacks
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_BROADCASTER && CONFIG_BT_NIMBLE_EXT_ADV */
#endif /* MAIN_BLEADVERTISING_H_ */

View File

@@ -0,0 +1,265 @@
/*
* NimBLEHIDDevice.cpp
*
* Created: on Oct 06 2020
* Author wakwak-koba
*
* Originally:
*
* BLEHIDDevice.cpp
*
* Created on: Jan 03, 2018
* Author: chegewara
*/
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#include "NimBLEHIDDevice.h"
#include "NimBLE2904.h"
/**
* @brief Construct a default NimBLEHIDDevice object.
* @param [in] server A pointer to the server instance this HID Device will use.
*/
NimBLEHIDDevice::NimBLEHIDDevice(NimBLEServer* server) {
/*
* Here we create mandatory services described in bluetooth specification
*/
m_deviceInfoService = server->createService(NimBLEUUID((uint16_t)0x180a));
m_hidService = server->createService(NimBLEUUID((uint16_t)0x1812));
m_batteryService = server->createService(NimBLEUUID((uint16_t)0x180f));
/*
* Mandatory characteristic for device info service
*/
m_pnpCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t)0x2a50, NIMBLE_PROPERTY::READ);
/*
* Non-mandatory characteristics for device info service
* Will be created on demand
*/
m_manufacturerCharacteristic = nullptr;
/*
* Mandatory characteristics for HID service
*/
m_hidInfoCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4a, NIMBLE_PROPERTY::READ);
m_reportMapCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4b, NIMBLE_PROPERTY::READ);
m_hidControlCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4c, NIMBLE_PROPERTY::WRITE_NR);
m_protocolModeCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4e, NIMBLE_PROPERTY::WRITE_NR | NIMBLE_PROPERTY::READ);
/*
* Mandatory battery level characteristic with notification and presence descriptor
*/
m_batteryLevelCharacteristic = m_batteryService->createCharacteristic((uint16_t)0x2a19, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY);
NimBLE2904 *batteryLevelDescriptor = (NimBLE2904*)m_batteryLevelCharacteristic->createDescriptor((uint16_t)0x2904);
batteryLevelDescriptor->setFormat(NimBLE2904::FORMAT_UINT8);
batteryLevelDescriptor->setNamespace(1);
batteryLevelDescriptor->setUnit(0x27ad);
/*
* This value is setup here because its default value in most usage cases, its very rare to use boot mode
* and we want to simplify library using as much as possible
*/
const uint8_t pMode[] = {0x01};
protocolMode()->setValue((uint8_t*)pMode, 1);
}
NimBLEHIDDevice::~NimBLEHIDDevice() {
}
/**
* @brief Set the report map data formatting information.
* @param [in] map A pointer to an array with the values to set.
* @param [in] size The number of values in the array.
*/
void NimBLEHIDDevice::reportMap(uint8_t* map, uint16_t size) {
m_reportMapCharacteristic->setValue(map, size);
}
/**
* @brief Start the HID device services.\n
* This function called when all the services have been created.
*/
void NimBLEHIDDevice::startServices() {
m_deviceInfoService->start();
m_hidService->start();
m_batteryService->start();
}
/**
* @brief Create a manufacturer characteristic (this characteristic is optional).
*/
NimBLECharacteristic* NimBLEHIDDevice::manufacturer() {
if (m_manufacturerCharacteristic == nullptr) {
m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t)0x2a29, NIMBLE_PROPERTY::READ);
}
return m_manufacturerCharacteristic;
}
/**
* @brief Set manufacturer name
* @param [in] name The manufacturer name of this HID device.
*/
void NimBLEHIDDevice::manufacturer(std::string name) {
manufacturer()->setValue(name);
}
/**
* @brief Sets the Plug n Play characteristic value.
* @param [in] sig The vendor ID source number.
* @param [in] vid The vendor ID number.
* @param [in] pid The product ID number.
* @param [in] version The produce version number.
*/
void NimBLEHIDDevice::pnp(uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version) {
uint8_t pnp[] = {
sig,
((uint8_t*)&vid)[0],
((uint8_t*)&vid)[1],
((uint8_t*)&pid)[0],
((uint8_t*)&pid)[1],
((uint8_t*)&version)[0],
((uint8_t*)&version)[1]
};
m_pnpCharacteristic->setValue(pnp, sizeof(pnp));
}
/**
* @brief Sets the HID Information characteristic value.
* @param [in] country The country code for the device.
* @param [in] flags The HID Class Specification release number to use.
*/
void NimBLEHIDDevice::hidInfo(uint8_t country, uint8_t flags) {
uint8_t info[] = {0x11, 0x1, country, flags};
m_hidInfoCharacteristic->setValue(info, sizeof(info));
}
/**
* @brief Create input report characteristic
* @param [in] reportID input report ID, the same as in report map for input object related to the characteristic
* @return pointer to new input report characteristic
*/
NimBLECharacteristic* NimBLEHIDDevice::inputReport(uint8_t reportID) {
NimBLECharacteristic *inputReportCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4d, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ_ENC);
NimBLEDescriptor *inputReportDescriptor = inputReportCharacteristic->createDescriptor((uint16_t)0x2908, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_ENC);
uint8_t desc1_val[] = {reportID, 0x01};
inputReportDescriptor->setValue((uint8_t*)desc1_val, 2);
return inputReportCharacteristic;
}
/**
* @brief Create output report characteristic
* @param [in] reportID Output report ID, the same as in report map for output object related to the characteristic
* @return Pointer to new output report characteristic
*/
NimBLECharacteristic* NimBLEHIDDevice::outputReport(uint8_t reportID) {
NimBLECharacteristic *outputReportCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4d, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::WRITE_ENC);
NimBLEDescriptor *outputReportDescriptor = outputReportCharacteristic->createDescriptor((uint16_t)0x2908, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::WRITE_ENC);
uint8_t desc1_val[] = {reportID, 0x02};
outputReportDescriptor->setValue((uint8_t*)desc1_val, 2);
return outputReportCharacteristic;
}
/**
* @brief Create feature report characteristic.
* @param [in] reportID Feature report ID, the same as in report map for feature object related to the characteristic
* @return Pointer to new feature report characteristic
*/
NimBLECharacteristic* NimBLEHIDDevice::featureReport(uint8_t reportID) {
NimBLECharacteristic *featureReportCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4d, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::WRITE_ENC);
NimBLEDescriptor *featureReportDescriptor = featureReportCharacteristic->createDescriptor((uint16_t)0x2908, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::WRITE_ENC);
uint8_t desc1_val[] = {reportID, 0x03};
featureReportDescriptor->setValue((uint8_t*)desc1_val, 2);
return featureReportCharacteristic;
}
/**
* @brief Creates a keyboard boot input report characteristic
*/
NimBLECharacteristic* NimBLEHIDDevice::bootInput() {
return m_hidService->createCharacteristic((uint16_t)0x2a22, NIMBLE_PROPERTY::NOTIFY);
}
/**
* @brief Create a keyboard boot output report characteristic
*/
NimBLECharacteristic* NimBLEHIDDevice::bootOutput() {
return m_hidService->createCharacteristic((uint16_t)0x2a32, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR);
}
/**
* @brief Returns a pointer to the HID control point characteristic.
*/
NimBLECharacteristic* NimBLEHIDDevice::hidControl() {
return m_hidControlCharacteristic;
}
/**
* @brief Returns a pointer to the protocol mode characteristic.
*/
NimBLECharacteristic* NimBLEHIDDevice::protocolMode() {
return m_protocolModeCharacteristic;
}
/**
* @brief Set the battery level characteristic value.
* @param [in] level The battery level value.
*/
void NimBLEHIDDevice::setBatteryLevel(uint8_t level) {
m_batteryLevelCharacteristic->setValue(&level, 1);
}
/*
* @brief Returns battery level characteristic
* @ return battery level characteristic
*/
NimBLECharacteristic* NimBLEHIDDevice::batteryLevel() {
return m_batteryLevelCharacteristic;
}
/*
BLECharacteristic* BLEHIDDevice::reportMap() {
return m_reportMapCharacteristic;
}
BLECharacteristic* BLEHIDDevice::pnp() {
return m_pnpCharacteristic;
}
BLECharacteristic* BLEHIDDevice::hidInfo() {
return m_hidInfoCharacteristic;
}
*/
/**
* @brief Returns a pointer to the device information service.
*/
NimBLEService* NimBLEHIDDevice::deviceInfo() {
return m_deviceInfoService;
}
/**
* @brief Returns a pointer to the HID service.
*/
NimBLEService* NimBLEHIDDevice::hidService() {
return m_hidService;
}
/**
* @brief @brief Returns a pointer to the battery service.
*/
NimBLEService* NimBLEHIDDevice::batteryService() {
return m_batteryService;
}
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */

View File

@@ -0,0 +1,87 @@
/*
* NimBLEHIDDevice.h
*
* Created: on Oct 06 2020
* Author wakwak-koba
*
* Originally:
*
* BLEHIDDevice.h
*
* Created on: Jan 03, 2018
* Author: chegewara
*/
#ifndef _BLEHIDDEVICE_H_
#define _BLEHIDDEVICE_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
#include "NimBLECharacteristic.h"
#include "NimBLEService.h"
#include "NimBLEDescriptor.h"
#include "HIDTypes.h"
#define GENERIC_HID 0x03C0
#define HID_KEYBOARD 0x03C1
#define HID_MOUSE 0x03C2
#define HID_JOYSTICK 0x03C3
#define HID_GAMEPAD 0x03C4
#define HID_TABLET 0x03C5
#define HID_CARD_READER 0x03C6
#define HID_DIGITAL_PEN 0x03C7
#define HID_BARCODE 0x03C8
#define PNPVersionField(MajorVersion, MinorVersion, PatchVersion) ((MajorVersion << 16) & 0xFF00) | ((MinorVersion << 8) & 0x00F0) | (PatchVersion & 0x000F)
/**
* @brief A model of a %BLE Human Interface Device.
*/
class NimBLEHIDDevice {
public:
NimBLEHIDDevice(NimBLEServer*);
virtual ~NimBLEHIDDevice();
void reportMap(uint8_t* map, uint16_t);
void startServices();
NimBLEService* deviceInfo();
NimBLEService* hidService();
NimBLEService* batteryService();
NimBLECharacteristic* manufacturer();
void manufacturer(std::string name);
//NimBLECharacteristic* pnp();
void pnp(uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version);
//NimBLECharacteristic* hidInfo();
void hidInfo(uint8_t country, uint8_t flags);
NimBLECharacteristic* batteryLevel();
void setBatteryLevel(uint8_t level);
//NimBLECharacteristic* reportMap();
NimBLECharacteristic* hidControl();
NimBLECharacteristic* inputReport(uint8_t reportID);
NimBLECharacteristic* outputReport(uint8_t reportID);
NimBLECharacteristic* featureReport(uint8_t reportID);
NimBLECharacteristic* protocolMode();
NimBLECharacteristic* bootInput();
NimBLECharacteristic* bootOutput();
private:
NimBLEService* m_deviceInfoService; //0x180a
NimBLEService* m_hidService; //0x1812
NimBLEService* m_batteryService = 0; //0x180f
NimBLECharacteristic* m_manufacturerCharacteristic; //0x2a29
NimBLECharacteristic* m_pnpCharacteristic; //0x2a50
NimBLECharacteristic* m_hidInfoCharacteristic; //0x2a4a
NimBLECharacteristic* m_reportMapCharacteristic; //0x2a4b
NimBLECharacteristic* m_hidControlCharacteristic; //0x2a4c
NimBLECharacteristic* m_protocolModeCharacteristic; //0x2a4e
NimBLECharacteristic* m_batteryLevelCharacteristic; //0x2a19
};
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_BROADCASTER */
#endif /* _BLEHIDDEVICE_H_ */

View File

@@ -0,0 +1,80 @@
/*
* NimBLELog.h
*
* Created: on Feb 24 2020
* Author H2zero
*
*/
#ifndef MAIN_NIMBLELOG_H_
#define MAIN_NIMBLELOG_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
#if defined(CONFIG_NIMBLE_CPP_IDF) // using esp-idf
# include "esp_log.h"
# ifndef CONFIG_NIMBLE_CPP_LOG_LEVEL
# define CONFIG_NIMBLE_CPP_LOG_LEVEL 0
# endif
# define NIMBLE_CPP_LOG_PRINT(level, tag, format, ...) do { \
if (CONFIG_NIMBLE_CPP_LOG_LEVEL >= level) \
ESP_LOG_LEVEL_LOCAL(level, tag, format, ##__VA_ARGS__); \
} while(0)
# define NIMBLE_LOGD(tag, format, ...) \
NIMBLE_CPP_LOG_PRINT(ESP_LOG_DEBUG, tag, format, ##__VA_ARGS__)
# define NIMBLE_LOGI(tag, format, ...) \
NIMBLE_CPP_LOG_PRINT(ESP_LOG_INFO, tag, format, ##__VA_ARGS__)
# define NIMBLE_LOGW(tag, format, ...) \
NIMBLE_CPP_LOG_PRINT(ESP_LOG_WARN, tag, format, ##__VA_ARGS__)
# define NIMBLE_LOGE(tag, format, ...) \
NIMBLE_CPP_LOG_PRINT(ESP_LOG_ERROR, tag, format, ##__VA_ARGS__)
# define NIMBLE_LOGC(tag, format, ...) \
NIMBLE_CPP_LOG_PRINT(ESP_LOG_ERROR, tag, format, ##__VA_ARGS__)
#else // using Arduino
# include "nimble/porting/nimble/include/syscfg/syscfg.h"
# include "nimble/console/console.h"
# ifndef CONFIG_NIMBLE_CPP_LOG_LEVEL
# if defined(ARDUINO_ARCH_ESP32) && defined(CORE_DEBUG_LEVEL)
# define CONFIG_NIMBLE_CPP_LOG_LEVEL CORE_DEBUG_LEVEL
# else
# define CONFIG_NIMBLE_CPP_LOG_LEVEL 0
# endif
# endif
# if CONFIG_NIMBLE_CPP_LOG_LEVEL >= 4
# define NIMBLE_LOGD( tag, format, ... ) console_printf("D %s: " format "\n", tag, ##__VA_ARGS__)
# else
# define NIMBLE_LOGD( tag, format, ... ) (void)tag
# endif
# if CONFIG_NIMBLE_CPP_LOG_LEVEL >= 3
# define NIMBLE_LOGI( tag, format, ... ) console_printf("I %s: " format "\n", tag, ##__VA_ARGS__)
# else
# define NIMBLE_LOGI( tag, format, ... ) (void)tag
# endif
# if CONFIG_NIMBLE_CPP_LOG_LEVEL >= 2
# define NIMBLE_LOGW( tag, format, ... ) console_printf("W %s: " format "\n", tag, ##__VA_ARGS__)
# else
# define NIMBLE_LOGW( tag, format, ... ) (void)tag
# endif
# if CONFIG_NIMBLE_CPP_LOG_LEVEL >= 1
# define NIMBLE_LOGE( tag, format, ... ) console_printf("E %s: " format "\n", tag, ##__VA_ARGS__)
# define NIMBLE_LOGC( tag, format, ... ) console_printf("CRIT %s: " format "\n", tag, ##__VA_ARGS__)
# else
# define NIMBLE_LOGE( tag, format, ... ) (void)tag
# define NIMBLE_LOGC( tag, format, ... ) (void)tag
# endif
#endif /* CONFIG_NIMBLE_CPP_IDF */
#endif /* CONFIG_BT_ENABLED */
#endif /* MAIN_NIMBLELOG_H_ */

View File

@@ -0,0 +1,806 @@
/*
* NimBLERemoteCharacteristic.cpp
*
* Created: on Jan 27 2020
* Author H2zero
*
* Originally:
*
* BLERemoteCharacteristic.cpp
*
* Created on: Mar 16, 2017
* Author: kolban
*/
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
#include "NimBLERemoteCharacteristic.h"
#include "NimBLEUtils.h"
#include "NimBLELog.h"
#include <climits>
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)
{
NIMBLE_LOGD(LOG_TAG, ">> NimBLERemoteCharacteristic()");
switch (chr->uuid.u.type) {
case BLE_UUID_TYPE_16:
m_uuid = NimBLEUUID(chr->uuid.u16.value);
break;
case BLE_UUID_TYPE_32:
m_uuid = NimBLEUUID(chr->uuid.u32.value);
break;
case BLE_UUID_TYPE_128:
m_uuid = NimBLEUUID(const_cast<ble_uuid128_t*>(&chr->uuid.u128));
break;
default:
break;
}
m_handle = chr->val_handle;
m_defHandle = chr->def_handle;
m_endHandle = 0;
m_charProp = chr->properties;
m_pRemoteService = pRemoteService;
m_notifyCallback = nullptr;
NIMBLE_LOGD(LOG_TAG, "<< NimBLERemoteCharacteristic(): %s", m_uuid.toString().c_str());
} // NimBLERemoteCharacteristic
/**
*@brief Destructor.
*/
NimBLERemoteCharacteristic::~NimBLERemoteCharacteristic() {
deleteDescriptors();
} // ~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)
{
int rc = error->status;
NIMBLE_LOGD(LOG_TAG, "Descriptor Discovered >> status: %d handle: %d",
rc, (rc == 0) ? dsc->handle : -1);
desc_filter_t *filter = (desc_filter_t*)arg;
const NimBLEUUID *uuid_filter = filter->uuid;
ble_task_data_t *pTaskData = (ble_task_data_t*)filter->task_data;
NimBLERemoteCharacteristic *characteristic = (NimBLERemoteCharacteristic*)pTaskData->pATT;
if (characteristic->getRemoteService()->getClient()->getConnId() != conn_handle){
return 0;
}
switch (rc) {
case 0: {
if (uuid_filter != nullptr) {
if (ble_uuid_cmp(&uuid_filter->getNative()->u, &dsc->uuid.u) != 0) {
return 0;
} else {
rc = BLE_HS_EDONE;
}
}
NimBLERemoteDescriptor* pNewRemoteDescriptor = new NimBLERemoteDescriptor(characteristic, dsc);
characteristic->m_descriptorVector.push_back(pNewRemoteDescriptor);
break;
}
default:
break;
}
/* If rc == BLE_HS_EDONE, resume the task with a success error code and stop the discovery process.
* Else if rc == 0, just return 0 to continue the discovery until we get BLE_HS_EDONE.
* If we get any other error code tell the application to abort by returning non-zero in the rc.
*/
if (rc == BLE_HS_EDONE) {
pTaskData->rc = 0;
xTaskNotifyGive(pTaskData->task);
} else if(rc != 0) {
// Error; abort discovery.
pTaskData->rc = rc;
xTaskNotifyGive(pTaskData->task);
}
NIMBLE_LOGD(LOG_TAG,"<< Descriptor Discovered. status: %d", pTaskData->rc);
return rc;
}
/**
* @brief callback from NimBLE when the next characteristic of the service is discovered.
*/
int NimBLERemoteCharacteristic::nextCharCB(uint16_t conn_handle,
const struct ble_gatt_error *error,
const struct ble_gatt_chr *chr, void *arg)
{
int rc = error->status;
NIMBLE_LOGD(LOG_TAG, "Next Characteristic >> status: %d handle: %d",
rc, (rc == 0) ? chr->val_handle : -1);
ble_task_data_t *pTaskData = (ble_task_data_t*)arg;
NimBLERemoteCharacteristic *pChar = (NimBLERemoteCharacteristic*)pTaskData->pATT;
if (pChar->getRemoteService()->getClient()->getConnId() != conn_handle) {
return 0;
}
if (rc == 0) {
pChar->m_endHandle = chr->def_handle - 1;
rc = BLE_HS_EDONE;
} else if (rc == BLE_HS_EDONE) {
pChar->m_endHandle = pChar->getRemoteService()->getEndHandle();
} else {
pTaskData->rc = rc;
}
xTaskNotifyGive(pTaskData->task);
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(const NimBLEUUID *uuid_filter) {
NIMBLE_LOGD(LOG_TAG, ">> retrieveDescriptors() for characteristic: %s", getUUID().toString().c_str());
// If this is the last handle then there are no descriptors
if (m_handle == getRemoteService()->getEndHandle()) {
return true;
}
int rc = 0;
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
ble_task_data_t taskData = {this, cur_task, 0, nullptr};
// If we don't know the end handle of this characteristic retrieve the next one in the service
// The end handle is the next characteristic definition handle -1.
if (m_endHandle == 0) {
rc = ble_gattc_disc_all_chrs(getRemoteService()->getClient()->getConnId(),
m_handle,
getRemoteService()->getEndHandle(),
NimBLERemoteCharacteristic::nextCharCB,
&taskData);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Error getting end handle rc=%d", rc);
return false;
}
#ifdef ulTaskNotifyValueClear
// Clear the task notification value to ensure we block
ulTaskNotifyValueClear(cur_task, ULONG_MAX);
#endif
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if (taskData.rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Could not retrieve end handle rc=%d", taskData.rc);
return false;
}
}
if (m_handle == m_endHandle) {
return true;
}
desc_filter_t filter = {uuid_filter, &taskData};
rc = ble_gattc_disc_all_dscs(getRemoteService()->getClient()->getConnId(),
m_handle,
m_endHandle,
NimBLERemoteCharacteristic::descriptorDiscCB,
&filter);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gattc_disc_all_dscs: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
return false;
}
#ifdef ulTaskNotifyValueClear
// Clear the task notification value to ensure we block
ulTaskNotifyValueClear(cur_task, ULONG_MAX);
#endif
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if (taskData.rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Failed to retrieve descriptors; startHandle:%d endHandle:%d taskData.rc=%d",
m_handle, m_endHandle, taskData.rc);
}
NIMBLE_LOGD(LOG_TAG, "<< retrieveDescriptors(): Found %d descriptors.", m_descriptorVector.size());
return (taskData.rc == 0);
} // retrieveDescriptors
/**
* @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(const NimBLEUUID &uuid) {
NIMBLE_LOGD(LOG_TAG, ">> getDescriptor: uuid: %s", uuid.toString().c_str());
for(auto &it: m_descriptorVector) {
if(it->getUUID() == uuid) {
NIMBLE_LOGD(LOG_TAG, "<< getDescriptor: found the descriptor with uuid: %s", uuid.toString().c_str());
return it;
}
}
size_t prev_size = m_descriptorVector.size();
if(retrieveDescriptors(&uuid)) {
if(m_descriptorVector.size() > prev_size) {
return m_descriptorVector.back();
}
// If the request was successful but 16/32 bit uuid not found
// try again with the 128 bit uuid.
if(uuid.bitSize() == BLE_UUID_TYPE_16 ||
uuid.bitSize() == BLE_UUID_TYPE_32)
{
NimBLEUUID uuid128(uuid);
uuid128.to128();
if(retrieveDescriptors(&uuid128)) {
if(m_descriptorVector.size() > prev_size) {
return m_descriptorVector.back();
}
}
} else {
// If the request was successful but the 128 bit uuid not found
// try again with the 16 bit uuid.
NimBLEUUID uuid16(uuid);
uuid16.to16();
// if the uuid was 128 bit but not of the BLE base type this check will fail
if (uuid16.bitSize() == BLE_UUID_TYPE_16) {
if(retrieveDescriptors(&uuid16)) {
if(m_descriptorVector.size() > prev_size) {
return m_descriptorVector.back();
}
}
}
}
}
NIMBLE_LOGD(LOG_TAG, "<< getDescriptor: Not found");
return nullptr;
} // getDescriptor
/**
* @brief Get a pointer to the vector of found descriptors.
* @param [in] refresh If true the current descriptor vector will be cleared and\n
* all descriptors for this characteristic retrieved from the peripheral.\n
* If false the vector will be returned with the currently stored descriptors
* of this characteristic.
* @return A pointer to the vector of descriptors for this characteristic.
*/
std::vector<NimBLERemoteDescriptor*>* NimBLERemoteCharacteristic::getDescriptors(bool refresh) {
if(refresh) {
deleteDescriptors();
if (!retrieveDescriptors()) {
NIMBLE_LOGE(LOG_TAG, "Error: Failed to get descriptors");
}
else{
NIMBLE_LOGI(LOG_TAG, "Found %d descriptor(s)", m_descriptorVector.size());
}
}
return &m_descriptorVector;
} // getDescriptors
/**
* @brief Get iterator to the beginning of the vector of remote descriptor pointers.
* @return An iterator to the beginning of the vector of remote descriptor pointers.
*/
std::vector<NimBLERemoteDescriptor*>::iterator NimBLERemoteCharacteristic::begin() {
return m_descriptorVector.begin();
}
/**
* @brief Get iterator to the end of the vector of remote descriptor pointers.
* @return An iterator to the end of the vector of remote descriptor pointers.
*/
std::vector<NimBLERemoteDescriptor*>::iterator NimBLERemoteCharacteristic::end() {
return m_descriptorVector.end();
}
/**
* @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 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 Get the value of the remote characteristic.
* @param [in] timestamp A pointer to a time_t struct to store the time the value was read.
* @return The value of the remote characteristic.
*/
NimBLEAttValue NimBLERemoteCharacteristic::getValue(time_t *timestamp) {
if(timestamp != nullptr) {
*timestamp = m_value.getTimeStamp();
}
return m_value;
} // getValue
/**
* @brief Read the value of the remote characteristic.
* @param [in] timestamp A pointer to a time_t struct to store the time the value was read.
* @return The value of the remote characteristic.
*/
NimBLEAttValue NimBLERemoteCharacteristic::readValue(time_t *timestamp) {
NIMBLE_LOGD(LOG_TAG, ">> readValue(): uuid: %s, handle: %d 0x%.2x",
getUUID().toString().c_str(), getHandle(), getHandle());
NimBLEClient* pClient = getRemoteService()->getClient();
NimBLEAttValue value;
if (!pClient->isConnected()) {
NIMBLE_LOGE(LOG_TAG, "Disconnected");
return value;
}
int rc = 0;
int retryCount = 1;
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
ble_task_data_t taskData = {this, cur_task, 0, &value};
do {
rc = ble_gattc_read_long(pClient->getConnId(), m_handle, 0,
NimBLERemoteCharacteristic::onReadCB,
&taskData);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Error: Failed to read characteristic; rc=%d, %s",
rc, NimBLEUtils::returnCodeToString(rc));
return value;
}
#ifdef ulTaskNotifyValueClear
// Clear the task notification value to ensure we block
ulTaskNotifyValueClear(cur_task, ULONG_MAX);
#endif
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
rc = taskData.rc;
switch(rc){
case 0:
case BLE_HS_EDONE:
rc = 0;
break;
// Characteristic is not long-readable, return with what we have.
case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG):
NIMBLE_LOGI(LOG_TAG, "Attribute not long");
rc = 0;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC):
if (retryCount && pClient->secureConnection())
break;
/* Else falls through. */
default:
NIMBLE_LOGE(LOG_TAG, "<< readValue rc=%d", rc);
return value;
}
} while(rc != 0 && retryCount--);
value.setTimeStamp();
m_value = value;
if(timestamp != nullptr) {
*timestamp = value.getTimeStamp();
}
NIMBLE_LOGD(LOG_TAG, "<< readValue length: %d rc=%d", value.length(), rc);
return value;
} // readValue
/**
* @brief Callback for characteristic read operation.
* @return success == 0 or error code.
*/
int NimBLERemoteCharacteristic::onReadCB(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg)
{
ble_task_data_t *pTaskData = (ble_task_data_t*)arg;
NimBLERemoteCharacteristic *characteristic = (NimBLERemoteCharacteristic*)pTaskData->pATT;
uint16_t conn_id = characteristic->getRemoteService()->getClient()->getConnId();
if(conn_id != conn_handle) {
return 0;
}
NIMBLE_LOGI(LOG_TAG, "Read complete; status=%d conn_handle=%d", error->status, conn_handle);
NimBLEAttValue *valBuf = (NimBLEAttValue*)pTaskData->buf;
int rc = error->status;
if(rc == 0) {
if(attr) {
uint16_t data_len = OS_MBUF_PKTLEN(attr->om);
if((valBuf->size() + data_len) > BLE_ATT_ATTR_MAX_LEN) {
rc = BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} else {
NIMBLE_LOGD(LOG_TAG, "Got %u bytes", data_len);
valBuf->append(attr->om->om_data, data_len);
return 0;
}
}
}
pTaskData->rc = rc;
xTaskNotifyGive(pTaskData->task);
return rc;
} // onReadCB
/**
* @brief Subscribe or unsubscribe for notifications or indications.
* @param [in] val 0x00 to unsubscribe, 0x01 for notifications, 0x02 for indications.
* @param [in] notifyCallback A callback to be invoked for a notification.
* @param [in] response If write response required set this to true.
* If NULL is provided then no callback is performed.
* @return false if writing to the descriptor failed.
*/
bool NimBLERemoteCharacteristic::setNotify(uint16_t val, notify_callback notifyCallback, bool response) {
NIMBLE_LOGD(LOG_TAG, ">> setNotify(): %s, %02x", toString().c_str(), val);
m_notifyCallback = notifyCallback;
NimBLERemoteDescriptor* desc = getDescriptor(NimBLEUUID((uint16_t)0x2902));
if(desc == nullptr) {
NIMBLE_LOGW(LOG_TAG, "<< setNotify(): Callback set, CCCD not found");
return true;
}
NIMBLE_LOGD(LOG_TAG, "<< setNotify()");
return desc->writeValue((uint8_t *)&val, 2, response);
} // setNotify
/**
* @brief Subscribe for notifications or indications.
* @param [in] notifications If true, subscribe for notifications, false subscribe for indications.
* @param [in] notifyCallback A callback to be invoked for a notification.
* @param [in] response If true, require a write response from the descriptor write operation.
* If NULL is provided then no callback is performed.
* @return false if writing to the descriptor failed.
*/
bool NimBLERemoteCharacteristic::subscribe(bool notifications, notify_callback notifyCallback, bool response) {
if(notifications) {
return setNotify(0x01, notifyCallback, response);
} else {
return setNotify(0x02, notifyCallback, response);
}
} // subscribe
/**
* @brief Unsubscribe for notifications or indications.
* @param [in] response bool if true, require a write response from the descriptor write operation.
* @return false if writing to the descriptor failed.
*/
bool NimBLERemoteCharacteristic::unsubscribe(bool response) {
return setNotify(0x00, nullptr, response);
} // unsubscribe
/**
* @brief Delete the descriptors in the descriptor vector.
* @details We maintain a vector called m_descriptorVector that contains pointers to NimBLERemoteDescriptors
* object references. Since we allocated these in this class, we are also responsible for deleting
* them. This method does just that.
*/
void NimBLERemoteCharacteristic::deleteDescriptors() {
NIMBLE_LOGD(LOG_TAG, ">> deleteDescriptors");
for(auto &it: m_descriptorVector) {
delete it;
}
m_descriptorVector.clear();
NIMBLE_LOGD(LOG_TAG, "<< deleteDescriptors");
} // deleteDescriptors
/**
* @brief Delete descriptor by UUID
* @param [in] uuid The UUID of the descriptor to be deleted.
* @return Number of descriptors left in the vector.
*/
size_t NimBLERemoteCharacteristic::deleteDescriptor(const NimBLEUUID &uuid) {
NIMBLE_LOGD(LOG_TAG, ">> deleteDescriptor");
for(auto it = m_descriptorVector.begin(); it != m_descriptorVector.end(); ++it) {
if((*it)->getUUID() == uuid) {
delete *it;
m_descriptorVector.erase(it);
break;
}
}
NIMBLE_LOGD(LOG_TAG, "<< deleteDescriptor");
return m_descriptorVector.size();
} // deleteDescriptor
/**
* @brief Convert a NimBLERemoteCharacteristic 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 &it: m_descriptorVector) {
res += "\n" + it->toString();
}
return res;
} // toString
/**
* @brief Write a new value to the remote characteristic from a std::vector<uint8_t>.
* @param [in] vec A std::vector<uint8_t> value to write to the remote characteristic.
* @param [in] response Whether we require a response from the write.
* @return false if not connected or otherwise cannot perform write.
*/
bool NimBLERemoteCharacteristic::writeValue(const std::vector<uint8_t>& vec, bool response) {
return writeValue((uint8_t*)&vec[0], vec.size(), response);
} // writeValue
/**
* @brief Write a new value to the remote characteristic from a const char*.
* @param [in] char_s A character string to write to the remote characteristic.
* @param [in] response Whether we require a response from the write.
* @return false if not connected or otherwise cannot perform write.
*/
bool NimBLERemoteCharacteristic::writeValue(const char* char_s, bool response) {
return writeValue((uint8_t*)char_s, strlen(char_s), response);
} // writeValue
/**
* @brief Write a new value to the remote characteristic from a data buffer.
* @param [in] data A pointer to a data buffer.
* @param [in] length The length of the data in the data buffer.
* @param [in] response Whether we require a response from the write.
* @return false if not connected or otherwise cannot perform write.
*/
bool NimBLERemoteCharacteristic::writeValue(const uint8_t* data, size_t length, bool response) {
NIMBLE_LOGD(LOG_TAG, ">> writeValue(), length: %d", length);
NimBLEClient* pClient = getRemoteService()->getClient();
if (!pClient->isConnected()) {
NIMBLE_LOGE(LOG_TAG, "Disconnected");
return false;
}
int rc = 0;
int retryCount = 1;
uint16_t mtu = ble_att_mtu(pClient->getConnId()) - 3;
// Check if the data length is longer than we can write in one connection event.
// If so we must do a long write which requires a response.
if(length <= mtu && !response) {
rc = ble_gattc_write_no_rsp_flat(pClient->getConnId(), m_handle, data, length);
return (rc==0);
}
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
ble_task_data_t taskData = {this, cur_task, 0, nullptr};
do {
if(length > mtu) {
NIMBLE_LOGI(LOG_TAG,"long write %d bytes", length);
os_mbuf *om = ble_hs_mbuf_from_flat(data, length);
rc = ble_gattc_write_long(pClient->getConnId(), m_handle, 0, om,
NimBLERemoteCharacteristic::onWriteCB,
&taskData);
} else {
rc = ble_gattc_write_flat(pClient->getConnId(), m_handle,
data, length,
NimBLERemoteCharacteristic::onWriteCB,
&taskData);
}
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Error: Failed to write characteristic; rc=%d", rc);
return false;
}
#ifdef ulTaskNotifyValueClear
// Clear the task notification value to ensure we block
ulTaskNotifyValueClear(cur_task, ULONG_MAX);
#endif
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
rc = taskData.rc;
switch(rc){
case 0:
case BLE_HS_EDONE:
rc = 0;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG):
NIMBLE_LOGE(LOG_TAG, "Long write not supported by peer; Truncating length to %d", mtu);
retryCount++;
length = mtu;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC):
if (retryCount && pClient->secureConnection())
break;
/* Else falls through. */
default:
NIMBLE_LOGE(LOG_TAG, "<< writeValue, rc: %d", rc);
return false;
}
} while(rc != 0 && retryCount--);
NIMBLE_LOGD(LOG_TAG, "<< writeValue, rc: %d", rc);
return (rc == 0);
} // writeValue
/**
* @brief Callback for characteristic write operation.
* @return success == 0 or error code.
*/
int NimBLERemoteCharacteristic::onWriteCB(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg)
{
ble_task_data_t *pTaskData = (ble_task_data_t*)arg;
NimBLERemoteCharacteristic *characteristic = (NimBLERemoteCharacteristic*)pTaskData->pATT;
if(characteristic->getRemoteService()->getClient()->getConnId() != conn_handle){
return 0;
}
NIMBLE_LOGI(LOG_TAG, "Write complete; status=%d conn_handle=%d", error->status, conn_handle);
pTaskData->rc = error->status;
xTaskNotifyGive(pTaskData->task);
return 0;
}
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,85 @@
/*
* 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 "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
#include "NimBLEClient.h"
#include "NimBLEUUID.h"
#include "NimBLERemoteCharacteristic.h"
#include <vector>
class NimBLEClient;
class NimBLERemoteCharacteristic;
/**
* @brief A model of a remote %BLE service.
*/
class NimBLERemoteService {
public:
virtual ~NimBLERemoteService();
// Public methods
std::vector<NimBLERemoteCharacteristic*>::iterator begin();
std::vector<NimBLERemoteCharacteristic*>::iterator end();
NimBLERemoteCharacteristic* getCharacteristic(const char* uuid);
NimBLERemoteCharacteristic* getCharacteristic(const NimBLEUUID &uuid);
void deleteCharacteristics();
size_t deleteCharacteristic(const NimBLEUUID &uuid);
NimBLEClient* getClient(void);
//uint16_t getHandle();
NimBLEUUID getUUID(void);
std::string getValue(const NimBLEUUID &characteristicUuid);
bool setValue(const NimBLEUUID &characteristicUuid,
const std::string &value);
std::string toString(void);
std::vector<NimBLERemoteCharacteristic*>* getCharacteristics(bool refresh = false);
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(const NimBLEUUID *uuid_filter = nullptr);
static int characteristicDiscCB(uint16_t conn_handle,
const struct ble_gatt_error *error,
const struct ble_gatt_chr *chr,
void *arg);
uint16_t getStartHandle();
uint16_t getEndHandle();
void releaseSemaphores();
// Properties
// We maintain a vector of characteristics owned by this service.
std::vector<NimBLERemoteCharacteristic*> m_characteristicVector;
NimBLEClient* m_pClient;
NimBLEUUID m_uuid;
uint16_t m_startHandle;
uint16_t m_endHandle;
}; // NimBLERemoteService
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */
#endif /* COMPONENTS_NIMBLEREMOTESERVICE_H_ */

View File

@@ -0,0 +1,574 @@
/*
* NimBLEScan.cpp
*
* Created: on Jan 24 2020
* Author H2zero
*
* Originally:
*
* BLEScan.cpp
*
* Created on: Jul 1, 2017
* Author: kolban
*/
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
#include "NimBLEScan.h"
#include "NimBLEDevice.h"
#include "NimBLELog.h"
#include <string>
#include <climits>
static const char* LOG_TAG = "NimBLEScan";
/**
* @brief Scan constuctor.
*/
NimBLEScan::NimBLEScan() {
m_scan_params.filter_policy = BLE_HCI_SCAN_FILT_NO_WL;
m_scan_params.passive = 1; // If set, dont send scan requests to advertisers (i.e., dont request additional advertising data).
m_scan_params.itvl = 0; // This is defined as the time interval from when the Controller started its last LE scan until it begins the subsequent LE scan. (units=0.625 msec)
m_scan_params.window = 0; // The duration of the LE scan. LE_Scan_Window shall be less than or equal to LE_Scan_Interval (units=0.625 msec)
m_scan_params.limited = 0; // If set, only discover devices in limited discoverable mode.
m_scan_params.filter_duplicates = 1; // If set, the controller ignores all but the first advertisement from each device.
m_pScanCallbacks = nullptr;
m_ignoreResults = false;
m_pTaskData = nullptr;
m_duration = BLE_HS_FOREVER; // make sure this is non-zero in the event of a host reset
m_maxResults = 0xFF;
}
/**
* @brief Scan destructor, release any allocated resources.
*/
NimBLEScan::~NimBLEScan() {
clearResults();
}
/**
* @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) {
(void)arg;
NimBLEScan* pScan = NimBLEDevice::getScan();
switch(event->type) {
case BLE_GAP_EVENT_EXT_DISC:
case BLE_GAP_EVENT_DISC: {
if(pScan->m_ignoreResults) {
NIMBLE_LOGI(LOG_TAG, "Scan op in progress - ignoring results");
return 0;
}
#if CONFIG_BT_NIMBLE_EXT_ADV
const auto& disc = event->ext_disc;
const bool isLegacyAdv = disc.props & BLE_HCI_ADV_LEGACY_MASK;
const auto event_type = isLegacyAdv ? disc.legacy_event_type : disc.props;
#else
const auto& disc = event->disc;
const bool isLegacyAdv = true;
const auto event_type = disc.event_type;
#endif
NimBLEAddress advertisedAddress(disc.addr);
// Examine our list of ignored addresses and stop processing if we don't want to see it or are already connected
if(NimBLEDevice::isIgnored(advertisedAddress)) {
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 vector
for(auto &it: pScan->m_scanResults.m_advertisedDevicesVector) {
#if CONFIG_BT_NIMBLE_EXT_ADV
// Same address but different set ID should create a new advertised device.
if (it->getAddress() == advertisedAddress && it->getSetId() == disc.sid) {
#else
if (it->getAddress() == advertisedAddress) {
#endif
advertisedDevice = it;
break;
}
}
// If we haven't seen this device before; create a new instance and insert it in the vector.
// Otherwise just update the relevant parameters of the already known device.
if (advertisedDevice == nullptr &&
(!isLegacyAdv || event_type != BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP)) {
// Check if we have reach the scan results limit, ignore this one if so.
// We still need to store each device when maxResults is 0 to be able to append the scan results
if (pScan->m_maxResults > 0 && pScan->m_maxResults < 0xFF &&
(pScan->m_scanResults.m_advertisedDevicesVector.size() >= pScan->m_maxResults)) {
return 0;
}
advertisedDevice = new NimBLEAdvertisedDevice();
advertisedDevice->setAddress(advertisedAddress);
advertisedDevice->setAdvType(event_type, isLegacyAdv);
#if CONFIG_BT_NIMBLE_EXT_ADV
advertisedDevice->setSetId(disc.sid);
advertisedDevice->setPrimaryPhy(disc.prim_phy);
advertisedDevice->setSecondaryPhy(disc.sec_phy);
advertisedDevice->setPeriodicInterval(disc.periodic_adv_itvl);
#endif
pScan->m_scanResults.m_advertisedDevicesVector.push_back(advertisedDevice);
NIMBLE_LOGI(LOG_TAG, "New advertiser: %s", advertisedAddress.toString().c_str());
} else if (advertisedDevice != nullptr) {
NIMBLE_LOGI(LOG_TAG, "Updated advertiser: %s", advertisedAddress.toString().c_str());
} else {
// Scan response from unknown device
return 0;
}
advertisedDevice->m_timestamp = time(nullptr);
advertisedDevice->setRSSI(disc.rssi);
advertisedDevice->setPayload(disc.data, disc.length_data, (isLegacyAdv &&
event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP));
if (pScan->m_pScanCallbacks) {
if (advertisedDevice->m_callbackSent == 0 || !pScan->m_scan_params.filter_duplicates) {
advertisedDevice->m_callbackSent = 1;
pScan->m_pScanCallbacks->onDiscovered(advertisedDevice);
}
if (pScan->m_scan_params.filter_duplicates && advertisedDevice->m_callbackSent >= 2) {
return 0;
}
// If not active scanning or scan response is not available
// or extended advertisement scanning, report the result to the callback now.
if(pScan->m_scan_params.passive || !isLegacyAdv ||
(advertisedDevice->getAdvType() != BLE_HCI_ADV_TYPE_ADV_IND &&
advertisedDevice->getAdvType() != BLE_HCI_ADV_TYPE_ADV_SCAN_IND))
{
advertisedDevice->m_callbackSent = 2;
pScan->m_pScanCallbacks->onResult(advertisedDevice);
// Otherwise, wait for the scan response so we can report the complete data.
} else if (isLegacyAdv && event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP) {
advertisedDevice->m_callbackSent = 2;
pScan->m_pScanCallbacks->onResult(advertisedDevice);
}
// If not storing results and we have invoked the callback, delete the device.
if(pScan->m_maxResults == 0 && advertisedDevice->m_callbackSent >= 2) {
pScan->erase(advertisedAddress);
}
}
return 0;
}
case BLE_GAP_EVENT_DISC_COMPLETE: {
NIMBLE_LOGD(LOG_TAG, "discovery complete; reason=%d",
event->disc_complete.reason);
if(pScan->m_maxResults == 0) {
pScan->clearResults();
}
if (pScan->m_pScanCallbacks != nullptr) {
pScan->m_pScanCallbacks->onScanEnd(pScan->m_scanResults);
}
if(pScan->m_pTaskData != nullptr) {
pScan->m_pTaskData->rc = event->disc_complete.reason;
xTaskNotifyGive(pScan->m_pTaskData->task);
}
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 request a scan response.
* @param [in] active If true, we perform an active scan otherwise a passive scan.
*/
void NimBLEScan::setActiveScan(bool active) {
m_scan_params.passive = !active;
} // setActiveScan
/**
* @brief Set whether or not the BLE controller should only report results
* from devices it has not already seen.
* @param [in] enabled If true, scanned devices will only be reported once.
* @details The controller has a limited buffer and will start reporting
* duplicate devices once the limit is reached.
*/
void NimBLEScan::setDuplicateFilter(bool enabled) {
m_scan_params.filter_duplicates = enabled;
} // setDuplicateFilter
/**
* @brief Set whether or not the BLE controller only report scan results
* from devices advertising in limited discovery mode, i.e. directed advertising.
* @param [in] enabled If true, only limited discovery devices will be in scan results.
*/
void NimBLEScan::setLimitedOnly(bool enabled) {
m_scan_params.limited = enabled;
} // setLimited
/**
* @brief Sets the scan filter policy.
* @param [in] filter Can be one of:
* * BLE_HCI_SCAN_FILT_NO_WL (0)
* Scanner processes all advertising packets (white list not used) except\n
* directed, connectable advertising packets not sent to the scanner.
* * BLE_HCI_SCAN_FILT_USE_WL (1)
* Scanner processes advertisements from white list only. A connectable,\n
* directed advertisement is ignored unless it contains scanners address.
* * BLE_HCI_SCAN_FILT_NO_WL_INITA (2)
* Scanner process all advertising packets (white list not used). A\n
* connectable, directed advertisement shall not be ignored if the InitA
* is a resolvable private address.
* * BLE_HCI_SCAN_FILT_USE_WL_INITA (3)
* Scanner process advertisements from white list only. A connectable,\n
* directed advertisement shall not be ignored if the InitA is a
* resolvable private address.
*/
void NimBLEScan::setFilterPolicy(uint8_t filter) {
m_scan_params.filter_policy = filter;
} // setFilterPolicy
/**
* @brief Sets the max number of results to store.
* @param [in] maxResults The number of results to limit storage to\n
* 0 == none (callbacks only) 0xFF == unlimited, any other value is the limit.
*/
void NimBLEScan::setMaxResults(uint8_t maxResults) {
m_maxResults = maxResults;
}
/**
* @brief Set the call backs to be invoked.
* @param [in] pScanCallbacks Call backs to be invoked.
* @param [in] wantDuplicates True if we wish to be called back with duplicates. Default is false.
*/
void NimBLEScan::setScanCallbacks(NimBLEScanCallbacks* pScanCallbacks, bool wantDuplicates) {
setDuplicateFilter(!wantDuplicates);
m_pScanCallbacks = pScanCallbacks;
} // setScanCallbacks
/**
* @brief Set the interval to scan.
* @param [in] intervalMSecs The scan interval (how often) in milliseconds.
*/
void NimBLEScan::setInterval(uint16_t intervalMSecs) {
m_scan_params.itvl = intervalMSecs / 0.625;
} // 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 Get the status of the scanner.
* @return true if scanning or scan starting.
*/
bool NimBLEScan::isScanning() {
return ble_gap_disc_active();
}
/**
* @brief Start scanning.
* @param [in] duration The duration in milliseconds for which to scan.
* @param [in] is_continue Set to true to save previous scan results, false to clear them.
* @return True if scan started or false if there was an error.
*/
bool NimBLEScan::start(uint32_t duration, bool is_continue) {
NIMBLE_LOGD(LOG_TAG, ">> start: duration=%" PRIu32, duration);
// 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;
}
// Set the flag to ignore the results while we are deleting the vector
if(!is_continue) {
m_ignoreResults = true;
}
# if CONFIG_BT_NIMBLE_EXT_ADV
ble_gap_ext_disc_params scan_params;
scan_params.passive = m_scan_params.passive;
scan_params.itvl = m_scan_params.itvl;
scan_params.window = m_scan_params.window;
int rc = ble_gap_ext_disc(NimBLEDevice::m_own_addr_type,
duration/10,
0,
m_scan_params.filter_duplicates,
m_scan_params.filter_policy,
m_scan_params.limited,
&scan_params,
&scan_params,
NimBLEScan::handleGapEvent,
NULL);
#else
int rc = ble_gap_disc(NimBLEDevice::m_own_addr_type,
duration,
&m_scan_params,
NimBLEScan::handleGapEvent,
NULL);
#endif
switch(rc) {
case 0:
if(!is_continue) {
clearResults();
}
break;
case BLE_HS_EALREADY:
// Clear the cache if already scanning in case an advertiser was missed.
clearDuplicateCache();
break;
case BLE_HS_EBUSY:
NIMBLE_LOGE(LOG_TAG, "Unable to scan - connection in progress.");
break;
case BLE_HS_ETIMEOUT_HCI:
case BLE_HS_EOS:
case BLE_HS_ECONTROLLER:
case BLE_HS_ENOTSYNCED:
NIMBLE_LOGC(LOG_TAG, "Unable to scan - Host Reset");
break;
default:
NIMBLE_LOGE(LOG_TAG, "Error initiating GAP discovery procedure; rc=%d, %s",
rc, NimBLEUtils::returnCodeToString(rc));
break;
}
m_ignoreResults = false;
NIMBLE_LOGD(LOG_TAG, "<< start()");
if(rc != 0 && rc != BLE_HS_EALREADY) {
return false;
}
return true;
} // start
/**
* @brief Stop an in progress scan.
* @return True if successful.
*/
bool 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", rc);
return false;
}
if(m_maxResults == 0) {
clearResults();
}
if (rc != BLE_HS_EALREADY && m_pScanCallbacks != nullptr) {
m_pScanCallbacks->onScanEnd(m_scanResults);
}
if(m_pTaskData != nullptr) {
xTaskNotifyGive(m_pTaskData->task);
}
NIMBLE_LOGD(LOG_TAG, "<< stop()");
return true;
} // stop
/**
* @brief Clears the duplicate scan filter cache.
*/
void NimBLEScan::clearDuplicateCache() {
#ifdef CONFIG_IDF_TARGET_ESP32 // Not available for ESP32C3
esp_ble_scan_dupilcate_list_flush();
#endif
}
/**
* @brief Delete peer device from the scan results vector.
* @param [in] address The address of the device to delete from the results.
* @details After disconnecting, it may be required in the case we were connected to a device without a public address.
*/
void NimBLEScan::erase(const NimBLEAddress &address) {
NIMBLE_LOGD(LOG_TAG, "erase device: %s", address.toString().c_str());
for(auto it = m_scanResults.m_advertisedDevicesVector.begin(); it != m_scanResults.m_advertisedDevicesVector.end(); ++it) {
if((*it)->getAddress() == address) {
delete *it;
m_scanResults.m_advertisedDevicesVector.erase(it);
break;
}
}
}
/**
* @brief Called when host reset, we set a flag to stop scanning until synced.
*/
void NimBLEScan::onHostReset() {
m_ignoreResults = true;
}
/**
* @brief If the host reset and re-synced this is called.
* If the application was scanning indefinitely with a callback, restart it.
*/
void NimBLEScan::onHostSync() {
m_ignoreResults = false;
if(m_duration == 0 && m_pScanCallbacks != nullptr) {
start(0, false);
}
}
/**
* @brief Start scanning and block until scanning has been completed.
* @param [in] duration The duration in milliseconds for which to scan.
* @param [in] is_continue Set to true to save previous scan results, false to clear them.
* @return The scan results.
*/
NimBLEScanResults NimBLEScan::getResults(uint32_t duration, bool is_continue) {
if(duration == 0) {
NIMBLE_LOGW(LOG_TAG, "Blocking scan called with duration = forever");
}
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
ble_task_data_t taskData = {nullptr, cur_task, 0, nullptr};
m_pTaskData = &taskData;
if(start(duration, is_continue)) {
#ifdef ulTaskNotifyValueClear
// Clear the task notification value to ensure we block
ulTaskNotifyValueClear(cur_task, ULONG_MAX);
#endif
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
}
m_pTaskData = nullptr;
return m_scanResults;
} // getResults
/**
* @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 &it: m_scanResults.m_advertisedDevicesVector) {
delete it;
}
m_scanResults.m_advertisedDevicesVector.clear();
clearDuplicateCache();
}
/**
* @brief Dump the scan results to the log.
*/
void NimBLEScanResults::dump() {
NIMBLE_LOGD(LOG_TAG, ">> Dump scan results:");
for (int i=0; i<getCount(); i++) {
NIMBLE_LOGI(LOG_TAG, "- %s", getDevice(i).toString().c_str());
}
} // dump
/**
* @brief Get the count of devices found in the last scan.
* @return The number of devices found in the last scan.
*/
int NimBLEScanResults::getCount() {
return m_advertisedDevicesVector.size();
} // getCount
/**
* @brief Return the specified device at the given index.
* The index should be between 0 and getCount()-1.
* @param [in] i The index of the device.
* @return The device at the specified index.
*/
NimBLEAdvertisedDevice NimBLEScanResults::getDevice(uint32_t i) {
return *m_advertisedDevicesVector[i];
}
/**
* @brief Get iterator to the beginning of the vector of advertised device pointers.
* @return An iterator to the beginning of the vector of advertised device pointers.
*/
std::vector<NimBLEAdvertisedDevice*>::iterator NimBLEScanResults::begin() {
return m_advertisedDevicesVector.begin();
}
/**
* @brief Get iterator to the end of the vector of advertised device pointers.
* @return An iterator to the end of the vector of advertised device pointers.
*/
std::vector<NimBLEAdvertisedDevice*>::iterator NimBLEScanResults::end() {
return m_advertisedDevicesVector.end();
}
/**
* @brief Get a pointer to the specified device at the given address.
* If the address is not found a nullptr is returned.
* @param [in] address The address of the device.
* @return A pointer to the device at the specified address.
*/
NimBLEAdvertisedDevice *NimBLEScanResults::getDevice(const NimBLEAddress &address) {
for(size_t index = 0; index < m_advertisedDevicesVector.size(); index++) {
if(m_advertisedDevicesVector[index]->getAddress() == address) {
return m_advertisedDevicesVector[index];
}
}
return nullptr;
}
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_OBSERVER */

View File

@@ -0,0 +1,128 @@
/*
* 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 "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
#include "NimBLEAdvertisedDevice.h"
#include "NimBLEUtils.h"
#if defined(CONFIG_NIMBLE_CPP_IDF)
#include "host/ble_gap.h"
#else
#include "nimble/nimble/host/include/host/ble_gap.h"
#endif
#include <vector>
class NimBLEDevice;
class NimBLEScan;
class NimBLEAdvertisedDevice;
class NimBLEScanCallbacks;
class NimBLEAddress;
/**
* @brief A class that contains and operates on the results of a BLE scan.
* @details When a scan completes, we have a set of found devices. Each device is described
* by a NimBLEAdvertisedDevice 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);
std::vector<NimBLEAdvertisedDevice*>::iterator begin();
std::vector<NimBLEAdvertisedDevice*>::iterator end();
NimBLEAdvertisedDevice *getDevice(const NimBLEAddress &address);
private:
friend NimBLEScan;
std::vector<NimBLEAdvertisedDevice*> m_advertisedDevicesVector;
};
/**
* @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, bool is_continue = false);
bool isScanning();
void setScanCallbacks(NimBLEScanCallbacks* pScanCallbacks, bool wantDuplicates = false);
void setActiveScan(bool active);
void setInterval(uint16_t intervalMSecs);
void setWindow(uint16_t windowMSecs);
void setDuplicateFilter(bool enabled);
void setLimitedOnly(bool enabled);
void setFilterPolicy(uint8_t filter);
void clearDuplicateCache();
bool stop();
void clearResults();
NimBLEScanResults getResults();
NimBLEScanResults getResults(uint32_t duration, bool is_continue = false);
void setMaxResults(uint8_t maxResults);
void erase(const NimBLEAddress &address);
private:
friend class NimBLEDevice;
NimBLEScan();
~NimBLEScan();
static int handleGapEvent(ble_gap_event* event, void* arg);
void onHostReset();
void onHostSync();
NimBLEScanCallbacks* m_pScanCallbacks;
ble_gap_disc_params m_scan_params;
bool m_ignoreResults;
NimBLEScanResults m_scanResults;
uint32_t m_duration;
ble_task_data_t *m_pTaskData;
uint8_t m_maxResults;
};
/**
* @brief A callback handler for callbacks associated device scanning.
*/
class NimBLEScanCallbacks {
public:
virtual ~NimBLEScanCallbacks() {}
/**
* @brief Called when a new device is discovered, before the scan result is received (if applicable).
* @param [in] advertisedDevice The device which was discovered.
*/
virtual void onDiscovered(NimBLEAdvertisedDevice* advertisedDevice) {};
/**
* @brief Called when a new scan result is complete, including scan response data (if applicable).
* @param [in] advertisedDevice The device for which the complete result is available.
*/
virtual void onResult(NimBLEAdvertisedDevice* advertisedDevice) {};
/**
* @brief Called when a scan operation ends.
* @param [in] scanResults The results of the scan that ended.
*/
virtual void onScanEnd(NimBLEScanResults scanResults) {};
};
#endif /* CONFIG_BT_ENABLED CONFIG_BT_NIMBLE_ROLE_OBSERVER */
#endif /* COMPONENTS_NIMBLE_SCAN_H_ */

View File

@@ -0,0 +1,887 @@
/*
* NimBLEServer.cpp
*
* Created: on March 2, 2020
* Author H2zero
*
* Originally:
*
* BLEServer.cpp
*
* Created on: Apr 16, 2017
* Author: kolban
*/
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#include "NimBLEServer.h"
#include "NimBLEDevice.h"
#include "NimBLELog.h"
#if defined(CONFIG_NIMBLE_CPP_IDF)
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
#else
#include "nimble/nimble/host/services/gap/include/services/gap/ble_svc_gap.h"
#include "nimble/nimble/host/services/gatt/include/services/gatt/ble_svc_gatt.h"
#endif
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 NimBLEDevice class.
*/
NimBLEServer::NimBLEServer() {
memset(m_indWait, BLE_HS_CONN_HANDLE_NONE, sizeof(m_indWait));
// m_svcChgChrHdl = 0xffff; // Future Use
m_pServerCallbacks = &defaultCallbacks;
m_gattsStarted = false;
#if !CONFIG_BT_NIMBLE_EXT_ADV
m_advertiseOnDisconnect = true;
#endif
m_svcChanged = false;
m_deleteCallbacks = true;
} // NimBLEServer
/**
* @brief Destructor: frees all resources / attributes created.
*/
NimBLEServer::~NimBLEServer() {
for(auto &it : m_svcVec) {
delete it;
}
if(m_deleteCallbacks && m_pServerCallbacks != &defaultCallbacks) {
delete m_pServerCallbacks;
}
}
/**
* @brief Create a %BLE Service.
* @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));
} // createService
/**
* @brief Create a %BLE Service.
* @param [in] uuid The UUID of the new service.
* @return A reference to the new service object.
*/
NimBLEService* NimBLEServer::createService(const NimBLEUUID &uuid) {
NIMBLE_LOGD(LOG_TAG, ">> createService - %s", uuid.toString().c_str());
// Check that a service with the supplied UUID does not already exist.
if(getServiceByUUID(uuid) != nullptr) {
NIMBLE_LOGW(LOG_TAG, "Warning creating a duplicate service UUID: %s",
std::string(uuid).c_str());
}
NimBLEService* pService = new NimBLEService(uuid);
m_svcVec.push_back(pService);
serviceChanged();
NIMBLE_LOGD(LOG_TAG, "<< createService");
return pService;
} // createService
/**
* @brief Get a %BLE Service by its UUID
* @param [in] uuid The UUID of the service.
* @param instanceId The index of the service to return (used when multiple services have the same UUID).
* @return A pointer to the service object or nullptr if not found.
*/
NimBLEService* NimBLEServer::getServiceByUUID(const char* uuid, uint16_t instanceId) {
return getServiceByUUID(NimBLEUUID(uuid), instanceId);
} // getServiceByUUID
/**
* @brief Get a %BLE Service by its UUID
* @param [in] uuid The UUID of the service.
* @param instanceId The index of the service to return (used when multiple services have the same UUID).
* @return A pointer to the service object or nullptr if not found.
*/
NimBLEService* NimBLEServer::getServiceByUUID(const NimBLEUUID &uuid, uint16_t instanceId) {
uint16_t position = 0;
for (auto &it : m_svcVec) {
if (it->getUUID() == uuid) {
if (position == instanceId){
return it;
}
position++;
}
}
return nullptr;
} // getServiceByUUID
/**
* @brief Get a %BLE Service by its handle
* @param handle The handle of the service.
* @return A pointer to the service object or nullptr if not found.
*/
NimBLEService *NimBLEServer::getServiceByHandle(uint16_t handle) {
for (auto &it : m_svcVec) {
if (it->getHandle() == handle) {
return it;
}
}
return nullptr;
}
#if CONFIG_BT_NIMBLE_EXT_ADV
/**
* @brief Retrieve the advertising object that can be used to advertise the existence of the server.
* @return An advertising object.
*/
NimBLEExtAdvertising* NimBLEServer::getAdvertising() {
return NimBLEDevice::getAdvertising();
} // getAdvertising
#endif
#if !CONFIG_BT_NIMBLE_EXT_ADV || defined(_DOXYGEN_)
/**
* @brief Retrieve the advertising object that can be used to advertise the existence of the server.
* @return An advertising object.
*/
NimBLEAdvertising* NimBLEServer::getAdvertising() {
return NimBLEDevice::getAdvertising();
} // getAdvertising
#endif
/**
* @brief Sends a service changed notification and resets the GATT server.
*/
void NimBLEServer::serviceChanged() {
if(m_gattsStarted) {
m_svcChanged = true;
ble_svc_gatt_changed(0x0001, 0xffff);
resetGATT();
}
}
/**
* @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_NIMBLE_CPP_LOG_LEVEL >= 4
ble_gatts_show_local();
#endif
/*** Future use ***
* TODO: implement service changed handling
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);
*/
// Get the assigned service handles and build a vector of characteristics
// with Notify / Indicate capabilities for event handling
for(auto &svc : m_svcVec) {
if(svc->m_removed == 0) {
rc = ble_gatts_find_svc(&svc->getUUID().getNative()->u, &svc->m_handle);
if(rc != 0) {
NIMBLE_LOGW(LOG_TAG, "GATT Server started without service: %s, Service %s",
svc->getUUID().toString().c_str(), svc->isStarted() ? "missing" : "not started");
continue; // Skip this service as it was not started
}
}
for(auto &chr : svc->m_chrVec) {
// if Notify / Indicate is enabled but we didn't create the descriptor
// we do it now.
if((chr->m_properties & BLE_GATT_CHR_F_INDICATE) ||
(chr->m_properties & BLE_GATT_CHR_F_NOTIFY)) {
m_notifyChrVec.push_back(chr);
}
}
}
m_gattsStarted = true;
} // start
/**
* @brief Disconnect the specified client with optional reason.
* @param [in] connId 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));
}
NIMBLE_LOGD(LOG_TAG, "<< disconnect()");
return rc;
} // disconnect
/**
* @brief Disconnect the specified client with optional reason.
* @param [in] connInfo Connection of the client to disconnect.
* @param [in] reason code for disconnecting.
* @return NimBLE host return code.
*/
int NimBLEServer::disconnect(const NimBLEConnInfo &connInfo, uint8_t reason) {
return disconnect(connInfo.getConnHandle(), reason);
} // disconnect
#if !CONFIG_BT_NIMBLE_EXT_ADV || defined(_DOXYGEN_)
/**
* @brief Set the server to automatically start advertising when a client disconnects.
* @param [in] aod true == advertise, false == don't advertise.
*/
void NimBLEServer::advertiseOnDisconnect(bool aod) {
m_advertiseOnDisconnect = aod;
} // advertiseOnDisconnect
#endif
/**
* @brief Return the number of connected clients.
* @return The number of connected clients.
*/
size_t NimBLEServer::getConnectedCount() {
return m_connectedPeersVec.size();
} // getConnectedCount
/**
* @brief Get the vector of the connected client ID's.
*/
std::vector<uint16_t> NimBLEServer::getPeerDevices() {
return m_connectedPeersVec;
} // getPeerDevices
/**
* @brief Get the connection information of a connected peer by vector index.
* @param [in] index The vector index of the peer.
*/
NimBLEConnInfo NimBLEServer::getPeerInfo(size_t index) {
if (index >= m_connectedPeersVec.size()) {
NIMBLE_LOGE(LOG_TAG, "No peer at index %u", index);
return NimBLEConnInfo();
}
return getPeerIDInfo(m_connectedPeersVec[index]);
} // getPeerInfo
/**
* @brief Get the connection information of a connected peer by address.
* @param [in] address The address of the peer.
*/
NimBLEConnInfo NimBLEServer::getPeerInfo(const NimBLEAddress& address) {
ble_addr_t peerAddr;
memcpy(&peerAddr.val, address.getNative(),6);
peerAddr.type = address.getType();
NimBLEConnInfo peerInfo;
int rc = ble_gap_conn_find_by_addr(&peerAddr, &peerInfo.m_desc);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Peer info not found");
}
return peerInfo;
} // getPeerInfo
/**
* @brief Get the connection information of a connected peer by connection ID.
* @param [in] id The connection id of the peer.
*/
NimBLEConnInfo NimBLEServer::getPeerIDInfo(uint16_t id) {
NimBLEConnInfo peerInfo;
int rc = ble_gap_conn_find(id, &peerInfo.m_desc);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Peer info not found");
}
return peerInfo;
} // getPeerIDInfo
/**
* @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) {
NIMBLE_LOGD(LOG_TAG, ">> handleGapEvent: %s", NimBLEUtils::gapEventToString(event->type));
int rc = 0;
NimBLEConnInfo peerInfo;
NimBLEServer* pServer = NimBLEDevice::getServer();
switch(event->type) {
case BLE_GAP_EVENT_CONNECT: {
if (event->connect.status != 0) {
/* Connection failed; resume advertising */
NIMBLE_LOGE(LOG_TAG, "Connection failed");
#if !CONFIG_BT_NIMBLE_EXT_ADV
NimBLEDevice::startAdvertising();
#endif
}
else {
pServer->m_connectedPeersVec.push_back(event->connect.conn_handle);
rc = ble_gap_conn_find(event->connect.conn_handle, &peerInfo.m_desc);
if (rc != 0) {
return 0;
}
pServer->m_pServerCallbacks->onConnect(pServer, peerInfo);
}
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 resync.
switch(event->disconnect.reason) {
case BLE_HS_ETIMEOUT_HCI:
case BLE_HS_EOS:
case BLE_HS_ECONTROLLER:
case BLE_HS_ENOTSYNCED:
NIMBLE_LOGC(LOG_TAG, "Disconnect - host reset, rc=%d", event->disconnect.reason);
NimBLEDevice::onReset(event->disconnect.reason);
break;
default:
break;
}
pServer->m_connectedPeersVec.erase(std::remove(pServer->m_connectedPeersVec.begin(),
pServer->m_connectedPeersVec.end(),
event->disconnect.conn.conn_handle),
pServer->m_connectedPeersVec.end());
if(pServer->m_svcChanged) {
pServer->resetGATT();
}
NimBLEConnInfo peerInfo(event->disconnect.conn);
pServer->m_pServerCallbacks->onDisconnect(pServer, peerInfo, event->disconnect.reason);
#if !CONFIG_BT_NIMBLE_EXT_ADV
if(pServer->m_advertiseOnDisconnect) {
pServer->startAdvertising();
}
#endif
return 0;
} // BLE_GAP_EVENT_DISCONNECT
case BLE_GAP_EVENT_SUBSCRIBE: {
NIMBLE_LOGI(LOG_TAG, "subscribe event; attr_handle=%d, subscribed: %s",
event->subscribe.attr_handle,
(event->subscribe.cur_notify ? "true":"false"));
for(auto &it : pServer->m_notifyChrVec) {
if(it->getHandle() == event->subscribe.attr_handle) {
if((it->getProperties() & BLE_GATT_CHR_F_READ_AUTHEN) ||
(it->getProperties() & BLE_GATT_CHR_F_READ_AUTHOR) ||
(it->getProperties() & BLE_GATT_CHR_F_READ_ENC))
{
rc = ble_gap_conn_find(event->subscribe.conn_handle, &peerInfo.m_desc);
if (rc != 0) {
break;
}
if(!peerInfo.isEncrypted()) {
NimBLEDevice::startSecurity(event->subscribe.conn_handle);
}
}
it->setSubscribe(event);
break;
}
}
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);
rc = ble_gap_conn_find(event->mtu.conn_handle, &peerInfo.m_desc);
if (rc != 0) {
return 0;
}
pServer->m_pServerCallbacks->onMTUChange(event->mtu.value, peerInfo);
return 0;
} // BLE_GAP_EVENT_MTU
case BLE_GAP_EVENT_NOTIFY_TX: {
NimBLECharacteristic *pChar = nullptr;
for(auto &it : pServer->m_notifyChrVec) {
if(it->getHandle() == event->notify_tx.attr_handle) {
pChar = it;
}
}
if(pChar == nullptr) {
return 0;
}
if(event->notify_tx.indication) {
if(event->notify_tx.status == 0) {
return 0; // Indication sent but not yet acknowledged.
}
pServer->clearIndicateWait(event->notify_tx.conn_handle);
}
pChar->m_pCallbacks->onStatus(pChar, event->notify_tx.status);
return 0;
} // BLE_GAP_EVENT_NOTIFY_TX
case BLE_GAP_EVENT_ADV_COMPLETE:
#if CONFIG_BT_NIMBLE_EXT_ADV
case BLE_GAP_EVENT_SCAN_REQ_RCVD:
return NimBLEExtAdvertising::handleGapEvent(event, arg);
#else
return NimBLEAdvertising::handleGapEvent(event, arg);
#endif
// BLE_GAP_EVENT_ADV_COMPLETE | BLE_GAP_EVENT_SCAN_REQ_RCVD
case BLE_GAP_EVENT_CONN_UPDATE: {
NIMBLE_LOGD(LOG_TAG, "Connection parameters updated.");
return 0;
} // BLE_GAP_EVENT_CONN_UPDATE
case BLE_GAP_EVENT_REPEAT_PAIRING: {
/* We already have a bond with the peer, but it is attempting to
* establish a new secure link. This app sacrifices security for
* convenience: just throw away the old bond and accept the new link.
*/
/* Delete the old bond. */
rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &peerInfo.m_desc);
if (rc != 0){
return BLE_GAP_REPEAT_PAIRING_IGNORE;
}
ble_store_util_delete_peer(&peerInfo.m_desc.peer_id_addr);
/* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should
* continue with the pairing operation.
*/
return BLE_GAP_REPEAT_PAIRING_RETRY;
} // BLE_GAP_EVENT_REPEAT_PAIRING
case BLE_GAP_EVENT_ENC_CHANGE: {
rc = ble_gap_conn_find(event->enc_change.conn_handle, &peerInfo.m_desc);
if(rc != 0) {
return BLE_ATT_ERR_INVALID_HANDLE;
}
pServer->m_pServerCallbacks->onAuthenticationComplete(peerInfo);
return 0;
} // BLE_GAP_EVENT_ENC_CHANGE
case BLE_GAP_EVENT_IDENTITY_RESOLVED: {
rc = ble_gap_conn_find(event->identity_resolved.conn_handle, &peerInfo.m_desc);
if(rc != 0) {
return BLE_ATT_ERR_INVALID_HANDLE;
}
pServer->m_pServerCallbacks->onIdentity(peerInfo);
return 0;
} // BLE_GAP_EVENT_IDENTITY_RESOLVED
case BLE_GAP_EVENT_PASSKEY_ACTION: {
struct ble_sm_io pkey = {0,0};
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 = pServer->m_pServerCallbacks->onPassKeyDisplay();
}
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
NIMBLE_LOGD(LOG_TAG, "BLE_SM_IOACT_DISP; ble_sm_inject_io result: %d", rc);
} else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) {
NIMBLE_LOGD(LOG_TAG, "Passkey on device's display: %" PRIu32, event->passkey.params.numcmp);
rc = ble_gap_conn_find(event->passkey.conn_handle, &peerInfo.m_desc);
if(rc != 0) {
return BLE_ATT_ERR_INVALID_HANDLE;
}
pServer->m_pServerCallbacks->onConfirmPIN(peerInfo, event->passkey.params.numcmp);
//TODO: Handle out of band pairing
} else if (event->passkey.params.action == BLE_SM_IOACT_OOB) {
static uint8_t tem_oob[16] = {0};
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_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;
} // handleGapEvent
/**
* @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.
* @param [in] deleteCallbacks if true callback class will be deleted when server is destructed.
*/
void NimBLEServer::setCallbacks(NimBLEServerCallbacks* pCallbacks, bool deleteCallbacks) {
if (pCallbacks != nullptr){
m_pServerCallbacks = pCallbacks;
m_deleteCallbacks = deleteCallbacks;
} else {
m_pServerCallbacks = &defaultCallbacks;
}
} // setCallbacks
/**
* @brief Remove a service from the server.
*
* @details Immediately removes access to the service by clients, sends a service changed indication,
* and removes the service (if applicable) from the advertisements.
* The service is not deleted unless the deleteSvc parameter is true, otherwise the service remains
* available and can be re-added in the future. If desired a removed but not deleted service can
* be deleted later by calling this method with deleteSvc set to true.
*
* @note The service will not be removed from the database until all open connections are closed
* as it requires resetting the GATT server. In the interim the service will have it's visibility disabled.
*
* @note Advertising will need to be restarted by the user after calling this as we must stop
* advertising in order to remove the service.
*
* @param [in] service The service object to remove.
* @param [in] deleteSvc true if the service should be deleted.
*/
void NimBLEServer::removeService(NimBLEService* service, bool deleteSvc) {
// Check if the service was already removed and if so check if this
// is being called to delete the object and do so if requested.
// Otherwise, ignore the call and return.
if(service->m_removed > 0) {
if(deleteSvc) {
for(auto it = m_svcVec.begin(); it != m_svcVec.end(); ++it) {
if ((*it) == service) {
delete *it;
m_svcVec.erase(it);
break;
}
}
}
return;
}
int rc = ble_gatts_svc_set_visibility(service->getHandle(), 0);
if(rc !=0) {
return;
}
service->m_removed = deleteSvc ? NIMBLE_ATT_REMOVE_DELETE : NIMBLE_ATT_REMOVE_HIDE;
serviceChanged();
#if !CONFIG_BT_NIMBLE_EXT_ADV
NimBLEDevice::getAdvertising()->removeServiceUUID(service->getUUID());
#endif
}
/**
* @brief Adds a service which was either already created but removed from availability,\n
* or created and later added to services list.
* @param [in] service The service object to add.
* @note If it is desired to advertise the service it must be added by
* calling NimBLEAdvertising::addServiceUUID.
*/
void NimBLEServer::addService(NimBLEService* service) {
// Check that a service with the supplied UUID does not already exist.
if(getServiceByUUID(service->getUUID()) != nullptr) {
NIMBLE_LOGW(LOG_TAG, "Warning creating a duplicate service UUID: %s",
std::string(service->getUUID()).c_str());
}
// If adding a service that was not removed add it and return.
// Else reset GATT and send service changed notification.
if(service->m_removed == 0) {
m_svcVec.push_back(service);
return;
}
service->m_removed = 0;
serviceChanged();
}
/**
* @brief Resets the GATT server, used when services are added/removed after initialization.
*/
void NimBLEServer::resetGATT() {
if(getConnectedCount() > 0) {
return;
}
NimBLEDevice::stopAdvertising();
ble_gatts_reset();
ble_svc_gap_init();
ble_svc_gatt_init();
for(auto it = m_svcVec.begin(); it != m_svcVec.end(); ) {
if ((*it)->m_removed > 0) {
if ((*it)->m_removed == NIMBLE_ATT_REMOVE_DELETE) {
delete *it;
it = m_svcVec.erase(it);
} else {
++it;
}
continue;
}
(*it)->start();
++it;
}
m_svcChanged = false;
m_gattsStarted = false;
}
#if CONFIG_BT_NIMBLE_EXT_ADV
/**
* @brief Start advertising.
* @param [in] inst_id The extended advertisement instance ID to start.
* @param [in] duration How long to advertise for in milliseconds, 0 = forever (default).
* @param [in] max_events Maximum number of advertisement events to send, 0 = no limit (default).
* @return True if advertising started successfully.
* @details Start the server advertising its existence. This is a convenience function and is equivalent to
* retrieving the advertising object and invoking start upon it.
*/
bool NimBLEServer::startAdvertising(uint8_t inst_id,
int duration,
int max_events) {
return getAdvertising()->start(inst_id, duration, max_events);
} // startAdvertising
/**
* @brief Convenience function to stop advertising a data set.
* @param [in] inst_id The extended advertisement instance ID to stop advertising.
* @return True if advertising stopped successfully.
*/
bool NimBLEServer::stopAdvertising(uint8_t inst_id) {
return getAdvertising()->stop(inst_id);
} // stopAdvertising
#endif
#if !CONFIG_BT_NIMBLE_EXT_ADV || defined(_DOXYGEN_)
/**
* @brief Start advertising.
* @param [in] duration The duration in milliseconds to advertise for, default = forever.
* @return True if advertising started successfully.
* @details Start the server advertising its existence. This is a convenience function and is equivalent to
* retrieving the advertising object and invoking start upon it.
*/
bool NimBLEServer::startAdvertising(uint32_t duration) {
return getAdvertising()->start(duration);
} // startAdvertising
#endif
/**
* @brief Stop advertising.
* @return True if advertising stopped successfully.
*/
bool NimBLEServer::stopAdvertising() {
return getAdvertising()->stop();
} // stopAdvertising
/**
* @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) {
return ble_att_mtu(conn_id);
} //getPeerMTU
/**
* @brief Request an Update the connection parameters:
* * Can only be used after a connection has been established.
* @param [in] conn_handle The connection handle of the peer to send the request to.
* @param [in] minInterval The minimum connection interval in 1.25ms units.
* @param [in] maxInterval The maximum connection interval in 1.25ms units.
* @param [in] latency The number of packets allowed to skip (extends max interval).
* @param [in] timeout The timeout time in 10ms units before disconnecting.
*/
void NimBLEServer::updateConnParams(uint16_t conn_handle,
uint16_t minInterval, uint16_t maxInterval,
uint16_t latency, uint16_t timeout)
{
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 = BLE_GAP_INITIAL_CONN_MIN_CE_LEN; // Minimum length of connection event in 0.625ms units
params.max_ce_len = BLE_GAP_INITIAL_CONN_MAX_CE_LEN; // Maximum length of connection event in 0.625ms units
int rc = ble_gap_update_params(conn_handle, &params);
if(rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Update params error: %d, %s", rc, NimBLEUtils::returnCodeToString(rc));
}
} // updateConnParams
/**
* @brief Request an update of the data packet length.
* * Can only be used after a connection has been established.
* @details Sends a data length update request to the peer.
* The Data Length Extension (DLE) allows to increase the Data Channel Payload from 27 bytes to up to 251 bytes.
* The peer needs to support the Bluetooth 4.2 specifications, to be capable of DLE.
* @param [in] conn_handle The connection handle of the peer to send the request to.
* @param [in] tx_octets The preferred number of payload octets to use (Range 0x001B-0x00FB).
*/
void NimBLEServer::setDataLen(uint16_t conn_handle, uint16_t tx_octets) {
#if defined(CONFIG_NIMBLE_CPP_IDF) && !defined(ESP_IDF_VERSION) || \
(ESP_IDF_VERSION_MAJOR * 100 + ESP_IDF_VERSION_MINOR * 10 + ESP_IDF_VERSION_PATCH) < 432
return;
#else
uint16_t tx_time = (tx_octets + 14) * 8;
int rc = ble_gap_set_data_len(conn_handle, tx_octets, tx_time);
if(rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Set data length error: %d, %s", rc, NimBLEUtils::returnCodeToString(rc));
}
#endif
} // setDataLen
bool NimBLEServer::setIndicateWait(uint16_t conn_handle) {
for(auto i = 0; i < CONFIG_BT_NIMBLE_MAX_CONNECTIONS; i++) {
if(m_indWait[i] == conn_handle) {
return false;
}
}
return true;
}
void NimBLEServer::clearIndicateWait(uint16_t conn_handle) {
for(auto i = 0; i < CONFIG_BT_NIMBLE_MAX_CONNECTIONS; i++) {
if(m_indWait[i] == conn_handle) {
m_indWait[i] = BLE_HS_CONN_HANDLE_NONE;
return;
}
}
}
/** Default callback handlers */
void NimBLEServerCallbacks::onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) {
NIMBLE_LOGD("NimBLEServerCallbacks", "onConnect(): Default");
} // onConnect
void NimBLEServerCallbacks::onDisconnect(NimBLEServer* pServer,
NimBLEConnInfo& connInfo, int reason) {
NIMBLE_LOGD("NimBLEServerCallbacks", "onDisconnect(): Default");
} // onDisconnect
void NimBLEServerCallbacks::onMTUChange(uint16_t MTU, NimBLEConnInfo& connInfo) {
NIMBLE_LOGD("NimBLEServerCallbacks", "onMTUChange(): Default");
} // onMTUChange
uint32_t NimBLEServerCallbacks::onPassKeyDisplay(){
NIMBLE_LOGD("NimBLEServerCallbacks", "onPassKeyDisplay: default: 123456");
return 123456;
} //onPassKeyDisplay
void NimBLEServerCallbacks::onConfirmPIN(const NimBLEConnInfo& connInfo, uint32_t pin){
NIMBLE_LOGD("NimBLEServerCallbacks", "onConfirmPIN: default: true");
NimBLEDevice::injectConfirmPIN(connInfo, true);
} // onConfirmPIN
void NimBLEServerCallbacks::onIdentity(const NimBLEConnInfo& connInfo){
NIMBLE_LOGD("NimBLEServerCallbacks", "onIdentity: default");
} // onIdentity
void NimBLEServerCallbacks::onAuthenticationComplete(const NimBLEConnInfo& connInfo){
NIMBLE_LOGD("NimBLEServerCallbacks", "onAuthenticationComplete: default");
} // onAuthenticationComplete
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */

View File

@@ -0,0 +1,185 @@
/*
* 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 "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#define NIMBLE_ATT_REMOVE_HIDE 1
#define NIMBLE_ATT_REMOVE_DELETE 2
#define onMtuChanged onMTUChange
#include "NimBLEUtils.h"
#include "NimBLEAddress.h"
#if CONFIG_BT_NIMBLE_EXT_ADV
#include "NimBLEExtAdvertising.h"
#else
#include "NimBLEAdvertising.h"
#endif
#include "NimBLEService.h"
#include "NimBLEConnInfo.h"
class NimBLEService;
class NimBLECharacteristic;
class NimBLEServerCallbacks;
/**
* @brief The model of a %BLE server.
*/
class NimBLEServer {
public:
size_t getConnectedCount();
NimBLEService* createService(const char* uuid);
NimBLEService* createService(const NimBLEUUID &uuid);
void removeService(NimBLEService* service, bool deleteSvc = false);
void addService(NimBLEService* service);
void setCallbacks(NimBLEServerCallbacks* pCallbacks,
bool deleteCallbacks = true);
#if CONFIG_BT_NIMBLE_EXT_ADV
NimBLEExtAdvertising* getAdvertising();
bool startAdvertising(uint8_t inst_id,
int duration = 0,
int max_events = 0);
bool stopAdvertising(uint8_t inst_id);
#endif
# if !CONFIG_BT_NIMBLE_EXT_ADV || defined(_DOXYGEN_)
NimBLEAdvertising* getAdvertising();
bool startAdvertising(uint32_t duration = 0);
#endif
bool stopAdvertising();
void start();
NimBLEService* getServiceByUUID(const char* uuid, uint16_t instanceId = 0);
NimBLEService* getServiceByUUID(const NimBLEUUID &uuid, uint16_t instanceId = 0);
NimBLEService* getServiceByHandle(uint16_t handle);
int disconnect(uint16_t connID,
uint8_t reason = BLE_ERR_REM_USER_CONN_TERM);
int disconnect(const NimBLEConnInfo &connInfo,
uint8_t reason = BLE_ERR_REM_USER_CONN_TERM);
void updateConnParams(uint16_t conn_handle,
uint16_t minInterval, uint16_t maxInterval,
uint16_t latency, uint16_t timeout);
void setDataLen(uint16_t conn_handle, uint16_t tx_octets);
uint16_t getPeerMTU(uint16_t conn_id);
std::vector<uint16_t> getPeerDevices();
NimBLEConnInfo getPeerInfo(size_t index);
NimBLEConnInfo getPeerInfo(const NimBLEAddress& address);
NimBLEConnInfo getPeerIDInfo(uint16_t id);
#if !CONFIG_BT_NIMBLE_EXT_ADV || defined(_DOXYGEN_)
void advertiseOnDisconnect(bool);
#endif
private:
NimBLEServer();
~NimBLEServer();
friend class NimBLECharacteristic;
friend class NimBLEService;
friend class NimBLEDevice;
friend class NimBLEAdvertising;
#if CONFIG_BT_NIMBLE_EXT_ADV
friend class NimBLEExtAdvertising;
friend class NimBLEExtAdvertisementData;
#endif
bool m_gattsStarted;
#if !CONFIG_BT_NIMBLE_EXT_ADV
bool m_advertiseOnDisconnect;
#endif
bool m_svcChanged;
NimBLEServerCallbacks* m_pServerCallbacks;
bool m_deleteCallbacks;
uint16_t m_indWait[CONFIG_BT_NIMBLE_MAX_CONNECTIONS];
std::vector<uint16_t> m_connectedPeersVec;
// uint16_t m_svcChgChrHdl; // Future use
std::vector<NimBLEService*> m_svcVec;
std::vector<NimBLECharacteristic*> m_notifyChrVec;
static int handleGapEvent(struct ble_gap_event *event, void *arg);
void serviceChanged();
void resetGATT();
bool setIndicateWait(uint16_t conn_handle);
void clearIndicateWait(uint16_t conn_handle);
}; // NimBLEServer
/**
* @brief Callbacks associated with the operation of a %BLE server.
*/
class NimBLEServerCallbacks {
public:
virtual ~NimBLEServerCallbacks() {};
/**
* @brief Handle a client connection.
* This is called when a client connects.
* @param [in] pServer A pointer to the %BLE server that received the client connection.
* @param [in] connInfo A reference to a NimBLEConnInfo instance with information
* about the peer connection parameters.
*/
virtual void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo);
/**
* @brief Handle a client disconnection.
* This is called when a client discconnects.
* @param [in] pServer A pointer to the %BLE server that received the client disconnection.
* @param [in] connInfo A reference to a NimBLEConnInfo instance with information
* about the peer connection parameters.
* @param [in] reason The reason code for the disconnection.
*/
virtual void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason);
/**
* @brief Called when the connection MTU changes.
* @param [in] MTU The new MTU value.
* @param [in] connInfo A reference to a NimBLEConnInfo instance with information
* about the peer connection parameters.
*/
virtual void onMTUChange(uint16_t MTU, NimBLEConnInfo& connInfo);
/**
* @brief Called when a client requests a passkey for pairing (display).
* @return The passkey to be sent to the client.
*/
virtual uint32_t onPassKeyDisplay();
/**
* @brief Called when using numeric comparision for pairing.
* @param [in] connInfo A reference to a NimBLEConnInfo instance with information
* Should be passed back to NimBLEDevice::injectConfirmPIN
* @param [in] pin The pin to compare with the client.
*/
virtual void onConfirmPIN(const NimBLEConnInfo& connInfo, uint32_t pin);
/**
* @brief Called when the pairing procedure is complete.
* @param [in] connInfo A reference to a NimBLEConnInfo instance with information
* about the peer connection parameters.
*/
virtual void onAuthenticationComplete(const NimBLEConnInfo& connInfo);
/**
* @brief Called when the peer identity address is resolved.
* @param [in] connInfo A reference to a NimBLEConnInfo instance with information
*/
virtual void onIdentity(const NimBLEConnInfo& connInfo);
}; // NimBLEServerCallbacks
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */
#endif /* MAIN_NIMBLESERVER_H_ */

View File

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

View File

@@ -0,0 +1,87 @@
/*
* 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 "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#include "NimBLEServer.h"
#include "NimBLECharacteristic.h"
#include "NimBLEUUID.h"
class NimBLEServer;
class NimBLECharacteristic;
/**
* @brief The model of a %BLE service.
*
*/
class NimBLEService {
public:
NimBLEService(const char* uuid);
NimBLEService(const NimBLEUUID &uuid);
~NimBLEService();
NimBLEServer* getServer();
NimBLEUUID getUUID();
uint16_t getHandle();
std::string toString();
void dump();
bool isStarted();
bool start();
NimBLECharacteristic* createCharacteristic(const char* uuid,
uint32_t properties =
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);
NimBLECharacteristic* createCharacteristic(const NimBLEUUID &uuid,
uint32_t properties =
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);
void addCharacteristic(NimBLECharacteristic* pCharacteristic);
void removeCharacteristic(NimBLECharacteristic* pCharacteristic, bool deleteChr = false);
NimBLECharacteristic* getCharacteristic(const char* uuid, uint16_t instanceId = 0);
NimBLECharacteristic* getCharacteristic(const NimBLEUUID &uuid, uint16_t instanceId = 0);
NimBLECharacteristic* getCharacteristicByHandle(uint16_t handle);
std::vector<NimBLECharacteristic*> getCharacteristics();
std::vector<NimBLECharacteristic*> getCharacteristics(const char* uuid);
std::vector<NimBLECharacteristic*> getCharacteristics(const NimBLEUUID &uuid);
private:
friend class NimBLEServer;
friend class NimBLEDevice;
uint16_t m_handle;
NimBLEUUID m_uuid;
ble_gatt_svc_def* m_pSvcDef;
uint8_t m_removed;
std::vector<NimBLECharacteristic*> m_chrVec;
}; // NimBLEService
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */
#endif /* MAIN_NIMBLESERVICE_H_ */

View File

@@ -0,0 +1,360 @@
/*
* NimBLEUUID.cpp
*
* Created: on Jan 24 2020
* Author H2zero
*
* Originally:
*
* BLEUUID.cpp
*
* Created on: Jun 21, 2017
* Author: kolban
*/
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEUtils.h"
#include "NimBLEUUID.h"
#include "NimBLELog.h"
#include <algorithm>
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(const std::string &value) {
m_valueSet = true;
if (value.length() == 4) {
m_uuid.u.type = BLE_UUID_TYPE_16;
m_uuid.u16.value = strtoul(value.c_str(), NULL, 16);
}
else if (value.length() == 8) {
m_uuid.u.type = BLE_UUID_TYPE_32;
m_uuid.u32.value = strtoul(value.c_str(), NULL, 16);
}
else if (value.length() == 16) {
*this = NimBLEUUID((uint8_t*)value.data(), 16, true);
}
else if (value.length() == 36) {
// If the length of the string is 36 bytes then we will assume it is a long hex string in
// UUID format.
char * position = const_cast<char *>(value.c_str());
uint32_t first = strtoul(position, &position, 16);
uint16_t second = strtoul(position + 1, &position, 16);
uint16_t third = strtoul(position + 1, &position, 16);
uint16_t fourth = strtoul(position + 1, &position, 16);
uint64_t fifth = strtoull(position + 1, NULL, 16);
*this = NimBLEUUID(first, second, third, (uint64_t(fourth) << 48) + fifth);
}
else {
m_valueSet = false;
}
} // NimBLEUUID(std::string)
/**
* @brief Create a UUID from 2, 4, 16 bytes of memory.
* @param [in] pData The pointer to the start of the UUID.
* @param [in] size The size of the data.
* @param [in] msbFirst Is the MSB first in pData memory?
*/
NimBLEUUID::NimBLEUUID(const uint8_t* pData, size_t size, bool msbFirst) {
uint8_t *uuidValue = nullptr;
switch(size) {
case 2:
uuidValue = (uint8_t*)&m_uuid.u16.value;
m_uuid.u.type = BLE_UUID_TYPE_16;
break;
case 4:
uuidValue = (uint8_t*)&m_uuid.u32.value;
m_uuid.u.type = BLE_UUID_TYPE_32;
break;
case 16:
uuidValue = m_uuid.u128.value;
m_uuid.u.type = BLE_UUID_TYPE_128;
break;
default:
m_valueSet = false;
NIMBLE_LOGE(LOG_TAG, "Invalid UUID size");
return;
}
if (msbFirst) {
std::reverse_copy(pData, pData + size, uuidValue);
} else {
memcpy(uuidValue, pData, size);
}
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(const ble_uuid128_t* uuid) {
m_uuid.u.type = BLE_UUID_TYPE_128;
memcpy(m_uuid.u128.value, uuid->value, 16);
m_valueSet = true;
} // NimBLEUUID
/**
* @brief Create a UUID from the 128bit value using hex parts instead of string,
* instead of NimBLEUUID("ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6"), it becomes
* NimBLEUUID(0xebe0ccb0, 0x7a0a, 0x4b0c, 0x8a1a6ff2997da3a6)
*
* @param [in] first The first 32bit of the UUID.
* @param [in] second The next 16bit of the UUID.
* @param [in] third The next 16bit of the UUID.
* @param [in] fourth The last 64bit of the UUID, combining the last 2 parts of the string equivalent
*/
NimBLEUUID::NimBLEUUID(uint32_t first, uint16_t second, uint16_t third, uint64_t fourth) {
m_uuid.u.type = BLE_UUID_TYPE_128;
memcpy(m_uuid.u128.value + 12, &first, 4);
memcpy(m_uuid.u128.value + 10, &second, 2);
memcpy(m_uuid.u128.value + 8, &third, 2);
memcpy(m_uuid.u128.value, &fourth, 8);
m_valueSet = true;
}
/**
* @brief Creates an empty UUID.
*/
NimBLEUUID::NimBLEUUID() {
m_valueSet = false;
} // NimBLEUUID
/**
* @brief Get the number of bits in this uuid.
* @return The number of bits in the UUID. One of 16, 32 or 128.
*/
uint8_t NimBLEUUID::bitSize() const {
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(const NimBLEUUID &uuid) const {
return *this == uuid;
}
/**
* Create a NimBLEUUID from a string of the form:
* 0xNNNN
* 0xNNNNNNNN
* 0x<UUID\>
* NNNN
* NNNNNNNN
* <UUID\>
*
* @param [in] uuid The string to create the UUID from.
*/
NimBLEUUID NimBLEUUID::fromString(const std::string &uuid) {
uint8_t start = 0;
if (strstr(uuid.c_str(), "0x") != nullptr) { // If the string starts with 0x, skip those characters.
start = 2;
}
uint8_t len = uuid.length() - start; // Calculate the length of the string we are going to use.
if(len == 4) {
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 nullptr if not set.
*/
const ble_uuid_any_t* NimBLEUUID::getNative() const {
if (m_valueSet == false) {
NIMBLE_LOGD(LOG_TAG,"<< Return of un-initialized UUID!");
return nullptr;
}
return &m_uuid;
} // getNative
/**
* @brief Convert a UUID to its 128 bit representation.
* @details A UUID can be internally represented as 16bit, 32bit or the full 128bit.
* This method will convert 16 or 32bit representations to the full 128bit.
* @return The NimBLEUUID converted to 128bit.
*/
const NimBLEUUID &NimBLEUUID::to128() {
// If we either don't have a value or are already a 128 bit UUID, nothing further to do.
if (!m_valueSet || m_uuid.u.type == BLE_UUID_TYPE_128) {
return *this;
}
// If we are 16 bit or 32 bit, then set the other bytes of the UUID.
if (m_uuid.u.type == BLE_UUID_TYPE_16) {
*this = NimBLEUUID(m_uuid.u16.value, 0x0000, 0x1000, 0x800000805f9b34fb);
}
else if (m_uuid.u.type == BLE_UUID_TYPE_32) {
*this = NimBLEUUID(m_uuid.u32.value, 0x0000, 0x1000, 0x800000805f9b34fb);
}
return *this;
} // to128
/**
* @brief Convert 128 bit UUID to its 16 bit representation.
* @details A UUID can be internally represented as 16bit, 32bit or the full 128bit.
* This method will convert a 128bit uuid to 16bit if it contains the ble base uuid.
* @return The NimBLEUUID converted to 16bit if successful, otherwise the original uuid.
*/
const NimBLEUUID& NimBLEUUID::to16() {
if (!m_valueSet || m_uuid.u.type == BLE_UUID_TYPE_16) {
return *this;
}
if (m_uuid.u.type == BLE_UUID_TYPE_128) {
uint8_t base128[] = {0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00,
0x00, 0x80, 0x00, 0x10, 0x00, 0x00};
if (memcmp(m_uuid.u128.value, base128, sizeof(base128)) == 0 ) {
*this = NimBLEUUID(*(uint16_t*)(m_uuid.u128.value + 12));
}
}
return *this;
}
/**
* @brief Get a string representation of the UUID.
* @details
* 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.
* @deprecated Use std::string() operator instead.
*/
std::string NimBLEUUID::toString() const {
return std::string(*this);
} // toString
/**
* @brief Convenience operator to check if this UUID is equal to another.
*/
bool NimBLEUUID::operator ==(const NimBLEUUID & rhs) const {
if(m_valueSet && rhs.m_valueSet) {
if(m_uuid.u.type != rhs.m_uuid.u.type) {
uint8_t uuidBase[16] = {
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80,
0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
if(m_uuid.u.type == BLE_UUID_TYPE_128){
if(rhs.m_uuid.u.type == BLE_UUID_TYPE_16){
memcpy(uuidBase+12, &rhs.m_uuid.u16.value, 2);
} else if (rhs.m_uuid.u.type == BLE_UUID_TYPE_32){
memcpy(uuidBase+12, &rhs.m_uuid.u32.value, 4);
}
return memcmp(m_uuid.u128.value,uuidBase,16) == 0;
} else if(rhs.m_uuid.u.type == BLE_UUID_TYPE_128) {
if(m_uuid.u.type == BLE_UUID_TYPE_16){
memcpy(uuidBase+12, &m_uuid.u16.value, 2);
} else if (m_uuid.u.type == BLE_UUID_TYPE_32){
memcpy(uuidBase+12, &m_uuid.u32.value, 4);
}
return memcmp(rhs.m_uuid.u128.value,uuidBase,16) == 0;
} else {
return false;
}
}
return ble_uuid_cmp(&m_uuid.u, &rhs.m_uuid.u) == 0;
}
return m_valueSet == rhs.m_valueSet;
}
/**
* @brief Convenience operator to check if this UUID is not equal to another.
*/
bool NimBLEUUID::operator !=(const NimBLEUUID & rhs) const {
return !this->operator==(rhs);
}
/**
* @brief Convenience operator to convert this UUID to string representation.
* @details This allows passing NimBLEUUID to functions
* that accept std::string and/or or it's methods as a parameter.
*/
NimBLEUUID::operator std::string() const {
if (!m_valueSet) return std::string(); // If we have no value, nothing to format.
char buf[BLE_UUID_STR_LEN];
return ble_uuid_to_str(&m_uuid.u, buf);
}
#endif /* CONFIG_BT_ENABLED */

View File

@@ -0,0 +1,64 @@
/*
* 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 "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
#if defined(CONFIG_NIMBLE_CPP_IDF)
#include "host/ble_uuid.h"
#else
#include "nimble/nimble/host/include/host/ble_uuid.h"
#endif
/**** FIX COMPILATION ****/
#undef min
#undef max
/**************************/
#include <string>
/**
* @brief A model of a %BLE UUID.
*/
class NimBLEUUID {
public:
NimBLEUUID(const std::string &uuid);
NimBLEUUID(uint16_t uuid);
NimBLEUUID(uint32_t uuid);
NimBLEUUID(const ble_uuid128_t* uuid);
NimBLEUUID(const uint8_t* pData, size_t size, bool msbFirst);
NimBLEUUID(uint32_t first, uint16_t second, uint16_t third, uint64_t fourth);
NimBLEUUID();
uint8_t bitSize() const;
bool equals(const NimBLEUUID &uuid) const;
const ble_uuid_any_t* getNative() const;
const NimBLEUUID & to128();
const NimBLEUUID& to16();
std::string toString() const;
static NimBLEUUID fromString(const std::string &uuid);
bool operator ==(const NimBLEUUID & rhs) const;
bool operator !=(const NimBLEUUID & rhs) const;
operator std::string() const;
private:
ble_uuid_any_t m_uuid;
bool m_valueSet = false;
}; // NimBLEUUID
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_NIMBLEUUID_H_ */

View File

@@ -0,0 +1,518 @@
/*
* NimBLEUtils.cpp
*
* Created: on Jan 25 2020
* Author H2zero
*
*/
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEUtils.h"
#include "NimBLELog.h"
#include <stdlib.h>
static const char* LOG_TAG = "NimBLEUtils";
/**
* @brief A function for checking validity of connection parameters.
* @param [in] params A pointer to the structure containing the parameters to check.
* @return valid == 0 or error code.
*/
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;
}
/**
* @brief Converts a return code from the NimBLE stack to a text string.
* @param [in] rc The return code to convert.
* @return A string representation of the return code.
*/
const char* NimBLEUtils::returnCodeToString(int rc) {
#if defined(CONFIG_NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT)
switch(rc) {
case 0:
return "SUCCESS";
case BLE_HS_EAGAIN:
return "Temporary failure; try again.";
case BLE_HS_EALREADY:
return "Operation already in progress or completed.";
case BLE_HS_EINVAL:
return "One or more arguments are invalid.";
case BLE_HS_EMSGSIZE:
return "The provided buffer is too small.";
case BLE_HS_ENOENT:
return "No entry matching the specified criteria.";
case BLE_HS_ENOMEM:
return "Operation failed due to resource exhaustion.";
case BLE_HS_ENOTCONN:
return "No open connection with the specified handle.";
case BLE_HS_ENOTSUP:
return "Operation disabled at compile time.";
case BLE_HS_EAPP:
return "Application callback behaved unexpectedly.";
case BLE_HS_EBADDATA:
return "Command from peer is invalid.";
case BLE_HS_EOS:
return "Mynewt OS error.";
case BLE_HS_ECONTROLLER:
return "Event from controller is invalid.";
case BLE_HS_ETIMEOUT:
return "Operation timed out.";
case BLE_HS_EDONE:
return "Operation completed successfully.";
case BLE_HS_EBUSY:
return "Operation cannot be performed until procedure completes.";
case BLE_HS_EREJECT:
return "Peer rejected a connection parameter update request.";
case BLE_HS_EUNKNOWN:
return "Unexpected failure; catch all.";
case BLE_HS_EROLE:
return "Operation requires different role (e.g., central vs. peripheral).";
case BLE_HS_ETIMEOUT_HCI:
return "HCI request timed out; controller unresponsive.";
case BLE_HS_ENOMEM_EVT:
return "Controller failed to send event due to memory exhaustion (combined host-controller only).";
case BLE_HS_ENOADDR:
return "Operation requires an identity address but none configured.";
case BLE_HS_ENOTSYNCED:
return "Attempt to use the host before it is synced with controller.";
case BLE_HS_EAUTHEN:
return "Insufficient authentication.";
case BLE_HS_EAUTHOR:
return "Insufficient authorization.";
case BLE_HS_EENCRYPT:
return "Insufficient encryption level.";
case BLE_HS_EENCRYPT_KEY_SZ:
return "Insufficient key size.";
case BLE_HS_ESTORE_CAP:
return "Storage at capacity.";
case BLE_HS_ESTORE_FAIL:
return "Storage IO error.";
case (0x0100+BLE_ATT_ERR_INVALID_HANDLE ):
return "The attribute handle given was not valid on this server.";
case (0x0100+BLE_ATT_ERR_READ_NOT_PERMITTED ):
return "The attribute cannot be read.";
case (0x0100+BLE_ATT_ERR_WRITE_NOT_PERMITTED ):
return "The attribute cannot be written.";
case (0x0100+BLE_ATT_ERR_INVALID_PDU ):
return "The attribute PDU was invalid.";
case (0x0100+BLE_ATT_ERR_INSUFFICIENT_AUTHEN ):
return "The attribute requires authentication before it can be read or written.";
case (0x0100+BLE_ATT_ERR_REQ_NOT_SUPPORTED ):
return "Attribute server does not support the request received from the client.";
case (0x0100+BLE_ATT_ERR_INVALID_OFFSET ):
return "Offset specified was past the end of the attribute.";
case (0x0100+BLE_ATT_ERR_INSUFFICIENT_AUTHOR ):
return "The attribute requires authorization before it can be read or written.";
case (0x0100+BLE_ATT_ERR_PREPARE_QUEUE_FULL ):
return "Too many prepare writes have been queued.";
case (0x0100+BLE_ATT_ERR_ATTR_NOT_FOUND ):
return "No attribute found within the given attribute handle range.";
case (0x0100+BLE_ATT_ERR_ATTR_NOT_LONG ):
return "The attribute cannot be read or written using the Read Blob Request.";
case (0x0100+BLE_ATT_ERR_INSUFFICIENT_KEY_SZ ):
return "The Encryption Key Size used for encrypting this link is insufficient.";
case (0x0100+BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN ):
return "The attribute value length is invalid for the operation.";
case (0x0100+BLE_ATT_ERR_UNLIKELY ):
return "The attribute request has encountered an error that was unlikely, could not be completed as requested.";
case (0x0100+BLE_ATT_ERR_INSUFFICIENT_ENC ):
return "The attribute requires encryption before it can be read or written.";
case (0x0100+BLE_ATT_ERR_UNSUPPORTED_GROUP ):
return "The attribute type is not a supported grouping attribute as defined by a higher layer specification.";
case (0x0100+BLE_ATT_ERR_INSUFFICIENT_RES ):
return "Insufficient Resources to complete the request.";
case (0x0200+BLE_ERR_UNKNOWN_HCI_CMD ):
return "Unknown HCI Command";
case (0x0200+BLE_ERR_UNK_CONN_ID ):
return "Unknown Connection Identifier";
case (0x0200+BLE_ERR_HW_FAIL ):
return "Hardware Failure";
case (0x0200+BLE_ERR_PAGE_TMO ):
return "Page Timeout";
case (0x0200+BLE_ERR_AUTH_FAIL ):
return "Authentication Failure";
case (0x0200+BLE_ERR_PINKEY_MISSING ):
return "PIN or Key Missing";
case (0x0200+BLE_ERR_MEM_CAPACITY ):
return "Memory Capacity Exceeded";
case (0x0200+BLE_ERR_CONN_SPVN_TMO ):
return "Connection Timeout";
case (0x0200+BLE_ERR_CONN_LIMIT ):
return "Connection Limit Exceeded";
case (0x0200+BLE_ERR_SYNCH_CONN_LIMIT ):
return "Synchronous Connection Limit To A Device Exceeded";
case (0x0200+BLE_ERR_ACL_CONN_EXISTS ):
return "ACL Connection Already Exists";
case (0x0200+BLE_ERR_CMD_DISALLOWED ):
return "Command Disallowed";
case (0x0200+BLE_ERR_CONN_REJ_RESOURCES ):
return "Connection Rejected due to Limited Resources";
case (0x0200+BLE_ERR_CONN_REJ_SECURITY ):
return "Connection Rejected Due To Security Reasons";
case (0x0200+BLE_ERR_CONN_REJ_BD_ADDR ):
return "Connection Rejected due to Unacceptable BD_ADDR";
case (0x0200+BLE_ERR_CONN_ACCEPT_TMO ):
return "Connection Accept Timeout Exceeded";
case (0x0200+BLE_ERR_UNSUPPORTED ):
return "Unsupported Feature or Parameter Value";
case (0x0200+BLE_ERR_INV_HCI_CMD_PARMS ):
return "Invalid HCI Command Parameters";
case (0x0200+BLE_ERR_REM_USER_CONN_TERM ):
return "Remote User Terminated Connection";
case (0x0200+BLE_ERR_RD_CONN_TERM_RESRCS ):
return "Remote Device Terminated Connection due to Low Resources";
case (0x0200+BLE_ERR_RD_CONN_TERM_PWROFF ):
return "Remote Device Terminated Connection due to Power Off";
case (0x0200+BLE_ERR_CONN_TERM_LOCAL ):
return "Connection Terminated By Local Host";
case (0x0200+BLE_ERR_REPEATED_ATTEMPTS ):
return "Repeated Attempts";
case (0x0200+BLE_ERR_NO_PAIRING ):
return "Pairing Not Allowed";
case (0x0200+BLE_ERR_UNK_LMP ):
return "Unknown LMP PDU";
case (0x0200+BLE_ERR_UNSUPP_REM_FEATURE ):
return "Unsupported Remote Feature / Unsupported LMP Feature";
case (0x0200+BLE_ERR_SCO_OFFSET ):
return "SCO Offset Rejected";
case (0x0200+BLE_ERR_SCO_ITVL ):
return "SCO Interval Rejected";
case (0x0200+BLE_ERR_SCO_AIR_MODE ):
return "SCO Air Mode Rejected";
case (0x0200+BLE_ERR_INV_LMP_LL_PARM ):
return "Invalid LMP Parameters / Invalid LL Parameters";
case (0x0200+BLE_ERR_UNSPECIFIED ):
return "Unspecified Error";
case (0x0200+BLE_ERR_UNSUPP_LMP_LL_PARM ):
return "Unsupported LMP Parameter Value / Unsupported LL Parameter Value";
case (0x0200+BLE_ERR_NO_ROLE_CHANGE ):
return "Role Change Not Allowed";
case (0x0200+BLE_ERR_LMP_LL_RSP_TMO ):
return "LMP Response Timeout / LL Response Timeout";
case (0x0200+BLE_ERR_LMP_COLLISION ):
return "LMP Error Transaction Collision";
case (0x0200+BLE_ERR_LMP_PDU ):
return "LMP PDU Not Allowed";
case (0x0200+BLE_ERR_ENCRYPTION_MODE ):
return "Encryption Mode Not Acceptable";
case (0x0200+BLE_ERR_LINK_KEY_CHANGE ):
return "Link Key cannot be Changed";
case (0x0200+BLE_ERR_UNSUPP_QOS ):
return "Requested QoS Not Supported";
case (0x0200+BLE_ERR_INSTANT_PASSED ):
return "Instant Passed";
case (0x0200+BLE_ERR_UNIT_KEY_PAIRING ):
return "Pairing With Unit Key Not Supported";
case (0x0200+BLE_ERR_DIFF_TRANS_COLL ):
return "Different Transaction Collision";
case (0x0200+BLE_ERR_QOS_PARM ):
return "QoS Unacceptable Parameter";
case (0x0200+BLE_ERR_QOS_REJECTED ):
return "QoS Rejected";
case (0x0200+BLE_ERR_CHAN_CLASS ):
return "Channel Classification Not Supported";
case (0x0200+BLE_ERR_INSUFFICIENT_SEC ):
return "Insufficient Security";
case (0x0200+BLE_ERR_PARM_OUT_OF_RANGE ):
return "Parameter Out Of Mandatory Range";
case (0x0200+BLE_ERR_PENDING_ROLE_SW ):
return "Role Switch Pending";
case (0x0200+BLE_ERR_RESERVED_SLOT ):
return "Reserved Slot Violation";
case (0x0200+BLE_ERR_ROLE_SW_FAIL ):
return "Role Switch Failed";
case (0x0200+BLE_ERR_INQ_RSP_TOO_BIG ):
return "Extended Inquiry Response Too Large";
case (0x0200+BLE_ERR_SEC_SIMPLE_PAIR ):
return "Secure Simple Pairing Not Supported By Host";
case (0x0200+BLE_ERR_HOST_BUSY_PAIR ):
return "Host Busy - Pairing";
case (0x0200+BLE_ERR_CONN_REJ_CHANNEL ):
return "Connection Rejected, No Suitable Channel Found";
case (0x0200+BLE_ERR_CTLR_BUSY ):
return "Controller Busy";
case (0x0200+BLE_ERR_CONN_PARMS ):
return "Unacceptable Connection Parameters";
case (0x0200+BLE_ERR_DIR_ADV_TMO ):
return "Directed Advertising Timeout";
case (0x0200+BLE_ERR_CONN_TERM_MIC ):
return "Connection Terminated due to MIC Failure";
case (0x0200+BLE_ERR_CONN_ESTABLISHMENT ):
return "Connection Failed to be Established";
case (0x0200+BLE_ERR_MAC_CONN_FAIL ):
return "MAC Connection Failed";
case (0x0200+BLE_ERR_COARSE_CLK_ADJ ):
return "Coarse Clock Adjustment Rejected";
case (0x0300+BLE_L2CAP_SIG_ERR_CMD_NOT_UNDERSTOOD ):
return "Invalid or unsupported incoming L2CAP sig command.";
case (0x0300+BLE_L2CAP_SIG_ERR_MTU_EXCEEDED ):
return "Incoming packet too large.";
case (0x0300+BLE_L2CAP_SIG_ERR_INVALID_CID ):
return "No channel with specified ID.";
case (0x0400+BLE_SM_ERR_PASSKEY ):
return "The user input of passkey failed, for example, the user cancelled the operation.";
case (0x0400+BLE_SM_ERR_OOB ):
return "The OOB data is not available.";
case (0x0400+BLE_SM_ERR_AUTHREQ ):
return "The pairing procedure cannot be performed as authentication requirements cannot be met due to IO capabilities of one or both devices.";
case (0x0400+BLE_SM_ERR_CONFIRM_MISMATCH ):
return "The confirm value does not match the calculated compare value.";
case (0x0400+BLE_SM_ERR_PAIR_NOT_SUPP ):
return "Pairing is not supported by the device.";
case (0x0400+BLE_SM_ERR_ENC_KEY_SZ ):
return "The resultant encryption key size is insufficient for the security requirements of this device.";
case (0x0400+BLE_SM_ERR_CMD_NOT_SUPP ):
return "The SMP command received is not supported on this device.";
case (0x0400+BLE_SM_ERR_UNSPECIFIED ):
return "Pairing failed due to an unspecified reason.";
case (0x0400+BLE_SM_ERR_REPEATED ):
return "Pairing or authentication procedure disallowed, too little time has elapsed since last pairing request or security request.";
case (0x0400+BLE_SM_ERR_INVAL ):
return "Command length is invalid or that a parameter is outside of the specified range.";
case (0x0400+BLE_SM_ERR_DHKEY ):
return "DHKey Check value received doesn't match the one calculated by the local device.";
case (0x0400+BLE_SM_ERR_NUMCMP ):
return "Confirm values in the numeric comparison protocol do not match.";
case (0x0400+BLE_SM_ERR_ALREADY ):
return "Pairing over the LE transport failed - Pairing Request sent over the BR/EDR transport in process.";
case (0x0400+BLE_SM_ERR_CROSS_TRANS ):
return "BR/EDR Link Key generated on the BR/EDR transport cannot be used to derive and distribute keys for the LE transport.";
case (0x0500+BLE_SM_ERR_PASSKEY ):
return "The user input of passkey failed or the user cancelled the operation.";
case (0x0500+BLE_SM_ERR_OOB ):
return "The OOB data is not available.";
case (0x0500+BLE_SM_ERR_AUTHREQ ):
return "The pairing procedure cannot be performed as authentication requirements cannot be met due to IO capabilities of one or both devices.";
case (0x0500+BLE_SM_ERR_CONFIRM_MISMATCH ):
return "The confirm value does not match the calculated compare value.";
case (0x0500+BLE_SM_ERR_PAIR_NOT_SUPP ):
return "Pairing is not supported by the device.";
case (0x0500+BLE_SM_ERR_ENC_KEY_SZ ):
return "The resultant encryption key size is insufficient for the security requirements of this device.";
case (0x0500+BLE_SM_ERR_CMD_NOT_SUPP ):
return "The SMP command received is not supported on this device.";
case (0x0500+BLE_SM_ERR_UNSPECIFIED ):
return "Pairing failed due to an unspecified reason.";
case (0x0500+BLE_SM_ERR_REPEATED ):
return "Pairing or authentication procedure is disallowed because too little time has elapsed since last pairing request or security request.";
case (0x0500+BLE_SM_ERR_INVAL ):
return "Command length is invalid or a parameter is outside of the specified range.";
case (0x0500+BLE_SM_ERR_DHKEY ):
return "Indicates to the remote device that the DHKey Check value received doesnt match the one calculated by the local device.";
case (0x0500+BLE_SM_ERR_NUMCMP ):
return "Confirm values in the numeric comparison protocol do not match.";
case (0x0500+BLE_SM_ERR_ALREADY ):
return "Pairing over the LE transport failed - Pairing Request sent over the BR/EDR transport in process.";
case (0x0500+BLE_SM_ERR_CROSS_TRANS ):
return "BR/EDR Link Key generated on the BR/EDR transport cannot be used to derive and distribute keys for the LE transport.";
default:
return "Unknown";
}
#else // #if defined(CONFIG_NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT)
(void)rc;
return "";
#endif // #if defined(CONFIG_NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT)
}
/**
* @brief Convert the advertising type flag to a string.
* @param advType The type to convert.
* @return A string representation of the advertising flags.
*/
const char* NimBLEUtils::advTypeToString(uint8_t advType) {
#if defined(CONFIG_NIMBLE_CPP_ENABLE_ADVERTISEMENT_TYPE_TEXT)
switch(advType) {
case BLE_HCI_ADV_TYPE_ADV_IND : //0
return "Undirected - Connectable / Scannable";
case BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_HD: //1
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";
}
#else // #if defined(CONFIG_NIMBLE_CPP_ENABLE_ADVERTISEMENT_TYPE_TEXT)
(void)advType;
return "";
#endif // #if defined(CONFIG_NIMBLE_CPP_ENABLE_ADVERTISEMENT_TYPE_TEXT)
} // adFlagsToString
/**
* @brief Create a hex representation of data.
*
* @param [in] target Where to write the hex string. If this is null, we malloc storage.
* @param [in] source The start of the binary data.
* @param [in] length The length of the data to convert.
* @return A pointer to the formatted buffer.
*/
char* NimBLEUtils::buildHexData(uint8_t* target, const uint8_t* source, uint8_t length) {
// Guard against too much data.
if (length > 100) length = 100;
if (target == nullptr) {
target = (uint8_t*) malloc(length * 2 + 1);
if (target == nullptr) {
NIMBLE_LOGE(LOG_TAG, "buildHexData: malloc failed");
return nullptr;
}
}
char* startOfData = (char*) target;
for (int i = 0; i < length; i++) {
sprintf((char*) target, "%.2x", (char) *source);
source++;
target += 2;
}
// Handle the special case where there was no data.
if (length == 0) {
*startOfData = 0;
}
return startOfData;
} // buildHexData
/**
* @brief Utility function to log the gap event info.
* @param [in] event A pointer to the gap event structure.
* @param [in] arg Unused.
*/
void NimBLEUtils::dumpGapEvent(ble_gap_event *event, void *arg){
(void)arg;
#if defined(CONFIG_NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT)
NIMBLE_LOGD(LOG_TAG, "Received a GAP event: %s", gapEventToString(event->type));
#else
(void)event;
#endif
}
/**
* @brief Convert a GAP event type to a string representation.
* @param [in] eventType The type of event.
* @return A string representation of the event type.
*/
const char* NimBLEUtils::gapEventToString(uint8_t eventType) {
#if defined(CONFIG_NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT)
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";
#ifdef BLE_GAP_EVENT_PERIODIC_SYNC // IDF 4.0 does not support these
case BLE_GAP_EVENT_PERIODIC_SYNC: //20
return "BLE_GAP_EVENT_PERIODIC_SYNC";
case BLE_GAP_EVENT_PERIODIC_REPORT: //21
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";
#endif
default:
NIMBLE_LOGD(LOG_TAG, "gapEventToString: Unknown event type %d 0x%.2x", eventType, eventType);
return "Unknown event type";
}
#else // #if defined(CONFIG_NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT)
(void)eventType;
return "";
#endif // #if defined(CONFIG_NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT)
} // gapEventToString
#endif //CONFIG_BT_ENABLED

View File

@@ -0,0 +1,51 @@
/*
* NimBLEUtils.h
*
* Created: on Jan 25 2020
* Author H2zero
*
*/
#ifndef COMPONENTS_NIMBLEUTILS_H_
#define COMPONENTS_NIMBLEUTILS_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
#if defined(CONFIG_NIMBLE_CPP_IDF)
#include "host/ble_gap.h"
#else
#include "nimble/nimble/host/include/host/ble_gap.h"
#endif
/**** FIX COMPILATION ****/
#undef min
#undef max
/**************************/
#include <string>
typedef struct {
void *pATT;
TaskHandle_t task;
int rc;
void *buf;
} ble_task_data_t;
/**
* @brief A BLE Utility class with methods for debugging and general purpose use.
*/
class NimBLEUtils {
public:
static void dumpGapEvent(ble_gap_event *event, void *arg);
static const char* gapEventToString(uint8_t eventType);
static char* buildHexData(uint8_t* target, const uint8_t* source, uint8_t length);
static const char* advTypeToString(uint8_t advType);
static const char* returnCodeToString(int rc);
static int checkConnParams(ble_gap_conn_params* params);
};
#endif // CONFIG_BT_ENABLED
#endif // COMPONENTS_NIMBLEUTILS_H_

View File

@@ -0,0 +1,140 @@
/** @file
*
* IGNORE THIS FILE IF USING ESP-IDF, USE MENUCONFIG TO SET NIMBLE OPTIONS.
*
* The config options here are for doxygen documentation only.
*/
#pragma once
#include "sdkconfig.h"
#include "nimconfig_rename.h"
#if defined(CONFIG_BT_ENABLED)
// Allows cpp wrapper to select the correct include paths when using esp-idf
#define CONFIG_NIMBLE_CPP_IDF
/* Cannot use client without scan */
#if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) && !defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
#define CONFIG_BT_NIMBLE_ROLE_OBSERVER
#endif
/* Cannot use server without advertise */
#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) && !defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
#define CONFIG_BT_NIMBLE_ROLE_BROADCASTER
#endif
/* Enables the use of Arduino String class for attribute values */
#if defined __has_include
# if __has_include (<Arduino.h>)
# define NIMBLE_CPP_ARDUINO_STRING_AVAILABLE
# endif
#endif
#endif /* CONFIG_BT_ENABLED */
#ifdef _DOXYGEN_
/** @brief Un-comment to change the number of simultaneous connections (esp controller max is 9) */
#define CONFIG_BT_NIMBLE_MAX_CONNECTIONS 3
/** @brief Un-comment to enable storing the timestamp when an attribute value is updated\n
* This allows for checking the last update time using getTimeStamp() or getValue(time_t*)\n
* If disabled, the timestamp returned from these functions will be 0.\n
* Disabling timestamps will reduce the memory used for each value.\n
* 1 = Enabled, 0 = Disabled; Default = Disabled
*/
#define CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED 0
/** @brief Uncomment to set the default allocation size (bytes) for each attribute if\n
* not specified when the constructor is called. This is also the size used when a remote\n
* characteristic or descriptor is constructed before a value is read/notifed.\n
* Increasing this will reduce reallocations but increase memory footprint.\n
* Default value is 20. Range: 1 : 512 (BLE_ATT_ATTR_MAX_LEN)
*/
#define CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH 20
/** @brief Un-comment to change the default MTU size */
#define CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU 255
/** @brief Un-comment to change default device name */
#define CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME "nimble"
/** @brief Un-comment to set the debug log messages level from the NimBLE host stack.\n
* Values: 0 = DEBUG, 1 = INFO, 2 = WARNING, 3 = ERROR, 4 = CRITICAL, 5+ = NONE\n
* Uses approx. 32kB of flash memory.
*/
#define CONFIG_BT_NIMBLE_LOG_LEVEL 5
/** @brief Un-comment to set the debug log messages level from the NimBLE CPP Wrapper.\n
* Values: 0 = NONE, 1 = ERROR, 2 = WARNING, 3 = INFO, 4+ = DEBUG\n
* Uses approx. 32kB of flash memory.
*/
#define CONFIG_NIMBLE_CPP_LOG_LEVEL 0
/** @brief Un-comment to see NimBLE host return codes as text debug log messages.
* Uses approx. 7kB of flash memory.
*/
#define CONFIG_NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT
/** @brief Un-comment to see GAP event codes as text in debug log messages.
* Uses approx. 1kB of flash memory.
*/
#define CONFIG_NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT
/** @brief Un-comment to see advertisment types as text while scanning in debug log messages.
* Uses approx. 250 bytes of flash memory.
*/
#define CONFIG_NIMBLE_CPP_ENABLE_ADVERTISMENT_TYPE_TEXT
/** @brief Un-comment to change the default GAP appearance */
#define CONFIG_BT_NIMBLE_SVC_GAP_APPEARANCE 0x0
/** @brief Un-comment if not using NimBLE Client functions \n
* Reduces flash size by approx. 7kB.
*/
#define CONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED
/** @brief Un-comment if not using NimBLE Scan functions \n
* Reduces flash size by approx. 26kB.
*/
#define CONFIG_BT_NIMBLE_ROLE_OBSERVER_DISABLED
/** @brief Un-comment if not using NimBLE Server functions \n
* Reduces flash size by approx. 16kB.
*/
#define CONFIG_BT_NIMBLE_ROLE_PERIPHERAL_DISABLED
/** @brief Un-comment if not using NimBLE Advertising functions \n
* Reduces flash size by approx. 5kB.
*/
#define CONFIG_BT_NIMBLE_ROLE_BROADCASTER_DISABLED
/** @brief Un-comment to change the number of devices allowed to store/bond with */
#define CONFIG_BT_NIMBLE_MAX_BONDS 3
/** @brief Un-comment to change the maximum number of CCCD subscriptions to store */
#define CONFIG_BT_NIMBLE_MAX_CCCDS 8
/** @brief Un-comment to change the random address refresh time (in seconds) */
#define CONFIG_BT_NIMBLE_RPA_TIMEOUT 900
/**
* @brief Un-comment to change the number of MSYS buffers available.
* @details MSYS is a system level mbuf registry. For prepare write & prepare \n
* responses MBUFs are allocated out of msys_1 pool. This may need to be increased if\n
* you are sending large blocks of data with a low MTU. E.g: 512 bytes with 23 MTU will fail.
*/
#define CONFIG_BT_NIMBLE_MSYS1_BLOCK_COUNT 12
/** @brief Un-comment to use external PSRAM for the NimBLE host */
#define CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL 1
/** @brief Un-comment to change the core NimBLE host runs on */
#define CONFIG_BT_NIMBLE_PINNED_TO_CORE 0
/** @brief Un-comment to change the stack size for the NimBLE host task */
#define CONFIG_BT_NIMBLE_TASK_STACK_SIZE 4096
#endif // _DOXYGEN_

View File

@@ -0,0 +1,61 @@
/*
* For ESP-IDF compatibility
* Some versions of ESP-IDF used the config name format "CONFIG_NIMBLE_".
* This converts them to "CONFIG_BT_NIMBLE_" format used in the latest IDF.
*/
#if defined(CONFIG_NIMBLE_ENABLED) && !defined(CONFIG_BT_NIMBLE_ENABLED)
#define CONFIG_BT_NIMBLE_ENABLED
#endif
#if defined(CONFIG_NIMBLE_ROLE_OBSERVER) && !defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
#define CONFIG_BT_NIMBLE_ROLE_OBSERVER
#endif
#if defined(CONFIG_NIMBLE_ROLE_BROADCASTER) && !defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
#define CONFIG_BT_NIMBLE_ROLE_BROADCASTER
#endif
#if defined(CONFIG_NIMBLE_ROLE_CENTRAL) && !defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
#define CONFIG_BT_NIMBLE_ROLE_CENTRAL
#endif
#if defined(CONFIG_NIMBLE_ROLE_PERIPHERAL) && !defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#define CONFIG_BT_NIMBLE_ROLE_PERIPHERAL
#endif
#if defined(CONFIG_NIMBLE_DEBUG) && !defined(CONFIG_BT_NIMBLE_DEBUG)
#define CONFIG_BT_NIMBLE_DEBUG
#endif
#if defined(CONFIG_SCAN_DUPLICATE_BY_DEVICE_ADDR) && !defined(CONFIG_BTDM_SCAN_DUPL_TYPE_DEVICE)
#define CONFIG_BTDM_SCAN_DUPL_TYPE_DEVICE CONFIG_SCAN_DUPLICATE_BY_DEVICE_ADDR
#endif
#if defined(CONFIG_SCAN_DUPLICATE_BY_ADV_DATA ) && !defined(CONFIG_BTDM_SCAN_DUPL_TYPE_DATA)
#define CONFIG_BTDM_SCAN_DUPL_TYPE_DATA CONFIG_SCAN_DUPLICATE_BY_ADV_DATA
#endif
#if defined(CONFIG_SCAN_DUPLICATE_BY_ADV_DATA_AND_DEVICE_ADDR) && !defined(CONFIG_BTDM_SCAN_DUPL_TYPE_DATA_DEVICE)
#define CONFIG_BTDM_SCAN_DUPL_TYPE_DATA_DEVICE CONFIG_SCAN_DUPLICATE_BY_ADV_DATA_AND_DEVICE_ADDR
#endif
#if defined(CONFIG_SCAN_DUPLICATE_TYPE) && !defined(CONFIG_BTDM_SCAN_DUPL_TYPE)
#define CONFIG_BTDM_SCAN_DUPL_TYPE CONFIG_SCAN_DUPLICATE_TYPE
#endif
#if defined(CONFIG_BT_CTRL_SCAN_DUPL_TYPE) && !defined(CONFIG_BTDM_SCAN_DUPL_TYPE)
#define CONFIG_BTDM_SCAN_DUPL_TYPE CONFIG_BT_CTRL_SCAN_DUPL_TYPE
#endif
#if defined(CONFIG_DUPLICATE_SCAN_CACHE_SIZE) && !defined(CONFIG_BTDM_SCAN_DUPL_CACHE_SIZE)
#define CONFIG_BTDM_SCAN_DUPL_CACHE_SIZE CONFIG_DUPLICATE_SCAN_CACHE_SIZE
#endif
#if defined(CONFIG_BT_CTRL_SCAN_DUPL_CACHE_SIZE) && !defined(CONFIG_BTDM_SCAN_DUPL_CACHE_SIZE)
#define CONFIG_BTDM_SCAN_DUPL_CACHE_SIZE CONFIG_BT_CTRL_SCAN_DUPL_CACHE_SIZE
#endif
#if defined(CONFIG_NIMBLE_MAX_CONNECTIONS ) && !defined(CONFIG_BT_NIMBLE_MAX_CONNECTIONS)
#define CONFIG_BT_NIMBLE_MAX_CONNECTIONS CONFIG_NIMBLE_MAX_CONNECTIONS
#endif