publish libraries to avoid users to have to download them one by one

This commit is contained in:
1technophile committed 2018-03-04 13:47:37 +01:00
1 parent 93c075e880
commit f0546fab8c
396 files changed
+536928

No files matched your search

+69
View File
@@ -0,0 +1,69 @@
/*
* BLE2902.cpp
*
* Created on: Jun 25, 2017
* Author: kolban
*/
/*
* See also:
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "BLE2902.h"
BLE2902::BLE2902() : BLEDescriptor(BLEUUID((uint16_t) 0x2902)) {
uint8_t data[2] = {0,0};
setValue(data, 2);
} // BLE2902
/**
* @brief Get the notifications value.
* @return The notifications value. True if notifications are enabled and false if not.
*/
bool BLE2902::getNotifications() {
return (getValue()[0] & (1 << 0)) != 0;
} // getNotifications
/**
* @brief Get the indications value.
* @return The indications value. True if indications are enabled and false if not.
*/
bool BLE2902::getIndications() {
return (getValue()[0] & (1 << 1)) != 0;
} // getIndications
/**
* @brief Set the indications flag.
* @param [in] flag The indications flag.
*/
void BLE2902::setIndications(bool flag) {
uint8_t *pValue = getValue();
if (flag) {
pValue[0] |= 1<<1;
} else {
pValue[0] &= ~(1<<1);
}
} // setIndications
/**
* @brief Set the notifications flag.
* @param [in] flag The notifications flag.
*/
void BLE2902::setNotifications(bool flag) {
uint8_t *pValue = getValue();
if (flag) {
pValue[0] |= 1<<0;
} else {
pValue[0] &= ~(1<<0);
}
} // setNotifications
#endif
+34
View File
@@ -0,0 +1,34 @@
/*
* BLE2902.h
*
* Created on: Jun 25, 2017
* Author: kolban
*/
#ifndef COMPONENTS_CPP_UTILS_BLE2902_H_
#define COMPONENTS_CPP_UTILS_BLE2902_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "BLEDescriptor.h"
/**
* @brief Descriptor for Client Characteristic Configuration.
*
* This is a convenience descriptor for the Client Characteristic Configuration which has a UUID of 0x2902.
*
* See also:
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
*/
class BLE2902: public BLEDescriptor {
public:
BLE2902();
bool getNotifications();
bool getIndications();
void setNotifications(bool flag);
void setIndications(bool flag);
}; // BLE2902
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_CPP_UTILS_BLE2902_H_ */
+96
View File
@@ -0,0 +1,96 @@
/*
* BLEAddress.cpp
*
* Created on: Jul 2, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "BLEAddress.h"
#include <string>
#include <sstream>
#include <iomanip>
#include <string.h>
#include <stdio.h>
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-log.h"
#endif
/**
* @brief Create an address from the native ESP32 representation.
* @param [in] address The native representation.
*/
BLEAddress::BLEAddress(esp_bd_addr_t address) {
memcpy(m_address, address, ESP_BD_ADDR_LEN);
} // BLEAddress
/**
* @brief Create an address from a hex string
*
* A hex string is of the format:
* ```
* 00:00:00:00:00:00
* ```
* which is 17 characters in length.
*
* @param [in] stringAddress The hex representation of the address.
*/
BLEAddress::BLEAddress(std::string stringAddress) {
if (stringAddress.length() != 17) {
return;
}
int data[6];
sscanf(stringAddress.c_str(), "%x:%x:%x:%x:%x:%x", &data[0], &data[1], &data[2], &data[3], &data[4], &data[5]);
m_address[0] = (uint8_t)data[0];
m_address[1] = (uint8_t)data[1];
m_address[2] = (uint8_t)data[2];
m_address[3] = (uint8_t)data[3];
m_address[4] = (uint8_t)data[4];
m_address[5] = (uint8_t)data[5];
} // BLEAddress
/**
* @brief Determine if this address equals another.
* @param [in] otherAddress The other address to compare against.
* @return True if the addresses are equal.
*/
bool BLEAddress::equals(BLEAddress otherAddress) {
return memcmp(otherAddress.getNative(), m_address, 6) == 0;
} // equals
/**
* @brief Return the native representation of the address.
* @return The native representation of the address.
*/
esp_bd_addr_t *BLEAddress::getNative() {
return &m_address;
} // getNative
/**
* @brief Convert a BLE address to a string.
*
* A string representation of an address is in the format:
*
* ```
* xx:xx:xx:xx:xx:xx
* ```
*
* @return The string representation of the address.
*/
std::string BLEAddress::toString() {
std::stringstream stream;
stream << std::setfill('0') << std::setw(2) << std::hex << (int)((uint8_t *)(m_address))[0] << ':';
stream << std::setfill('0') << std::setw(2) << std::hex << (int)((uint8_t *)(m_address))[1] << ':';
stream << std::setfill('0') << std::setw(2) << std::hex << (int)((uint8_t *)(m_address))[2] << ':';
stream << std::setfill('0') << std::setw(2) << std::hex << (int)((uint8_t *)(m_address))[3] << ':';
stream << std::setfill('0') << std::setw(2) << std::hex << (int)((uint8_t *)(m_address))[4] << ':';
stream << std::setfill('0') << std::setw(2) << std::hex << (int)((uint8_t *)(m_address))[5];
return stream.str();
} // toString
#endif
+34
View File
@@ -0,0 +1,34 @@
/*
* BLEAddress.h
*
* Created on: Jul 2, 2017
* Author: kolban
*/
#ifndef COMPONENTS_CPP_UTILS_BLEADDRESS_H_
#define COMPONENTS_CPP_UTILS_BLEADDRESS_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <esp_gap_ble_api.h> // ESP32 BLE
#include <string>
/**
* @brief A %BLE device address.
*
* Every %BLE device has a unique address which can be used to identify it and form connections.
*/
class BLEAddress {
public:
BLEAddress(esp_bd_addr_t address);
BLEAddress(std::string stringAddress);
bool equals(BLEAddress otherAddress);
esp_bd_addr_t* getNative();
std::string toString();
private:
esp_bd_addr_t m_address;
};
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_CPP_UTILS_BLEADDRESS_H_ */
@@ -0,0 +1,513 @@
/*
* BLEAdvertisedDevice.cpp
*
* During the scanning procedure, we will be finding advertised BLE devices. This class
* models a found device.
*
*
* See also:
* https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile
*
* Created on: Jul 3, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <esp_log.h>
#include <sstream>
#include "BLEAdvertisedDevice.h"
#include "BLEUtils.h"
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-log.h"
#endif
static const char* LOG_TAG="BLEAdvertisedDevice";
BLEAdvertisedDevice::BLEAdvertisedDevice() {
m_adFlag = 0;
m_appearance = 0;
m_deviceType = 0;
m_manufacturerData = "";
m_name = "";
m_rssi = -9999;
m_serviceData = "";
m_txPower = 0;
m_pScan = nullptr;
m_haveAppearance = false;
m_haveManufacturerData = false;
m_haveName = false;
m_haveRSSI = false;
m_haveServiceData = false;
m_haveServiceUUID = false;
m_haveTXPower = false;
} // BLEAdvertisedDevice
/**
* @brief Get the address.
*
* Every %BLE device exposes an address that is used to identify it and subsequently connect to it.
* Call this function to obtain the address of the advertised device.
*
* @return The address of the advertised device.
*/
BLEAddress BLEAdvertisedDevice::getAddress() {
return m_address;
} // getAddress
/**
* @brief Get the appearance.
*
* A %BLE device can declare its own appearance. The appearance is how it would like to be shown to an end user
* typcially in the form of an icon.
*
* @return The appearance of the advertised device.
*/
uint16_t BLEAdvertisedDevice::getAppearance() {
return m_appearance;
} // getAppearance
/**
* @brief Get the manufacturer data.
* @return The manufacturer data of the advertised device.
*/
std::string BLEAdvertisedDevice::getManufacturerData() {
return m_manufacturerData;
} // getManufacturerData
/**
* @brief Get the name.
* @return The name of the advertised device.
*/
std::string BLEAdvertisedDevice::getName() {
return m_name;
} // getName
/**
* @brief Get the RSSI.
* @return The RSSI of the advertised device.
*/
int BLEAdvertisedDevice::getRSSI() {
return m_rssi;
} // getRSSI
/**
* @brief Get the scan object that created this advertisement.
* @return The scan object.
*/
BLEScan* BLEAdvertisedDevice::getScan() {
return m_pScan;
} // getScan
/**
* @brief Get the service data.
* @return The ServiceData of the advertised device.
*/
std::string BLEAdvertisedDevice::getServiceData() {
return m_serviceData;
} //getServiceData
/**
* @brief Get the service data UUID.
* @return The service data UUID.
*/
BLEUUID BLEAdvertisedDevice::getServiceDataUUID() {
return m_serviceDataUUID;
} // getServiceDataUUID
/**
* @brief Get the Service UUID.
* @return The Service UUID of the advertised device.
*/
BLEUUID BLEAdvertisedDevice::getServiceUUID() { //TODO Remove it eventually, is no longer useful
return m_serviceUUIDs[0];
} // getServiceUUID
/**
* @brief Check advertised serviced for existence required UUID
* @return Return true if service is advertised
*/
bool BLEAdvertisedDevice::isAdvertisingService(BLEUUID uuid){
for (int i = 0; i < m_serviceUUIDs.size(); ++i) {
if(m_serviceUUIDs[i].equals(uuid))
return true;
}
return false;
}
/**
* @brief Get the TX Power.
* @return The TX Power of the advertised device.
*/
int8_t BLEAdvertisedDevice::getTXPower() {
return m_txPower;
} // getTXPower
/**
* @brief Does this advertisement have an appearance value?
* @return True if there is an appearance value present.
*/
bool BLEAdvertisedDevice::haveAppearance() {
return m_haveAppearance;
} // haveAppearance
/**
* @brief Does this advertisement have manufacturer data?
* @return True if there is manufacturer data present.
*/
bool BLEAdvertisedDevice::haveManufacturerData() {
return m_haveManufacturerData;
} // haveManufacturerData
/**
* @brief Does this advertisement have a name value?
* @return True if there is a name value present.
*/
bool BLEAdvertisedDevice::haveName() {
return m_haveName;
} // haveName
/**
* @brief Does this advertisement have a signal strength value?
* @return True if there is a signal strength value present.
*/
bool BLEAdvertisedDevice::haveRSSI() {
return m_haveRSSI;
} // haveRSSI
/**
* @brief Does this advertisement have a service data value?
* @return True if there is a service data value present.
*/
bool BLEAdvertisedDevice::haveServiceData() {
return m_haveServiceData;
} // haveServiceData
/**
* @brief Does this advertisement have a service UUID value?
* @return True if there is a service UUID value present.
*/
bool BLEAdvertisedDevice::haveServiceUUID() {
return m_haveServiceUUID;
} // haveServiceUUID
/**
* @brief Does this advertisement have a transmission power value?
* @return True if there is a transmission power value present.
*/
bool BLEAdvertisedDevice::haveTXPower() {
return m_haveTXPower;
} // haveTXPower
/**
* @brief Parse the advertising pay load.
*
* The pay load is a buffer of bytes that is either 31 bytes long or terminated by
* a 0 length value. Each entry in the buffer has the format:
* [length][type][data...]
*
* The length does not include itself but does include everything after it until the next record. A record
* with a length value of 0 indicates a terminator.
*
* https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile
*/
void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) {
uint8_t length;
uint8_t ad_type;
uint8_t sizeConsumed = 0;
bool finished = false;
while(!finished) {
length = *payload; // Retrieve the length of the record.
payload++; // Skip to type
sizeConsumed += 1 + length; // increase the size consumed.
if (length != 0) { // A length of 0 indicates that we have reached the end.
ad_type = *payload;
payload++;
length--;
char* pHex = BLEUtils::buildHexData(nullptr, payload, length);
ESP_LOGD(LOG_TAG, "Type: 0x%.2x (%s), length: %d, data: %s",
ad_type, BLEUtils::advTypeToString(ad_type), length, pHex);
free(pHex);
switch(ad_type) {
case ESP_BLE_AD_TYPE_NAME_CMPL: { // Adv Data Type: 0x09
setName(std::string(reinterpret_cast<char*>(payload), length));
break;
} // ESP_BLE_AD_TYPE_NAME_CMPL
case ESP_BLE_AD_TYPE_TX_PWR: { // Adv Data Type: 0x0A
setTXPower(*payload);
break;
} // ESP_BLE_AD_TYPE_TX_PWR
case ESP_BLE_AD_TYPE_APPEARANCE: { // Adv Data Type: 0x19
setAppearance(*reinterpret_cast<uint16_t*>(payload));
break;
} // ESP_BLE_AD_TYPE_APPEARANCE
case ESP_BLE_AD_TYPE_FLAG: { // Adv Data Type: 0x01
setAdFlag(*payload);
break;
} // ESP_BLE_AD_TYPE_FLAG
case ESP_BLE_AD_TYPE_16SRV_CMPL:
case ESP_BLE_AD_TYPE_16SRV_PART: { // Adv Data Type: 0x02
for (int var = 0; var < length/2; ++var) {
setServiceUUID(BLEUUID(*reinterpret_cast<uint16_t*>(payload+var*2)));
}
break;
} // ESP_BLE_AD_TYPE_16SRV_PART
case ESP_BLE_AD_TYPE_32SRV_CMPL:
case ESP_BLE_AD_TYPE_32SRV_PART: { // Adv Data Type: 0x04
for (int var = 0; var < length/4; ++var) {
setServiceUUID(BLEUUID(*reinterpret_cast<uint32_t*>(payload+var*4)));
}
break;
} // ESP_BLE_AD_TYPE_32SRV_PART
case ESP_BLE_AD_TYPE_128SRV_CMPL: { // Adv Data Type: 0x07
setServiceUUID(BLEUUID(payload, 16, false));
break;
} // ESP_BLE_AD_TYPE_128SRV_CMPL
case ESP_BLE_AD_TYPE_128SRV_PART: { // Adv Data Type: 0x06
setServiceUUID(BLEUUID(payload, 16, false));
break;
} // ESP_BLE_AD_TYPE_128SRV_PART
// See CSS Part A 1.4 Manufacturer Specific Data
case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: {
setManufacturerData(std::string(reinterpret_cast<char*>(payload), length));
break;
} // ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE
case ESP_BLE_AD_TYPE_SERVICE_DATA: { // Adv Data Type: 0x16 (Service Data) - 2 byte UUID
if (length < 2) {
ESP_LOGE(LOG_TAG, "Length too small for ESP_BLE_AD_TYPE_SERVICE_DATA");
break;
}
uint16_t uuid = *(uint16_t *)payload;
setServiceDataUUID(BLEUUID(uuid));
if (length > 2) {
setServiceData(std::string(reinterpret_cast<char*>(payload+2), length-2));
}
break;
} //ESP_BLE_AD_TYPE_SERVICE_DATA
case ESP_BLE_AD_TYPE_32SERVICE_DATA: { // Adv Data Type: 0x20 (Service Data) - 4 byte UUID
if (length < 4) {
ESP_LOGE(LOG_TAG, "Length too small for ESP_BLE_AD_TYPE_32SERVICE_DATA");
break;
}
uint32_t uuid = *(uint32_t *)payload;
setServiceDataUUID(BLEUUID(uuid));
if (length > 4) {
setServiceData(std::string(reinterpret_cast<char*>(payload+4), length-4));
}
break;
} //ESP_BLE_AD_TYPE_32SERVICE_DATA
case ESP_BLE_AD_TYPE_128SERVICE_DATA: { // Adv Data Type: 0x21 (Service Data) - 16 byte UUID
if (length < 16) {
ESP_LOGE(LOG_TAG, "Length too small for ESP_BLE_AD_TYPE_128SERVICE_DATA");
break;
}
setServiceDataUUID(BLEUUID(payload, (size_t)16, false));
if (length > 16) {
setServiceData(std::string(reinterpret_cast<char*>(payload+16), length-16));
}
break;
} //ESP_BLE_AD_TYPE_32SERVICE_DATA
default: {
ESP_LOGD(LOG_TAG, "Unhandled type: adType: %d - 0x%.2x", ad_type, ad_type);
break;
}
} // switch
payload += length;
} // Length <> 0
if (sizeConsumed >=31 || length == 0) {
finished = true;
}
} // !finished
} // parseAdvertisement
/**
* @brief Set the address of the advertised device.
* @param [in] address The address of the advertised device.
*/
void BLEAdvertisedDevice::setAddress(BLEAddress address) {
m_address = address;
} // setAddress
/**
* @brief Set the adFlag for this device.
* @param [in] The discovered adFlag.
*/
void BLEAdvertisedDevice::setAdFlag(uint8_t adFlag) {
m_adFlag = adFlag;
} // setAdFlag
/**
* @brief Set the appearance for this device.
* @param [in] The discovered appearance.
*/
void BLEAdvertisedDevice::setAppearance(uint16_t appearance) {
m_appearance = appearance;
m_haveAppearance = true;
ESP_LOGD(LOG_TAG, "- appearance: %d", m_appearance);
} // setAppearance
/**
* @brief Set the manufacturer data for this device.
* @param [in] The discovered manufacturer data.
*/
void BLEAdvertisedDevice::setManufacturerData(std::string manufacturerData) {
m_manufacturerData = manufacturerData;
m_haveManufacturerData = true;
char* pHex = BLEUtils::buildHexData(nullptr, (uint8_t*)m_manufacturerData.data(), (uint8_t)m_manufacturerData.length());
ESP_LOGD(LOG_TAG, "- manufacturer data: %s", pHex);
free(pHex);
} // setManufacturerData
/**
* @brief Set the name for this device.
* @param [in] name The discovered name.
*/
void BLEAdvertisedDevice::setName(std::string name) {
m_name = name;
m_haveName = true;
ESP_LOGD(LOG_TAG, "- setName(): name: %s", m_name.c_str());
} // setName
/**
* @brief Set the RSSI for this device.
* @param [in] rssi The discovered RSSI.
*/
void BLEAdvertisedDevice::setRSSI(int rssi) {
m_rssi = rssi;
m_haveRSSI = true;
ESP_LOGD(LOG_TAG, "- setRSSI(): rssi: %d", m_rssi);
} // setRSSI
/**
* @brief Set the Scan that created this advertised device.
* @param pScan The Scan that created this advertised device.
*/
void BLEAdvertisedDevice::setScan(BLEScan* pScan) {
m_pScan = pScan;
} // setScan
/**
* @brief Set the Service UUID for this device.
* @param [in] serviceUUID The discovered serviceUUID
*/
void BLEAdvertisedDevice::setServiceUUID(const char* serviceUUID) {
return setServiceUUID(BLEUUID(serviceUUID));
} // setServiceUUID
/**
* @brief Set the Service UUID for this device.
* @param [in] serviceUUID The discovered serviceUUID
*/
void BLEAdvertisedDevice::setServiceUUID(BLEUUID serviceUUID) {
m_serviceUUIDs.push_back(serviceUUID);
m_haveServiceUUID = true;
ESP_LOGD(LOG_TAG, "- addServiceUUID(): serviceUUID: %s", serviceUUID.toString().c_str());
} // setServiceUUID
/**
* @brief Set the ServiceData value.
* @param [in] data ServiceData value.
*/
void BLEAdvertisedDevice::setServiceData(std::string serviceData) {
m_haveServiceData = true; // Set the flag that indicates we have service data.
m_serviceData = serviceData; // Save the service data that we received.
} //setServiceData
/**
* @brief Set the ServiceDataUUID value.
* @param [in] data ServiceDataUUID value.
*/
void BLEAdvertisedDevice::setServiceDataUUID(BLEUUID uuid) {
m_haveServiceData = true; // Set the flag that indicates we have service data.
m_serviceDataUUID = uuid;
} // setServiceDataUUID
/**
* @brief Set the power level for this device.
* @param [in] txPower The discovered power level.
*/
void BLEAdvertisedDevice::setTXPower(int8_t txPower) {
m_txPower = txPower;
m_haveTXPower = true;
ESP_LOGD(LOG_TAG, "- txPower: %d", m_txPower);
} // setTXPower
/**
* @brief Create a string representation of this device.
* @return A string representation of this device.
*/
std::string BLEAdvertisedDevice::toString() {
std::stringstream ss;
ss << "Name: " << getName() << ", Address: " << getAddress().toString();
if (haveAppearance()) {
ss << ", appearance: " << getAppearance();
}
if (haveManufacturerData()) {
char *pHex = BLEUtils::buildHexData(nullptr, (uint8_t*)getManufacturerData().data(), getManufacturerData().length());
ss << ", manufacturer data: " << pHex;
free(pHex);
}
if (haveServiceUUID()) {
ss << ", serviceUUID: " << getServiceUUID().toString();
}
if (haveTXPower()) {
ss << ", txPower: " << (int)getTXPower();
}
return ss.str();
} // toString
#endif /* CONFIG_BT_ENABLED */
@@ -0,0 +1,117 @@
/*
* BLEAdvertisedDevice.h
*
* Created on: Jul 3, 2017
* Author: kolban
*/
#ifndef COMPONENTS_CPP_UTILS_BLEADVERTISEDDEVICE_H_
#define COMPONENTS_CPP_UTILS_BLEADVERTISEDDEVICE_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <esp_gattc_api.h>
#include <map>
#include "BLEAddress.h"
#include "BLEScan.h"
#include "BLEUUID.h"
class BLEScan;
/**
* @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 BLEAdvertisedDevice {
public:
BLEAdvertisedDevice();
BLEAddress getAddress();
uint16_t getAppearance();
std::string getManufacturerData();
std::string getName();
int getRSSI();
BLEScan* getScan();
std::string getServiceData();
BLEUUID getServiceDataUUID();
BLEUUID getServiceUUID();
int8_t getTXPower();
bool isAdvertisingService(BLEUUID uuid);
bool haveAppearance();
bool haveManufacturerData();
bool haveName();
bool haveRSSI();
bool haveServiceData();
bool haveServiceUUID();
bool haveTXPower();
std::string toString();
private:
friend class BLEScan;
void parseAdvertisement(uint8_t* payload);
void setAddress(BLEAddress address);
void setAdFlag(uint8_t adFlag);
void setAdvertizementResult(uint8_t* payload);
void setAppearance(uint16_t appearance);
void setManufacturerData(std::string manufacturerData);
void setName(std::string name);
void setRSSI(int rssi);
void setScan(BLEScan* pScan);
void setServiceData(std::string data);
void setServiceDataUUID(BLEUUID uuid);
void setServiceUUID(const char* serviceUUID);
void setServiceUUID(BLEUUID serviceUUID);
void setTXPower(int8_t txPower);
bool m_haveAppearance;
bool m_haveManufacturerData;
bool m_haveName;
bool m_haveRSSI;
bool m_haveServiceData;
bool m_haveServiceUUID;
bool m_haveTXPower;
BLEAddress m_address = BLEAddress((uint8_t*)"\0\0\0\0\0\0");
uint8_t m_adFlag;
uint16_t m_appearance;
int m_deviceType;
std::string m_manufacturerData;
std::string m_name;
BLEScan* m_pScan;
int m_rssi;
std::vector<BLEUUID> m_serviceUUIDs;
int8_t m_txPower;
std::string m_serviceData;
BLEUUID m_serviceDataUUID;
};
/**
* @brief A callback handler for callbacks associated device scanning.
*
* When we are performing a scan as a %BLE client, we may wish to know when a new device that is advertising
* has been found. This class can be sub-classed and registered such that when a scan is performed and
* a new advertised device has been found, we will be called back to be notified.
*/
class BLEAdvertisedDeviceCallbacks {
public:
virtual ~BLEAdvertisedDeviceCallbacks() {}
/**
* @brief Called when a new scan result is detected.
*
* As we are scanning, we will find new devices. When found, this call back is invoked with a reference to the
* device that was found. During any individual scan, a device will only be detected one time.
*/
virtual void onResult(BLEAdvertisedDevice advertisedDevice) = 0;
};
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_CPP_UTILS_BLEADVERTISEDDEVICE_H_ */
@@ -0,0 +1,157 @@
/*
* BLEAdvertising.cpp
*
* This class encapsulates advertising a BLE Server.
* Created on: Jun 21, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "BLEAdvertising.h"
#include <esp_log.h>
#include <esp_err.h>
#include "BLEUtils.h"
#include "GeneralUtils.h"
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-log.h"
#endif
static const char* LOG_TAG = "BLEAdvertising";
/**
* @brief Construct a default advertising object.
*
*/
BLEAdvertising::BLEAdvertising() {
m_advData.set_scan_rsp = false;
m_advData.include_name = true;
m_advData.include_txpower = true;
m_advData.min_interval = 0x20;
m_advData.max_interval = 0x40;
m_advData.appearance = 0x00;
m_advData.manufacturer_len = 0;
m_advData.p_manufacturer_data = nullptr;
m_advData.service_data_len = 0;
m_advData.p_service_data = nullptr;
m_advData.service_uuid_len = 0;
m_advData.p_service_uuid = nullptr;
m_advData.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT);
m_advParams.adv_int_min = 0x20;
m_advParams.adv_int_max = 0x40;
m_advParams.adv_type = ADV_TYPE_IND;
m_advParams.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
m_advParams.channel_map = ADV_CHNL_ALL;
m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY;
} // BLEAdvertising
/**
* @brief Add a service uuid to exposed list of services.
* @param [in] serviceUUID The UUID of the service to expose.
*/
void BLEAdvertising::addServiceUUID(BLEUUID serviceUUID) {
m_serviceUUIDs.push_back(serviceUUID);
} // addServiceUUID
/**
* @brief Add a service uuid to exposed list of services.
* @param [in] serviceUUID The string representation of the service to expose.
*/
void BLEAdvertising::addServiceUUID(const char* serviceUUID) {
addServiceUUID(BLEUUID(serviceUUID));
} // addServiceUUID
/**
* @brief Set the device appearance in the advertising data.
* The appearance attribute is of type 0x19. The codes for distinct appearances can be found here:
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.gap.appearance.xml.
* @param [in] appearance The appearance of the device in the advertising data.
* @return N/A.
*/
void BLEAdvertising::setAppearance(uint16_t appearance) {
m_advData.appearance = appearance;
} // setAppearance
/**
* @brief Start advertising.
* Start advertising.
* @return N/A.
*/
void BLEAdvertising::start() {
ESP_LOGD(LOG_TAG, ">> start");
// We have a vector of service UUIDs that we wish to advertise. In order to use the
// ESP-IDF framework, these must be supplied in a contiguous array of their 128bit (16 byte)
// representations. If we have 1 or more services to advertise then we allocate enough
// storage to host them and then copy them in one at a time into the contiguous storage.
int numServices = m_serviceUUIDs.size();
if (numServices > 0) {
m_advData.service_uuid_len = 16*numServices;
m_advData.p_service_uuid = new uint8_t[m_advData.service_uuid_len];
uint8_t* p = m_advData.p_service_uuid;
for (int i=0; i<numServices; i++) {
ESP_LOGD(LOG_TAG, "- advertising service: %s", m_serviceUUIDs[i].toString().c_str());
BLEUUID serviceUUID128 = m_serviceUUIDs[i].to128();
memcpy(p, serviceUUID128.getNative()->uuid.uuid128, 16);
p+=16;
}
} else {
m_advData.service_uuid_len = 0;
ESP_LOGD(LOG_TAG, "- no services advertised");
}
// Set the configuration for advertising.
m_advData.set_scan_rsp = false;
esp_err_t errRc = ::esp_ble_gap_config_adv_data(&m_advData);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "<< esp_ble_gap_config_adv_data: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
m_advData.set_scan_rsp = true;
errRc = ::esp_ble_gap_config_adv_data(&m_advData);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "<< esp_ble_gap_config_adv_data (Scan response): rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
// If we had services to advertise then we previously allocated some storage for them.
// Here we release that storage.
if (m_advData.service_uuid_len > 0) {
delete[] m_advData.p_service_uuid;
m_advData.p_service_uuid = nullptr;
}
// Start advertising.
errRc = ::esp_ble_gap_start_advertising(&m_advParams);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "<< esp_ble_gap_start_advertising: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
ESP_LOGD(LOG_TAG, "<< start");
} // start
/**
* @brief Stop advertising.
* Stop advertising.
* @return N/A.
*/
void BLEAdvertising::stop() {
ESP_LOGD(LOG_TAG, ">> stop");
esp_err_t errRc = ::esp_ble_gap_stop_advertising();
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gap_stop_advertising: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
ESP_LOGD(LOG_TAG, "<< stop");
} // stop
#endif /* CONFIG_BT_ENABLED */
@@ -0,0 +1,35 @@
/*
* BLEAdvertising.h
*
* Created on: Jun 21, 2017
* Author: kolban
*/
#ifndef COMPONENTS_CPP_UTILS_BLEADVERTISING_H_
#define COMPONENTS_CPP_UTILS_BLEADVERTISING_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <esp_gap_ble_api.h>
#include "BLEUUID.h"
#include <vector>
/**
* @brief Perform and manage %BLE advertising.
*
* A %BLE server will want to perform advertising in order to make itself known to %BLE clients.
*/
class BLEAdvertising {
public:
BLEAdvertising();
void addServiceUUID(BLEUUID serviceUUID);
void addServiceUUID(const char* serviceUUID);
void start();
void stop();
void setAppearance(uint16_t appearance);
private:
esp_ble_adv_data_t m_advData;
esp_ble_adv_params_t m_advParams;
std::vector<BLEUUID> m_serviceUUIDs;
};
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_CPP_UTILS_BLEADVERTISING_H_ */
@@ -0,0 +1,719 @@
/*
* BLECharacteristic.cpp
*
* Created on: Jun 22, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <sstream>
#include <string.h>
#include <iomanip>
#include <stdlib.h>
#include "sdkconfig.h"
#include <esp_log.h>
#include <esp_err.h>
#include "BLECharacteristic.h"
#include "BLEService.h"
#include "BLEUtils.h"
#include "BLE2902.h"
#include "GeneralUtils.h"
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-log.h"
#endif
static const char* LOG_TAG = "BLECharacteristic";
#define NULL_HANDLE (0xffff)
/**
* @brief Construct a characteristic
* @param [in] uuid - UUID (const char*) for the characteristic.
* @param [in] properties - Properties for the characteristic.
*/
BLECharacteristic::BLECharacteristic(const char* uuid, uint32_t properties) : BLECharacteristic(BLEUUID(uuid), properties) {
}
/**
* @brief Construct a characteristic
* @param [in] uuid - UUID for the characteristic.
* @param [in] properties - Properties for the characteristic.
*/
BLECharacteristic::BLECharacteristic(BLEUUID uuid, uint32_t properties) {
m_bleUUID = uuid;
m_handle = NULL_HANDLE;
m_properties = (esp_gatt_char_prop_t)0;
m_pCallbacks = nullptr;
setBroadcastProperty((properties & PROPERTY_BROADCAST) !=0);
setReadProperty((properties & PROPERTY_READ) !=0);
setWriteProperty((properties & PROPERTY_WRITE) !=0);
setNotifyProperty((properties & PROPERTY_NOTIFY) !=0);
setIndicateProperty((properties & PROPERTY_INDICATE) !=0);
setWriteNoResponseProperty((properties & PROPERTY_WRITE_NR) !=0);
} // BLECharacteristic
/**
* @brief Destructor.
*/
BLECharacteristic::~BLECharacteristic() {
//free(m_value.attr_value); // Release the storage for the value.
} // ~BLECharacteristic
/**
* @brief Associate a descriptor with this characteristic.
* @param [in] pDescriptor
* @return N/A.
*/
void BLECharacteristic::addDescriptor(BLEDescriptor* pDescriptor) {
ESP_LOGD(LOG_TAG, ">> addDescriptor(): Adding %s to %s", pDescriptor->toString().c_str(), toString().c_str());
m_descriptorMap.setByUUID(pDescriptor->getUUID(), pDescriptor);
ESP_LOGD(LOG_TAG, "<< addDescriptor()");
} // addDescriptor
/**
* @brief Register a new characteristic with the ESP runtime.
* @param [in] pService The service with which to associate this characteristic.
*/
void BLECharacteristic::executeCreate(BLEService* pService) {
ESP_LOGD(LOG_TAG, ">> executeCreate()");
if (m_handle != NULL_HANDLE) {
ESP_LOGE(LOG_TAG, "Characteristic already has a handle.");
return;
}
m_pService = pService; // Save the service for to which this characteristic belongs.
ESP_LOGD(LOG_TAG, "Registering characteristic (esp_ble_gatts_add_char): uuid: %s, service: %s",
getUUID().toString().c_str(),
m_pService->toString().c_str());
esp_attr_control_t control;
control.auto_rsp = ESP_GATT_RSP_BY_APP;
m_semaphoreCreateEvt.take("executeCreate");
/*
esp_attr_value_t value;
value.attr_len = m_value.getLength();
value.attr_max_len = ESP_GATT_MAX_ATTR_LEN;
value.attr_value = m_value.getData();
*/
esp_err_t errRc = ::esp_ble_gatts_add_char(
m_pService->getHandle(),
getUUID().getNative(),
static_cast<esp_gatt_perm_t>(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE),
getProperties(),
//&value,
nullptr,
&control); // Whether to auto respond or not.
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_add_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
m_semaphoreCreateEvt.wait("executeCreate");
// Now that we have registered the characteristic, we must also register all the descriptors associated with this
// characteristic. We iterate through each of those and invoke the registration call to register them with the
// ESP environment.
BLEDescriptor* pDescriptor = m_descriptorMap.getFirst();
while (pDescriptor != nullptr) {
pDescriptor->executeCreate(this);
pDescriptor = m_descriptorMap.getNext();
} // End while
ESP_LOGD(LOG_TAG, "<< executeCreate");
} // executeCreate
/**
* @brief Return the BLE Descriptor for the given UUID if associated with this characteristic.
* @param [in] descriptorUUID The UUID of the descriptor that we wish to retrieve.
* @return The BLE Descriptor. If no such descriptor is associated with the characteristic, nullptr is returned.
*/
BLEDescriptor* BLECharacteristic::getDescriptorByUUID(const char* descriptorUUID) {
return m_descriptorMap.getByUUID(BLEUUID(descriptorUUID));
} // getDescriptorByUUID
/**
* @brief Return the BLE Descriptor for the given UUID if associated with this characteristic.
* @param [in] descriptorUUID The UUID of the descriptor that we wish to retrieve.
* @return The BLE Descriptor. If no such descriptor is associated with the characteristic, nullptr is returned.
*/
BLEDescriptor* BLECharacteristic::getDescriptorByUUID(BLEUUID descriptorUUID) {
return m_descriptorMap.getByUUID(descriptorUUID);
} // getDescriptorByUUID
/**
* @brief Get the handle of the characteristic.
* @return The handle of the characteristic.
*/
uint16_t BLECharacteristic::getHandle() {
return m_handle;
} // getHandle
esp_gatt_char_prop_t BLECharacteristic::getProperties() {
return m_properties;
} // getProperties
/**
* @brief Get the service associated with this characteristic.
*/
BLEService* BLECharacteristic::getService() {
return m_pService;
} // getService
/**
* @brief Get the UUID of the characteristic.
* @return The UUID of the characteristic.
*/
BLEUUID BLECharacteristic::getUUID() {
return m_bleUUID;
} // getUUID
/**
* @brief Retrieve the current value of the characteristic.
* @return A pointer to storage containing the current characteristic value.
*/
std::string BLECharacteristic::getValue() {
return m_value.getValue();
} // getValue
void BLECharacteristic::handleGATTServerEvent(
esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t* param) {
switch(event) {
// Events handled:
// ESP_GATTS_ADD_CHAR_EVT
// ESP_GATTS_WRITE_EVT
// ESP_GATTS_READ_EVT
//
// ESP_GATTS_EXEC_WRITE_EVT
// When we receive this event it is an indication that a previous write long needs to be committed.
//
// exec_write:
// - uint16_t conn_id
// - uint32_t trans_id
// - esp_bd_addr_t bda
// - uint8_t exec_write_flag
//
case ESP_GATTS_EXEC_WRITE_EVT: {
if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) {
m_value.commit();
if (m_pCallbacks != nullptr) {
m_pCallbacks->onWrite(this); // Invoke the onWrite callback handler.
}
} else {
m_value.cancel();
}
esp_err_t errRc = ::esp_ble_gatts_send_response(
gatts_if,
param->write.conn_id,
param->write.trans_id, ESP_GATT_OK, nullptr);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
}
break;
} // ESP_GATTS_EXEC_WRITE_EVT
// ESP_GATTS_ADD_CHAR_EVT - Indicate that a characteristic was added to the service.
// add_char:
// - esp_gatt_status_t status
// - uint16_t attr_handle
// - uint16_t service_handle
// - esp_bt_uuid_t char_uuid
case ESP_GATTS_ADD_CHAR_EVT: {
if (getUUID().equals(BLEUUID(param->add_char.char_uuid)) &&
getHandle() == param->add_char.attr_handle &&
getService()->getHandle()==param->add_char.service_handle) {
m_semaphoreCreateEvt.give();
}
break;
} // ESP_GATTS_ADD_CHAR_EVT
// ESP_GATTS_WRITE_EVT - A request to write the value of a characteristic has arrived.
//
// write:
// - uint16_t conn_id
// - uint16_t trans_id
// - esp_bd_addr_t bda
// - uint16_t handle
// - uint16_t offset
// - bool need_rsp
// - bool is_prep
// - uint16_t len
// - uint8_t *value
//
case ESP_GATTS_WRITE_EVT: {
// We check if this write request is for us by comparing the handles in the event. If it is for us
// we save the new value. Next we look at the need_rsp flag which indicates whether or not we need
// to send a response. If we do, then we formulate a response and send it.
if (param->write.handle == m_handle) {
if (param->write.is_prep) {
m_value.addPart(param->write.value, param->write.len);
} else {
setValue(param->write.value, param->write.len);
}
ESP_LOGD(LOG_TAG, " - Response to write event: New value: handle: %.2x, uuid: %s",
getHandle(), getUUID().toString().c_str());
char* pHexData = BLEUtils::buildHexData(nullptr, param->write.value, param->write.len);
ESP_LOGD(LOG_TAG, " - Data: length: %d, data: %s", param->write.len, pHexData);
free(pHexData);
if (param->write.need_rsp) {
esp_gatt_rsp_t rsp;
rsp.attr_value.len = param->write.len;
rsp.attr_value.handle = m_handle;
rsp.attr_value.offset = param->write.offset;
rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
memcpy(rsp.attr_value.value, param->write.value, param->write.len);
esp_err_t errRc = ::esp_ble_gatts_send_response(
gatts_if,
param->write.conn_id,
param->write.trans_id, ESP_GATT_OK, &rsp);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
}
} // Response needed
if (m_pCallbacks != nullptr && param->write.is_prep != true) {
m_pCallbacks->onWrite(this); // Invoke the onWrite callback handler.
}
} // Match on handles.
break;
} // ESP_GATTS_WRITE_EVT
// ESP_GATTS_READ_EVT - A request to read the value of a characteristic has arrived.
//
// read:
// - uint16_t conn_id
// - uint32_t trans_id
// - esp_bd_addr_t bda
// - uint16_t handle
// - uint16_t offset
// - bool is_long
// - bool need_rsp
//
case ESP_GATTS_READ_EVT: {
ESP_LOGD(LOG_TAG, "- Testing: 0x%.2x == 0x%.2x", param->read.handle, m_handle);
if (param->read.handle == m_handle) {
if (m_pCallbacks != nullptr) {
m_pCallbacks->onRead(this); // Invoke the read callback.
}
// Here's an interesting thing. The read request has the option of saying whether we need a response
// or not. What would it "mean" to receive a read request and NOT send a response back? That feels like
// a very strange read.
//
// We have to handle the case where the data we wish to send back to the client is greater than the maximum
// packet size of 22 bytes. In this case, we become responsible for chunking the data into uints of 22 bytes.
// The apparent algorithm is as follows.
// If the is_long flag is set then this is a follow on from an original read and we will already have sent at least 22 bytes.
// If the is_long flag is not set then we need to check how much data we are going to send. If we are sending LESS than
// 22 bytes, then we "just" send it and thats the end of the story.
// If we are sending 22 bytes exactly, we just send it BUT we will get a follow on request.
// If we are sending more than 22 bytes, we send the first 22 bytes and we will get a follow on request.
// Because of follow on request processing, we need to maintain an offset of how much data we have already sent
// so that when a follow on request arrives, we know where to start in the data to send the next sequence.
// Note that the indication that the client will send a follow on request is that we sent exactly 22 bytes as a response.
// If our payload is divisible by 22 then the last response will be a response of 0 bytes in length.
//
// The following code has deliberately not been factored to make it fewer statements because this would cloud the
// the logic flow comprehension.
//
if (param->read.need_rsp) {
ESP_LOGD(LOG_TAG, "Sending a response (esp_ble_gatts_send_response)");
esp_gatt_rsp_t rsp;
std::string value = m_value.getValue();
if (param->read.is_long) {
if (value.length() - m_value.getReadOffset() < 22) {
// This is the last in the chain
rsp.attr_value.len = value.length() - m_value.getReadOffset();
rsp.attr_value.offset = m_value.getReadOffset();
memcpy(rsp.attr_value.value, value.data() + rsp.attr_value.offset, rsp.attr_value.len);
m_value.setReadOffset(0);
} else {
// There will be more to come.
rsp.attr_value.len = 22;
rsp.attr_value.offset = m_value.getReadOffset();
memcpy(rsp.attr_value.value, value.data() + rsp.attr_value.offset, rsp.attr_value.len);
m_value.setReadOffset(rsp.attr_value.offset + 22);
}
} else {
if (value.length() > 21) {
// Too big for a single shot entry.
m_value.setReadOffset(22);
rsp.attr_value.len = 22;
rsp.attr_value.offset = 0;
memcpy(rsp.attr_value.value, value.data(), rsp.attr_value.len);
} else {
// Will fit in a single packet with no callbacks required.
rsp.attr_value.len = value.length();
rsp.attr_value.offset = 0;
memcpy(rsp.attr_value.value, value.data(), rsp.attr_value.len);
}
}
rsp.attr_value.handle = param->read.handle;
rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
char *pHexData = BLEUtils::buildHexData(nullptr, rsp.attr_value.value, rsp.attr_value.len);
ESP_LOGD(LOG_TAG, " - Data: length=%d, data=%s, offset=%d", rsp.attr_value.len, pHexData, rsp.attr_value.offset);
free(pHexData);
esp_err_t errRc = ::esp_ble_gatts_send_response(
gatts_if, param->read.conn_id,
param->read.trans_id,
ESP_GATT_OK,
&rsp);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
}
} // Response needed
} // Handle matches this characteristic.
break;
} // ESP_GATTS_READ_EVT
// ESP_GATTS_CONF_EVT
//
// conf:
// - esp_gatt_status_t status – The status code.
// - uint16_t conn_id – The connection used.
//
case ESP_GATTS_CONF_EVT: {
m_semaphoreConfEvt.give();
break;
}
default: {
break;
} // default
} // switch event
// Give each of the descriptors associated with this characteristic the opportunity to handle the
// event.
m_descriptorMap.handleGATTServerEvent(event, gatts_if, param);
} // handleGATTServerEvent
/**
* @brief Send an indication.
* An indication is a transmission of up to the first 20 bytes of the characteristic value. An indication
* will block waiting a positive confirmation from the client.
* @return N/A
*/
void BLECharacteristic::indicate() {
ESP_LOGD(LOG_TAG, ">> indicate: length: %d", m_value.getValue().length());
assert(getService() != nullptr);
assert(getService()->getServer() != nullptr);
GeneralUtils::hexDump((uint8_t*)m_value.getValue().data(), m_value.getValue().length());
if (getService()->getServer()->getConnectedCount() == 0) {
ESP_LOGD(LOG_TAG, "<< indicate: No connected clients.");
return;
}
// Test to see if we have a 0x2902 descriptor. If we do, then check to see if indications are enabled
// and, if not, prevent the indication.
BLE2902 *p2902 = (BLE2902*)getDescriptorByUUID((uint16_t)0x2902);
if (p2902 != nullptr && !p2902->getIndications()) {
ESP_LOGD(LOG_TAG, "<< indications disabled; ignoring");
return;
}
if (m_value.getValue().length() > 20) {
ESP_LOGD(LOG_TAG, "- Truncating to 20 bytes (maximum notify size)");
}
size_t length = m_value.getValue().length();
if (length > 20) {
length = 20;
}
m_semaphoreConfEvt.take("indicate");
esp_err_t errRc = ::esp_ble_gatts_send_indicate(
getService()->getServer()->getGattsIf(),
getService()->getServer()->getConnId(),
getHandle(), length, (uint8_t*)m_value.getValue().data(), true); // The need_confirm = true makes this an indication.
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_send_indicate: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
m_semaphoreConfEvt.wait("indicate");
ESP_LOGD(LOG_TAG, "<< indicate");
} // indicate
/**
* @brief Send a notify.
* A notification is a transmission of up to the first 20 bytes of the characteristic value. An notification
* will not block; it is a fire and forget.
* @return N/A.
*/
void BLECharacteristic::notify() {
ESP_LOGD(LOG_TAG, ">> notify: length: %d", m_value.getValue().length());
assert(getService() != nullptr);
assert(getService()->getServer() != nullptr);
GeneralUtils::hexDump((uint8_t*)m_value.getValue().data(), m_value.getValue().length());
if (getService()->getServer()->getConnectedCount() == 0) {
ESP_LOGD(LOG_TAG, "<< notify: No connected clients.");
return;
}
// Test to see if we have a 0x2902 descriptor. If we do, then check to see if notification is enabled
// and, if not, prevent the notification.
BLE2902 *p2902 = (BLE2902*)getDescriptorByUUID((uint16_t)0x2902);
if (p2902 != nullptr && !p2902->getNotifications()) {
ESP_LOGD(LOG_TAG, "<< notifications disabled; ignoring");
return;
}
if (m_value.getValue().length() > 20) {
ESP_LOGD(LOG_TAG, "- Truncating to 20 bytes (maximum notify size)");
}
size_t length = m_value.getValue().length();
if (length > 20) {
length = 20;
}
esp_err_t errRc = ::esp_ble_gatts_send_indicate(
getService()->getServer()->getGattsIf(),
getService()->getServer()->getConnId(),
getHandle(), length, (uint8_t*)m_value.getValue().data(), false); // The need_confirm = false makes this a notify.
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_send_indicate: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
ESP_LOGD(LOG_TAG, "<< notify");
} // Notify
/**
* @brief Set the permission to broadcast.
* A characteristics has properties associated with it which define what it is capable of doing.
* One of these is the broadcast flag.
* @param [in] value The flag value of the property.
* @return N/A
*/
void BLECharacteristic::setBroadcastProperty(bool value) {
//ESP_LOGD(LOG_TAG, "setBroadcastProperty(%d)", value);
if (value) {
m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_BROADCAST);
} else {
m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST);
}
} // setBroadcastProperty
/**
* @brief Set the callback handlers for this characteristic.
* @param [in] pCallbacks An instance of a callbacks structure used to define any callbacks for the characteristic.
*/
void BLECharacteristic::setCallbacks(BLECharacteristicCallbacks* pCallbacks) {
ESP_LOGD(LOG_TAG, ">> setCallbacks: 0x%x", (uint32_t)pCallbacks);
m_pCallbacks = pCallbacks;
ESP_LOGD(LOG_TAG, "<< setCallbacks");
} // setCallbacks
/**
* @brief Set the BLE handle associated with this characteristic.
* A user program will request that a characteristic be created against a service. When the characteristic has been
* registered, the service will be given a "handle" that it knows the characteristic as. This handle is unique to the
* server/service but it is told to the service, not the characteristic associated with the service. This internally
* exposed function can be invoked by the service against this model of the characteristic to allow the characteristic
* to learn its own handle. Once the characteristic knows its own handle, it will be able to see incoming GATT events
* that will be propagated down to it which contain a handle value and now know that the event is destined for it.
* @param [in] handle The handle associated with this characteristic.
*/
void BLECharacteristic::setHandle(uint16_t handle) {
ESP_LOGD(LOG_TAG, ">> setHandle: handle=0x%.2x, characteristic uuid=%s", handle, getUUID().toString().c_str());
m_handle = handle;
ESP_LOGD(LOG_TAG, "<< setHandle");
} // setHandle
/**
* @brief Set the Indicate property value.
* @param [in] value Set to true if we are to allow indicate messages.
*/
void BLECharacteristic::setIndicateProperty(bool value) {
//ESP_LOGD(LOG_TAG, "setIndicateProperty(%d)", value);
if (value) {
m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_INDICATE);
} else {
m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_INDICATE);
}
} // setIndicateProperty
/**
* @brief Set the Notify property value.
* @param [in] value Set to true if we are to allow notification messages.
*/
void BLECharacteristic::setNotifyProperty(bool value) {
//ESP_LOGD(LOG_TAG, "setNotifyProperty(%d)", value);
if (value) {
m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_NOTIFY);
} else {
m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY);
}
} // setNotifyProperty
/**
* @brief Set the Read property value.
* @param [in] value Set to true if we are to allow reads.
*/
void BLECharacteristic::setReadProperty(bool value) {
//ESP_LOGD(LOG_TAG, "setReadProperty(%d)", value);
if (value) {
m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_READ);
} else {
m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_READ);
}
} // setReadProperty
/**
* @brief Set the value of the characteristic.
* @param [in] data The data to set for the characteristic.
* @param [in] length The length of the data in bytes.
*/
void BLECharacteristic::setValue(uint8_t* data, size_t length) {
char *pHex = BLEUtils::buildHexData(nullptr, data, length);
ESP_LOGD(LOG_TAG, ">> setValue: length=%d, data=%s, characteristic UUID=%s", length, pHex, getUUID().toString().c_str());
free(pHex);
if (length > ESP_GATT_MAX_ATTR_LEN) {
ESP_LOGE(LOG_TAG, "Size %d too large, must be no bigger than %d", length, ESP_GATT_MAX_ATTR_LEN);
return;
}
m_value.setValue(data, length);
ESP_LOGD(LOG_TAG, "<< setValue");
} // setValue
/**
* @brief Set the value of the characteristic from string data.
* We set the value of the characteristic from the bytes contained in the
* string.
* @param [in] Set the value of the characteristic.
* @return N/A.
*/
void BLECharacteristic::setValue(std::string value) {
setValue((uint8_t*)(value.data()), value.length());
} // setValue
/**
* @brief Set the Write No Response property value.
* @param [in] value Set to true if we are to allow writes with no response.
*/
void BLECharacteristic::setWriteNoResponseProperty(bool value) {
//ESP_LOGD(LOG_TAG, "setWriteNoResponseProperty(%d)", value);
if (value) {
m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_WRITE_NR);
} else {
m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR);
}
} // setWriteNoResponseProperty
/**
* @brief Set the Write property value.
* @param [in] value Set to true if we are to allow writes.
*/
void BLECharacteristic::setWriteProperty(bool value) {
//ESP_LOGD(LOG_TAG, "setWriteProperty(%d)", value);
if (value) {
m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_WRITE);
} else {
m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_WRITE);
}
} // setWriteProperty
/**
* @brief Return a string representation of the characteristic.
* @return A string representation of the characteristic.
*/
std::string BLECharacteristic::toString() {
std::stringstream stringstream;
stringstream << std::hex << std::setfill('0');
stringstream << "UUID: " << m_bleUUID.toString() + ", handle: 0x" << std::setw(2) << m_handle;
stringstream << " " <<
((m_properties & ESP_GATT_CHAR_PROP_BIT_READ)?"Read ":"") <<
((m_properties & ESP_GATT_CHAR_PROP_BIT_WRITE)?"Write ":"") <<
((m_properties & ESP_GATT_CHAR_PROP_BIT_WRITE_NR)?"WriteNoResponse ":"") <<
((m_properties & ESP_GATT_CHAR_PROP_BIT_BROADCAST)?"Broadcast ":"") <<
((m_properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY)?"Notify ":"") <<
((m_properties & ESP_GATT_CHAR_PROP_BIT_INDICATE)?"Indicate ":"");
return stringstream.str();
} // toString
BLECharacteristicCallbacks::~BLECharacteristicCallbacks() {}
/**
* @brief Callback function to support a read request.
* @param [in] pCharacteristic The characteristic that is the source of the event.
*/
void BLECharacteristicCallbacks::onRead(BLECharacteristic *pCharacteristic) {
ESP_LOGD("BLECharacteristicCallbacks", ">> onRead: default");
ESP_LOGD("BLECharacteristicCallbacks", "<< onRead");
} // onRead
/**
* @brief Callback function to support a write request.
* @param [in] pCharacteristic The characteristic that is the source of the event.
*/
void BLECharacteristicCallbacks::onWrite(BLECharacteristic *pCharacteristic) {
ESP_LOGD("BLECharacteristicCallbacks", ">> onWrite: default");
ESP_LOGD("BLECharacteristicCallbacks", "<< onWrite");
} // onWrite
#endif /* CONFIG_BT_ENABLED */
@@ -0,0 +1,132 @@
/*
* BLECharacteristic.h
*
* Created on: Jun 22, 2017
* Author: kolban
*/
#ifndef COMPONENTS_CPP_UTILS_BLECHARACTERISTIC_H_
#define COMPONENTS_CPP_UTILS_BLECHARACTERISTIC_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <string>
#include <map>
#include "BLEUUID.h"
#include <esp_gatts_api.h>
#include "BLEDescriptor.h"
#include "BLEValue.h"
#include "FreeRTOS.h"
class BLEService;
class BLEDescriptor;
class BLECharacteristicCallbacks;
/**
* @brief A management structure for %BLE descriptors.
*/
class BLEDescriptorMap {
public:
void setByUUID(const char* uuid, BLEDescriptor *pDescriptor);
void setByUUID(BLEUUID uuid, BLEDescriptor *pDescriptor);
void setByHandle(uint16_t handle, BLEDescriptor *pDescriptor);
BLEDescriptor* getByUUID(const char* uuid);
BLEDescriptor* getByUUID(BLEUUID uuid);
BLEDescriptor* getByHandle(uint16_t handle);
std::string toString();
void handleGATTServerEvent(
esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t* param);
BLEDescriptor* getFirst();
BLEDescriptor* getNext();
private:
std::map<std::string, BLEDescriptor *> m_uuidMap;
std::map<uint16_t, BLEDescriptor *> m_handleMap;
std::map<std::string, BLEDescriptor *>::iterator m_iterator;
};
/**
* @brief The model of a %BLE Characteristic.
*
* A %BLE Characteristic is an identified value container that manages a value. It is exposed by a %BLE server and
* can be read and written to by a %BLE client.
*/
class BLECharacteristic {
public:
BLECharacteristic(const char* uuid, uint32_t properties = 0);
BLECharacteristic(BLEUUID uuid, uint32_t properties = 0);
virtual ~BLECharacteristic();
void addDescriptor(BLEDescriptor* pDescriptor);
BLEDescriptor* getDescriptorByUUID(const char* descriptorUUID);
BLEDescriptor* getDescriptorByUUID(BLEUUID descriptorUUID);
//size_t getLength();
BLEUUID getUUID();
std::string getValue();
void indicate();
void notify();
void setBroadcastProperty(bool value);
void setCallbacks(BLECharacteristicCallbacks* pCallbacks);
void setIndicateProperty(bool value);
void setNotifyProperty(bool value);
void setReadProperty(bool value);
void setValue(uint8_t* data, size_t size);
void setValue(std::string value);
void setWriteProperty(bool value);
void setWriteNoResponseProperty(bool value);
std::string toString();
uint16_t getHandle();
static const uint32_t PROPERTY_READ = 1<<0;
static const uint32_t PROPERTY_WRITE = 1<<1;
static const uint32_t PROPERTY_NOTIFY = 1<<2;
static const uint32_t PROPERTY_BROADCAST = 1<<3;
static const uint32_t PROPERTY_INDICATE = 1<<4;
static const uint32_t PROPERTY_WRITE_NR = 1<<5;
private:
friend class BLEServer;
friend class BLEService;
friend class BLEDescriptor;
friend class BLECharacteristicMap;
BLEUUID m_bleUUID;
BLEDescriptorMap m_descriptorMap;
uint16_t m_handle;
esp_gatt_char_prop_t m_properties;
BLECharacteristicCallbacks* m_pCallbacks;
BLEService* m_pService;
BLEValue m_value;
void handleGATTServerEvent(
esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t* param);
void executeCreate(BLEService* pService);
esp_gatt_char_prop_t getProperties();
BLEService* getService();
void setHandle(uint16_t handle);
FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt");
FreeRTOS::Semaphore m_semaphoreConfEvt = FreeRTOS::Semaphore("ConfEvt");
}; // BLECharacteristic
/**
* @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 BLECharacteristicCallbacks {
public:
virtual ~BLECharacteristicCallbacks();
virtual void onRead(BLECharacteristic* pCharacteristic);
virtual void onWrite(BLECharacteristic* pCharacteristic);
};
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_CPP_UTILS_BLECHARACTERISTIC_H_ */
@@ -0,0 +1,143 @@
/*
* BLECharacteristicMap.cpp
*
* Created on: Jun 22, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <sstream>
#include <iomanip>
#include "BLEService.h"
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-log.h"
#endif
/**
* @brief Return the characteristic by handle.
* @param [in] handle The handle to look up the characteristic.
* @return The characteristic.
*/
BLECharacteristic* BLECharacteristicMap::getByHandle(uint16_t handle) {
return m_handleMap.at(handle);
} // getByHandle
/**
* @brief Return the characteristic by UUID.
* @param [in] UUID The UUID to look up the characteristic.
* @return The characteristic.
*/
BLECharacteristic* BLECharacteristicMap::getByUUID(const char* uuid) {
return getByUUID(BLEUUID(uuid));
}
/**
* @brief Return the characteristic by UUID.
* @param [in] UUID The UUID to look up the characteristic.
* @return The characteristic.
*/
BLECharacteristic* BLECharacteristicMap::getByUUID(BLEUUID uuid) {
for (auto &myPair : m_uuidMap) {
if (myPair.first->getUUID().equals(uuid)) {
return myPair.first;
}
}
//return m_uuidMap.at(uuid.toString());
return nullptr;
} // getByUUID
/**
* @brief Get the first characteristic in the map.
* @return The first characteristic in the map.
*/
BLECharacteristic* BLECharacteristicMap::getFirst() {
m_iterator = m_uuidMap.begin();
if (m_iterator == m_uuidMap.end()) {
return nullptr;
}
BLECharacteristic* pRet = m_iterator->first;
m_iterator++;
return pRet;
} // getFirst
/**
* @brief Get the next characteristic in the map.
* @return The next characteristic in the map.
*/
BLECharacteristic* BLECharacteristicMap::getNext() {
if (m_iterator == m_uuidMap.end()) {
return nullptr;
}
BLECharacteristic* pRet = m_iterator->first;
m_iterator++;
return pRet;
} // getNext
/**
* @brief Pass the GATT server event onwards to each of the characteristics found in the mapping
* @param [in] event
* @param [in] gatts_if
* @param [in] param
*/
void BLECharacteristicMap::handleGATTServerEvent(
esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t* param) {
// Invoke the handler for every Service we have.
for (auto &myPair : m_uuidMap) {
myPair.first->handleGATTServerEvent(event, gatts_if, param);
}
} // handleGATTServerEvent
/**
* @brief Set the characteristic by handle.
* @param [in] handle The handle of the characteristic.
* @param [in] characteristic The characteristic to cache.
* @return N/A.
*/
void BLECharacteristicMap::setByHandle(uint16_t handle,
BLECharacteristic *characteristic) {
m_handleMap.insert(std::pair<uint16_t, BLECharacteristic *>(handle, characteristic));
} // setByHandle
/**
* @brief Set the characteristic by UUID.
* @param [in] uuid The uuid of the characteristic.
* @param [in] characteristic The characteristic to cache.
* @return N/A.
*/
void BLECharacteristicMap::setByUUID(
BLECharacteristic *pCharacteristic,
BLEUUID uuid) {
m_uuidMap.insert(std::pair<BLECharacteristic *, std::string>(pCharacteristic, uuid.toString()));
} // setByUUID
/**
* @brief Return a string representation of the characteristic map.
* @return A string representation of the characteristic map.
*/
std::string BLECharacteristicMap::toString() {
std::stringstream stringStream;
stringStream << std::hex << std::setfill('0');
int count=0;
for (auto &myPair: m_uuidMap) {
if (count > 0) {
stringStream << "\n";
}
count++;
stringStream << "handle: 0x" << std::setw(2) << myPair.first->getHandle() << ", uuid: " + myPair.first->getUUID().toString();
}
return stringStream.str();
} // toString
#endif /* CONFIG_BT_ENABLED */
+414
View File
@@ -0,0 +1,414 @@
/*
* BLEDevice.cpp
*
* Created on: Mar 22, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <esp_log.h>
#include <bt.h>
#include <esp_bt_main.h>
#include <esp_gap_ble_api.h>
#include <esp_gattc_api.h>
#include "BLEClient.h"
#include "BLEUtils.h"
#include "BLEService.h"
#include "GeneralUtils.h"
#include <string>
#include <sstream>
#include <unordered_set>
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-log.h"
#endif
/*
* Design
* ------
* When we perform a searchService() requests, we are asking the BLE server to return each of the services
* that it exposes. For each service, we received an ESP_GATTC_SEARCH_RES_EVT event which contains details
* of the exposed service including its UUID.
*
* The objects we will invent for a BLEClient will be as follows:
* * BLERemoteService - A model of a remote service.
* * BLERemoteCharacteristic - A model of a remote characteristic
* * BLERemoteDescriptor - A model of a remote descriptor.
*
* Since there is a hierarchical relationship here, we will have the idea that from a BLERemoteService will own
* zero or more remote characteristics and a BLERemoteCharacteristic will own zero or more remote BLEDescriptors.
*
* We will assume that a BLERemoteService contains a map that maps BLEUUIDs to the set of owned characteristics
* and that a BLECharacteristic contains a map that maps BLEUUIDs to the set of owned descriptors.
*
*
*/
static const char* LOG_TAG = "BLEClient";
BLEClient::BLEClient() {
m_pClientCallbacks = nullptr;
m_conn_id = 0;
m_gattc_if = 0;
m_haveServices = false;
m_isConnected = false; // Initially, we are flagged as not connected.
} // BLEClient
/**
* @brief Destructor.
*/
BLEClient::~BLEClient() {
// We may have allocated service references associated with this client. Before we are finished
// with the client, we must release resources.
for (auto &myPair : m_servicesMap) {
delete myPair.second;
}
m_servicesMap.clear();
} // ~BLEClient
/**
* @brief Connect to the partner (BLE Server).
* @param [in] address The address of the partner.
* @return True on success.
*/
bool BLEClient::connect(BLEAddress address) {
ESP_LOGD(LOG_TAG, ">> connect(%s)", address.toString().c_str());
// We need the connection handle that we get from registering the application. We register the app
// and then block on its completion. When the event has arrived, we will have the handle.
m_semaphoreRegEvt.take("connect");
esp_err_t errRc = ::esp_ble_gattc_app_register(0);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gattc_app_register: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return false;
}
m_semaphoreRegEvt.wait("connect");
m_peerAddress = address;
// Perform the open connection request against the target BLE Server.
m_semaphoreOpenEvt.take("connect");
errRc = ::esp_ble_gattc_open(
getGattcIf(),
*getPeerAddress().getNative(), // address
1 // direct connection
);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gattc_open: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return false;
}
uint32_t rc = m_semaphoreOpenEvt.wait("connect");
ESP_LOGD(LOG_TAG, "<< connect(), rc=%d", rc==ESP_GATT_OK);
return rc == ESP_GATT_OK;
} // connect
/**
* @brief Disconnect from the peer.
* @return N/A.
*/
void BLEClient::disconnect() {
ESP_LOGD(LOG_TAG, ">> disconnect()");
esp_err_t errRc = ::esp_ble_gattc_close(getGattcIf(), getConnId());
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gattc_close: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
esp_ble_gattc_app_unregister(getGattcIf());
m_peerAddress = BLEAddress("00:00:00:00:00:00");
ESP_LOGD(LOG_TAG, "<< disconnect()");
} // disconnect
/**
* @brief Handle GATT Client events
*/
void BLEClient::gattClientEventHandler(
esp_gattc_cb_event_t event,
esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t* evtParam) {
// Execute handler code based on the type of event received.
switch(event) {
//
// ESP_GATTC_DISCONNECT_EVT
//
// disconnect:
// - esp_gatt_status_t status
// - uint16_t conn_id
// - esp_bd_addr_t remote_bda
case ESP_GATTC_DISCONNECT_EVT: {
// If we receive a disconnect event, set the class flag that indicates that we are
// no longer connected.
m_isConnected = false;
break;
} // ESP_GATTC_DISCONNECT_EVT
//
// ESP_GATTC_OPEN_EVT
//
// open:
// - esp_gatt_status_t status
// - uint16_t conn_id
// - esp_bd_addr_t remote_bda
// - uint16_t mtu
//
case ESP_GATTC_OPEN_EVT: {
m_conn_id = evtParam->open.conn_id;
if (m_pClientCallbacks != nullptr) {
m_pClientCallbacks->onConnect(this);
}
if (evtParam->open.status == ESP_GATT_OK) {
m_isConnected = true; // Flag us as connected.
}
m_semaphoreOpenEvt.give(evtParam->open.status);
break;
} // ESP_GATTC_OPEN_EVT
//
// ESP_GATTC_REG_EVT
//
// reg:
// esp_gatt_status_t status
// uint16_t app_id
//
case ESP_GATTC_REG_EVT: {
m_gattc_if = gattc_if;
m_semaphoreRegEvt.give();
break;
} // ESP_GATTC_REG_EVT
//
// ESP_GATTC_SEARCH_CMPL_EVT
//
// search_cmpl:
// - esp_gatt_status_t status
// - uint16_t conn_id
//
case ESP_GATTC_SEARCH_CMPL_EVT: {
m_semaphoreSearchCmplEvt.give();
break;
} // ESP_GATTC_SEARCH_CMPL_EVT
//
// ESP_GATTC_SEARCH_RES_EVT
//
// search_res:
// - uint16_t conn_id
// - uint16_t start_handle
// - uint16_t end_handle
// - esp_gatt_id_t srvc_id
//
case ESP_GATTC_SEARCH_RES_EVT: {
BLEUUID uuid = BLEUUID(evtParam->search_res.srvc_id);
BLERemoteService* pRemoteService = new BLERemoteService(
evtParam->search_res.srvc_id,
this,
evtParam->search_res.start_handle,
evtParam->search_res.end_handle
);
m_servicesMap.insert(std::pair<std::string, BLERemoteService *>(uuid.toString(), pRemoteService));
break;
} // ESP_GATTC_SEARCH_RES_EVT
default: {
break;
}
} // Switch
// Pass the request on to all services.
for (auto &myPair : m_servicesMap) {
myPair.second->gattClientEventHandler(event, gattc_if, evtParam);
}
} // gattClientEventHandler
uint16_t BLEClient::getConnId() {
return m_conn_id;
} // getConnId
esp_gatt_if_t BLEClient::getGattcIf() {
return m_gattc_if;
} // getGattcIf
/**
* @brief Retrieve the address of the peer.
*
* Returns the Bluetooth device address of the %BLE peer to which this client is connected.
*/
BLEAddress BLEClient::getPeerAddress() {
return m_peerAddress;
} // getAddress
/**
* @brief Ask the BLE server for the RSSI value.
* @return The RSSI value.
*/
int BLEClient::getRssi() {
ESP_LOGD(LOG_TAG, ">> getRssi()");
if (!isConnected()) {
ESP_LOGD(LOG_TAG, "<< getRssi(): Not connected");
return 0;
}
// We make the API call to read the RSSI value which is an asynchronous operation. We expect to receive
// an ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT to indicate completion.
//
m_semaphoreRssiCmplEvt.take("getRssi");
esp_err_t rc = ::esp_ble_gap_read_rssi(*getPeerAddress().getNative());
if (rc != ESP_OK) {
ESP_LOGE(LOG_TAG, "<< getRssi: esp_ble_gap_read_rssi: rc=%d %s", rc, GeneralUtils::errorToString(rc));
return 0;
}
int rssiValue = m_semaphoreRssiCmplEvt.wait("getRssi");
ESP_LOGD(LOG_TAG, "<< getRssi(): %d", rssiValue);
return rssiValue;
} // getRssi
/**
* @brief Get the service BLE Remote Service instance corresponding to the uuid.
* @param [in] uuid The UUID of the service being sought.
* @return A reference to the Service or nullptr if don't know about it.
*/
BLERemoteService* BLEClient::getService(const char* uuid) {
return getService(BLEUUID(uuid));
} // getService
/**
* @brief Get the service object corresponding to the uuid.
* @param [in] uuid The UUID of the service being sought.
* @return A reference to the Service or nullptr if don't know about it.
*/
BLERemoteService* BLEClient::getService(BLEUUID uuid) {
ESP_LOGD(LOG_TAG, ">> getService: uuid: %s", uuid.toString().c_str());
// Design
// ------
// We wish to retrieve the service given its UUID. It is possible that we have not yet asked the
// device what services it has in which case we have nothing to match against. If we have not
// asked the device about its services, then we do that now. Once we get the results we can then
// examine the services map to see if it has the service we are looking for.
if (!m_haveServices) {
getServices();
}
std::string uuidStr = uuid.toString();
for (auto &myPair : m_servicesMap) {
if (myPair.first == uuidStr) {
ESP_LOGD(LOG_TAG, "<< getService: found the service with uuid: %s", uuid.toString().c_str());
return myPair.second;
}
} // End of each of the services.
ESP_LOGD(LOG_TAG, "<< getService: not found");
return nullptr;
} // getService
/**
* @brief Ask the remote %BLE server for its services.
* A %BLE Server exposes a set of services for its partners. Here we ask the server for its set of
* services and wait until we have received them all.
* @return N/A
*/
std::map<std::string, BLERemoteService*>* BLEClient::getServices() {
/*
* Design
* ------
* We invoke esp_ble_gattc_search_service. This will request a list of the service exposed by the
* peer BLE partner to be returned as events. Each event will be an an instance of ESP_GATTC_SEARCH_RES_EVT
* and will culminate with an ESP_GATTC_SEARCH_CMPL_EVT when all have been received.
*/
ESP_LOGD(LOG_TAG, ">> getServices");
m_servicesMap.clear();
esp_err_t errRc = esp_ble_gattc_search_service(
getGattcIf(),
getConnId(),
nullptr // Filter UUID
);
m_semaphoreSearchCmplEvt.take("getServices");
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gattc_search_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return &m_servicesMap;
}
m_semaphoreSearchCmplEvt.wait("getServices");
m_haveServices = true; // Remember that we now have services.
ESP_LOGD(LOG_TAG, "<< getServices");
return &m_servicesMap;
} // getServices
/**
* @brief Handle a received GAP event.
*
* @param [in] event
* @param [in] param
*/
void BLEClient::handleGAPEvent(
esp_gap_ble_cb_event_t event,
esp_ble_gap_cb_param_t* param) {
ESP_LOGD(LOG_TAG, "BLEClient ... handling GAP event!");
switch(event) {
//
// ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT
//
// read_rssi_cmpl
// - esp_bt_status_t status
// - int8_t rssi
// - esp_bd_addr_t remote_addr
//
case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: {
m_semaphoreRssiCmplEvt.give((uint32_t)param->read_rssi_cmpl.rssi);
break;
} // ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT
default:
break;
}
} // handleGAPEvent
/**
* @brief Are we connected to a partner?
* @return True if we are connected and false if we are not connected.
*/
bool BLEClient::isConnected() {
return m_isConnected;
} // isConnected
/**
* @brief Set the callbacks that will be invoked.
*/
void BLEClient::setClientCallbacks(BLEClientCallbacks* pClientCallbacks) {
m_pClientCallbacks = pClientCallbacks;
} // setClientCallbacks
/**
* @brief Return a string representation of this client.
* @return A string representation of this client.
*/
std::string BLEClient::toString() {
std::ostringstream ss;
ss << "peer address: " << m_peerAddress.toString();
ss << "\nServices:\n";
for (auto &myPair : m_servicesMap) {
ss << myPair.second->toString() << "\n";
// myPair.second is the value
}
return ss.str();
} // toString
#endif // CONFIG_BT_ENABLED
+85
View File
@@ -0,0 +1,85 @@
/*
* BLEDevice.h
*
* Created on: Mar 22, 2017
* Author: kolban
*/
#ifndef MAIN_BLEDEVICE_H_
#define MAIN_BLEDEVICE_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <esp_gattc_api.h>
#include <string.h>
#include <map>
#include <string>
#include "BLERemoteService.h"
#include "BLEService.h"
#include "BLEAddress.h"
class BLERemoteService;
class BLEClientCallbacks;
/**
* @brief A model of a %BLE client.
*/
class BLEClient {
public:
BLEClient();
~BLEClient();
bool connect(BLEAddress address);
void disconnect();
BLEAddress getPeerAddress();
int getRssi();
std::map<std::string, BLERemoteService*>* getServices();
BLERemoteService* getService(const char* uuid);
BLERemoteService* getService(BLEUUID uuid);
void handleGAPEvent(
esp_gap_ble_cb_event_t event,
esp_ble_gap_cb_param_t* param);
bool isConnected();
void setClientCallbacks(BLEClientCallbacks *pClientCallbacks);
std::string toString();
private:
friend class BLEDevice;
friend class BLERemoteService;
friend class BLERemoteCharacteristic;
friend class BLERemoteDescriptor;
void gattClientEventHandler(
esp_gattc_cb_event_t event,
esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t* param);
uint16_t getConnId();
esp_gatt_if_t getGattcIf();
BLEAddress m_peerAddress = BLEAddress((uint8_t*)"\0\0\0\0\0\0");
uint16_t m_conn_id;
// int m_deviceType;
esp_gatt_if_t m_gattc_if;
bool m_isConnected;
BLEClientCallbacks* m_pClientCallbacks;
FreeRTOS::Semaphore m_semaphoreRegEvt = FreeRTOS::Semaphore("RegEvt");
FreeRTOS::Semaphore m_semaphoreOpenEvt = FreeRTOS::Semaphore("OpenEvt");
FreeRTOS::Semaphore m_semaphoreSearchCmplEvt = FreeRTOS::Semaphore("SearchCmplEvt");
FreeRTOS::Semaphore m_semaphoreRssiCmplEvt = FreeRTOS::Semaphore("RssiCmplEvt");
std::map<std::string, BLERemoteService*> m_servicesMap;
bool m_haveServices; // Have we previously obtain the set of services.
}; // class BLEDevice
/**
* @brief Callbacks associated with a %BLE client.
*/
class BLEClientCallbacks {
public:
virtual ~BLEClientCallbacks() {};
virtual void onConnect(BLEClient *pClient) = 0;
};
#endif // CONFIG_BT_ENABLED
#endif /* MAIN_BLEDEVICE_H_ */
+339
View File
@@ -0,0 +1,339 @@
/*
* BLEDescriptor.cpp
*
* Created on: Jun 22, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <sstream>
#include <string.h>
#include <iomanip>
#include <stdlib.h>
#include "sdkconfig.h"
#include <esp_log.h>
#include <esp_err.h>
#include "BLEService.h"
#include "BLEDescriptor.h"
#include "GeneralUtils.h"
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-log.h"
#endif
static const char* LOG_TAG = "BLEDescriptor";
#define NULL_HANDLE (0xffff)
/**
* @brief BLEDescriptor constructor.
*/
BLEDescriptor::BLEDescriptor(const char* uuid) : BLEDescriptor(BLEUUID(uuid)) {
}
/**
* @brief BLEDescriptor constructor.
*/
BLEDescriptor::BLEDescriptor(BLEUUID uuid) {
m_bleUUID = uuid;
m_value.attr_value = (uint8_t *)malloc(ESP_GATT_MAX_ATTR_LEN); // Allocate storage for the value.
m_value.attr_len = 0;
m_value.attr_max_len = ESP_GATT_MAX_ATTR_LEN;
m_handle = NULL_HANDLE;
m_pCharacteristic = nullptr; // No initial characteristic.
m_pCallback = nullptr; // No initial callback.
} // BLEDescriptor
/**
* @brief BLEDescriptor destructor.
*/
BLEDescriptor::~BLEDescriptor() {
free(m_value.attr_value);
} // ~BLEDescriptor
/**
* @brief Execute the creation of the descriptor with the BLE runtime in ESP.
* @param [in] pCharacteristic The characteristic to which to register this descriptor.
*/
void BLEDescriptor::executeCreate(BLECharacteristic* pCharacteristic) {
ESP_LOGD(LOG_TAG, ">> executeCreate(): %s", toString().c_str());
if (m_handle != NULL_HANDLE) {
ESP_LOGE(LOG_TAG, "Descriptor already has a handle.");
return;
}
m_pCharacteristic = pCharacteristic; // Save the characteristic associated with this service.
esp_attr_control_t control;
control.auto_rsp = ESP_GATT_RSP_BY_APP;
m_semaphoreCreateEvt.take("executeCreate");
esp_err_t errRc = ::esp_ble_gatts_add_char_descr(
pCharacteristic->getService()->getHandle(),
getUUID().getNative(),
(esp_gatt_perm_t)(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE),
&m_value,
&control);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_add_char_descr: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
m_semaphoreCreateEvt.wait("executeCreate");
ESP_LOGD(LOG_TAG, "<< executeCreate");
} // executeCreate
/**
* @brief Get the BLE handle for this descriptor.
* @return The handle for this descriptor.
*/
uint16_t BLEDescriptor::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 BLEDescriptor::getLength() {
return m_value.attr_len;
} // getLength
/**
* @brief Get the UUID of the descriptor.
*/
BLEUUID BLEDescriptor::getUUID() {
return m_bleUUID;
} // getUUID
/**
* @brief Get the value of this descriptor.
* @return A pointer to the value of this descriptor.
*/
uint8_t* BLEDescriptor::getValue() {
return m_value.attr_value;
} // getValue
/**
* @brief Handle GATT server events for the descripttor.
* @param [in] event
* @param [in] gatts_if
* @param [in] param
*/
void BLEDescriptor::handleGATTServerEvent(
esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param) {
switch(event) {
// ESP_GATTS_ADD_CHAR_DESCR_EVT
//
// add_char_descr:
// - esp_gatt_status_t status
// - uint16_t attr_handle
// - uint16_t service_handle
// - esp_bt_uuid_t char_uuid
case ESP_GATTS_ADD_CHAR_DESCR_EVT: {
/*
ESP_LOGD(LOG_TAG, "DEBUG: m_pCharacteristic: %x", (uint32_t)m_pCharacteristic);
ESP_LOGD(LOG_TAG, "DEBUG: m_bleUUID: %s, add_char_descr.char_uuid: %s, equals: %d",
m_bleUUID.toString().c_str(),
BLEUUID(param->add_char_descr.char_uuid).toString().c_str(),
m_bleUUID.equals(BLEUUID(param->add_char_descr.char_uuid)));
ESP_LOGD(LOG_TAG, "DEBUG: service->getHandle: %x, add_char_descr.service_handle: %x",
m_pCharacteristic->getService()->getHandle(), param->add_char_descr.service_handle);
ESP_LOGD(LOG_TAG, "DEBUG: service->lastCharacteristic: %x",
(uint32_t)m_pCharacteristic->getService()->getLastCreatedCharacteristic());
*/
if (m_pCharacteristic != nullptr &&
m_bleUUID.equals(BLEUUID(param->add_char_descr.char_uuid)) &&
m_pCharacteristic->getService()->getHandle() == param->add_char_descr.service_handle &&
m_pCharacteristic == m_pCharacteristic->getService()->getLastCreatedCharacteristic()) {
setHandle(param->add_char_descr.attr_handle);
m_semaphoreCreateEvt.give();
}
break;
} // ESP_GATTS_ADD_CHAR_DESCR_EVT
// ESP_GATTS_WRITE_EVT - A request to write the value of a descriptor has arrived.
//
// write:
// - uint16_t conn_id
// - uint16_t trans_id
// - esp_bd_addr_t bda
// - uint16_t handle
// - uint16_t offset
// - bool need_rsp
// - bool is_prep
// - uint16_t len
// - uint8_t *value
case ESP_GATTS_WRITE_EVT: {
if (param->write.handle == m_handle) {
setValue(param->write.value, param->write.len); // Set the value of the descriptor.
esp_gatt_rsp_t rsp; // Build a response.
rsp.attr_value.len = getLength();
rsp.attr_value.handle = m_handle;
rsp.attr_value.offset = 0;
rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
memcpy(rsp.attr_value.value, getValue(), rsp.attr_value.len);
esp_err_t errRc = ::esp_ble_gatts_send_response(
gatts_if,
param->write.conn_id,
param->write.trans_id,
ESP_GATT_OK,
&rsp);
if (errRc != ESP_OK) { // Check the return code from the send of the response.
ESP_LOGE(LOG_TAG, "esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
}
if (m_pCallback != nullptr) { // We have completed the write, if there is a user supplied callback handler, invoke it now.
m_pCallback->onWrite(this); // Invoke the onWrite callback handler.
}
} // End of ... this is our handle.
break;
} // ESP_GATTS_WRITE_EVT
// ESP_GATTS_READ_EVT - A request to read the value of a descriptor has arrived.
//
// read:
// - uint16_t conn_id
// - uint32_t trans_id
// - esp_bd_addr_t bda
// - uint16_t handle
// - uint16_t offset
// - bool is_long
// - bool need_rsp
//
case ESP_GATTS_READ_EVT: {
if (param->read.handle == m_handle) { // If this event is for this descriptor ... process it
if (m_pCallback != nullptr) { // If we have a user supplied callback, invoke it now.
m_pCallback->onRead(this); // Invoke the onRead callback method in the callback handler.
}
if (param->read.need_rsp) { // Do we need a response
ESP_LOGD(LOG_TAG, "Sending a response (esp_ble_gatts_send_response)");
esp_gatt_rsp_t rsp;
rsp.attr_value.len = getLength();
rsp.attr_value.handle = param->read.handle;
rsp.attr_value.offset = 0;
rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
memcpy(rsp.attr_value.value, getValue(), rsp.attr_value.len);
esp_err_t errRc = ::esp_ble_gatts_send_response(
gatts_if,
param->read.conn_id,
param->read.trans_id,
ESP_GATT_OK,
&rsp);
if (errRc != ESP_OK) { // Check the return code from the send of the response.
ESP_LOGE(LOG_TAG, "esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
}
} // End of need a response.
} // End of this is our handle
break;
} // ESP_GATTS_READ_EVT
default: {
break;
}
}// switch event
} // handleGATTServerEvent
/**
* @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 BLEDescriptor::setCallbacks(BLEDescriptorCallbacks* pCallback) {
ESP_LOGD(LOG_TAG, ">> setCallbacks: 0x%x", (uint32_t)pCallback);
m_pCallback = pCallback;
ESP_LOGD(LOG_TAG, "<< setCallbacks");
} // 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 BLEDescriptor::setHandle(uint16_t handle) {
ESP_LOGD(LOG_TAG, ">> setHandle(0x%.2x): Setting descriptor handle to be 0x%.2x", handle, handle);
m_handle = handle;
ESP_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 BLEDescriptor::setValue(uint8_t* data, size_t length) {
if (length > ESP_GATT_MAX_ATTR_LEN) {
ESP_LOGE(LOG_TAG, "Size %d too large, must be no bigger than %d", length, ESP_GATT_MAX_ATTR_LEN);
return;
}
m_value.attr_len = length;
memcpy(m_value.attr_value, data, length);
} // setValue
/**
* @brief Set the value of the descriptor.
* @param [in] value The value of the descriptor in string form.
*/
void BLEDescriptor::setValue(std::string value) {
setValue((uint8_t *)value.data(), value.length());
} // setValue
/**
* @brief Return a string representation of the descriptor.
* @return A string representation of the descriptor.
*/
std::string BLEDescriptor::toString() {
std::stringstream stringstream;
stringstream << std::hex << std::setfill('0');
stringstream << "UUID: " << m_bleUUID.toString() + ", handle: 0x" << std::setw(2) << m_handle;
return stringstream.str();
} // toString
BLEDescriptorCallbacks::~BLEDescriptorCallbacks() {}
/**
* @brief Callback function to support a read request.
* @param [in] pDescriptor The descriptor that is the source of the event.
*/
void BLEDescriptorCallbacks::onRead(BLEDescriptor* pDescriptor) {
ESP_LOGD("BLEDescriptorCallbacks", ">> onRead: default");
ESP_LOGD("BLEDescriptorCallbacks", "<< onRead");
} // onRead
/**
* @brief Callback function to support a write request.
* @param [in] pDescriptor The descriptor that is the source of the event.
*/
void BLEDescriptorCallbacks::onWrite(BLEDescriptor* pDescriptor) {
ESP_LOGD("BLEDescriptorCallbacks", ">> onWrite: default");
ESP_LOGD("BLEDescriptorCallbacks", "<< onWrite");
} // onWrite
#endif /* CONFIG_BT_ENABLED */
+71
View File
@@ -0,0 +1,71 @@
/*
* BLEDescriptor.h
*
* Created on: Jun 22, 2017
* Author: kolban
*/
#ifndef COMPONENTS_CPP_UTILS_BLEDESCRIPTOR_H_
#define COMPONENTS_CPP_UTILS_BLEDESCRIPTOR_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <string>
#include "BLEUUID.h"
#include "BLECharacteristic.h"
#include <esp_gatts_api.h>
#include "FreeRTOS.h"
class BLEService;
class BLECharacteristic;
class BLEDescriptorCallbacks;
/**
* @brief A model of a %BLE descriptor.
*/
class BLEDescriptor {
public:
BLEDescriptor(const char* uuid);
BLEDescriptor(BLEUUID uuid);
virtual ~BLEDescriptor();
uint16_t getHandle();
size_t getLength();
BLEUUID getUUID();
uint8_t* getValue();
void handleGATTServerEvent(
esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t* param);
void setCallbacks(BLEDescriptorCallbacks* pCallbacks);
void setValue(uint8_t* data, size_t size);
void setValue(std::string value);
std::string toString();
private:
friend class BLEDescriptorMap;
friend class BLECharacteristic;
BLEUUID m_bleUUID;
esp_attr_value_t m_value;
uint16_t m_handle;
BLECharacteristic* m_pCharacteristic;
BLEDescriptorCallbacks* m_pCallback;
void executeCreate(BLECharacteristic* pCharacteristic);
void setHandle(uint16_t handle);
FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt");
};
/**
* @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 BLEDescriptorCallbacks {
public:
virtual ~BLEDescriptorCallbacks();
virtual void onRead(BLEDescriptor* pDescriptor);
virtual void onWrite(BLEDescriptor* pDescriptor);
};
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_CPP_UTILS_BLEDESCRIPTOR_H_ */
@@ -0,0 +1,152 @@
/*
* BLEDescriptorMap.cpp
*
* Created on: Jun 22, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <sstream>
#include <iomanip>
#include "BLECharacteristic.h"
#include "BLEDescriptor.h"
#include <esp_gatts_api.h> // ESP32 BLE
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-log.h"
#endif
/**
* @brief Return the descriptor by UUID.
* @param [in] UUID The UUID to look up the descriptor.
* @return The descriptor. If not present, then nullptr is returned.
*/
BLEDescriptor* BLEDescriptorMap::getByUUID(const char* uuid) {
return getByUUID(BLEUUID(uuid));
}
/**
* @brief Return the descriptor by UUID.
* @param [in] UUID The UUID to look up the descriptor.
* @return The descriptor. If not present, then nullptr is returned.
*/
BLEDescriptor* BLEDescriptorMap::getByUUID(BLEUUID uuid) {
for (auto &myPair : m_uuidMap) {
if (myPair.second->getUUID().equals(uuid)) {
return myPair.second;
}
}
//return m_uuidMap.at(uuid.toString());
return nullptr;
} // getByUUID
/**
* @brief Return the descriptor by handle.
* @param [in] handle The handle to look up the descriptor.
* @return The descriptor.
*/
BLEDescriptor* BLEDescriptorMap::getByHandle(uint16_t handle) {
return m_handleMap.at(handle);
} // getByHandle
/**
* @brief Set the descriptor by UUID.
* @param [in] uuid The uuid of the descriptor.
* @param [in] characteristic The descriptor to cache.
* @return N/A.
*/
void BLEDescriptorMap::setByUUID(const char* uuid, BLEDescriptor *pDescriptor){
m_uuidMap.insert(std::pair<std::string, BLEDescriptor *>(uuid, pDescriptor));
} // setByUUID
/**
* @brief Set the descriptor by UUID.
* @param [in] uuid The uuid of the descriptor.
* @param [in] characteristic The descriptor to cache.
* @return N/A.
*/
void BLEDescriptorMap::setByUUID(BLEUUID uuid, BLEDescriptor *pDescriptor) {
m_uuidMap.insert(std::pair<std::string, BLEDescriptor *>(uuid.toString(), pDescriptor));
} // setByUUID
/**
* @brief Set the descriptor by handle.
* @param [in] handle The handle of the descriptor.
* @param [in] descriptor The descriptor to cache.
* @return N/A.
*/
void BLEDescriptorMap::setByHandle(uint16_t handle,
BLEDescriptor *pDescriptor) {
m_handleMap.insert(std::pair<uint16_t, BLEDescriptor *>(handle, pDescriptor));
} // setByHandle
/**
* @brief Return a string representation of the descriptor map.
* @return A string representation of the descriptor map.
*/
std::string BLEDescriptorMap::toString() {
std::stringstream stringStream;
stringStream << std::hex << std::setfill('0');
int count=0;
for (auto &myPair: m_uuidMap) {
if (count > 0) {
stringStream << "\n";
}
count++;
stringStream << "handle: 0x" << std::setw(2) << myPair.second->getHandle() << ", uuid: " + myPair.second->getUUID().toString();
}
return stringStream.str();
} // toString
/**
* @breif Pass the GATT server event onwards to each of the descriptors found in the mapping
* @param [in] event
* @param [in] gatts_if
* @param [in] param
*/
void BLEDescriptorMap::handleGATTServerEvent(
esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param) {
// Invoke the handler for every descriptor we have.
for (auto &myPair : m_uuidMap) {
myPair.second->handleGATTServerEvent(event, gatts_if, param);
}
} // handleGATTServerEvent
/**
* @brief Get the first descriptor in the map.
* @return The first descriptor in the map.
*/
BLEDescriptor* BLEDescriptorMap::getFirst() {
m_iterator = m_uuidMap.begin();
if (m_iterator == m_uuidMap.end()) {
return nullptr;
}
BLEDescriptor *pRet = m_iterator->second;
m_iterator++;
return pRet;
} // getFirst
/**
* @brief Get the next descriptor in the map.
* @return The next descriptor in the map.
*/
BLEDescriptor* BLEDescriptorMap::getNext() {
if (m_iterator == m_uuidMap.end()) {
return nullptr;
}
BLEDescriptor *pRet = m_iterator->second;
m_iterator++;
return pRet;
} // getNext
#endif /* CONFIG_BT_ENABLED */
+279
View File
@@ -0,0 +1,279 @@
/*
* BLE.cpp
*
* Created on: Mar 16, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <freertos/task.h>
#include <esp_err.h>
#include <nvs_flash.h>
#include <bt.h> // ESP32 BLE
#include <esp_bt_main.h> // ESP32 BLE
#include <esp_gap_ble_api.h> // ESP32 BLE
#include <esp_gatts_api.h> // ESP32 BLE
#include <esp_gattc_api.h> // ESP32 BLE
#include <esp_err.h> // ESP32 ESP-IDF
#include <esp_log.h> // ESP32 ESP-IDF
#include <map> // Part of C++ Standard library
#include <sstream> // Part of C++ Standard library
#include <iomanip> // Part of C++ Standard library
#include "BLEDevice.h"
#include "BLEClient.h"
#include "BLEUtils.h"
#include "GeneralUtils.h"
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-log.h"
#endif
static const char* LOG_TAG = "BLEDevice";
/**
* Singletons for the BLEDevice.
*/
BLEServer* BLEDevice::m_pServer = nullptr;
BLEScan* BLEDevice::m_pScan = nullptr;
BLEClient* BLEDevice::m_pClient = nullptr;
bool initialized = false;
/**
* @brief Create a new instance of a client.
* @return A new instance of the client.
*/
BLEClient* BLEDevice::createClient() {
m_pClient = new BLEClient();
return m_pClient;
} // createClient
/**
* @brief Create a new instance of a server.
* @return A new instance of the server.
*/
BLEServer* BLEDevice::createServer() {
ESP_LOGD(LOG_TAG, ">> createServer");
m_pServer = new BLEServer();
m_pServer->createApp(0);
ESP_LOGD(LOG_TAG, "<< createServer");
return m_pServer;
} // createServer
/**
* @brief Handle GATT server events.
*
* @param [in] event The event that has been newly received.
* @param [in] gatts_if The connection to the GATT interface.
* @param [in] param Parameters for the event.
*/
void BLEDevice::gattServerEventHandler(
esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t* param
) {
ESP_LOGD(LOG_TAG, "gattServerEventHandler [esp_gatt_if: %d] ... %s",
gatts_if,
BLEUtils::gattServerEventTypeToString(event).c_str());
BLEUtils::dumpGattServerEvent(event, gatts_if, param);
if (BLEDevice::m_pServer != nullptr) {
BLEDevice::m_pServer->handleGATTServerEvent(event, gatts_if, param);
}
} // gattServerEventHandler
/**
* @brief Handle GATT client events.
*
* Handler for the GATT client events.
*
* @param [in] event
* @param [in] gattc_if
* @param [in] param
*/
void BLEDevice::gattClientEventHandler(
esp_gattc_cb_event_t event,
esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t* param) {
ESP_LOGD(LOG_TAG, "gattClientEventHandler [esp_gatt_if: %d] ... %s",
gattc_if, BLEUtils::gattClientEventTypeToString(event).c_str());
BLEUtils::dumpGattClientEvent(event, gattc_if, param);
/*
switch(event) {
default: {
break;
}
} // switch
*/
// If we have a client registered, call it.
if (BLEDevice::m_pClient != nullptr) {
BLEDevice::m_pClient->gattClientEventHandler(event, gattc_if, param);
}
} // gattClientEventHandler
/**
* @brief Handle GAP events.
*/
void BLEDevice::gapEventHandler(
esp_gap_ble_cb_event_t event,
esp_ble_gap_cb_param_t *param) {
BLEUtils::dumpGapEvent(event, param);
switch(event) {
case ESP_GAP_BLE_SEC_REQ_EVT: {
esp_err_t errRc = ::esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gap_security_rsp: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
}
break;
}
default: {
break;
}
} // switch
if (BLEDevice::m_pServer != nullptr) {
BLEDevice::m_pServer->handleGAPEvent(event, param);
}
if (BLEDevice::m_pClient != nullptr) {
BLEDevice::m_pClient->handleGAPEvent(event, param);
}
if (BLEDevice::m_pScan != nullptr) {
BLEDevice::getScan()->handleGAPEvent(event, param);
}
} // gapEventHandler
/**
* @brief Retrieve the Scan object that we use for scanning.
* @return The scanning object reference. This is a singleton object. The caller should not
* try and release/delete it.
*/
BLEScan* BLEDevice::getScan() {
//ESP_LOGD(LOG_TAG, ">> getScan");
if (m_pScan == nullptr) {
m_pScan = new BLEScan();
//ESP_LOGD(LOG_TAG, " - creating a new scan object");
}
//ESP_LOGD(LOG_TAG, "<< getScan: Returning object at 0x%x", (uint32_t)m_pScan);
return m_pScan;
} // getScan
/**
* @brief Initialize the %BLE environment.
* @param deviceName The device name of the device.
*/
void BLEDevice::init(std::string deviceName) {
if(!initialized){
initialized = true;
esp_err_t errRc = ::nvs_flash_init();
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "nvs_flash_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
errRc = esp_bt_controller_init(&bt_cfg);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_bt_controller_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
#ifndef CLASSIC_BT_ENABLED
// esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); //FIXME waiting for response from esp-idf issue
errRc = esp_bt_controller_enable(ESP_BT_MODE_BLE);
//errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
#else
errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
#endif
errRc = esp_bluedroid_init();
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_bluedroid_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
errRc = esp_bluedroid_enable();
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_bluedroid_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
errRc = esp_ble_gap_register_callback(BLEDevice::gapEventHandler);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gap_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
errRc = esp_ble_gattc_register_callback(BLEDevice::gattClientEventHandler);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gattc_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
errRc = esp_ble_gatts_register_callback(BLEDevice::gattServerEventHandler);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gatts_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
errRc = ::esp_ble_gap_set_device_name(deviceName.c_str());
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gap_set_device_name: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
};
esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE;
errRc = ::esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gap_set_security_param: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
};
}
vTaskDelay(200/portTICK_PERIOD_MS); // Delay for 200 msecs as a workaround to an apparent Arduino environment issue.
} // init
/**
* @brief Set the transmission power.
* The power level can be one of:
* * ESP_PWR_LVL_N14
* * ESP_PWR_LVL_N11
* * ESP_PWR_LVL_N8
* * ESP_PWR_LVL_N5
* * ESP_PWR_LVL_N2
* * ESP_PWR_LVL_P1
* * ESP_PWR_LVL_P4
* * ESP_PWR_LVL_P7
* @param [in] powerLevel.
*/
/* STATIC */ void BLEDevice::setPower(esp_power_level_t powerLevel) {
ESP_LOGD(LOG_TAG, ">> setPower: %d", powerLevel);
esp_err_t errRc = ::esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, powerLevel);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_tx_power_set: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
};
ESP_LOGD(LOG_TAG, "<< setPower");
} // setPower
#endif // CONFIG_BT_ENABLED
+62
View File
@@ -0,0 +1,62 @@
/*
* BLEDevice.h
*
* Created on: Mar 16, 2017
* Author: kolban
*/
#ifndef MAIN_BLEDevice_H_
#define MAIN_BLEDevice_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <esp_gap_ble_api.h> // ESP32 BLE
#include <esp_gattc_api.h> // ESP32 BLE
#include <map> // Part of C++ STL
#include <string>
#include <bt.h>
#include "BLEServer.h"
#include "BLEClient.h"
#include "BLEUtils.h"
#include "BLEScan.h"
#include "BLEAddress.h"
/**
* @brief %BLE functions.
*/
class BLEDevice {
public:
static BLEClient* createClient();
static BLEServer* createServer();
static void dumpDevices();
static BLEScan* getScan();
static void init(std::string deviceName);
private:
static BLEServer *m_pServer;
static BLEScan *m_pScan;
static BLEClient *m_pClient;
static esp_gatt_if_t getGattcIF();
static void gattClientEventHandler(
esp_gattc_cb_event_t event,
esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t* param);
static void gattServerEventHandler(
esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t* param);
static void gapEventHandler(
esp_gap_ble_cb_event_t event,
esp_ble_gap_cb_param_t* param);
public:
static void setPower(esp_power_level_t powerLevel);
}; // class BLE
#endif // CONFIG_BT_ENABLED
#endif /* MAIN_BLEDevice_H_ */
@@ -0,0 +1,593 @@
/*
* BLERemoteCharacteristic.cpp
*
* Created on: Jul 8, 2017
* Author: kolban
*/
#include "BLERemoteCharacteristic.h"
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <esp_gattc_api.h>
#include <esp_log.h>
#include <esp_err.h>
#include <sstream>
#include "BLEUtils.h"
#include "GeneralUtils.h"
#include "BLERemoteDescriptor.h"
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-log.h"
#endif
static const char* LOG_TAG = "BLERemoteCharacteristic"; // The logging tag for this class.
/**
* @brief Constructor.
* @param [in] handle The BLE server side handle of this characteristic.
* @param [in] uuid The UUID of this characteristic.
* @param [in] charProp The properties of this characteristic.
* @param [in] pRemoteService A reference to the remote service to which this remote characteristic pertains.
*/
BLERemoteCharacteristic::BLERemoteCharacteristic(
uint16_t handle,
BLEUUID uuid,
esp_gatt_char_prop_t charProp,
BLERemoteService* pRemoteService) {
ESP_LOGD(LOG_TAG, ">> BLERemoteCharacteristic: handle: %d 0x%d, uuid: %s", handle, handle, uuid.toString().c_str());
m_handle = handle;
m_uuid = uuid;
m_charProp = charProp;
m_pRemoteService = pRemoteService;
m_notifyCallback = nullptr;
retrieveDescriptors(); // Get the descriptors for this characteristic
ESP_LOGD(LOG_TAG, "<< BLERemoteCharacteristic");
} // BLERemoteCharacteristic
/**
*@brief Destructor.
*/
BLERemoteCharacteristic::~BLERemoteCharacteristic() {
removeDescriptors(); // Release resources for any descriptor information we may have allocated.
} // ~BLERemoteCharacteristic
/**
* @brief Does the characteristic support broadcasting?
* @return True if the characteristic supports broadcasting.
*/
bool BLERemoteCharacteristic::canBroadcast() {
return (m_charProp & ESP_GATT_CHAR_PROP_BIT_BROADCAST) != 0;
} // canBroadcast
/**
* @brief Does the characteristic support indications?
* @return True if the characteristic supports indications.
*/
bool BLERemoteCharacteristic::canIndicate() {
return (m_charProp & ESP_GATT_CHAR_PROP_BIT_INDICATE) != 0;
} // canIndicate
/**
* @brief Does the characteristic support notifications?
* @return True if the characteristic supports notifications.
*/
bool BLERemoteCharacteristic::canNotify() {
return (m_charProp & ESP_GATT_CHAR_PROP_BIT_NOTIFY) != 0;
} // canNotify
/**
* @brief Does the characteristic support reading?
* @return True if the characteristic supports reading.
*/
bool BLERemoteCharacteristic::canRead() {
return (m_charProp & ESP_GATT_CHAR_PROP_BIT_READ) != 0;
} // canRead
/**
* @brief Does the characteristic support writing?
* @return True if the characteristic supports writing.
*/
bool BLERemoteCharacteristic::canWrite() {
return (m_charProp & ESP_GATT_CHAR_PROP_BIT_WRITE) != 0;
} // canWrite
/**
* @brief Does the characteristic support writing with no response?
* @return True if the characteristic supports writing with no response.
*/
bool BLERemoteCharacteristic::canWriteNoResponse() {
return (m_charProp & ESP_GATT_CHAR_PROP_BIT_WRITE_NR) != 0;
} // canWriteNoResponse
/*
static bool compareSrvcId(esp_gatt_srvc_id_t id1, esp_gatt_srvc_id_t id2) {
if (id1.id.inst_id != id2.id.inst_id) {
return false;
}
if (!BLEUUID(id1.id.uuid).equals(BLEUUID(id2.id.uuid))) {
return false;
}
return true;
} // compareSrvcId
*/
/*
static bool compareGattId(esp_gatt_id_t id1, esp_gatt_id_t id2) {
if (id1.inst_id != id2.inst_id) {
return false;
}
if (!BLEUUID(id1.uuid).equals(BLEUUID(id2.uuid))) {
return false;
}
return true;
} // compareCharId
*/
/**
* @brief Handle GATT Client events.
* When an event arrives for a GATT client we give this characteristic the opportunity to
* take a look at it to see if there is interest in it.
* @param [in] event The type of event.
* @param [in] gattc_if The interface on which the event was received.
* @param [in] evtParam Payload data for the event.
* @returns N/A
*/
void BLERemoteCharacteristic::gattClientEventHandler(
esp_gattc_cb_event_t event,
esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t* evtParam) {
switch(event) {
//
// ESP_GATTC_NOTIFY_EVT
//
// notify
// - uint16_t conn_id - The connection identifier of the server.
// - esp_bd_addr_t remote_bda - The device address of the BLE server.
// - uint16_t handle - The handle of the characteristic for which the event is being received.
// - uint16_t value_len - The length of the received data.
// - uint8_t* value - The received data.
// - bool is_notify - True if this is a notify, false if it is an indicate.
//
// We have received a notification event which means that the server wishes us to know about a notification
// piece of data. What we must now do is find the characteristic with the associated handle and then
// invoke its notification callback (if it has one).
//
case ESP_GATTC_NOTIFY_EVT: {
if (evtParam->notify.handle != getHandle()) {
break;
}
if (m_notifyCallback != nullptr) {
ESP_LOGD(LOG_TAG, "Invoking callback for notification on characteristic %s", toString().c_str());
m_notifyCallback(
this,
evtParam->notify.value,
evtParam->notify.value_len,
evtParam->notify.is_notify
);
} // End we have a callback function ...
break;
} // ESP_GATTC_NOTIFY_EVT
//
// ESP_GATTC_READ_CHAR_EVT
// This event indicates that the server has responded to the read request.
//
// read:
// - esp_gatt_status_t status
// - uint16_t conn_id
// - uint16_t handle
// - uint8_t* value
// - uint16_t value_len
//
case ESP_GATTC_READ_CHAR_EVT: {
// If this event is not for us, then nothing further to do.
if (evtParam->read.handle != getHandle()) {
break;
}
// At this point, we have determined that the event is for us, so now we save the value
// and unlock the semaphore to ensure that the requestor of the data can continue.
if (evtParam->read.status == ESP_GATT_OK) {
m_value = std::string((char*)evtParam->read.value, evtParam->read.value_len);
} else {
m_value = "";
}
m_semaphoreReadCharEvt.give();
break;
} // ESP_GATTC_READ_CHAR_EVT
//
// ESP_GATTC_REG_FOR_NOTIFY_EVT
//
// reg_for_notify:
// - esp_gatt_status_t status
// - uint16_t handle
//
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
// If the request is not for this BLERemoteCharacteristic then move on to the next.
if (evtParam->reg_for_notify.handle != getHandle()) {
break;
}
// We have processed the notify registration and can unlock the semaphore.
m_semaphoreRegForNotifyEvt.give();
break;
} // ESP_GATTC_REG_FOR_NOTIFY_EVT
//
// ESP_GATTC_UNREG_FOR_NOTIFY_EVT
//
// unreg_for_notify:
// - esp_gatt_status_t status
// - uint16_t handle
//
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
if (evtParam->unreg_for_notify.handle != getHandle()) {
break;
}
// We have processed the notify un-registration and can unlock the semaphore.
m_semaphoreRegForNotifyEvt.give();
break;
} // ESP_GATTC_UNREG_FOR_NOTIFY_EVT:
//
// ESP_GATTC_WRITE_CHAR_EVT
//
// write:
// - esp_gatt_status_t status
// - uint16_t conn_id
// - uint16_t handle
//
case ESP_GATTC_WRITE_CHAR_EVT: {
// Determine if this event is for us and, if not, pass onwards.
if (evtParam->write.handle != getHandle()) {
break;
}
// There is nothing further we need to do here. This is merely an indication
// that the write has completed and we can unlock the caller.
m_semaphoreWriteCharEvt.give();
break;
} // ESP_GATTC_WRITE_CHAR_EVT
default: {
break;
}
} // End switch
}; // gattClientEventHandler
/**
* @brief Populate the descriptors (if any) for this characteristic.
*/
void BLERemoteCharacteristic::retrieveDescriptors() {
ESP_LOGD(LOG_TAG, ">> retrieveDescriptors() for characteristic: %s", getUUID().toString().c_str());
removeDescriptors(); // Remove any existing descriptors.
// Loop over each of the descriptors within the service associated with this characteristic.
// For each descriptor we find, create a BLERemoteDescriptor instance.
uint16_t offset = 0;
esp_gattc_descr_elem_t result;
while(1) {
uint16_t count = 1;
esp_gatt_status_t status = ::esp_ble_gattc_get_all_descr(
getRemoteService()->getClient()->getGattcIf(),
getRemoteService()->getClient()->getConnId(),
getHandle(),
&result,
&count,
offset
);
if (status == ESP_GATT_INVALID_OFFSET) { // We have reached the end of the entries.
break;
}
if (status != ESP_GATT_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_all_descr: %s", BLEUtils::gattStatusToString(status).c_str());
break;
}
if (count == 0) {
break;
}
ESP_LOGE(LOG_TAG, "");
ESP_LOGD(LOG_TAG, "Found a descriptor: Handle: %d, UUID: %s", result.handle, BLEUUID(result.uuid).toString().c_str());
// We now have a new characteristic ... let us add that to our set of known characteristics
BLERemoteDescriptor *pNewRemoteDescriptor = new BLERemoteDescriptor(
result.handle,
BLEUUID(result.uuid),
this
);
m_descriptorMap.insert(std::pair<std::string, BLERemoteDescriptor*>(pNewRemoteDescriptor->getUUID().toString(), pNewRemoteDescriptor));
offset++;
} // while true
//m_haveCharacteristics = true; // Remember that we have received the characteristics.
ESP_LOGD(LOG_TAG, "<< retrieveDescriptors(): Found %d descriptors.", offset);
} // getDescriptors
/**
* @brief Retrieve the map of descriptors keyed by UUID.
*/
std::map<std::string, BLERemoteDescriptor *>* BLERemoteCharacteristic::getDescriptors() {
return &m_descriptorMap;
} // getDescriptors
/**
* @brief Get the handle for this characteristic.
* @return The handle for this characteristic.
*/
uint16_t BLERemoteCharacteristic::getHandle() {
//ESP_LOGD(LOG_TAG, ">> getHandle: Characteristic: %s", getUUID().toString().c_str());
//ESP_LOGD(LOG_TAG, "<< getHandle: %d 0x%.2x", m_handle, m_handle);
return m_handle;
} // getHandle
/**
* @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.
*/
BLERemoteDescriptor* BLERemoteCharacteristic::getDescriptor(BLEUUID uuid) {
ESP_LOGD(LOG_TAG, ">> getDescriptor: uuid: %s", uuid.toString().c_str());
std::string v = uuid.toString();
for (auto &myPair : m_descriptorMap) {
if (myPair.first == v) {
ESP_LOGD(LOG_TAG, "<< getDescriptor: found");
return myPair.second;
}
}
ESP_LOGD(LOG_TAG, "<< getDescriptor: Not found");
return nullptr;
} // getDescriptor
/**
* @brief Get the remote service associated with this characteristic.
* @return The remote service associated with this characteristic.
*/
BLERemoteService* BLERemoteCharacteristic::getRemoteService() {
return m_pRemoteService;
} // getRemoteService
/**
* @brief Get the UUID for this characteristic.
* @return The UUID for this characteristic.
*/
BLEUUID BLERemoteCharacteristic::getUUID() {
return m_uuid;
} // getUUID
/**
* @brief Read an unsigned 16 bit value
* @return The unsigned 16 bit value.
*/
uint16_t BLERemoteCharacteristic::readUInt16(void) {
std::string value = readValue();
if (value.length() >= 2) {
return *(uint16_t*)(value.data());
}
return 0;
} // readUInt16
/**
* @brief Read an unsigned 32 bit value.
* @return the unsigned 32 bit value.
*/
uint32_t BLERemoteCharacteristic::readUInt32(void) {
std::string value = readValue();
if (value.length() >= 4) {
return *(uint32_t*)(value.data());
}
return 0;
} // readUInt32
/**
* @brief Read a byte value
* @return The value as a byte
*/
uint8_t BLERemoteCharacteristic::readUInt8(void) {
std::string value = readValue();
if (value.length() >= 1) {
return (uint8_t)value[0];
}
return 0;
} // readUInt8
/**
* @brief Read the value of the remote characteristic.
* @return The value of the remote characteristic.
*/
std::string BLERemoteCharacteristic::readValue() {
ESP_LOGD(LOG_TAG, ">> readValue(): uuid: %s, handle: %d 0x%.2x", getUUID().toString().c_str(), getHandle(), getHandle());
m_semaphoreReadCharEvt.take("readValue");
// Ask the BLE subsystem to retrieve the value for the remote hosted characteristic.
// This is an asynchronous request which means that we must block waiting for the response
// to become available.
esp_err_t errRc = ::esp_ble_gattc_read_char(
m_pRemoteService->getClient()->getGattcIf(),
m_pRemoteService->getClient()->getConnId(), // The connection ID to the BLE server
getHandle(), // The handle of this characteristic
ESP_GATT_AUTH_REQ_NONE); // Security
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gattc_read_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return "";
}
// Block waiting for the event that indicates that the read has completed. When it has, the std::string found
// in m_value will contain our data.
m_semaphoreReadCharEvt.wait("readValue");
ESP_LOGD(LOG_TAG, "<< readValue(): length: %d", m_value.length());
return m_value;
} // readValue
/**
* @brief Register for notifications.
* @param [in] notifyCallback A callback to be invoked for a notification. If NULL is provided then we are
* unregistering a notification.
* @return N/A.
*/
void BLERemoteCharacteristic::registerForNotify(
void (*notifyCallback)(
BLERemoteCharacteristic* pBLERemoteCharacteristic,
uint8_t* pData,
size_t length,
bool isNotify)) {
ESP_LOGD(LOG_TAG, ">> registerForNotify(): %s", toString().c_str());
m_notifyCallback = notifyCallback; // Save the notification callback.
m_semaphoreRegForNotifyEvt.take("registerForNotify");
if (notifyCallback != nullptr) { // If we have a callback function, then this is a registration.
esp_err_t errRc = ::esp_ble_gattc_register_for_notify(
m_pRemoteService->getClient()->getGattcIf(),
*m_pRemoteService->getClient()->getPeerAddress().getNative(),
getHandle()
);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gattc_register_for_notify: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
}
} // End Register
else { // If we weren't passed a callback function, then this is an unregistration.
esp_err_t errRc = ::esp_ble_gattc_unregister_for_notify(
m_pRemoteService->getClient()->getGattcIf(),
*m_pRemoteService->getClient()->getPeerAddress().getNative(),
getHandle()
);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gattc_unregister_for_notify: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
}
} // End Unregister
m_semaphoreRegForNotifyEvt.wait("registerForNotify");
ESP_LOGD(LOG_TAG, "<< registerForNotify()");
} // registerForNotify
/**
* @brief Delete the descriptors in the descriptor map.
* We maintain a map called m_descriptorMap that contains pointers to BLERemoteDescriptors
* object references. Since we allocated these in this class, we are also responsible for deleteing
* them. This method does just that.
* @return N/A.
*/
void BLERemoteCharacteristic::removeDescriptors() {
// Iterate through all the descriptors releasing their storage and erasing them from the map.
for (auto &myPair : m_descriptorMap) {
m_descriptorMap.erase(myPair.first);
delete myPair.second;
}
m_descriptorMap.clear(); // Technically not neeeded, but just to be sure.
} // removeCharacteristics
/**
* @brief Convert a BLERemoteCharacteristic to a string representation;
* @return a String representation.
*/
std::string BLERemoteCharacteristic::toString() {
std::ostringstream ss;
ss << "Characteristic: uuid: " << m_uuid.toString() <<
", handle: " << getHandle() << " 0x" << std::hex << getHandle() <<
", props: " << BLEUtils::characteristicPropertiesToString(m_charProp);
return ss.str();
} // toString
/**
* @brief Write the new value for the characteristic.
* @param [in] newValue The new value to write.
* @param [in] response Do we expect a response?
* @return N/A.
*/
void BLERemoteCharacteristic::writeValue(std::string newValue, bool response) {
ESP_LOGD(LOG_TAG, ">> writeValue(), length: %d", newValue.length());
m_semaphoreWriteCharEvt.take("writeValue");
// Invoke the ESP-IDF API to perform the write.
esp_err_t errRc = ::esp_ble_gattc_write_char(
m_pRemoteService->getClient()->getGattcIf(),
m_pRemoteService->getClient()->getConnId(),
getHandle(),
newValue.length(),
(uint8_t*)newValue.data(),
response?ESP_GATT_WRITE_TYPE_RSP:ESP_GATT_WRITE_TYPE_NO_RSP,
ESP_GATT_AUTH_REQ_NONE
);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gattc_write_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
m_semaphoreWriteCharEvt.wait("writeValue");
ESP_LOGD(LOG_TAG, "<< writeValue");
} // writeValue
/**
* @brief Write the new value for the characteristic.
*
* This is a convenience function. Many BLE characteristics are a single byte of data.
* @param [in] newValue The new byte value to write.
* @param [in] response Whether we require a response from the write.
* @return N/A.
*/
void BLERemoteCharacteristic::writeValue(uint8_t newValue, bool response) {
writeValue(std::string(reinterpret_cast<char*>(&newValue), 1), response);
} // writeValue
/**
* @brief Write the new value for the characteristic from a data buffer.
* @param [in] data A pointer to a data buffer.
* @param [in] length The length of the data in the data buffer.
* @param [in] response Whether we require a response from the write.
*/
void BLERemoteCharacteristic::writeValue(uint8_t* data, size_t length, bool response) {
writeValue(std::string((char *)data, length), response);
} // writeValue
#endif /* CONFIG_BT_ENABLED */
@@ -0,0 +1,85 @@
/*
* BLERemoteCharacteristic.h
*
* Created on: Jul 8, 2017
* Author: kolban
*/
#ifndef COMPONENTS_CPP_UTILS_BLEREMOTECHARACTERISTIC_H_
#define COMPONENTS_CPP_UTILS_BLEREMOTECHARACTERISTIC_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <string>
#include <esp_gattc_api.h>
#include "BLERemoteService.h"
#include "BLERemoteDescriptor.h"
#include "BLEUUID.h"
#include "FreeRTOS.h"
class BLERemoteService;
class BLERemoteDescriptor;
/**
* @brief A model of a remote %BLE characteristic.
*/
class BLERemoteCharacteristic {
public:
~BLERemoteCharacteristic();
// Public member functions
bool canBroadcast();
bool canIndicate();
bool canNotify();
bool canRead();
bool canWrite();
bool canWriteNoResponse();
BLERemoteDescriptor* getDescriptor(BLEUUID uuid);
std::map<std::string, BLERemoteDescriptor *>* getDescriptors();
uint16_t getHandle();
BLEUUID getUUID();
std::string readValue(void);
uint8_t readUInt8(void);
uint16_t readUInt16(void);
uint32_t readUInt32(void);
void registerForNotify(void (*notifyCallback)(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify));
void writeValue(uint8_t* data, size_t length, bool response = false);
void writeValue(std::string newValue, bool response = false);
void writeValue(uint8_t newValue, bool response = false);
std::string toString(void);
private:
BLERemoteCharacteristic(uint16_t handle, BLEUUID uuid, esp_gatt_char_prop_t charProp, BLERemoteService* pRemoteService);
friend class BLEClient;
friend class BLERemoteService;
friend class BLERemoteDescriptor;
// Private member functions
void gattClientEventHandler(
esp_gattc_cb_event_t event,
esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t* evtParam);
BLERemoteService* getRemoteService();
void removeDescriptors();
void retrieveDescriptors();
// Private properties
BLEUUID m_uuid;
esp_gatt_char_prop_t m_charProp;
uint16_t m_handle;
BLERemoteService* m_pRemoteService;
FreeRTOS::Semaphore m_semaphoreReadCharEvt = FreeRTOS::Semaphore("ReadCharEvt");
FreeRTOS::Semaphore m_semaphoreRegForNotifyEvt = FreeRTOS::Semaphore("RegForNotifyEvt");
FreeRTOS::Semaphore m_semaphoreWriteCharEvt = FreeRTOS::Semaphore("WriteCharEvt");
std::string m_value;
void (*m_notifyCallback)(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify);
// We maintain a map of descriptors owned by this characteristic keyed by a string representation of the UUID.
std::map<std::string, BLERemoteDescriptor*> m_descriptorMap;
}; // BLERemoteCharacteristic
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_CPP_UTILS_BLEREMOTECHARACTERISTIC_H_ */
@@ -0,0 +1,162 @@
/*
* BLERemoteDescriptor.cpp
*
* Created on: Jul 8, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <sstream>
#include "BLERemoteDescriptor.h"
#include "GeneralUtils.h"
#include <esp_log.h>
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-log.h"
#endif
static const char* LOG_TAG = "BLERemoteDescriptor";
BLERemoteDescriptor::BLERemoteDescriptor(
uint16_t handle,
BLEUUID uuid,
BLERemoteCharacteristic* pRemoteCharacteristic) {
m_handle = handle;
m_uuid = uuid;
m_pRemoteCharacteristic = pRemoteCharacteristic;
}
/**
* @brief Retrieve the handle associated with this remote descriptor.
* @return The handle associated with this remote descriptor.
*/
uint16_t BLERemoteDescriptor::getHandle() {
return m_handle;
} // getHandle
/**
* @brief Retrieve the UUID associated this remote descriptor.
* @return The UUID associated this remote descriptor.
*/
BLEUUID BLERemoteDescriptor::getUUID() {
return m_uuid;
} // getUUID
std::string BLERemoteDescriptor::readValue(void) {
m_semaphoreReadDescrEvt.take("readValue");
// Ask the BLE subsystem to retrieve the value for the remote hosted characteristic.
esp_err_t errRc = ::esp_ble_gattc_read_char_descr(
m_pRemoteCharacteristic->getRemoteService()->getClient()->getGattcIf(),
m_pRemoteCharacteristic->getRemoteService()->getClient()->getConnId(), // The connection ID to the BLE server
getHandle(), // The handle of this characteristic
ESP_GATT_AUTH_REQ_NONE); // Security
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gattc_read_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return "";
}
// Block waiting for the event that indicates that the read has completed. When it has, the std::string found
// in m_value will contain our data.
m_semaphoreReadDescrEvt.wait("readValue");
ESP_LOGD(LOG_TAG, "<< readValue(): length: %d", m_value.length());
return m_value;
return "";
} // readValue
uint8_t BLERemoteDescriptor::readUInt8(void) {
std::string value = readValue();
if (value.length() >= 1) {
return (uint8_t)value[0];
}
return 0;
} // readUInt8
uint16_t BLERemoteDescriptor::readUInt16(void) {
std::string value = readValue();
if (value.length() >= 2) {
return *(uint16_t*)(value.data());
}
return 0;
} // readUInt16
uint32_t BLERemoteDescriptor::readUInt32(void) {
std::string value = readValue();
if (value.length() >= 4) {
return *(uint32_t*)(value.data());
}
return 0;
} // readUInt32
/**
* @brief Return a string representation of this BLE Remote Descriptor.
* @retun A string representation of this BLE Remote Descriptor.
*/
std::string BLERemoteDescriptor::toString(void) {
std::stringstream ss;
ss << "handle: " << getHandle() << ", uuid: " << getUUID().toString();
return ss.str();
} // toString
/**
* @brief Write data to the BLE Remote Descriptor.
* @param [in] data The data to send to the remote descriptor.
* @param [in] length The length of the data to send.
* @param [in] response True if we expect a response.
*/
void BLERemoteDescriptor::writeValue(
uint8_t* data,
size_t length,
bool response) {
ESP_LOGD(LOG_TAG, ">> writeValue: %s", toString().c_str());
esp_err_t errRc = ::esp_ble_gattc_write_char_descr(
m_pRemoteCharacteristic->getRemoteService()->getClient()->getGattcIf(),
m_pRemoteCharacteristic->getRemoteService()->getClient()->getConnId(),
getHandle(),
length, // Data length
data, // Data
ESP_GATT_WRITE_TYPE_NO_RSP,
ESP_GATT_AUTH_REQ_NONE
);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gattc_write_char_descr: %d", errRc);
}
ESP_LOGD(LOG_TAG, "<< writeValue");
} // writeValue
/**
* @brief Write data represented as a string to the BLE Remote Descriptor.
* @param [in] newValue The data to send to the remote descriptor.
* @param [in] response True if we expect a response.
*/
void BLERemoteDescriptor::writeValue(
std::string newValue,
bool response) {
writeValue(newValue.data(), newValue.length());
} // writeValue
/**
* @brief Write a byte value to the Descriptor.
* @param [in] The single byte to write.
* @param [in] True if we expect a response.
*/
void BLERemoteDescriptor::writeValue(
uint8_t newValue,
bool response) {
writeValue(&newValue, 1, response);
} // writeValue
#endif /* CONFIG_BT_ENABLED */
@@ -0,0 +1,55 @@
/*
* BLERemoteDescriptor.h
*
* Created on: Jul 8, 2017
* Author: kolban
*/
#ifndef COMPONENTS_CPP_UTILS_BLEREMOTEDESCRIPTOR_H_
#define COMPONENTS_CPP_UTILS_BLEREMOTEDESCRIPTOR_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <string>
#include <esp_gattc_api.h>
#include "BLERemoteCharacteristic.h"
#include "BLEUUID.h"
#include "FreeRTOS.h"
class BLERemoteCharacteristic;
/**
* @brief A model of remote %BLE descriptor.
*/
class BLERemoteDescriptor {
public:
uint16_t getHandle();
BLEUUID getUUID();
std::string readValue(void);
uint8_t readUInt8(void);
uint16_t readUInt16(void);
uint32_t readUInt32(void);
std::string toString(void);
//void registerForNotify(void (*notifyCallback)(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify));
void writeValue(uint8_t* data, size_t length, bool response = false);
void writeValue(std::string newValue, bool response = false);
void writeValue(uint8_t newValue, bool response = false);
private:
friend class BLERemoteCharacteristic;
BLERemoteDescriptor(
uint16_t handle,
BLEUUID uuid,
BLERemoteCharacteristic* pRemoteCharacteristic
);
uint16_t m_handle; // Server handle of this descriptor.
BLEUUID m_uuid; // UUID of this descriptor.
std::string m_value; // Last received value of the descriptor.
BLERemoteCharacteristic* m_pRemoteCharacteristic; // Reference to the Remote characteristic of which this descriptor is associated.
FreeRTOS::Semaphore m_semaphoreReadDescrEvt = FreeRTOS::Semaphore("ReadDescrEvt");
};
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_CPP_UTILS_BLEREMOTEDESCRIPTOR_H_ */
@@ -0,0 +1,356 @@
/*
* BLERemoteService.cpp
*
* Created on: Jul 8, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <sstream>
#include "BLERemoteService.h"
#include "BLEUtils.h"
#include "GeneralUtils.h"
#include <esp_log.h>
#include <esp_err.h>
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-log.h"
#endif
static const char* LOG_TAG = "BLERemoteService";
BLERemoteService::BLERemoteService(
esp_gatt_id_t srvcId,
BLEClient* pClient,
uint16_t startHandle,
uint16_t endHandle
) {
ESP_LOGD(LOG_TAG, ">> BLERemoteService()");
m_srvcId = srvcId;
m_pClient = pClient;
m_uuid = BLEUUID(m_srvcId);
m_haveCharacteristics = false;
m_startHandle = startHandle;
m_endHandle = endHandle;
ESP_LOGD(LOG_TAG, "<< BLERemoteService()");
}
BLERemoteService::~BLERemoteService() {
removeCharacteristics();
}
/*
static bool compareSrvcId(esp_gatt_srvc_id_t id1, esp_gatt_srvc_id_t id2) {
if (id1.id.inst_id != id2.id.inst_id) {
return false;
}
if (!BLEUUID(id1.id.uuid).equals(BLEUUID(id2.id.uuid))) {
return false;
}
return true;
} // compareSrvcId
*/
/**
* @brief Handle GATT Client events
*/
void BLERemoteService::gattClientEventHandler(
esp_gattc_cb_event_t event,
esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *evtParam) {
switch(event) {
//
// ESP_GATTC_GET_CHAR_EVT
//
// get_char:
// - esp_gatt_status_t status
// - uin1t6_t conn_id
// - esp_gatt_srvc_id_t srvc_id
// - esp_gatt_id_t char_id
// - esp_gatt_char_prop_t char_prop
//
/*
case ESP_GATTC_GET_CHAR_EVT: {
// Is this event for this service? If yes, then the local srvc_id and the event srvc_id will be
// the same.
if (compareSrvcId(m_srvcId, evtParam->get_char.srvc_id) == false) {
break;
}
// If the status is NOT OK then we have a problem and continue.
if (evtParam->get_char.status != ESP_GATT_OK) {
m_semaphoreGetCharEvt.give();
break;
}
// This is an indication that we now have the characteristic details for a characteristic owned
// by this service so remember it.
m_characteristicMap.insert(std::pair<std::string, BLERemoteCharacteristic*>(
BLEUUID(evtParam->get_char.char_id.uuid).toString(),
new BLERemoteCharacteristic(evtParam->get_char.char_id, evtParam->get_char.char_prop, this) ));
// Now that we have received a characteristic, lets ask for the next one.
esp_err_t errRc = ::esp_ble_gattc_get_characteristic(
m_pClient->getGattcIf(),
m_pClient->getConnId(),
&m_srvcId,
&evtParam->get_char.char_id);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_characteristic: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
break;
}
//m_semaphoreGetCharEvt.give();
break;
} // ESP_GATTC_GET_CHAR_EVT
*/
default: {
break;
}
} // switch
// Send the event to each of the characteristics owned by this service.
for (auto &myPair : m_characteristicMap) {
myPair.first->gattClientEventHandler(event, gattc_if, evtParam);
}
} // gattClientEventHandler
/**
* @brief Get the remote characteristic object for the characteristic UUID.
* @param [in] uuid Remote characteristic uuid.
* @return Reference to the remote characteristic object.
*/
BLERemoteCharacteristic* BLERemoteService::getCharacteristic(const char* uuid) {
return getCharacteristic(BLEUUID(uuid));
} // getCharacteristic
/**
* @brief Get the characteristic object for the UUID.
* @param [in] uuid Characteristic uuid.
* @return Reference to the characteristic object.
*/
BLERemoteCharacteristic* BLERemoteService::getCharacteristic(BLEUUID uuid) {
// Design
// ------
// We wish to retrieve the characteristic given its UUID. It is possible that we have not yet asked the
// device what characteristics it has in which case we have nothing to match against. If we have not
// asked the device about its characteristics, then we do that now. Once we get the results we can then
// examine the characteristics map to see if it has the characteristic we are looking for.
if (!m_haveCharacteristics) {
retrieveCharacteristics();
}
std::string v = uuid.toString();
for (auto &myPair : m_characteristicMap) {
if (myPair.second == v) {
return myPair.first;
}
}
return nullptr;
} // getCharacteristic
/**
* @brief Retrieve all the characteristics for this service.
* This function will not return until we have all the characteristics.
* @return N/A
*/
void BLERemoteService::retrieveCharacteristics() {
ESP_LOGD(LOG_TAG, ">> getCharacteristics() for service: %s", getUUID().toString().c_str());
removeCharacteristics(); // Forget any previous characteristics.
/*
m_semaphoreGetCharEvt.take("getCharacteristics");
esp_err_t errRc = ::esp_ble_gattc_get_characteristic(
m_pClient->getGattcIf(),
m_pClient->getConnId(),
&m_srvcId,
nullptr);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_characteristic: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
m_semaphoreGetCharEvt.wait("getCharacteristics"); // Wait for the characteristics to become available.
m_haveCharacteristics = true; // Remember that we have received the characteristics.
*/
//ESP_LOGE(LOG_TAG, "!!! NOT IMPLEMENTED !!!");
//ESP_LOGD(LOG_TAG, "--- test code ---");
/*
uint16_t count;
esp_gatt_status_t status = ::esp_ble_gattc_get_attr_count(
getClient()->getGattcIf(),
getClient()->getConnId(),
ESP_GATT_DB_CHARACTERISTIC,
m_startHandle,
m_endHandle,
0, // Characteristic handle ... only used for ESP_GATT_DB_DESCRIPTOR
&count
);
if (status != ESP_GATT_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_attr_count: %s", BLEUtils::gattStatusToString(status).c_str());
} else {
ESP_LOGD(LOG_TAG, "Number of characteristics associated with service is %d", count);
}
count = 1;
esp_gattc_service_elem_t srvcElem;
status = ::esp_ble_gattc_get_service(
getClient()->getGattcIf(),
getClient()->getConnId(),
&m_srvcId.uuid, // UUID of service
&srvcElem, // Records
&count, // records retrieved
0 // offset
);
if (status != ESP_GATT_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_service: %s", BLEUtils::gattStatusToString(status).c_str());
}
else {
ESP_LOGD(LOG_TAG, "%s", BLEUtils::gattcServiceElementToString(&srvcElem).c_str());
}
*/
uint16_t offset = 0;
esp_gattc_char_elem_t result;
while(1) {
uint16_t count = 1;
esp_gatt_status_t status = ::esp_ble_gattc_get_all_char(
getClient()->getGattcIf(),
getClient()->getConnId(),
m_startHandle,
m_endHandle,
&result,
&count,
offset
);
if (status == ESP_GATT_INVALID_OFFSET) { // We have reached the end of the entries.
break;
}
if (status != ESP_GATT_OK) { // If we got an error, end.
ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_all_char: %s", BLEUtils::gattStatusToString(status).c_str());
break;
}
if (count == 0) { // If we failed to get any new records, end.
break;
}
ESP_LOGD(LOG_TAG, "Found a characteristic: Handle: %d, UUID: %s", result.char_handle, BLEUUID(result.uuid).toString().c_str());
// We now have a new characteristic ... let us add that to our set of known characteristics
BLERemoteCharacteristic *pNewRemoteCharacteristic = new BLERemoteCharacteristic(
result.char_handle,
BLEUUID(result.uuid),
result.properties,
this
);
m_characteristicMap.insert(std::pair<BLERemoteCharacteristic*, std::string>(pNewRemoteCharacteristic, pNewRemoteCharacteristic->getUUID().toString()));
offset++; // Increment our count of number of descriptors found.
} // Loop forever (until we break inside the loop).
m_haveCharacteristics = true; // Remember that we have received the characteristics.
ESP_LOGD(LOG_TAG, "<< getCharacteristics()");
} // getCharacteristics
/**
* @brief Retrieve a map of all the characteristics of this service.
* @return A map of all the characteristics of this service.
*/
std::map<BLERemoteCharacteristic*, std::string>* BLERemoteService::getCharacteristics() {
ESP_LOGD(LOG_TAG, ">> getCharacteristics() for service: %s", getUUID().toString().c_str());
// If is possible that we have not read the characteristics associated with the service so do that
// now. The request to retrieve the characteristics by calling "retrieveCharacteristics" is a blocking
// call and does not return until all the characteristics are available.
if (!m_haveCharacteristics) {
retrieveCharacteristics();
}
ESP_LOGD(LOG_TAG, "<< getCharacteristics() for service: %s", getUUID().toString().c_str());
return &m_characteristicMap;
} // getCharacteristics
BLEClient* BLERemoteService::getClient() {
return m_pClient;
}
uint16_t BLERemoteService::getEndHandle() {
return m_endHandle;
}
esp_gatt_id_t* BLERemoteService::getSrvcId() {
return &m_srvcId;
}
uint16_t BLERemoteService::getStartHandle() {
return m_startHandle;
}
uint16_t BLERemoteService::getHandle() {
ESP_LOGD(LOG_TAG, ">> getHandle: service: %s", getUUID().toString().c_str());
//ESP_LOGE(LOG_TAG, "!!! getHandle: NOT IMPLEMENTED !!!");
ESP_LOGD(LOG_TAG, "<< getHandle: %d 0x%.2x", getStartHandle(), getStartHandle());
return getStartHandle();
}
BLEUUID BLERemoteService::getUUID() {
return m_uuid;
}
/**
* @brief Delete the characteristics in the characteristics map.
* We maintain a map called m_characteristicsMap that contains pointers to BLERemoteCharacteristic
* object references. Since we allocated these in this class, we are also responsible for deleteing
* them. This method does just that.
* @return N/A.
*/
void BLERemoteService::removeCharacteristics() {
for (auto &myPair : m_characteristicMap) {
delete myPair.first;
m_characteristicMap.erase(myPair.first);
}
m_characteristicMap.clear(); // Clear the map
} // removeCharacteristics
/**
* @brief Create a string representation of this remote service.
* @return A string representation of this remote service.
*/
std::string BLERemoteService::toString() {
std::ostringstream ss;
ss << "Service: uuid: " + m_uuid.toString();
ss << ", start_handle: " << std::dec << m_startHandle << " 0x" << std::hex << m_startHandle <<
", end_handle: " << std::dec << m_endHandle << " 0x" << std::hex << m_endHandle;
for (auto &myPair : m_characteristicMap) {
ss << "\n" << myPair.first->toString();
// myPair.second is the value
}
return ss.str();
} // toString
#endif /* CONFIG_BT_ENABLED */
@@ -0,0 +1,80 @@
/*
* BLERemoteService.h
*
* Created on: Jul 8, 2017
* Author: kolban
*/
#ifndef COMPONENTS_CPP_UTILS_BLEREMOTESERVICE_H_
#define COMPONENTS_CPP_UTILS_BLEREMOTESERVICE_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <map>
#include "BLEClient.h"
#include "BLERemoteCharacteristic.h"
#include "BLEUUID.h"
#include "FreeRTOS.h"
class BLEClient;
class BLERemoteCharacteristic;
/**
* @brief A model of a remote %BLE service.
*/
class BLERemoteService {
public:
virtual ~BLERemoteService();
// Public methods
BLERemoteCharacteristic* getCharacteristic(const char* uuid);
BLERemoteCharacteristic* getCharacteristic(BLEUUID uuid);
BLERemoteCharacteristic* getCharacteristic(uint16_t uuid);
std::map<BLERemoteCharacteristic*, std::string>* getCharacteristics();
void getCharacteristics(std::map<uint16_t, BLERemoteCharacteristic*>* ptr);
BLEClient* getClient(void);
uint16_t getHandle();
BLEUUID getUUID(void);
std::string toString(void);
private:
// Private constructor ... never meant to be created by a user application.
BLERemoteService(esp_gatt_id_t srvcId, BLEClient* pClient, uint16_t startHandle, uint16_t endHandle);
// Friends
friend class BLEClient;
friend class BLERemoteCharacteristic;
// Private methods
void retrieveCharacteristics(void);
esp_gatt_id_t* getSrvcId(void);
uint16_t getStartHandle();
uint16_t getEndHandle();
void gattClientEventHandler(
esp_gattc_cb_event_t event,
esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t* evtParam);
void removeCharacteristics();
// Properties
// We maintain a map of characteristics owned by this service keyed by a string representation of the UUID.
std::map<BLERemoteCharacteristic *, std::string> m_characteristicMap;
// We maintain a map of characteristics owned by this service keyed by a handle.
std::map<uint16_t, BLERemoteCharacteristic *> m_characteristicMapByHandle;
bool m_haveCharacteristics; // Have we previously obtained the characteristics.
BLEClient* m_pClient;
FreeRTOS::Semaphore m_semaphoreGetCharEvt = FreeRTOS::Semaphore("GetCharEvt");
esp_gatt_id_t m_srvcId;
BLEUUID m_uuid;
uint16_t m_startHandle;
uint16_t m_endHandle;
}; // BLERemoteService
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_CPP_UTILS_BLEREMOTESERVICE_H_ */
+271
View File
@@ -0,0 +1,271 @@
/*
* BLEScan.cpp
*
* Created on: Jul 1, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <esp_log.h>
#include <esp_err.h>
#include <map>
#include "BLEAdvertisedDevice.h"
#include "BLEScan.h"
#include "BLEUtils.h"
#include "GeneralUtils.h"
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-log.h"
#endif
static const char* LOG_TAG = "BLEScan";
/**
* Constructor
*/
BLEScan::BLEScan() {
m_scan_params.scan_type = BLE_SCAN_TYPE_PASSIVE; // Default is a passive scan.
m_scan_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
m_scan_params.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL;
m_pAdvertisedDeviceCallbacks = nullptr;
m_stopped = true;
setInterval(100);
setWindow(100);
} // BLEScan
/**
* @brief Handle GAP events related to scans.
* @param [in] event The event type for this event.
* @param [in] param Parameter data for this event.
*/
void BLEScan::handleGAPEvent(
esp_gap_ble_cb_event_t event,
esp_ble_gap_cb_param_t* param) {
switch(event) {
// ESP_GAP_BLE_SCAN_RESULT_EVT
// ---------------------------
// scan_rst:
// esp_gap_search_evt_t search_evt
// esp_bd_addr_t bda
// esp_bt_dev_type_t dev_type
// esp_ble_addr_type_t ble_addr_type
// esp_ble_evt_type_t ble_evt_type
// int rssi
// uint8_t ble_adv[ESP_BLE_ADV_DATA_LEN_MAX]
// int flag
// int num_resps
// uint8_t adv_data_len
// uint8_t scan_rsp_len
case ESP_GAP_BLE_SCAN_RESULT_EVT: {
switch(param->scan_rst.search_evt) {
//
// ESP_GAP_SEARCH_INQ_CMPL_EVT
//
// Event that indicates that the duration allowed for the search has completed or that we have been
// asked to stop.
case ESP_GAP_SEARCH_INQ_CMPL_EVT: {
m_stopped = true;
m_semaphoreScanEnd.give();
break;
} // ESP_GAP_SEARCH_INQ_CMPL_EVT
//
// ESP_GAP_SEARCH_INQ_RES_EVT
//
// Result that has arrived back from a Scan inquiry.
case ESP_GAP_SEARCH_INQ_RES_EVT: {
if (m_stopped) { // If we are not scanning, nothing to do with the extra results.
break;
}
// Examine our list of previously scanned addresses and, if we found this one already,
// ignore it.
BLEAddress advertisedAddress(param->scan_rst.bda);
bool found = false;
for (int i=0; i<m_scanResults.getCount(); i++) {
if (m_scanResults.getDevice(i).getAddress().equals(advertisedAddress)) {
found = true;
break;
}
}
if (found) {
ESP_LOGD(LOG_TAG, "Ignoring %s, already seen it.", advertisedAddress.toString().c_str());
break;
}
// We now construct a model of the advertised device that we have just found for the first
// time.
BLEAdvertisedDevice advertisedDevice;
advertisedDevice.setAddress(advertisedAddress);
advertisedDevice.setRSSI(param->scan_rst.rssi);
advertisedDevice.setAdFlag(param->scan_rst.flag);
advertisedDevice.parseAdvertisement((uint8_t*)param->scan_rst.ble_adv);
advertisedDevice.setScan(this);
if (m_pAdvertisedDeviceCallbacks) {
m_pAdvertisedDeviceCallbacks->onResult(advertisedDevice);
}
m_scanResults.m_vectorAdvertisedDevices.push_back(advertisedDevice);
break;
} // ESP_GAP_SEARCH_INQ_RES_EVT
default: {
break;
}
} // switch - search_evt
break;
} // ESP_GAP_BLE_SCAN_RESULT_EVT
default: {
break;
} // default
} // End switch
} // gapEventHandler
/**
* @brief Should we perform an active or passive scan?
* The default is a passive scan. An active scan means that we will wish a scan response.
* @param [in] active If true, we perform an active scan otherwise a passive scan.
* @return N/A.
*/
void BLEScan::setActiveScan(bool active) {
if (active) {
m_scan_params.scan_type = BLE_SCAN_TYPE_ACTIVE;
} else {
m_scan_params.scan_type = BLE_SCAN_TYPE_PASSIVE;
}
} // setActiveScan
/**
* @brief Set the call backs to be invoked.
* @param [in] pAdvertisedDeviceCallbacks Call backs to be invoked.
*/
void BLEScan::setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks) {
m_pAdvertisedDeviceCallbacks = pAdvertisedDeviceCallbacks;
} // setAdvertisedDeviceCallbacks
/**
* @brief Set the interval to scan.
* @param [in] The interval in msecs.
*/
void BLEScan::setInterval(uint16_t intervalMSecs) {
m_scan_params.scan_interval = intervalMSecs / 0.625;
} // setInterval
/**
* @brief Set the window to actively scan.
* @param [in] windowMSecs How long to actively scan.
*/
void BLEScan::setWindow(uint16_t windowMSecs) {
m_scan_params.scan_window = windowMSecs / 0.625;
} // setWindow
/**
* @brief Start scanning.
* @param [in] duration The duration in seconds for which to scan.
* @return N/A.
*/
BLEScanResults BLEScan::start(uint32_t duration) {
ESP_LOGD(LOG_TAG, ">> start(duration=%d)", duration);
m_semaphoreScanEnd.take(std::string("start"));
m_scanResults.m_vectorAdvertisedDevices.clear();
esp_err_t errRc = ::esp_ble_gap_set_scan_params(&m_scan_params);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gap_set_scan_params: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc));
m_semaphoreScanEnd.give();
return m_scanResults;
}
errRc = ::esp_ble_gap_start_scanning(duration);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gap_start_scanning: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc));
m_semaphoreScanEnd.give();
return m_scanResults;
}
m_stopped = false;
m_semaphoreScanEnd.wait("start"); // Wait for the semaphore to release.
ESP_LOGD(LOG_TAG, "<< start()");
return m_scanResults;
} // start
/**
* @brief Stop an in progress scan.
* @return N/A.
*/
void BLEScan::stop() {
ESP_LOGD(LOG_TAG, ">> stop()");
esp_err_t errRc = ::esp_ble_gap_stop_scanning();
m_stopped = true;
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gap_stop_scanning: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
m_semaphoreScanEnd.give();
ESP_LOGD(LOG_TAG, "<< stop()");
} // stop
/**
* @brief Dump the scan results to the log.
*/
void BLEScanResults::dump() {
ESP_LOGD(LOG_TAG, ">> Dump scan results:");
for (int i=0; i<getCount(); i++) {
ESP_LOGD(LOG_TAG, "- %s", getDevice(i).toString().c_str());
}
} // dump
/**
* @brief Return the count of devices found in the last scan.
* @return The number of devices found in the last scan.
*/
int BLEScanResults::getCount() {
return m_vectorAdvertisedDevices.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.
*/
BLEAdvertisedDevice BLEScanResults::getDevice(uint32_t i) {
return m_vectorAdvertisedDevices.at(i);
}
#endif /* CONFIG_BT_ENABLED */
+74
View File
@@ -0,0 +1,74 @@
/*
* BLEScan.h
*
* Created on: Jul 1, 2017
* Author: kolban
*/
#ifndef COMPONENTS_CPP_UTILS_BLESCAN_H_
#define COMPONENTS_CPP_UTILS_BLESCAN_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <esp_gap_ble_api.h>
#include <vector>
#include "BLEAdvertisedDevice.h"
#include "BLEClient.h"
#include "FreeRTOS.h"
class BLEAdvertisedDevice;
class BLEAdvertisedDeviceCallbacks;
class BLEClient;
class BLEScan;
/**
* @brief The result of having performed a scan.
* When a scan completes, we have a set of found devices. Each device is described
* by a BLEAdvertisedDevice object. The number of items in the set is given by
* getCount(). We can retrieve a device by calling getDevice() passing in the
* index (starting at 0) of the desired device.
*/
class BLEScanResults {
public:
void dump();
int getCount();
BLEAdvertisedDevice getDevice(uint32_t i);
private:
friend BLEScan;
std::vector<BLEAdvertisedDevice> m_vectorAdvertisedDevices;
};
/**
* @brief Perform and manage %BLE scans.
*
* Scanning is associated with a %BLE client that is attempting to locate BLE servers.
*/
class BLEScan {
public:
void setActiveScan(bool active);
void setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks);
void setInterval(uint16_t intervalMSecs);
void setWindow(uint16_t windowMSecs);
BLEScanResults start(uint32_t duration);
void stop();
private:
BLEScan(); // One doesn't create a new instance instead one asks the BLEDevice for the singleton.
friend class BLEDevice;
void handleGAPEvent(
esp_gap_ble_cb_event_t event,
esp_ble_gap_cb_param_t* param);
void parseAdvertisement(BLEClient* pRemoteDevice, uint8_t *payload);
esp_ble_scan_params_t m_scan_params;
BLEAdvertisedDeviceCallbacks* m_pAdvertisedDeviceCallbacks;
bool m_stopped;
FreeRTOS::Semaphore m_semaphoreScanEnd = FreeRTOS::Semaphore("ScanEnd");
BLEScanResults m_scanResults;
}; // BLEScan
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_CPP_UTILS_BLESCAN_H_ */
+365
View File
@@ -0,0 +1,365 @@
/*
* BLEServer.cpp
*
* Created on: Apr 16, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <esp_log.h>
#include <bt.h>
#include <esp_bt_main.h>
#include <esp_gap_ble_api.h>
#include <esp_gatts_api.h>
#include "BLEDevice.h"
#include "BLEServer.h"
#include "BLEService.h"
#include "BLEUtils.h"
#include <string.h>
#include <string>
#include <gatt_api.h>
#include <unordered_set>
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-log.h"
#endif
static const char* LOG_TAG = "BLEServer";
/**
* @brief Construct a %BLE Server
*
* This class is not designed to be individually instantiated. Instead one should create a server by asking
* the BLEDevice class.
*/
BLEServer::BLEServer() {
m_appId = -1;
m_gatts_if = -1;
m_connectedCount = 0;
m_connId = -1;
m_pServerCallbacks = nullptr;
//createApp(0);
} // BLEServer
void BLEServer::createApp(uint16_t appId) {
m_appId = appId;
registerApp();
} // createApp
/**
* @brief Create a %BLE Service.
*
* With a %BLE server, we can host one or more services. Invoking this function causes the creation of a definition
* of a new service. Every service must have a unique UUID.
* @param [in] uuid The UUID of the new service.
* @return A reference to the new service object.
*/
BLEService* BLEServer::createService(const char* uuid) {
return createService(BLEUUID(uuid));
}
/**
* @brief Create a %BLE Service.
*
* With a %BLE server, we can host one or more services. Invoking this function causes the creation of a definition
* of a new service. Every service must have a unique UUID.
* @param [in] uuid The UUID of the new service.
* @param [in] numHandles The maximum number of handles associated with this service.
* @return A reference to the new service object.
*/
BLEService* BLEServer::createService(BLEUUID uuid, uint32_t numHandles) {
ESP_LOGD(LOG_TAG, ">> createService - %s", uuid.toString().c_str());
m_semaphoreCreateEvt.take("createService");
// Check that a service with the supplied UUID does not already exist.
if (m_serviceMap.getByUUID(uuid) != nullptr) {
ESP_LOGE(LOG_TAG, "<< Attempt to create a new service with uuid %s but a service with that UUID already exists.",
uuid.toString().c_str());
m_semaphoreCreateEvt.give();
return nullptr;
}
BLEService* pService = new BLEService(uuid, numHandles);
m_serviceMap.setByUUID(uuid, pService); // Save a reference to this service being on this server.
pService->executeCreate(this); // Perform the API calls to actually create the service.
m_semaphoreCreateEvt.wait("createService");
ESP_LOGD(LOG_TAG, "<< createService");
return pService;
} // createService
/**
* @brief Retrieve the advertising object that can be used to advertise the existence of the server.
*
* @return An advertising object.
*/
BLEAdvertising* BLEServer::getAdvertising() {
return &m_bleAdvertising;
}
uint16_t BLEServer::getConnId() {
return m_connId;
}
/**
* @brief Return the number of connected clients.
* @return The number of connected clients.
*/
uint32_t BLEServer::getConnectedCount() {
return m_connectedCount;
} // getConnectedCount
uint16_t BLEServer::getGattsIf() {
return m_gatts_if;
}
/**
* @brief Handle a received GAP event.
*
* @param [in] event
* @param [in] param
*/
void BLEServer::handleGAPEvent(
esp_gap_ble_cb_event_t event,
esp_ble_gap_cb_param_t* param) {
ESP_LOGD(LOG_TAG, "BLEServer ... handling GAP event!");
switch(event) {
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: {
/*
esp_ble_adv_params_t adv_params;
adv_params.adv_int_min = 0x20;
adv_params.adv_int_max = 0x40;
adv_params.adv_type = ADV_TYPE_IND;
adv_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
adv_params.channel_map = ADV_CHNL_ALL;
adv_params.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY;
ESP_LOGD(tag, "Starting advertising");
esp_err_t errRc = ::esp_ble_gap_start_advertising(&adv_params);
if (errRc != ESP_OK) {
ESP_LOGE(tag, "esp_ble_gap_start_advertising: rc=%d %s", errRc, espToString(errRc));
return;
}
*/
break;
}
default:
break;
}
} // handleGAPEvent
/**
* @brief Handle a GATT Server Event.
*
* @param [in] event
* @param [in] gatts_if
* @param [in] param
*
*/
void BLEServer::handleGATTServerEvent(
esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t* param) {
ESP_LOGD(LOG_TAG, ">> handleGATTServerEvent: %s",
BLEUtils::gattServerEventTypeToString(event).c_str());
// Invoke the handler for every Service we have.
m_serviceMap.handleGATTServerEvent(event, gatts_if, param);
switch(event) {
// ESP_GATTS_CONNECT_EVT
// connect:
// - uint16_t conn_id
// - esp_bd_addr_t remote_bda
// - bool is_connected
case ESP_GATTS_CONNECT_EVT: {
m_connId = param->connect.conn_id; // Save the connection id.
if (m_pServerCallbacks != nullptr) {
m_pServerCallbacks->onConnect(this);
}
m_connectedCount++;
break;
} // ESP_GATTS_CONNECT_EVT
// ESP_GATTS_REG_EVT
// reg:
// - esp_gatt_status_t status
// - uint16_t app_id
case ESP_GATTS_REG_EVT: {
m_gatts_if = gatts_if;
m_semaphoreRegisterAppEvt.give(); // Unlock the mutex waiting for the registration of the app.
break;
} // ESP_GATTS_REG_EVT
// ESP_GATTS_CREATE_EVT
// Called when a new service is registered as having been created.
//
// create:
// * esp_gatt_status_t status
// * uint16_t service_handle
// * esp_gatt_srvc_id_t service_id
//
case ESP_GATTS_CREATE_EVT: {
BLEService* pService = m_serviceMap.getByUUID(param->create.service_id.id.uuid);
m_serviceMap.setByHandle(param->create.service_handle, pService);
//pService->setHandle(param->create.service_handle);
m_semaphoreCreateEvt.give();
break;
} // ESP_GATTS_CREATE_EVT
// ESP_GATTS_READ_EVT - A request to read the value of a characteristic has arrived.
//
// read:
// - uint16_t conn_id
// - uint32_t trans_id
// - esp_bd_addr_t bda
// - uint16_t handle
// - uint16_t offset
// - bool is_long
// - bool need_rsp
//
case ESP_GATTS_READ_EVT: {
break;
} // ESP_GATTS_READ_EVT
// ESP_GATTS_WRITE_EVT - A request to write the value of a characteristic has arrived.
//
// write:
// - uint16_t conn_id
// - uint16_t trans_id
// - esp_bd_addr_t bda
// - uint16_t handle
// - uint16_t offset
// - bool need_rsp
// - bool is_prep
// - uint16_t len
// - uint8_t *value
case ESP_GATTS_WRITE_EVT: {
break;
}
// ESP_GATTS_DISCONNECT_EVT
// If we receive a disconnect event then invoke the callback for disconnects (if one is present).
// we also want to start advertising again.
case ESP_GATTS_DISCONNECT_EVT: {
m_connectedCount--;
if (m_pServerCallbacks != nullptr) {
m_pServerCallbacks->onDisconnect(this);
}
startAdvertising();
break;
} // ESP_GATTS_DISCONNECT_EVT
// ESP_GATTS_ADD_CHAR_EVT - Indicate that a characteristic was added to the service.
// add_char:
// - esp_gatt_status_t status
// - uint16_t attr_handle
// - uint16_t service_handle
// - esp_bt_uuid_t char_uuid
case ESP_GATTS_ADD_CHAR_EVT: {
break;
} // ESP_GATTS_ADD_CHAR_EVT
default: {
break;
}
}
ESP_LOGD(LOG_TAG, "<< handleGATTServerEvent");
} // handleGATTServerEvent
/**
* @brief Register the app.
*
* @return N/A
*/
void BLEServer::registerApp() {
ESP_LOGD(LOG_TAG, ">> registerApp - %d", m_appId);
m_semaphoreRegisterAppEvt.take("registerApp"); // Take the mutex, will be released by ESP_GATTS_REG_EVT event.
::esp_ble_gatts_app_register(m_appId);
m_semaphoreRegisterAppEvt.wait("registerApp");
ESP_LOGD(LOG_TAG, "<< registerApp");
} // registerApp
/**
* @brief Set the callbacks.
*
* As a %BLE server operates, it will generate server level events such as a new client connecting or a previous client
* disconnecting. This function can be called to register a callback handler that will be invoked when these
* events are detected.
*
* @param [in] pCallbacks The callbacks to be invoked.
*/
void BLEServer::setCallbacks(BLEServerCallbacks* pCallbacks) {
m_pServerCallbacks = pCallbacks;
} // setCallbacks
/**
* @brief Start advertising.
*
* Start the server advertising its existence. This is a convenience function and is equivalent to
* retrieving the advertising object and invoking start upon it.
*/
void BLEServer::startAdvertising() {
ESP_LOGD(LOG_TAG, ">> startAdvertising");
m_bleAdvertising.start();
ESP_LOGD(LOG_TAG, "<< startAdvertising");
} // startAdvertising
/*
void BLEServer::addCharacteristic(BLECharacteristic *characteristic, BLEService *pService) {
ESP_LOGD(tag, "Adding characteristic (esp_ble_gatts_add_char): uuid=%s, serviceHandle=0x%.2x",
characteristic->m_bleUUID.toString().c_str(),
pService->getHandle());
m_characteristicMap.setByUUID(characteristic->m_bleUUID, characteristic);
esp_err_t errRc = ::esp_ble_gatts_add_char(
pService->getHandle(),
characteristic->getUUID().getNative(),
(esp_gatt_perm_t)(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE),
characteristic->getProperties(),
&characteristic->m_value,
NULL);
if (errRc != ESP_OK) {
ESP_LOGE(tag, "esp_ble_gatts_add_char: rc=%d %s", errRc, espToString(errRc));
return;
}
}
*/
void BLEServerCallbacks::onConnect(BLEServer* pServer) {
ESP_LOGD("BLEServerCallbacks", ">> onConnect(): Default");
ESP_LOGD("BLEServerCallbacks", "<< onConnect()");
} // onConnect
void BLEServerCallbacks::onDisconnect(BLEServer* pServer) {
ESP_LOGD("BLEServerCallbacks", ">> onDisconnect(): Default");
ESP_LOGD("BLEServerCallbacks", "<< onDisconnect()");
} // onDisconnect
#endif // CONFIG_BT_ENABLED
+115
View File
@@ -0,0 +1,115 @@
/*
* BLEServer.h
*
* Created on: Apr 16, 2017
* Author: kolban
*/
#ifndef COMPONENTS_CPP_UTILS_BLESERVER_H_
#define COMPONENTS_CPP_UTILS_BLESERVER_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <esp_gatts_api.h>
#include <string>
#include <string.h>
#include "BLEUUID.h"
#include "BLEAdvertising.h"
#include "BLECharacteristic.h"
#include "BLEService.h"
#include "FreeRTOS.h"
class BLEServerCallbacks;
/**
* @brief A data structure that manages the %BLE servers owned by a BLE server.
*/
class BLEServiceMap {
public:
BLEService* getByHandle(uint16_t handle);
BLEService* getByUUID(const char* uuid);
BLEService* getByUUID(BLEUUID uuid);
void handleGATTServerEvent(
esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t* param);
void setByHandle(uint16_t handle, BLEService* service);
void setByUUID(const char* uuid, BLEService* service);
void setByUUID(BLEUUID uuid, BLEService* service);
std::string toString();
private:
std::map<std::string, BLEService*> m_uuidMap;
std::map<uint16_t, BLEService*> m_handleMap;
};
/**
* @brief The model of a %BLE server.
*/
class BLEServer {
public:
uint32_t getConnectedCount();
BLEService* createService(const char* uuid);
BLEService* createService(BLEUUID uuid, uint32_t numHandles=15);
BLEAdvertising* getAdvertising();
void setCallbacks(BLEServerCallbacks *pCallbacks);
void startAdvertising();
private:
BLEServer();
friend class BLEService;
friend class BLECharacteristic;
friend class BLEDevice;
esp_ble_adv_data_t m_adv_data;
uint16_t m_appId;
BLEAdvertising m_bleAdvertising;
uint16_t m_connId;
uint32_t m_connectedCount;
uint16_t m_gatts_if;
FreeRTOS::Semaphore m_semaphoreRegisterAppEvt = FreeRTOS::Semaphore("RegisterAppEvt");
FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt");
BLEServiceMap m_serviceMap;
BLEServerCallbacks* m_pServerCallbacks;
void createApp(uint16_t appId);
uint16_t getConnId();
uint16_t getGattsIf();
void handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
void registerApp();
}; // BLEServer
/**
* @brief Callbacks associated with the operation of a %BLE server.
*/
class BLEServerCallbacks {
public:
virtual ~BLEServerCallbacks() {};
/**
* @brief Handle a new client connection.
*
* When a new client connects, we are invoked.
*
* @param [in] pServer A reference to the %BLE server that received the client connection.
*/
virtual void onConnect(BLEServer* pServer);
/**
* @brief Handle an existing client disconnection.
*
* When an existing client disconnects, we are invoked.
*
* @param [in] pServer A reference to the %BLE server that received the existing client disconnection.
*/
virtual void onDisconnect(BLEServer* pServer);
}; // BLEServerCallbacks
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_CPP_UTILS_BLESERVER_H_ */
+356
View File
@@ -0,0 +1,356 @@
/*
* BLEService.cpp
*
* Created on: Mar 25, 2017
* Author: kolban
*/
// A service is identified by a UUID. A service is also the container for one or more characteristics.
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <esp_err.h>
#include <esp_gatts_api.h>
#include <esp_log.h>
#include <iomanip>
#include <sstream>
#include <string>
#include "BLEServer.h"
#include "BLEService.h"
#include "BLEUtils.h"
#include "GeneralUtils.h"
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-log.h"
#endif
#define NULL_HANDLE (0xffff)
static const char* LOG_TAG = "BLEService"; // Tag for logging.
/**
* @brief Construct an instance of the BLEService
* @param [in] uuid The UUID of the service.
* @param [in] numHandles The maximum number of handles associated with the service.
*/
BLEService::BLEService(const char* uuid, uint32_t numHandles) : BLEService(BLEUUID(uuid), numHandles) {
}
/**
* @brief Construct an instance of the BLEService
* @param [in] uuid The UUID of the service.
* @param [in] numHandles The maximum number of handles associated with the service.
*/
BLEService::BLEService(BLEUUID uuid, uint32_t numHandles) {
m_uuid = uuid;
m_handle = NULL_HANDLE;
m_pServer = nullptr;
//m_serializeMutex.setName("BLEService");
m_lastCreatedCharacteristic = nullptr;
m_numHandles = numHandles;
} // BLEService
/**
* @brief Create the service.
* Create the service.
* @param [in] gatts_if The handle of the GATT server interface.
* @return N/A.
*/
void BLEService::executeCreate(BLEServer *pServer) {
//ESP_LOGD(LOG_TAG, ">> executeCreate() - Creating service (esp_ble_gatts_create_service) service uuid: %s", getUUID().toString().c_str());
//getUUID(); // Needed for a weird bug fix
//char x[1000];
//memcpy(x, &m_uuid, sizeof(m_uuid));
//char x[10];
//memcpy(x, &deleteMe, 10);
m_pServer = pServer;
m_semaphoreCreateEvt.take("executeCreate"); // Take the mutex and release at event ESP_GATTS_CREATE_EVT
esp_gatt_srvc_id_t srvc_id;
srvc_id.id.inst_id = 0;
srvc_id.id.uuid = *m_uuid.getNative();
esp_err_t errRc = ::esp_ble_gatts_create_service(
getServer()->getGattsIf(),
&srvc_id,
m_numHandles // The maximum number of handles associated with the service.
);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gatts_create_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
m_semaphoreCreateEvt.wait("executeCreate");
ESP_LOGD(LOG_TAG, "<< executeCreate");
} // executeCreate
/**
* @brief Dump details of this BLE GATT service.
* @return N/A.
*/
void BLEService::dump() {
ESP_LOGD(LOG_TAG, "Service: uuid:%s, handle: 0x%.2x",
m_uuid.toString().c_str(),
m_handle);
ESP_LOGD(LOG_TAG, "Characteristics:\n%s", m_characteristicMap.toString().c_str());
} // dump
/**
* @brief Get the UUID of the service.
* @return the UUID of the service.
*/
BLEUUID BLEService::getUUID() {
return m_uuid;
} // getUUID
/**
* @brief Start the service.
* Here we wish to start the service which means that we will respond to partner requests about it.
* Starting a service also means that we can create the corresponding characteristics.
* @return Start the service.
*/
void BLEService::start() {
// We ask the BLE runtime to start the service and then create each of the characteristics.
// We start the service through its local handle which was returned in the ESP_GATTS_CREATE_EVT event
// obtained as a result of calling esp_ble_gatts_create_service().
//
ESP_LOGD(LOG_TAG, ">> start(): Starting service (esp_ble_gatts_start_service): %s", toString().c_str());
if (m_handle == NULL_HANDLE) {
ESP_LOGE(LOG_TAG, "<< !!! We attempted to start a service but don't know its handle!");
return;
}
BLECharacteristic *pCharacteristic = m_characteristicMap.getFirst();
while(pCharacteristic != nullptr) {
m_lastCreatedCharacteristic = pCharacteristic;
pCharacteristic->executeCreate(this);
pCharacteristic = m_characteristicMap.getNext();
}
// Start each of the characteristics ... these are found in the m_characteristicMap.
m_semaphoreStartEvt.take("start");
esp_err_t errRc = ::esp_ble_gatts_start_service(m_handle);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_start_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
m_semaphoreStartEvt.wait("start");
ESP_LOGD(LOG_TAG, "<< start()");
} // start
/**
* @brief Set the handle associated with this service.
* @param [in] handle The handle associated with the service.
*/
void BLEService::setHandle(uint16_t handle) {
ESP_LOGD(LOG_TAG, ">> setHandle - Handle=0x%.2x, service UUID=%s)", handle, getUUID().toString().c_str());
if (m_handle != NULL_HANDLE) {
ESP_LOGE(LOG_TAG, "!!! Handle is already set %.2x", m_handle);
return;
}
m_handle = handle;
ESP_LOGD(LOG_TAG, "<< setHandle");
} // setHandle
/**
* @brief Get the handle associated with this service.
* @return The handle associated with this service.
*/
uint16_t BLEService::getHandle() {
return m_handle;
} // getHandle
/**
* @brief Add a characteristic to the service.
* @param [in] pCharacteristic A pointer to the characteristic to be added.
*/
void BLEService::addCharacteristic(BLECharacteristic* pCharacteristic) {
// We maintain a mapping of characteristics owned by this service. These are managed by the
// BLECharacteristicMap class instance found in m_characteristicMap. We add the characteristic
// to the map and then ask the service to add the characteristic at the BLE level (ESP-IDF).
//
ESP_LOGD(LOG_TAG, ">> addCharacteristic()");
ESP_LOGD(LOG_TAG, "Adding characteristic: uuid=%s to service: %s",
pCharacteristic->getUUID().toString().c_str(),
toString().c_str());
// Check that we don't add the same characteristic twice.
if (m_characteristicMap.getByUUID(pCharacteristic->getUUID()) != nullptr) {
ESP_LOGE(LOG_TAG, "<< Attempt to add a characteristic but we already have one with this UUID");
//return;
}
// Remember this characteristic in our map of characteristics. At this point, we can lookup by UUID
// but not by handle. The handle is allocated to us on the ESP_GATTS_ADD_CHAR_EVT.
m_characteristicMap.setByUUID(pCharacteristic, pCharacteristic->getUUID());
ESP_LOGD(LOG_TAG, "<< addCharacteristic()");
} // addCharacteristic
/**
* @brief Create a new BLE Characteristic associated with this service.
* @param [in] uuid - The UUID of the characteristic.
* @param [in] properties - The properties of the characteristic.
* @return The new BLE characteristic.
*/
BLECharacteristic* BLEService::createCharacteristic(const char* uuid, uint32_t properties) {
return createCharacteristic(BLEUUID(uuid), properties);
}
/**
* @brief Create a new BLE Characteristic associated with this service.
* @param [in] uuid - The UUID of the characteristic.
* @param [in] properties - The properties of the characteristic.
* @return The new BLE characteristic.
*/
BLECharacteristic* BLEService::createCharacteristic(BLEUUID uuid, uint32_t properties) {
BLECharacteristic *pCharacteristic = new BLECharacteristic(uuid, properties);
addCharacteristic(pCharacteristic);
return pCharacteristic;
} // createCharacteristic
/**
* @brief Handle a GATTS server event.
*/
void BLEService::handleGATTServerEvent(
esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param) {
switch(event) {
// ESP_GATTS_ADD_CHAR_EVT - Indicate that a characteristic was added to the service.
// add_char:
// - esp_gatt_status_t status
// - uint16_t attr_handle
// - uint16_t service_handle
// - esp_bt_uuid_t char_uuid
// If we have reached the correct service, then locate the characteristic and remember the handle
// for that characteristic.
case ESP_GATTS_ADD_CHAR_EVT: {
if (m_handle == param->add_char.service_handle) {
BLECharacteristic *pCharacteristic = getLastCreatedCharacteristic();
if (pCharacteristic == nullptr) {
ESP_LOGE(LOG_TAG, "Expected to find characteristic with UUID: %s, but didnt!",
BLEUUID(param->add_char.char_uuid).toString().c_str());
dump();
break;
}
pCharacteristic->setHandle(param->add_char.attr_handle);
m_characteristicMap.setByHandle(param->add_char.attr_handle, pCharacteristic);
//ESP_LOGD(tag, "Characteristic map: %s", m_characteristicMap.toString().c_str());
break;
} // Reached the correct service.
break;
} // ESP_GATTS_ADD_CHAR_EVT
// ESP_GATTS_START_EVT
//
// start:
// esp_gatt_status_t status
// uint16_t service_handle
case ESP_GATTS_START_EVT: {
if (param->start.service_handle == getHandle()) {
m_semaphoreStartEvt.give();
}
break;
} // ESP_GATTS_START_EVT
// ESP_GATTS_CREATE_EVT
// Called when a new service is registered as having been created.
//
// create:
// * esp_gatt_status_t status
// * uint16_t service_handle
// * esp_gatt_srvc_id_t service_id
// * - esp_gatt_id id
// * - esp_bt_uuid uuid
// * - uint8_t inst_id
// * - bool is_primary
//
case ESP_GATTS_CREATE_EVT: {
if (getUUID().equals(BLEUUID(param->create.service_id.id.uuid))) {
setHandle(param->create.service_handle);
m_semaphoreCreateEvt.give();
}
break;
} // ESP_GATTS_CREATE_EVT
default: {
break;
} // Default
} // Switch
// Invoke the GATTS handler in each of the associated characteristics.
m_characteristicMap.handleGATTServerEvent(event, gatts_if, param);
} // handleGATTServerEvent
BLECharacteristic* BLEService::getCharacteristic(const char* uuid) {
return getCharacteristic(BLEUUID(uuid));
}
BLECharacteristic* BLEService::getCharacteristic(BLEUUID uuid) {
return m_characteristicMap.getByUUID(uuid);
}
/**
* @brief Return a string representation of this service.
* A service is defined by:
* * Its UUID
* * Its handle
* @return A string representation of this service.
*/
std::string BLEService::toString() {
std::stringstream stringStream;
stringStream << "UUID: " << getUUID().toString() <<
", handle: 0x" << std::hex << std::setfill('0') << std::setw(2) << getHandle();
return stringStream.str();
} // toString
/**
* @brief Get the last created characteristic.
* It is lamentable that this function has to exist. It returns the last created characteristic.
* We need this because the descriptor API is built around the notion that a new descriptor, when created,
* is associated with the last characteristics created and we need that information.
* @return The last created characteristic.
*/
BLECharacteristic* BLEService::getLastCreatedCharacteristic() {
return m_lastCreatedCharacteristic;
} // getLastCreatedCharacteristic
/**
* @brief Get the BLE server associated with this service.
* @return The BLEServer associated with this service.
*/
BLEServer* BLEService::getServer() {
return m_pServer;
} // getServer
#endif // CONFIG_BT_ENABLED
+101
View File
@@ -0,0 +1,101 @@
/*
* BLEService.h
*
* Created on: Mar 25, 2017
* Author: kolban
*/
#ifndef COMPONENTS_CPP_UTILS_BLESERVICE_H_
#define COMPONENTS_CPP_UTILS_BLESERVICE_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <esp_gatts_api.h>
#include "BLECharacteristic.h"
#include "BLEServer.h"
#include "BLEUUID.h"
#include "FreeRTOS.h"
class BLEServer;
/**
* @brief A data mapping used to manage the set of %BLE characteristics known to the server.
*/
class BLECharacteristicMap {
public:
void setByUUID(BLECharacteristic* pCharacteristic, const char* uuid);
void setByUUID(BLECharacteristic* pCharacteristic, BLEUUID uuid);
void setByHandle(uint16_t handle, BLECharacteristic* pCharacteristic);
BLECharacteristic* getByUUID(const char* uuid);
BLECharacteristic* getByUUID(BLEUUID uuid);
BLECharacteristic* getByHandle(uint16_t handle);
BLECharacteristic* getFirst();
BLECharacteristic* getNext();
std::string toString();
void handleGATTServerEvent(
esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t* param);
private:
std::map<BLECharacteristic*, std::string> m_uuidMap;
std::map<uint16_t, BLECharacteristic*> m_handleMap;
std::map<BLECharacteristic*, std::string>::iterator m_iterator;
};
/**
* @brief The model of a %BLE service.
*
*/
class BLEService {
public:
BLEService(const char* uuid, uint32_t numHandles=10);
BLEService(BLEUUID uuid, uint32_t numHandles=10);
void addCharacteristic(BLECharacteristic* pCharacteristic);
BLECharacteristic* createCharacteristic(const char* uuid, uint32_t properties);
BLECharacteristic* createCharacteristic(BLEUUID uuid, uint32_t properties);
void dump();
void executeCreate(BLEServer* pServer);
BLECharacteristic* getCharacteristic(const char* uuid);
BLECharacteristic* getCharacteristic(BLEUUID uuid);
BLEUUID getUUID();
BLEServer* getServer();
void start();
std::string toString();
uint16_t getHandle();
private:
friend class BLEServer;
friend class BLEServiceMap;
friend class BLEDescriptor;
friend class BLECharacteristic;
friend class BLEDevice;
BLECharacteristicMap m_characteristicMap;
uint16_t m_handle;
BLECharacteristic* m_lastCreatedCharacteristic;
BLEServer* m_pServer;
BLEUUID m_uuid;
char deleteMe[10];
//FreeRTOS::Semaphore m_serializeMutex;
FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt");
FreeRTOS::Semaphore m_semaphoreStartEvt = FreeRTOS::Semaphore("StartEvt");
uint32_t m_numHandles;
BLECharacteristic* getLastCreatedCharacteristic();
void handleGATTServerEvent(
esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t* param);
void setHandle(uint16_t handle);
//void setService(esp_gatt_srvc_id_t srvc_id);
}; // BLEService
#endif // CONFIG_BT_ENABLED
#endif /* COMPONENTS_CPP_UTILS_BLESERVICE_H_ */
@@ -0,0 +1,95 @@
/*
* BLEServiceMap.cpp
*
* Created on: Jun 22, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <sstream>
#include <iomanip>
#include "BLEService.h"
/**
* @brief Return the service by UUID.
* @param [in] UUID The UUID to look up the service.
* @return The characteristic.
*/
BLEService* BLEServiceMap::getByUUID(const char* uuid) {
return getByUUID(BLEUUID(uuid));
}
/**
* @brief Return the service by UUID.
* @param [in] UUID The UUID to look up the service.
* @return The characteristic.
*/
BLEService* BLEServiceMap::getByUUID(BLEUUID uuid) {
for (auto &myPair : m_uuidMap) {
if (myPair.second->getUUID().equals(uuid)) {
return myPair.second;
}
}
//return m_uuidMap.at(uuid.toString());
return nullptr;
} // getByUUID
/**
* @brief Return the service by handle.
* @param [in] handle The handle to look up the service.
* @return The service.
*/
BLEService* BLEServiceMap::getByHandle(uint16_t handle) {
return m_handleMap.at(handle);
} // getByHandle
/**
* @brief Set the service by UUID.
* @param [in] uuid The uuid of the service.
* @param [in] characteristic The service to cache.
* @return N/A.
*/
void BLEServiceMap::setByUUID(BLEUUID uuid,
BLEService *service) {
m_uuidMap.insert(std::pair<std::string, BLEService *>(uuid.toString(), service));
} // setByUUID
/**
* @brief Set the service by handle.
* @param [in] handle The handle of the service.
* @param [in] service The service to cache.
* @return N/A.
*/
void BLEServiceMap::setByHandle(uint16_t handle,
BLEService* service) {
m_handleMap.insert(std::pair<uint16_t, BLEService *>(handle, service));
} // setByHandle
/**
* @brief Return a string representation of the service map.
* @return A string representation of the service map.
*/
std::string BLEServiceMap::toString() {
std::stringstream stringStream;
stringStream << std::hex << std::setfill('0');
for (auto &myPair: m_handleMap) {
stringStream << "handle: 0x" << std::setw(2) << myPair.first << ", uuid: " + myPair.second->getUUID().toString() << "\n";
}
return stringStream.str();
} // toString
void BLEServiceMap::handleGATTServerEvent(
esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param) {
// Invoke the handler for every Service we have.
for (auto &myPair : m_uuidMap) {
myPair.second->handleGATTServerEvent(event, gatts_if, param);
}
}
#endif /* CONFIG_BT_ENABLED */
+370
View File
@@ -0,0 +1,370 @@
/*
* BLEUUID.cpp
*
* Created on: Jun 21, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <esp_log.h>
#include <string.h>
#include <sstream>
#include <iomanip>
#include <stdio.h>
#include "BLEUUID.h"
static const char* LOG_TAG = "BLEUUID";
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-log.h"
#endif
/**
* @brief Copy memory from source to target but in reverse order.
*
* When we move memory from one location it is normally:
*
* ```
* [0][1][2]...[n] -> [0][1][2]...[n]
* ```
*
* with this function, it is:
*
* ```
* [0][1][2]...[n] -> [n][n-1][n-2]...[0]
* ```
*
* @param [in] target The target of the copy
* @param [in] source The source of the copy
* @param [in] size The number of bytes to copy
*/
static void memrcpy(uint8_t* target, uint8_t* source, uint32_t size) {
assert(size > 0);
target+=(size-1); // Point target to the last byte of the target data
while (size > 0) {
*target = *source;
target--;
source++;
size--;
}
} // memrcpy
/**
* @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.
*/
BLEUUID::BLEUUID(std::string value) {
m_valueSet = true;
if (value.length() == 2) {
m_uuid.len = ESP_UUID_LEN_16;
m_uuid.uuid.uuid16 = value[0] | (value[1] << 8);
}
else if (value.length() == 4) {
m_uuid.len = ESP_UUID_LEN_32;
m_uuid.uuid.uuid32 = value[0] | (value[1] << 8) | (value[2] << 16) | (value[3] << 24);
}
else if (value.length() == 16) {
m_uuid.len = ESP_UUID_LEN_128;
memrcpy(m_uuid.uuid.uuid128, (uint8_t*)value.data(), 16);
}
else if (value.length() == 36) {
// If the length of the string is 36 bytes then we will assume it is a long hex string in
// UUID format.
m_uuid.len = ESP_UUID_LEN_128;
int vals[16];
sscanf(value.c_str(), "%2x%2x%2x%2x-%2x%2x-%2x%2x-%2x%2x-%2x%2x%2x%2x%2x%2x",
&vals[15],
&vals[14],
&vals[13],
&vals[12],
&vals[11],
&vals[10],
&vals[9],
&vals[8],
&vals[7],
&vals[6],
&vals[5],
&vals[4],
&vals[3],
&vals[2],
&vals[1],
&vals[0]
);
int i;
for (i=0; i<16; i++) {
m_uuid.uuid.uuid128[i] = vals[i];
}
}
else {
ESP_LOGE(LOG_TAG, "ERROR: UUID value not 2, 4, 16 or 36 bytes");
m_valueSet = false;
}
} //BLEUUID(std::string)
/**
* @brief Create a UUID from 16 bytes of memory.
*
* @param [in] pData The pointer to the start of the UUID.
* @param [in] size The size of the data.
* @param [in] msbFirst Is the MSB first in pData memory?
*/
BLEUUID::BLEUUID(uint8_t* pData, size_t size, bool msbFirst) {
if (size != 16) {
ESP_LOGE(LOG_TAG, "ERROR: UUID length not 16 bytes");
return;
}
m_uuid.len = ESP_UUID_LEN_128;
if (msbFirst) {
memrcpy(m_uuid.uuid.uuid128, pData, 16);
} else {
memcpy(m_uuid.uuid.uuid128, pData, 16);
}
m_valueSet = true;
} // BLEUUID
/**
* @brief Create a UUID from the 16bit value.
*
* @param [in] uuid The 16bit short form UUID.
*/
BLEUUID::BLEUUID(uint16_t uuid) {
m_uuid.len = ESP_UUID_LEN_16;
m_uuid.uuid.uuid16 = uuid;
m_valueSet = true;
} // BLEUUID
/**
* @brief Create a UUID from the 32bit value.
*
* @param [in] uuid The 32bit short form UUID.
*/
BLEUUID::BLEUUID(uint32_t uuid) {
m_uuid.len = ESP_UUID_LEN_32;
m_uuid.uuid.uuid32 = uuid;
m_valueSet = true;
} // BLEUUID
/**
* @brief Create a UUID from the native UUID.
*
* @param [in] uuid The native UUID.
*/
BLEUUID::BLEUUID(esp_bt_uuid_t uuid) {
m_uuid = uuid;
m_valueSet = true;
} // BLEUUID
/**
* @brief Create a UUID from the ESP32 esp_gat_id_t.
*
* @param [in] gattId The data to create the UUID from.
*/
BLEUUID::BLEUUID(esp_gatt_id_t gattId) : BLEUUID(gattId.uuid) {
} // BLEUUID
BLEUUID::BLEUUID() {
m_valueSet = false;
} // BLEUUID
/**
* @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 BLEUUID::equals(BLEUUID uuid) {
//ESP_LOGD(TAG, "Comparing: %s to %s", toString().c_str(), uuid.toString().c_str());
if (m_valueSet == false || uuid.m_valueSet == false) {
return false;
}
if (uuid.m_uuid.len != m_uuid.len) {
return uuid.toString() == toString();
}
if (uuid.m_uuid.len == ESP_UUID_LEN_16) {
return uuid.m_uuid.uuid.uuid16 == m_uuid.uuid.uuid16;
}
if (uuid.m_uuid.len == ESP_UUID_LEN_32) {
return uuid.m_uuid.uuid.uuid32 == m_uuid.uuid.uuid32;
}
return memcmp(uuid.m_uuid.uuid.uuid128, m_uuid.uuid.uuid128, 16) == 0;
} // equals
/**
* @brief Get the native UUID value.
*
* @return The native UUID value or NULL if not set.
*/
esp_bt_uuid_t* BLEUUID::getNative() {
//ESP_LOGD(TAG, ">> getNative()")
if (m_valueSet == false) {
ESP_LOGD(LOG_TAG, "<< Return of un-initialized UUID!");
return nullptr;
}
//ESP_LOGD(TAG, "<< getNative()");
return &m_uuid;
} // getNative
/**
* @brief Convert a UUID to its 128 bit representation.
*
* A UUID can be internally represented as 16bit, 32bit or the full 128bit. This method
* will convert 16 or 32 bit representations to the full 128bit.
*/
BLEUUID BLEUUID::to128() {
//ESP_LOGD(LOG_TAG, ">> toFull() - %s", toString().c_str());
// If we either don't have a value or are already a 128 bit UUID, nothing further to do.
if (m_valueSet == false || m_uuid.len == ESP_UUID_LEN_128) {
return *this;
}
// If we are 16 bit or 32 bit, then set the 4 bytes of the variable part of the UUID.
if (m_uuid.len == ESP_UUID_LEN_16) {
uint16_t temp = m_uuid.uuid.uuid16;
m_uuid.uuid.uuid128[15] = 0;
m_uuid.uuid.uuid128[14] = 0;
m_uuid.uuid.uuid128[13] = (temp >> 8) & 0xff;
m_uuid.uuid.uuid128[12] = temp & 0xff;
}
else if (m_uuid.len == ESP_UUID_LEN_32) {
uint32_t temp = m_uuid.uuid.uuid32;
m_uuid.uuid.uuid128[15] = (temp >> 24) & 0xff;
m_uuid.uuid.uuid128[14] = (temp >> 16) & 0xff;
m_uuid.uuid.uuid128[13] = (temp >> 8) & 0xff;
m_uuid.uuid.uuid128[12] = temp & 0xff;
}
// Set the fixed parts of the UUID.
m_uuid.uuid.uuid128[11] = 0x00;
m_uuid.uuid.uuid128[10] = 0x00;
m_uuid.uuid.uuid128[9] = 0x10;
m_uuid.uuid.uuid128[8] = 0x00;
m_uuid.uuid.uuid128[7] = 0x80;
m_uuid.uuid.uuid128[6] = 0x00;
m_uuid.uuid.uuid128[5] = 0x00;
m_uuid.uuid.uuid128[4] = 0x80;
m_uuid.uuid.uuid128[3] = 0x5f;
m_uuid.uuid.uuid128[2] = 0x9b;
m_uuid.uuid.uuid128[1] = 0x34;
m_uuid.uuid.uuid128[0] = 0xfb;
m_uuid.len = ESP_UUID_LEN_128;
//ESP_LOGD(TAG, "<< toFull <- %s", toString().c_str());
return *this;
} // to128
/**
* @brief Get a string representation of the UUID.
*
* The format of a string is:
* 01234567 8901 2345 6789 012345678901
* 0000180d-0000-1000-8000-00805f9b34fb
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
*
* @return A string representation of the UUID.
*/
std::string BLEUUID::toString() {
if (m_valueSet == false) { // If we have no value, nothing to format.
return "<NULL>";
}
// If the UUIDs are 16 or 32 bit, pad correctly.
std::stringstream ss;
if (m_uuid.len == ESP_UUID_LEN_16) { // If the UUID is 16bit, pad correctly.
ss << "0000" <<
std::hex <<
std::setfill('0') <<
std::setw(4) <<
m_uuid.uuid.uuid16 <<
"-0000-1000-8000-00805f9b34fb";
return ss.str(); // Return the string
} // End 16bit UUID
if (m_uuid.len == ESP_UUID_LEN_32) { // If the UUID is 32bit, pad correctly.
ss << std::hex <<
std::setfill('0') <<
std::setw(8) <<
m_uuid.uuid.uuid32 <<
"-0000-1000-8000-00805f9b34fb";
return ss.str(); // return the string
} // End 32bit UUID
// The UUID is not 16bit or 32bit which means that it is 128bit.
//
// UUID string format:
// AABBCCDD-EEFF-GGHH-IIJJ-KKLLMMNNOOPP
//
ss << std::hex << std::setfill('0') <<
std::setw(2) << (int)m_uuid.uuid.uuid128[15] <<
std::setw(2) << (int)m_uuid.uuid.uuid128[14] <<
std::setw(2) << (int)m_uuid.uuid.uuid128[13] <<
std::setw(2) << (int)m_uuid.uuid.uuid128[12] << "-" <<
std::setw(2) << (int)m_uuid.uuid.uuid128[11] <<
std::setw(2) << (int)m_uuid.uuid.uuid128[10] << "-" <<
std::setw(2) << (int)m_uuid.uuid.uuid128[9] <<
std::setw(2) << (int)m_uuid.uuid.uuid128[8] << "-" <<
std::setw(2) << (int)m_uuid.uuid.uuid128[7] <<
std::setw(2) << (int)m_uuid.uuid.uuid128[6] << "-" <<
std::setw(2) << (int)m_uuid.uuid.uuid128[5] <<
std::setw(2) << (int)m_uuid.uuid.uuid128[4] <<
std::setw(2) << (int)m_uuid.uuid.uuid128[3] <<
std::setw(2) << (int)m_uuid.uuid.uuid128[2] <<
std::setw(2) << (int)m_uuid.uuid.uuid128[1] <<
std::setw(2) << (int)m_uuid.uuid.uuid128[0];
return ss.str();
} // toString
BLEUUID BLEUUID::fromString(std::string _uuid){
uint8_t start = 0;
if(strstr(_uuid.c_str(), "0x") != nullptr){
start = 2;
}
uint8_t len = _uuid.length() - start;
if(len == 4 ){
uint16_t x = strtoul(_uuid.substr(start, len).c_str(), NULL, 16);
return BLEUUID(x);
}else if(len == 8){
uint32_t x = strtoul(_uuid.substr(start, len).c_str(), NULL, 16);
return BLEUUID(x);
}else if (len == 36){
return BLEUUID(_uuid);
}
return BLEUUID();
}
#endif /* CONFIG_BT_ENABLED */
+38
View File
@@ -0,0 +1,38 @@
/*
* BLEUUID.h
*
* Created on: Jun 21, 2017
* Author: kolban
*/
#ifndef COMPONENTS_CPP_UTILS_BLEUUID_H_
#define COMPONENTS_CPP_UTILS_BLEUUID_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <esp_gatt_defs.h>
#include <string>
/**
* @brief A model of a %BLE UUID.
*/
class BLEUUID {
public:
BLEUUID(std::string uuid);
BLEUUID(uint16_t uuid);
BLEUUID(uint32_t uuid);
BLEUUID(esp_bt_uuid_t uuid);
BLEUUID(uint8_t* pData, size_t size, bool msbFirst);
BLEUUID(esp_gatt_id_t gattId);
BLEUUID();
bool equals(BLEUUID uuid);
esp_bt_uuid_t* getNative();
BLEUUID to128();
std::string toString();
static BLEUUID fromString(std::string uuid);
private:
esp_bt_uuid_t m_uuid; // The underlying UUID structure that this class wraps.
bool m_valueSet; // Is there a value set for this instance.
}; // BLEUUID
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_CPP_UTILS_BLEUUID_H_ */
File diff suppressed because it is too large. Load diff
+63
View File
@@ -0,0 +1,63 @@
/*
* BLEUtils.h
*
* Created on: Mar 25, 2017
* Author: kolban
*/
#ifndef COMPONENTS_CPP_UTILS_BLEUTILS_H_
#define COMPONENTS_CPP_UTILS_BLEUTILS_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <esp_gattc_api.h> // ESP32 BLE
#include <esp_gatts_api.h> // ESP32 BLE
#include <esp_gap_ble_api.h> // ESP32 BLE
#include <string>
#include "BLEClient.h"
/**
* @brief A set of general %BLE utilities.
*/
class BLEUtils {
public:
static const char* addressTypeToString(esp_ble_addr_type_t type);
static std::string adFlagsToString(uint8_t adFlags);
static const char* advTypeToString(uint8_t advType);
static esp_gatt_id_t buildGattId(esp_bt_uuid_t uuid, uint8_t inst_id=0);
static esp_gatt_srvc_id_t buildGattSrvcId(esp_gatt_id_t gattId, bool is_primary=true);
static char* buildHexData(uint8_t* target, uint8_t* source, uint8_t length);
static std::string buildPrintData(uint8_t* source, size_t length);
static std::string characteristicPropertiesToString(esp_gatt_char_prop_t prop);
static const char* devTypeToString(esp_bt_dev_type_t type);
static void dumpGapEvent(
esp_gap_ble_cb_event_t event,
esp_ble_gap_cb_param_t* param);
static void dumpGattClientEvent(
esp_gattc_cb_event_t event,
esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t* evtParam);
static void dumpGattServerEvent(
esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t* evtParam);
static const char* eventTypeToString(esp_ble_evt_type_t eventType);
static BLEClient* findByAddress(BLEAddress address);
static BLEClient* findByConnId(uint16_t conn_id);
static const char* gapEventToString(uint32_t eventType);
static std::string gattCharacteristicUUIDToString(uint32_t characteristicUUID);
static std::string gattClientEventTypeToString(esp_gattc_cb_event_t eventType);
static std::string gattCloseReasonToString(esp_gatt_conn_reason_t reason);
static std::string gattcServiceElementToString(esp_gattc_service_elem_t *pGATTCServiceElement);
static std::string gattDescriptorUUIDToString(uint32_t descriptorUUID);
static std::string gattServerEventTypeToString(esp_gatts_cb_event_t eventType);
static std::string gattServiceIdToString(esp_gatt_srvc_id_t srvcId);
static std::string gattServiceToString(uint32_t serviceId);
static std::string gattStatusToString(esp_gatt_status_t status);
static std::string getMember(uint32_t memberId);
static void registerByAddress(BLEAddress address, BLEClient* pDevice);
static void registerByConnId(uint16_t conn_id, BLEClient* pDevice);
static const char* searchEventTypeToString(esp_gap_search_evt_t searchEvt);
};
#endif // CONFIG_BT_ENABLED
#endif /* COMPONENTS_CPP_UTILS_BLEUTILS_H_ */
+142
View File
@@ -0,0 +1,142 @@
/*
* BLEValue.cpp
*
* Created on: Jul 17, 2017
* Author: kolban
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <esp_log.h>
#include "BLEValue.h"
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-log.h"
#endif
static const char* LOG_TAG="BLEValue";
BLEValue::BLEValue() {
m_accumulation = "";
m_value = "";
m_readOffset = 0;
} // BLEValue
/**
* @brief Add a message part to the accumulation.
* The accumulation is a growing set of data that is added to until a commit or cancel.
* @param [in] part A message part being added.
*/
void BLEValue::addPart(std::string part) {
ESP_LOGD(LOG_TAG, ">> addPart: length=%d", part.length());
m_accumulation += part;
} // addPart
/**
* @brief Add a message part to the accumulation.
* The accumulation is a growing set of data that is added to until a commit or cancel.
* @param [in] pData A message part being added.
* @param [in] length The number of bytes being added.
*/
void BLEValue::addPart(uint8_t* pData, size_t length) {
ESP_LOGD(LOG_TAG, ">> addPart: length=%d", length);
m_accumulation += std::string((char *)pData, length);
} // addPart
/**
* @brief Cancel the current accumulation.
*/
void BLEValue::cancel() {
ESP_LOGD(LOG_TAG, ">> cancel");
m_accumulation = "";
m_readOffset = 0;
} // cancel
/**
* @brief Commit the current accumulation.
* When writing a value, we may find that we write it in "parts" meaning that the writes come in in pieces
* of the overall message. After the last part has been received, we may perform a commit which means that
* we now have the complete message and commit the change as a unit.
*/
void BLEValue::commit() {
ESP_LOGD(LOG_TAG, ">> commit");
// If there is nothing to commit, do nothing.
if (m_accumulation.length() == 0) {
return;
}
setValue(m_accumulation);
m_accumulation = "";
m_readOffset = 0;
} // commit
/**
* @brief Get a pointer to the data.
* @return A pointer to the data.
*/
uint8_t* BLEValue::getData() {
return (uint8_t*)m_value.data();
}
/**
* @brief Get the length of the data in bytes.
* @return The length of the data in bytes.
*/
size_t BLEValue::getLength() {
return m_value.length();
} // getLength
/**
* @brief Get the read offset.
* @return The read offset into the read.
*/
uint16_t BLEValue::getReadOffset() {
return m_readOffset;
} // getReadOffset
/**
* @brief Get the current value.
*/
std::string BLEValue::getValue() {
return m_value;
} // getValue
/**
* @brief Set the read offset
* @param [in] readOffset The offset into the read.
*/
void BLEValue::setReadOffset(uint16_t readOffset) {
m_readOffset = readOffset;
} // setReadOffset
/**
* @brief Set the current value.
*/
void BLEValue::setValue(std::string value) {
m_value = value;
} // setValue
/**
* @brief Set the current value.
* @param [in] pData The data for the current value.
* @param [in] The length of the new current value.
*/
void BLEValue::setValue(uint8_t* pData, size_t length) {
m_value = std::string((char*)pData, length);
} // setValue
#endif // CONFIG_BT_ENABLED
+38
View File
@@ -0,0 +1,38 @@
/*
* BLEValue.h
*
* Created on: Jul 17, 2017
* Author: kolban
*/
#ifndef COMPONENTS_CPP_UTILS_BLEVALUE_H_
#define COMPONENTS_CPP_UTILS_BLEVALUE_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <string>
/**
* @brief The model of a %BLE value.
*/
class BLEValue {
public:
BLEValue();
void addPart(std::string part);
void addPart(uint8_t* pData, size_t length);
void cancel();
void commit();
uint8_t* getData();
size_t getLength();
uint16_t getReadOffset();
std::string getValue();
void setReadOffset(uint16_t readOffset);
void setValue(std::string value);
void setValue(uint8_t* pData, size_t length);
private:
std::string m_accumulation;
uint16_t m_readOffset;
std::string m_value;
};
#endif // CONFIG_BT_ENABLED
#endif /* COMPONENTS_CPP_UTILS_BLEVALUE_H_ */
+262
View File
@@ -0,0 +1,262 @@
/*
* FreeRTOS.cpp
*
* Created on: Feb 24, 2017
* Author: kolban
*/
#include <freertos/FreeRTOS.h> // Include the base FreeRTOS definitions
#include <freertos/task.h> // Include the task definitions
#include <freertos/semphr.h> // Include the semaphore definitions
#include <string>
#include <sstream>
#include <iomanip>
#include "FreeRTOS.h"
#include <esp_log.h>
#include "sdkconfig.h"
static const char* LOG_TAG = "FreeRTOS";
/**
* Sleep for the specified number of milliseconds.
* @param[in] ms The period in milliseconds for which to sleep.
*/
void FreeRTOS::sleep(uint32_t ms) {
::vTaskDelay(ms/portTICK_PERIOD_MS);
} // sleep
/**
* Start a new task.
* @param[in] task The function pointer to the function to be run in the task.
* @param[in] taskName A string identifier for the task.
* @param[in] param An optional parameter to be passed to the started task.
* @param[in] stackSize An optional paremeter supplying the size of the stack in which to run the task.
*/
void FreeRTOS::startTask(void task(void*), std::string taskName, void *param, int stackSize) {
::xTaskCreate(task, taskName.data(), stackSize, param, 5, NULL);
} // startTask
/**
* Delete the task.
* @param[in] pTask An optional handle to the task to be deleted. If not supplied the calling task will be deleted.
*/
void FreeRTOS::deleteTask(TaskHandle_t pTask) {
::vTaskDelete(pTask);
} // deleteTask
/**
* Get the time in milliseconds since the %FreeRTOS scheduler started.
* @return The time in milliseconds since the %FreeRTOS scheduler started.
*/
uint32_t FreeRTOS::getTimeSinceStart() {
return (uint32_t)(xTaskGetTickCount()*portTICK_PERIOD_MS);
} // getTimeSinceStart
/*
* public:
Semaphore(std::string = "<Unknown>");
~Semaphore();
void give();
void take(std::string owner="<Unknown>");
void take(uint32_t timeoutMs, std::string owner="<Unknown>");
private:
SemaphoreHandle_t m_semaphore;
std::string m_name;
std::string m_owner;
};
*
*/
/**
* @brief Wait for a semaphore to be released by trying to take it and
* then releasing it again.
* @param [in] owner A debug tag.
* @return The value associated with the semaphore.
*/
uint32_t FreeRTOS::Semaphore::wait(std::string owner) {
ESP_LOGV(LOG_TAG, ">> wait: Semaphore waiting: %s for %s", toString().c_str(), owner.c_str());
if (m_usePthreads) {
pthread_mutex_lock(&m_pthread_mutex);
} else {
xSemaphoreTake(m_semaphore, portMAX_DELAY);
}
m_owner = owner;
if (m_usePthreads) {
pthread_mutex_unlock(&m_pthread_mutex);
} else {
xSemaphoreGive(m_semaphore);
}
ESP_LOGV(LOG_TAG, "<< wait: Semaphore released: %s", toString().c_str());
m_owner = std::string("<N/A>");
return m_value;
} // wait
FreeRTOS::Semaphore::Semaphore(std::string name) {
m_usePthreads = false; // Are we using pThreads or FreeRTOS?
if (m_usePthreads) {
pthread_mutex_init(&m_pthread_mutex, nullptr);
} else {
m_semaphore = xSemaphoreCreateMutex();
}
m_name = name;
m_owner = std::string("<N/A>");
m_value = 0;
}
FreeRTOS::Semaphore::~Semaphore() {
if (m_usePthreads) {
pthread_mutex_destroy(&m_pthread_mutex);
} else {
vSemaphoreDelete(m_semaphore);
}
}
/**
* @brief Give a semaphore.
* The Semaphore is given.
*/
void FreeRTOS::Semaphore::give() {
ESP_LOGV(LOG_TAG, "Semaphore giving: %s", toString().c_str());
if (m_usePthreads) {
pthread_mutex_unlock(&m_pthread_mutex);
} else {
xSemaphoreGive(m_semaphore);
}
#ifdef ARDUINO_ARCH_ESP32
FreeRTOS::sleep(10);
#endif
m_owner = std::string("<N/A>");
} // Semaphore::give
/**
* @brief Give a semaphore.
* The Semaphore is given with an associated value.
* @param [in] value The value to associate with the semaphore.
*/
void FreeRTOS::Semaphore::give(uint32_t value) {
m_value = value;
give();
} // give
/**
* @brief Give a semaphore from an ISR.
*/
void FreeRTOS::Semaphore::giveFromISR() {
BaseType_t higherPriorityTaskWoken;
if (m_usePthreads) {
assert(false);
} else {
xSemaphoreGiveFromISR(m_semaphore, &higherPriorityTaskWoken);
}
} // giveFromISR
/**
* @brief Take a semaphore.
* Take a semaphore and wait indefinitely.
*/
void FreeRTOS::Semaphore::take(std::string owner)
{
ESP_LOGD(LOG_TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str());
if (m_usePthreads) {
pthread_mutex_lock(&m_pthread_mutex);
} else {
xSemaphoreTake(m_semaphore, portMAX_DELAY);
}
m_owner = owner;
ESP_LOGD(LOG_TAG, "Semaphore taken: %s", toString().c_str());
} // Semaphore::take
/**
* @brief Take a semaphore.
* Take a semaphore but return if we haven't obtained it in the given period of milliseconds.
* @param [in] timeoutMs Timeout in milliseconds.
*/
void FreeRTOS::Semaphore::take(uint32_t timeoutMs, std::string owner) {
ESP_LOGV(LOG_TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str());
if (m_usePthreads) {
assert(false);
} else {
xSemaphoreTake(m_semaphore, timeoutMs/portTICK_PERIOD_MS);
}
m_owner = owner;
ESP_LOGV(LOG_TAG, "Semaphore taken: %s", toString().c_str());
} // Semaphore::take
std::string FreeRTOS::Semaphore::toString() {
std::stringstream stringStream;
stringStream << "name: "<< m_name << " (0x" << std::hex << std::setfill('0') << (uint32_t)m_semaphore << "), owner: " << m_owner;
return stringStream.str();
} // toString
void FreeRTOS::Semaphore::setName(std::string name) {
m_name = name;
} // setName
/**
* @brief Create a ring buffer.
* @param [in] length The amount of storage to allocate for the ring buffer.
* @param [in] type The type of buffer. One of RINGBUF_TYPE_NOSPLIT, RINGBUF_TYPE_ALLOWSPLIT, RINGBUF_TYPE_BYTEBUF.
*/
Ringbuffer::Ringbuffer(size_t length, ringbuf_type_t type) {
m_handle = ::xRingbufferCreate(length, type);
} // Ringbuffer
Ringbuffer::~Ringbuffer() {
::vRingbufferDelete(m_handle);
} // ~Ringbuffer
/**
* @brief Receive data from the buffer.
* @param [out] size On return, the size of data returned.
* @param [in] wait How long to wait.
* @return A pointer to the storage retrieved.
*/
void* Ringbuffer::receive(size_t* size, TickType_t wait) {
return ::xRingbufferReceive(m_handle, size, wait);
} // receive
/**
* @brief Return an item.
* @param [in] item The item to be returned/released.
*/
void Ringbuffer::returnItem(void* item) {
::vRingbufferReturnItem(m_handle, item);
} // returnItem
/**
* @brief Send data to the buffer.
* @param [in] data The data to place into the buffer.
* @param [in] length The length of data to place into the buffer.
* @param [in] wait How long to wait before giving up. The default is to wait indefinitely.
* @return
*/
uint32_t Ringbuffer::send(void* data, size_t length, TickType_t wait) {
return ::xRingbufferSend(m_handle, data, length, wait);
} // send
+70
View File
@@ -0,0 +1,70 @@
/*
* FreeRTOS.h
*
* Created on: Feb 24, 2017
* Author: kolban
*/
#ifndef MAIN_FREERTOS_H_
#define MAIN_FREERTOS_H_
#include <stdint.h>
#include <string>
#include <pthread.h>
#include <freertos/FreeRTOS.h> // Include the base FreeRTOS definitions.
#include <freertos/task.h> // Include the task definitions.
#include <freertos/semphr.h> // Include the semaphore definitions.
#include <freertos/ringbuf.h> // Include the ringbuffer definitions.
/**
* @brief Interface to %FreeRTOS functions.
*/
class FreeRTOS {
public:
static void sleep(uint32_t ms);
static void startTask(void task(void *), std::string taskName, void *param=nullptr, int stackSize = 2048);
static void deleteTask(TaskHandle_t pTask = nullptr);
static uint32_t getTimeSinceStart();
class Semaphore {
public:
Semaphore(std::string owner = "<Unknown>");
~Semaphore();
void give();
void give(uint32_t value);
void giveFromISR();
void setName(std::string name);
void take(std::string owner="<Unknown>");
void take(uint32_t timeoutMs, std::string owner="<Unknown>");
std::string toString();
uint32_t wait(std::string owner="<Unknown>");
private:
SemaphoreHandle_t m_semaphore;
pthread_mutex_t m_pthread_mutex;
std::string m_name;
std::string m_owner;
uint32_t m_value;
bool m_usePthreads;
};
};
/**
* @brief Ringbuffer.
*/
class Ringbuffer {
public:
Ringbuffer(size_t length, ringbuf_type_t type = RINGBUF_TYPE_NOSPLIT);
~Ringbuffer();
void* receive(size_t* size, TickType_t wait = portMAX_DELAY);
void returnItem(void* item);
uint32_t send(void* data, size_t length, TickType_t wait = portMAX_DELAY);
private:
RingbufHandle_t m_handle;
};
#endif /* MAIN_FREERTOS_H_ */
+406
View File
@@ -0,0 +1,406 @@
/*
* GeneralUtils.cpp
*
* Created on: May 20, 2017
* Author: kolban
*/
#include "GeneralUtils.h"
#include <esp_log.h>
#include <esp_system.h>
#include <string.h>
#include <stdio.h>
#include <string>
#include <sstream>
#include <iomanip>
#include <FreeRTOS.h>
#include <esp_err.h>
#include <nvs.h>
#include <esp_wifi.h>
static const char* LOG_TAG = "GeneralUtils";
static const char kBase64Alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
static int base64EncodedLength(size_t length) {
return (length + 2 - ((length + 2) % 3)) / 3 * 4;
} // base64EncodedLength
static int base64EncodedLength(const std::string &in) {
return base64EncodedLength(in.length());
} // base64EncodedLength
static void a3_to_a4(unsigned char * a4, unsigned char * a3) {
a4[0] = (a3[0] & 0xfc) >> 2;
a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4);
a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6);
a4[3] = (a3[2] & 0x3f);
} // a3_to_a4
static void a4_to_a3(unsigned char * a3, unsigned char * a4) {
a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4);
a3[1] = ((a4[1] & 0xf) << 4) + ((a4[2] & 0x3c) >> 2);
a3[2] = ((a4[2] & 0x3) << 6) + a4[3];
} // a4_to_a3
/**
* @brief Encode a string into base 64.
* @param [in] in
* @param [out] out
*/
bool GeneralUtils::base64Encode(const std::string &in, std::string *out) {
int i = 0, j = 0;
size_t enc_len = 0;
unsigned char a3[3];
unsigned char a4[4];
out->resize(base64EncodedLength(in));
int input_len = in.size();
std::string::const_iterator input = in.begin();
while (input_len--) {
a3[i++] = *(input++);
if (i == 3) {
a3_to_a4(a4, a3);
for (i = 0; i < 4; i++) {
(*out)[enc_len++] = kBase64Alphabet[a4[i]];
}
i = 0;
}
}
if (i) {
for (j = i; j < 3; j++) {
a3[j] = '\0';
}
a3_to_a4(a4, a3);
for (j = 0; j < i + 1; j++) {
(*out)[enc_len++] = kBase64Alphabet[a4[j]];
}
while ((i++ < 3)) {
(*out)[enc_len++] = '=';
}
}
return (enc_len == out->size());
} // base64Encode
/**
* @brief Does the string end with a specific character?
* @param [in] str The string to examine.
* @param [in] c The character to look form.
* @return True if the string ends with the given character.
*/
bool GeneralUtils::endsWith(std::string str, char c) {
if (str.empty()) {
return false;
}
if (str.at(str.length()-1) == c) {
return true;
}
return false;
} // endsWidth
static int DecodedLength(const std::string &in) {
int numEq = 0;
int n = in.size();
for (std::string::const_reverse_iterator it = in.rbegin(); *it == '='; ++it) {
++numEq;
}
return ((6 * n) / 8) - numEq;
} // DecodedLength
static unsigned char b64_lookup(unsigned char c) {
if(c >='A' && c <='Z') return c - 'A';
if(c >='a' && c <='z') return c - 71;
if(c >='0' && c <='9') return c + 4;
if(c == '+') return 62;
if(c == '/') return 63;
return 255;
}; // b64_lookup
/**
* @brief Decode a chunk of data that is base64 encoded.
* @param [in] in The string to be decoded.
* @param [out] out The resulting data.
*/
bool GeneralUtils::base64Decode(const std::string &in, std::string *out) {
int i = 0, j = 0;
size_t dec_len = 0;
unsigned char a3[3];
unsigned char a4[4];
int input_len = in.size();
std::string::const_iterator input = in.begin();
out->resize(DecodedLength(in));
while (input_len--) {
if (*input == '=') {
break;
}
a4[i++] = *(input++);
if (i == 4) {
for (i = 0; i <4; i++) {
a4[i] = b64_lookup(a4[i]);
}
a4_to_a3(a3,a4);
for (i = 0; i < 3; i++) {
(*out)[dec_len++] = a3[i];
}
i = 0;
}
}
if (i) {
for (j = i; j < 4; j++) {
a4[j] = '\0';
}
for (j = 0; j < 4; j++) {
a4[j] = b64_lookup(a4[j]);
}
a4_to_a3(a3,a4);
for (j = 0; j < i - 1; j++) {
(*out)[dec_len++] = a3[j];
}
}
return (dec_len == out->size());
} // base64Decode
/*
void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) {
uint32_t index=0;
std::stringstream ascii;
std::stringstream hex;
char asciiBuf[80];
char hexBuf[80];
hex.str("");
ascii.str("");
while(index < length) {
hex << std::setfill('0') << std::setw(2) << std::hex << (int)pData[index] << ' ';
if (std::isprint(pData[index])) {
ascii << pData[index];
} else {
ascii << '.';
}
index++;
if (index % 16 == 0) {
strcpy(hexBuf, hex.str().c_str());
strcpy(asciiBuf, ascii.str().c_str());
ESP_LOGD(tag, "%s %s", hexBuf, asciiBuf);
hex.str("");
ascii.str("");
}
}
if (index %16 != 0) {
while(index % 16 != 0) {
hex << " ";
index++;
}
strcpy(hexBuf, hex.str().c_str());
strcpy(asciiBuf, ascii.str().c_str());
ESP_LOGD(tag, "%s %s", hexBuf, asciiBuf);
//ESP_LOGD(tag, "%s %s", hex.str().c_str(), ascii.str().c_str());
}
FreeRTOS::sleep(1000);
}
*/
/*
void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) {
uint32_t index=0;
static std::stringstream ascii;
static std::stringstream hex;
hex.str("");
ascii.str("");
while(index < length) {
hex << std::setfill('0') << std::setw(2) << std::hex << (int)pData[index] << ' ';
if (std::isprint(pData[index])) {
ascii << pData[index];
} else {
ascii << '.';
}
index++;
if (index % 16 == 0) {
ESP_LOGD(tag, "%s %s", hex.str().c_str(), ascii.str().c_str());
hex.str("");
ascii.str("");
}
}
if (index %16 != 0) {
while(index % 16 != 0) {
hex << " ";
index++;
}
ESP_LOGD(tag, "%s %s", hex.str().c_str(), ascii.str().c_str());
}
FreeRTOS::sleep(1000);
}
*/
/**
* @brief Dump a representation of binary data to the console.
*
* @param [in] pData Pointer to the start of data to be logged.
* @param [in] length Length of the data (in bytes) to be logged.
* @return N/A.
*/
void GeneralUtils::hexDump(const uint8_t* pData, uint32_t length) {
char ascii[80];
char hex[80];
char tempBuf[80];
uint32_t lineNumber = 0;
ESP_LOGD(LOG_TAG, " 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ----------------");
strcpy(ascii, "");
strcpy(hex, "");
uint32_t index=0;
while(index < length) {
sprintf(tempBuf, "%.2x ", pData[index]);
strcat(hex, tempBuf);
if (isprint(pData[index])) {
sprintf(tempBuf, "%c", pData[index]);
} else {
sprintf(tempBuf, ".");
}
strcat(ascii, tempBuf);
index++;
if (index % 16 == 0) {
ESP_LOGD(LOG_TAG, "%.4x %s %s", lineNumber*16, hex, ascii);
strcpy(ascii, "");
strcpy(hex, "");
lineNumber++;
}
}
if (index %16 != 0) {
while(index % 16 != 0) {
strcat(hex, " ");
index++;
}
ESP_LOGD(LOG_TAG, "%.4x %s %s", lineNumber*16, hex, ascii);
}
} // hexDump
/**
* @brief Convert an IP address to string.
* @param ip The 4 byte IP address.
* @return A string representation of the IP address.
*/
std::string GeneralUtils::ipToString(uint8_t *ip) {
std::stringstream s;
s << (int)ip[0] << '.' << (int)ip[1] << '.' << (int)ip[2] << '.' << (int)ip[3];
return s.str();
} // ipToString
/**
* @brief Convert an ESP error code to a string.
* @param [in] errCode The errCode to be converted.
* @return A string representation of the error code.
*/
const char* GeneralUtils::errorToString(esp_err_t errCode) {
switch(errCode) {
case ESP_OK:
return "OK";
case ESP_FAIL:
return "Fail";
case ESP_ERR_NO_MEM:
return "No memory";
case ESP_ERR_INVALID_ARG:
return "Invalid argument";
case ESP_ERR_INVALID_SIZE:
return "Invalid state";
case ESP_ERR_INVALID_STATE:
return "Invalid state";
case ESP_ERR_NOT_FOUND:
return "Not found";
case ESP_ERR_NOT_SUPPORTED:
return "Not supported";
case ESP_ERR_TIMEOUT:
return "Timeout";
case ESP_ERR_NVS_NOT_INITIALIZED:
return "ESP_ERR_NVS_NOT_INITIALIZED";
case ESP_ERR_NVS_NOT_FOUND:
return "ESP_ERR_NVS_NOT_FOUND";
case ESP_ERR_NVS_TYPE_MISMATCH:
return "ESP_ERR_NVS_TYPE_MISMATCH";
case ESP_ERR_NVS_READ_ONLY:
return "ESP_ERR_NVS_READ_ONLY";
case ESP_ERR_NVS_NOT_ENOUGH_SPACE:
return "ESP_ERR_NVS_NOT_ENOUGH_SPACE";
case ESP_ERR_NVS_INVALID_NAME:
return "ESP_ERR_NVS_INVALID_NAME";
case ESP_ERR_NVS_INVALID_HANDLE:
return "ESP_ERR_NVS_INVALID_HANDLE";
case ESP_ERR_NVS_REMOVE_FAILED:
return "ESP_ERR_NVS_REMOVE_FAILED";
case ESP_ERR_NVS_KEY_TOO_LONG:
return "ESP_ERR_NVS_KEY_TOO_LONG";
case ESP_ERR_NVS_PAGE_FULL:
return "ESP_ERR_NVS_PAGE_FULL";
case ESP_ERR_NVS_INVALID_STATE:
return "ESP_ERR_NVS_INVALID_STATE";
case ESP_ERR_NVS_INVALID_LENGTH:
return "ESP_ERR_NVS_INVALID_LENGTH";
case ESP_ERR_WIFI_NOT_INIT:
return "ESP_ERR_WIFI_NOT_INIT";
//case ESP_ERR_WIFI_NOT_START:
// return "ESP_ERR_WIFI_NOT_START";
case ESP_ERR_WIFI_IF:
return "ESP_ERR_WIFI_IF";
case ESP_ERR_WIFI_MODE:
return "ESP_ERR_WIFI_MODE";
case ESP_ERR_WIFI_STATE:
return "ESP_ERR_WIFI_STATE";
case ESP_ERR_WIFI_CONN:
return "ESP_ERR_WIFI_CONN";
case ESP_ERR_WIFI_NVS:
return "ESP_ERR_WIFI_NVS";
case ESP_ERR_WIFI_MAC:
return "ESP_ERR_WIFI_MAC";
case ESP_ERR_WIFI_SSID:
return "ESP_ERR_WIFI_SSID";
case ESP_ERR_WIFI_PASSWORD:
return "ESP_ERR_WIFI_PASSWORD";
case ESP_ERR_WIFI_TIMEOUT:
return "ESP_ERR_WIFI_TIMEOUT";
case ESP_ERR_WIFI_WAKE_FAIL:
return "ESP_ERR_WIFI_WAKE_FAIL";
}
return "Unknown ESP_ERR error";
} // errorToString
/**
* @brief Restart the ESP32.
*/
void GeneralUtils::restart() {
esp_restart();
} // restart
+28
View File
@@ -0,0 +1,28 @@
/*
* GeneralUtils.h
*
* Created on: May 20, 2017
* Author: kolban
*/
#ifndef COMPONENTS_CPP_UTILS_GENERALUTILS_H_
#define COMPONENTS_CPP_UTILS_GENERALUTILS_H_
#include <stdint.h>
#include <string>
#include <esp_err.h>
/**
* @brief General utilities.
*/
class GeneralUtils {
public:
static bool base64Decode(const std::string& in, std::string* out);
static bool base64Encode(const std::string& in, std::string* out);
static bool endsWith(std::string str, char c);
static const char* errorToString(esp_err_t errCode);
static void hexDump(const uint8_t* pData, uint32_t length);
static std::string ipToString(uint8_t* ip);
static void restart();
};
#endif /* COMPONENTS_CPP_UTILS_GENERALUTILS_H_ */