[2.0.0] BtClassic Discovery with info without connect (#4811)

Hey guys,
so I wanted to do a BtClassic Discovery without the need to call connect
and to list all found devices on a display and continue work with that list.

I wasn't capable to test the example code with my file structure, but I did use the discovery already in some different situations.

However when I noted that the Bluedroid stack won't let me enforce an RfComm SPP connection to a GPS Device (Skytraxx 2 plus, I guess its interface is built so simple that it doesn't advertise its SPP over SDP), I will probably have to switch to BtStack (BlueKitchen) and stop on this side meanwhile
This commit is contained in:
Thomas M 2021-04-16 00:37:33 +02:00 committed by GitHub
parent 223acb3511
commit 41c372c143
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 609 additions and 6 deletions

View File

@ -45,6 +45,9 @@ set(LIBRARY_SRCS
libraries/ArduinoOTA/src/ArduinoOTA.cpp
libraries/AsyncUDP/src/AsyncUDP.cpp
libraries/BluetoothSerial/src/BluetoothSerial.cpp
libraries/BluetoothSerial/src/BTAddress.cpp
libraries/BluetoothSerial/src/BTAdvertisedDeviceSet.cpp
libraries/BluetoothSerial/src/BTScanResultsSet.cpp
libraries/DNSServer/src/DNSServer.cpp
libraries/EEPROM/src/EEPROM.cpp
libraries/ESPmDNS/src/ESPmDNS.cpp

View File

@ -0,0 +1,52 @@
#include <BluetoothSerial.h>
#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif
BluetoothSerial SerialBT;
#define BT_DISCOVER_TIME 10000
static bool btScanAsync = true;
static bool btScanSync = true;
void btAdvertisedDeviceFound(BTAdvertisedDevice* pDevice) {
Serial.printf("Found a device asynchronously: %s\n", pDevice->toString().c_str());
}
void setup() {
Serial.begin(115200);
SerialBT.begin("ESP32test"); //Bluetooth device name
Serial.println("The device started, now you can pair it with bluetooth!");
if (btScanAsync) {
Serial.print("Starting discoverAsync...");
if (SerialBT.discoverAsync(btAdvertisedDeviceFound)) {
Serial.println("Findings will be reported in \"btAdvertisedDeviceFound\"");
delay(10000);
Serial.print("Stopping discoverAsync... ");
SerialBT.discoverAsyncStop();
Serial.println("stopped");
} else {
Serial.println("Error on discoverAsync f.e. not workin after a \"connect\"");
}
}
if (btScanSync) {
Serial.println("Starting discover...");
BTScanResults *pResults = SerialBT.discover(BT_DISCOVER_TIME);
if (pResults)
pResults->dump(&Serial);
else
Serial.println("Error on BT Scan, no result!");
}
}
void loop() {
delay(100);
}

View File

@ -0,0 +1,96 @@
/*
* BTAddress.cpp
*
* Created on: Jul 2, 2017
* Author: kolban
* Ported on: Feb 5, 2021
* Author: Thomas M. (ArcticSnowSky)
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "BTAddress.h"
#include <string>
#include <sstream>
#include <iomanip>
#include <string.h>
#include <stdio.h>
#include <malloc.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.
*/
BTAddress::BTAddress(esp_bd_addr_t address) {
memcpy(m_address, address, ESP_BD_ADDR_LEN);
} // BTAddress
/**
* @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.
*/
BTAddress::BTAddress(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];
} // BTAddress
/**
* @brief Determine if this address equals another.
* @param [in] otherAddress The other address to compare against.
* @return True if the addresses are equal.
*/
bool BTAddress::equals(BTAddress 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 *BTAddress::getNative() {
return &m_address;
} // getNative
/**
* @brief Convert a BT 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 BTAddress::toString() {
auto size = 18;
char *res = (char*)malloc(size);
snprintf(res, size, "%02x:%02x:%02x:%02x:%02x:%02x", m_address[0], m_address[1], m_address[2], m_address[3], m_address[4], m_address[5]);
std::string ret(res);
free(res);
return ret;
} // toString
#endif

View File

@ -0,0 +1,36 @@
/*
* BTAddress.h
*
* Created on: Jul 2, 2017
* Author: kolban
* Ported on: Feb 5, 2021
* Author: Thomas M. (ArcticSnowSky)
*/
#ifndef COMPONENTS_CPP_UTILS_BTADDRESS_H_
#define COMPONENTS_CPP_UTILS_BTADDRESS_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <esp_gap_bt_api.h> // ESP32 BT
#include <string>
/**
* @brief A %BT device address.
*
* Every %BT device has a unique address which can be used to identify it and form connections.
*/
class BTAddress {
public:
BTAddress(esp_bd_addr_t address);
BTAddress(std::string stringAddress);
bool equals(BTAddress otherAddress);
esp_bd_addr_t* getNative();
std::string toString();
private:
esp_bd_addr_t m_address;
};
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_CPP_UTILS_BTADDRESS_H_ */

View File

@ -0,0 +1,65 @@
/*
* BTAdvertisedDevice.h
*
* Created on: Feb 5, 2021
* Author: Thomas M. (ArcticSnowSky)
*/
#ifndef __BTADVERTISEDDEVICE_H__
#define __BTADVERTISEDDEVICE_H__
#include "BTAddress.h"
class BTAdvertisedDevice {
public:
virtual ~BTAdvertisedDevice() = default;
virtual BTAddress getAddress();
virtual uint32_t getCOD();
virtual std::string getName();
virtual int8_t getRSSI();
virtual bool haveCOD();
virtual bool haveName();
virtual bool haveRSSI();
virtual std::string toString();
};
class BTAdvertisedDeviceSet : public virtual BTAdvertisedDevice {
public:
BTAdvertisedDeviceSet();
//~BTAdvertisedDeviceSet() = default;
BTAddress getAddress();
uint32_t getCOD();
std::string getName();
int8_t getRSSI();
bool haveCOD();
bool haveName();
bool haveRSSI();
std::string toString();
void setAddress(BTAddress address);
void setCOD(uint32_t cod);
void setName(std::string name);
void setRSSI(int8_t rssi);
bool m_haveCOD;
bool m_haveName;
bool m_haveRSSI;
BTAddress m_address = BTAddress((uint8_t*)"\0\0\0\0\0\0");
uint32_t m_cod;
std::string m_name;
int8_t m_rssi;
};
#endif

View File

@ -0,0 +1,78 @@
/*
* BTAdvertisedDeviceSet.cpp
*
* Created on: Feb 5, 2021
* Author: Thomas M. (ArcticSnowSky)
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
//#include <map>
#include "BTAdvertisedDevice.h"
//#include "BTScan.h"
BTAdvertisedDeviceSet::BTAdvertisedDeviceSet() {
m_cod = 0;
m_name = "";
m_rssi = 0;
m_haveCOD = false;
m_haveName = false;
m_haveRSSI = false;
} // BTAdvertisedDeviceSet
BTAddress BTAdvertisedDeviceSet::getAddress() { return m_address; }
uint32_t BTAdvertisedDeviceSet::getCOD() { return m_cod; }
std::string BTAdvertisedDeviceSet::getName() { return m_name; }
int8_t BTAdvertisedDeviceSet::getRSSI() { return m_rssi; }
bool BTAdvertisedDeviceSet::haveCOD() { return m_haveCOD; }
bool BTAdvertisedDeviceSet::haveName() { return m_haveName; }
bool BTAdvertisedDeviceSet::haveRSSI() { return m_haveRSSI; }
/**
* @brief Create a string representation of this device.
* @return A string representation of this device.
*/
std::string BTAdvertisedDeviceSet::toString() {
std::string res = "Name: " + getName() + ", Address: " + getAddress().toString();
if (haveCOD()) {
char val[6];
snprintf(val, sizeof(val), "%d", getCOD());
res += ", cod: ";
res += val;
}
if (haveRSSI()) {
char val[4];
snprintf(val, sizeof(val), "%d", getRSSI());
res += ", rssi: ";
res += val;
}
return res;
} // toString
void BTAdvertisedDeviceSet::setAddress(BTAddress address) {
m_address = address;
}
void BTAdvertisedDeviceSet::setCOD(uint32_t cod) {
m_cod = cod;
m_haveCOD = true;
}
void BTAdvertisedDeviceSet::setName(std::string name) {
m_name = name;
m_haveName = true;
}
void BTAdvertisedDeviceSet::setRSSI(int8_t rssi) {
m_rssi = rssi;
m_haveRSSI = true;
}
#endif /* CONFIG_BT_ENABLED */

View File

@ -0,0 +1,42 @@
/*
* BTScan.h
*
* Created on: Feb 5, 2021
* Author: Thomas M. (ArcticSnowSky)
*/
#ifndef __BTSCAN_H__
#define __BTSCAN_H__
#include <map>
#include <string>
#include <Print.h>
#include "BTAddress.h"
#include "BTAdvertisedDevice.h"
class BTAdvertisedDevice;
class BTAdvertisedDeviceSet;
class BTScanResults {
public:
virtual ~BTScanResults() = default;
virtual void dump(Print *print = nullptr);
virtual int getCount();
virtual BTAdvertisedDevice* getDevice(uint32_t i);
};
class BTScanResultsSet : public BTScanResults {
public:
void dump(Print *print = nullptr);
int getCount();
BTAdvertisedDevice* getDevice(uint32_t i);
bool add(BTAdvertisedDeviceSet advertisedDevice, bool unique = true);
void clear();
std::map<std::string, BTAdvertisedDeviceSet> m_vectorAdvertisedDevices;
};
#endif

View File

@ -0,0 +1,95 @@
/*
* BTScanResultsSet.cpp
*
* Created on: Feb 5, 2021
* Author: Thomas M. (ArcticSnowSky)
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <esp_err.h>
#include "BTAdvertisedDevice.h"
#include "BTScan.h"
//#include "GeneralUtils.h"
#include "esp32-hal-log.h"
class BTAdvertisedDevice;
/**
* @brief Dump the scan results to the log.
*/
void BTScanResultsSet::dump(Print *print) {
int cnt = getCount();
if (print == nullptr) {
log_v(">> Dump scan results : %d", cnt);
for (int i=0; i < cnt; i++) {
BTAdvertisedDevice* dev = getDevice(i);
if (dev)
log_d("- %d: %s\n", i+1, dev->toString().c_str());
else
log_d("- %d is null\n", i+1);
}
log_v("-- dump finished --");
} else {
print->printf(">> Dump scan results: %d\n", cnt);
for (int i=0; i < cnt; i++) {
BTAdvertisedDevice* dev = getDevice(i);
if (dev)
print->printf("- %d: %s\n", i+1, dev->toString().c_str());
else
print->printf("- %d is null\n", i+1);
}
print->println("-- Dump finished --");
}
} // dump
/**
* @brief Return the count of devices found in the last scan.
* @return The number of devices found in the last scan.
*/
int BTScanResultsSet::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.
*/
BTAdvertisedDevice* BTScanResultsSet::getDevice(uint32_t i) {
if (i < 0)
return nullptr;
uint32_t x = 0;
BTAdvertisedDeviceSet* pDev = &m_vectorAdvertisedDevices.begin()->second;
for (auto it = m_vectorAdvertisedDevices.begin(); it != m_vectorAdvertisedDevices.end(); it++) {
pDev = &it->second;
if (x==i) break;
x++;
}
return x==i ? pDev : nullptr;
}
void BTScanResultsSet::clear() {
//for(auto _dev : m_vectorAdvertisedDevices)
// delete _dev.second;
m_vectorAdvertisedDevices.clear();
}
bool BTScanResultsSet::add(BTAdvertisedDeviceSet advertisedDevice, bool unique) {
std::string key = advertisedDevice.getAddress().toString();
if (!unique || m_vectorAdvertisedDevices.count(key) == 0) {
m_vectorAdvertisedDevices.insert(std::pair<std::string, BTAdvertisedDeviceSet>(key, advertisedDevice));
return true;
} else
return false;
}
#endif

135
libraries/BluetoothSerial/src/BluetoothSerial.cpp Executable file → Normal file
View File

@ -52,6 +52,7 @@ static xQueueHandle _spp_tx_queue = NULL;
static SemaphoreHandle_t _spp_tx_done = NULL;
static TaskHandle_t _spp_task_handle = NULL;
static EventGroupHandle_t _spp_event_group = NULL;
static EventGroupHandle_t _bt_event_group = NULL;
static boolean secondConnectionAttempt;
static esp_spp_cb_t * custom_spp_callback = NULL;
static BluetoothSerialDataCb custom_data_callback = NULL;
@ -72,11 +73,18 @@ static int _pin_len;
static bool _isPinSet;
static bool _enableSSP;
static BTScanResultsSet scanResults;
static BTAdvertisedDeviceCb advertisedDeviceCb = nullptr;
#define SPP_RUNNING 0x01
#define SPP_CONNECTED 0x02
#define SPP_CONGESTED 0x04
#define SPP_DISCONNECTED 0x08
#define BT_DISCOVERY_RUNNING 0x01
#define BT_DISCOVERY_COMPLETED 0x02
typedef struct {
size_t len;
uint8_t data[];
@ -368,15 +376,16 @@ void BluetoothSerial::onData(BluetoothSerialDataCb cb){
static void esp_bt_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
{
switch(event){
case ESP_BT_GAP_DISC_RES_EVT:
case ESP_BT_GAP_DISC_RES_EVT: {
log_i("ESP_BT_GAP_DISC_RES_EVT");
#if (ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO)
char bda_str[18];
log_i("Scanned device: %s", bda2str(param->disc_res.bda, bda_str, 18));
#endif
BTAdvertisedDeviceSet advertisedDevice;
uint8_t peer_bdname_len = 0;
char peer_bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1];
for (int i = 0; i < param->disc_res.num_prop; i++) {
uint8_t peer_bdname_len;
char peer_bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1];
switch(param->disc_res.prop[i].type) {
case ESP_BT_GAP_DEV_PROP_EIR:
if (get_name_from_eir((uint8_t*)param->disc_res.prop[i].val, peer_bdname, &peer_bdname_len)) {
@ -409,10 +418,24 @@ static void esp_bt_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *pa
case ESP_BT_GAP_DEV_PROP_COD:
log_d("ESP_BT_GAP_DEV_PROP_COD");
if (param->disc_res.prop[i].len <= sizeof(int)) {
uint32_t cod = 0;
memcpy(&cod, param->disc_res.prop[i].val, param->disc_res.prop[i].len);
advertisedDevice.setCOD(cod);
} else {
log_d("Value size larger than integer");
}
break;
case ESP_BT_GAP_DEV_PROP_RSSI:
log_d("ESP_BT_GAP_DEV_PROP_RSSI");
if (param->disc_res.prop[i].len <= sizeof(int)) {
uint8_t rssi = 0;
memcpy(&rssi, param->disc_res.prop[i].val, param->disc_res.prop[i].len);
advertisedDevice.setRSSI(rssi);
} else {
log_d("Value size larger than integer");
}
break;
default:
@ -421,17 +444,33 @@ static void esp_bt_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *pa
if (_isRemoteAddressSet)
break;
}
break;
if (peer_bdname_len)
advertisedDevice.setName(peer_bdname);
esp_bd_addr_t addr;
memcpy(addr, param->disc_res.bda, ESP_BD_ADDR_LEN);
advertisedDevice.setAddress(BTAddress(addr));
if (scanResults.add(advertisedDevice) && advertisedDeviceCb)
advertisedDeviceCb(&advertisedDevice);
}
break;
case ESP_BT_GAP_DISC_STATE_CHANGED_EVT:
log_i("ESP_BT_GAP_DISC_STATE_CHANGED_EVT");
if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) {
xEventGroupClearBits(_bt_event_group, BT_DISCOVERY_RUNNING);
xEventGroupSetBits(_bt_event_group, BT_DISCOVERY_COMPLETED);
} else { // ESP_BT_GAP_DISCOVERY_STARTED
xEventGroupClearBits(_bt_event_group, BT_DISCOVERY_COMPLETED);
xEventGroupSetBits(_bt_event_group, BT_DISCOVERY_RUNNING);
}
break;
case ESP_BT_GAP_RMT_SRVCS_EVT:
log_i( "ESP_BT_GAP_RMT_SRVCS_EVT");
log_i( "ESP_BT_GAP_RMT_SRVCS_EVT: status = %d, num_uuids = %d", param->rmt_srvcs.stat, param->rmt_srvcs.num_uuids);
break;
case ESP_BT_GAP_RMT_SRVC_REC_EVT:
log_i("ESP_BT_GAP_RMT_SRVC_REC_EVT");
log_i("ESP_BT_GAP_RMT_SRVC_REC_EVT: status = %d", param->rmt_srvc_rec.stat);
break;
case ESP_BT_GAP_AUTH_CMPL_EVT:
@ -490,6 +529,14 @@ static void esp_bt_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *pa
static bool _init_bt(const char *deviceName)
{
if(!_bt_event_group){
_bt_event_group = xEventGroupCreate();
if(!_bt_event_group){
log_e("BT Event Group Create Failed!");
return false;
}
xEventGroupClearBits(_bt_event_group, 0xFFFFFF);
}
if(!_spp_event_group){
_spp_event_group = xEventGroupCreate();
if(!_spp_event_group){
@ -634,6 +681,10 @@ static bool _stop_bt()
vSemaphoreDelete(_spp_tx_done);
_spp_tx_done = NULL;
}
if (_bt_event_group) {
vEventGroupDelete(_bt_event_group);
_bt_event_group = NULL;
}
return true;
}
@ -642,6 +693,11 @@ static bool waitForConnect(int timeout) {
return (xEventGroupWaitBits(_spp_event_group, SPP_CONNECTED, pdFALSE, pdTRUE, xTicksToWait) & SPP_CONNECTED) != 0;
}
static bool waitForDiscovered(int timeout) {
TickType_t xTicksToWait = timeout / portTICK_PERIOD_MS;
return (xEventGroupWaitBits(_spp_event_group, BT_DISCOVERY_COMPLETED, pdFALSE, pdTRUE, xTicksToWait) & BT_DISCOVERY_COMPLETED) != 0;
}
/*
* Serial Bluetooth Arduino
*
@ -881,6 +937,73 @@ bool BluetoothSerial::isReady(bool checkMaster, int timeout) {
return (xEventGroupWaitBits(_spp_event_group, SPP_RUNNING, pdFALSE, pdTRUE, xTicksToWait) & SPP_RUNNING) != 0;
}
/**
* @brief RemoteName or address are not allowed to be set during discovery
* (otherwhise it might connect automatically and stop discovery)
* @param[in] timeoutMs can range from MIN_INQ_TIME to MAX_INQ_TIME
* @return in case of Error immediately Empty ScanResults.
*/
BTScanResults* BluetoothSerial::discover(int timeoutMs) {
scanResults.clear();
if (timeoutMs < MIN_INQ_TIME || timeoutMs > MAX_INQ_TIME || strlen(_remote_name) || _isRemoteAddressSet)
return nullptr;
int timeout = timeoutMs / INQ_TIME;
log_i("discover::disconnect");
disconnect();
log_i("discovering");
// will resolve name to address first - it may take a while
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
if (esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, timeout, 0) == ESP_OK) {
waitForDiscovered(timeoutMs);
esp_bt_gap_cancel_discovery();
}
return &scanResults;
}
/**
* @brief RemoteName or address are not allowed to be set during discovery
* (otherwhise it might connect automatically and stop discovery)
* @param[in] cb called when a [b]new[/b] device has been discovered
* @param[in] timeoutMs can be 0 or range from MIN_INQ_TIME to MAX_INQ_TIME
*
* @return Wheter start was successfull or problems with params
*/
bool BluetoothSerial::discoverAsync(BTAdvertisedDeviceCb cb, int timeoutMs) {
scanResults.clear();
if (strlen(_remote_name) || _isRemoteAddressSet)
return false;
int timeout = timeoutMs / INQ_TIME;
disconnect();
advertisedDeviceCb = cb;
log_i("discovering");
// will resolve name to address first - it may take a while
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
if (timeout > 0)
return esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, timeout, 0) == ESP_OK;
else return esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, ESP_BT_GAP_MAX_INQ_LEN, 0) == ESP_OK;
}
/** @brief Stops the asynchronous discovery and clears the callback */
void BluetoothSerial::discoverAsyncStop() {
esp_bt_gap_cancel_discovery();
advertisedDeviceCb = nullptr;
}
/** @brief Clears scanresult entries */
void BluetoothSerial::discoverClear() {
scanResults.clear();
}
/** @brief Can be used while discovering asynchronously
* Will be returned also on synchronous discovery.
*
* @return BTScanResults contains several information of found devices
*/
BTScanResults* BluetoothSerial::getScanResults() {
return &scanResults;
}
BluetoothSerial::operator bool() const
{
return true;

13
libraries/BluetoothSerial/src/BluetoothSerial.h Executable file → Normal file
View File

@ -21,12 +21,15 @@
#include "Arduino.h"
#include "Stream.h"
#include <esp_gap_bt_api.h>
#include <esp_spp_api.h>
#include <functional>
#include "BTScan.h"
typedef std::function<void(const uint8_t *buffer, size_t size)> BluetoothSerialDataCb;
typedef std::function<void(uint32_t num_val)> ConfirmRequestCb;
typedef std::function<void(boolean success)> AuthCompleteCb;
typedef std::function<void(BTAdvertisedDevice* pAdvertisedDevice)> BTAdvertisedDeviceCb;
class BluetoothSerial: public Stream
{
@ -64,6 +67,16 @@ class BluetoothSerial: public Stream
bool disconnect();
bool unpairDevice(uint8_t remoteAddress[]);
BTScanResults* discover(int timeout=0x30*1280);
bool discoverAsync(BTAdvertisedDeviceCb cb, int timeout=0x30*1280);
void discoverAsyncStop();
void discoverClear();
BTScanResults* getScanResults();
const int INQ_TIME = 1280; // Inquire Time unit 1280 ms
const int MIN_INQ_TIME = (ESP_BT_GAP_MIN_INQ_LEN * INQ_TIME);
const int MAX_INQ_TIME = (ESP_BT_GAP_MAX_INQ_LEN * INQ_TIME);
operator bool() const;
private:
String local_name;