From 2f43b3e0899c4d8708ca5d2a42116f36d669ba29 Mon Sep 17 00:00:00 2001 From: "torok.istvan" Date: Sun, 23 Nov 2025 09:16:12 +0100 Subject: [PATCH] Add bluetooth low energy mode. Change partitions table. --- gnss_partitions_4mb.csv | 7 + lib/BluetoothSerial/BTAddress.cpp | 88 ++ lib/BluetoothSerial/BTAddress.h | 27 + lib/BluetoothSerial/BTAdvertisedDevice.h | 58 + lib/BluetoothSerial/BTAdvertisedDeviceSet.cpp | 71 ++ lib/BluetoothSerial/BTScan.h | 35 + lib/BluetoothSerial/BTScanResultsSet.cpp | 88 ++ lib/BluetoothSerial/BluetoothSerial.cpp | 1004 +++++++++++++++++ lib/BluetoothSerial/BluetoothSerial.h | 75 ++ platformio.ini | 5 + src/bluetoothSelect.h | 175 +++ src/main.cpp | 16 +- src/mybluetooth.h | 76 -- src/settings.h | 5 +- 14 files changed, 1644 insertions(+), 86 deletions(-) create mode 100644 gnss_partitions_4mb.csv create mode 100644 lib/BluetoothSerial/BTAddress.cpp create mode 100644 lib/BluetoothSerial/BTAddress.h create mode 100644 lib/BluetoothSerial/BTAdvertisedDevice.h create mode 100644 lib/BluetoothSerial/BTAdvertisedDeviceSet.cpp create mode 100644 lib/BluetoothSerial/BTScan.h create mode 100644 lib/BluetoothSerial/BTScanResultsSet.cpp create mode 100644 lib/BluetoothSerial/BluetoothSerial.cpp create mode 100644 lib/BluetoothSerial/BluetoothSerial.h create mode 100644 src/bluetoothSelect.h delete mode 100644 src/mybluetooth.h diff --git a/gnss_partitions_4mb.csv b/gnss_partitions_4mb.csv new file mode 100644 index 0000000..7e41936 --- /dev/null +++ b/gnss_partitions_4mb.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x180000, +app1, app, ota_1, 0x190000,0x100000, +spiffs, data, spiffs, 0x290000,0x160000, +coredump, data, coredump,0x3F0000,0x10000, \ No newline at end of file diff --git a/lib/BluetoothSerial/BTAddress.cpp b/lib/BluetoothSerial/BTAddress.cpp new file mode 100644 index 0000000..417cf42 --- /dev/null +++ b/lib/BluetoothSerial/BTAddress.cpp @@ -0,0 +1,88 @@ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BLUEDROID_ENABLED) + +#include "BTAddress.h" +#include +#include +#include +#include +#include +#include +#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 \ No newline at end of file diff --git a/lib/BluetoothSerial/BTAddress.h b/lib/BluetoothSerial/BTAddress.h new file mode 100644 index 0000000..86301ec --- /dev/null +++ b/lib/BluetoothSerial/BTAddress.h @@ -0,0 +1,27 @@ +#ifndef COMPONENTS_CPP_UTILS_BTADDRESS_H_ +#define COMPONENTS_CPP_UTILS_BTADDRESS_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BLUEDROID_ENABLED) +#include // ESP32 BT +#include + + +/** + * @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_ */ \ No newline at end of file diff --git a/lib/BluetoothSerial/BTAdvertisedDevice.h b/lib/BluetoothSerial/BTAdvertisedDevice.h new file mode 100644 index 0000000..779c770 --- /dev/null +++ b/lib/BluetoothSerial/BTAdvertisedDevice.h @@ -0,0 +1,58 @@ +#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 \ No newline at end of file diff --git a/lib/BluetoothSerial/BTAdvertisedDeviceSet.cpp b/lib/BluetoothSerial/BTAdvertisedDeviceSet.cpp new file mode 100644 index 0000000..5760367 --- /dev/null +++ b/lib/BluetoothSerial/BTAdvertisedDeviceSet.cpp @@ -0,0 +1,71 @@ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BLUEDROID_ENABLED) + +//#include + +#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[6]; + snprintf(val, sizeof(val), "%d", (int8_t)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 */ \ No newline at end of file diff --git a/lib/BluetoothSerial/BTScan.h b/lib/BluetoothSerial/BTScan.h new file mode 100644 index 0000000..cc15c8c --- /dev/null +++ b/lib/BluetoothSerial/BTScan.h @@ -0,0 +1,35 @@ +#ifndef __BTSCAN_H__ +#define __BTSCAN_H__ + +#include +#include +#include +#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 m_vectorAdvertisedDevices; +}; + +#endif \ No newline at end of file diff --git a/lib/BluetoothSerial/BTScanResultsSet.cpp b/lib/BluetoothSerial/BTScanResultsSet.cpp new file mode 100644 index 0000000..6e42ab6 --- /dev/null +++ b/lib/BluetoothSerial/BTScanResultsSet.cpp @@ -0,0 +1,88 @@ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BLUEDROID_ENABLED) + + +#include + +#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(key, advertisedDevice)); + return true; + } else + return false; +} + +#endif \ No newline at end of file diff --git a/lib/BluetoothSerial/BluetoothSerial.cpp b/lib/BluetoothSerial/BluetoothSerial.cpp new file mode 100644 index 0000000..24d83be --- /dev/null +++ b/lib/BluetoothSerial/BluetoothSerial.cpp @@ -0,0 +1,1004 @@ +#include "sdkconfig.h" +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + + +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BLUEDROID_ENABLED) + +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif + +#include "BluetoothSerial.h" + +#include "esp_bt.h" +#include "esp_bt_main.h" +#include "esp_gap_bt_api.h" +#include "esp_bt_device.h" +#include "esp_spp_api.h" +#include + +#include "esp32-hal-log.h" + +const char * _spp_server_name = "ESP32SPP"; + +//Now passed in during begin() +//#define RX_QUEUE_SIZE 512 //Original +//#define TX_QUEUE_SIZE 32 + +#define SPP_TX_QUEUE_TIMEOUT 1000 +#define SPP_TX_DONE_TIMEOUT 1000 +#define SPP_CONGESTED_TIMEOUT 1000 + +static uint32_t _spp_client = 0; +static xQueueHandle _spp_rx_queue = NULL; +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; +static esp_bd_addr_t current_bd_addr; +static ConfirmRequestCb confirm_request_callback = NULL; +static AuthCompleteCb auth_complete_callback = NULL; + +#define INQ_LEN 0x10 +#define INQ_NUM_RSPS 20 +#define READY_TIMEOUT (10 * 1000) +#define SCAN_TIMEOUT (INQ_LEN * 2 * 1000) +static esp_bd_addr_t _peer_bd_addr; +static char _remote_name[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; +static bool _isRemoteAddressSet; +static bool _isMaster; +static esp_bt_pin_code_t _pin_code; +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[]; +} spp_packet_t; + +#if (ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO) +static char *bda2str(esp_bd_addr_t bda, char *str, size_t size) +{ + if (bda == NULL || str == NULL || size < 18) { + return NULL; + } + + uint8_t *p = bda; + sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x", + p[0], p[1], p[2], p[3], p[4], p[5]); + return str; +} +#endif + +static bool get_name_from_eir(uint8_t *eir, char *bdname, uint8_t *bdname_len) +{ + if (!eir || !bdname || !bdname_len) { + return false; + } + + uint8_t *rmt_bdname, rmt_bdname_len; + *bdname = *bdname_len = rmt_bdname_len = 0; + + rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_CMPL_LOCAL_NAME, &rmt_bdname_len); + if (!rmt_bdname) { + rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_SHORT_LOCAL_NAME, &rmt_bdname_len); + } + if (rmt_bdname) { + rmt_bdname_len = rmt_bdname_len > ESP_BT_GAP_MAX_BDNAME_LEN ? ESP_BT_GAP_MAX_BDNAME_LEN : rmt_bdname_len; + memcpy(bdname, rmt_bdname, rmt_bdname_len); + bdname[rmt_bdname_len] = 0; + *bdname_len = rmt_bdname_len; + return true; + } + return false; +} + +static bool btSetPin() { + esp_bt_pin_type_t pin_type; + if (_isPinSet) { + if (_pin_len) { + log_i("pin set"); + pin_type = ESP_BT_PIN_TYPE_FIXED; + } else { + _isPinSet = false; + log_i("pin reset"); + pin_type = ESP_BT_PIN_TYPE_VARIABLE; // pin_code would be ignored (default) + } + return (esp_bt_gap_set_pin(pin_type, _pin_len, _pin_code) == ESP_OK); + } + return false; +} + +static esp_err_t _spp_queue_packet(uint8_t *data, size_t len){ + if(!data || !len){ + log_w("No data provided"); + return ESP_OK; + } + spp_packet_t * packet = (spp_packet_t*)malloc(sizeof(spp_packet_t) + len); + if(!packet){ + log_e("SPP TX Packet Malloc Failed!"); + return ESP_FAIL; + } + packet->len = len; + memcpy(packet->data, data, len); + if (!_spp_tx_queue || xQueueSend(_spp_tx_queue, &packet, SPP_TX_QUEUE_TIMEOUT) != pdPASS) { + log_e("SPP TX Queue Send Failed!"); + free(packet); + return ESP_FAIL; + } + return ESP_OK; +} + +const uint16_t SPP_TX_MAX = 330; +static uint8_t _spp_tx_buffer[SPP_TX_MAX]; +static uint16_t _spp_tx_buffer_len = 0; + +static bool _spp_send_buffer(){ + if((xEventGroupWaitBits(_spp_event_group, SPP_CONGESTED, pdFALSE, pdTRUE, SPP_CONGESTED_TIMEOUT) & SPP_CONGESTED) != 0){ + if(!_spp_client){ + log_v("SPP Client Gone!"); + return false; + } + log_v("SPP Write %u", _spp_tx_buffer_len); + esp_err_t err = esp_spp_write(_spp_client, _spp_tx_buffer_len, _spp_tx_buffer); + if(err != ESP_OK){ + log_e("SPP Write Failed! [0x%X]", err); + return false; + } + _spp_tx_buffer_len = 0; + if(xSemaphoreTake(_spp_tx_done, SPP_TX_DONE_TIMEOUT) != pdTRUE){ + log_e("SPP Ack Failed!"); + return false; + } + return true; + } + log_e("SPP Write Congested!"); + return false; +} + +static void _spp_tx_task(void * arg){ + spp_packet_t *packet = NULL; + size_t len = 0, to_send = 0; + uint8_t * data = NULL; + for (;;) { + if(_spp_tx_queue && xQueueReceive(_spp_tx_queue, &packet, portMAX_DELAY) == pdTRUE && packet){ + if(packet->len <= (SPP_TX_MAX - _spp_tx_buffer_len)){ + memcpy(_spp_tx_buffer+_spp_tx_buffer_len, packet->data, packet->len); + _spp_tx_buffer_len+=packet->len; + free(packet); + packet = NULL; + if(SPP_TX_MAX == _spp_tx_buffer_len || uxQueueMessagesWaiting(_spp_tx_queue) == 0){ + _spp_send_buffer(); + } + } else { + len = packet->len; + data = packet->data; + to_send = SPP_TX_MAX - _spp_tx_buffer_len; + memcpy(_spp_tx_buffer+_spp_tx_buffer_len, data, to_send); + _spp_tx_buffer_len = SPP_TX_MAX; + data += to_send; + len -= to_send; + if(!_spp_send_buffer()){ + len = 0; + } + while(len >= SPP_TX_MAX){ + memcpy(_spp_tx_buffer, data, SPP_TX_MAX); + _spp_tx_buffer_len = SPP_TX_MAX; + data += SPP_TX_MAX; + len -= SPP_TX_MAX; + if(!_spp_send_buffer()){ + len = 0; + break; + } + } + if(len){ + memcpy(_spp_tx_buffer, data, len); + _spp_tx_buffer_len += len; + if(uxQueueMessagesWaiting(_spp_tx_queue) == 0){ + _spp_send_buffer(); + } + } + free(packet); + packet = NULL; + } + } else { + log_e("Something went horribly wrong"); + } + } + vTaskDelete(NULL); + _spp_task_handle = NULL; +} + +static void esp_spp_cb(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) +{ + switch (event) + { + case ESP_SPP_INIT_EVT: + log_i("ESP_SPP_INIT_EVT"); +#ifdef ESP_IDF_VERSION_MAJOR + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); +#else + esp_bt_gap_set_scan_mode(ESP_BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE); +#endif + if (!_isMaster) { + log_i("ESP_SPP_INIT_EVT: slave: start"); + esp_spp_start_srv(ESP_SPP_SEC_NONE, ESP_SPP_ROLE_SLAVE, 0, _spp_server_name); + } + xEventGroupSetBits(_spp_event_group, SPP_RUNNING); + break; + + case ESP_SPP_SRV_OPEN_EVT://Server connection open + if (param->srv_open.status == ESP_SPP_SUCCESS) { + log_i("ESP_SPP_SRV_OPEN_EVT: %u", _spp_client); + if (!_spp_client){ + _spp_client = param->srv_open.handle; + _spp_tx_buffer_len = 0; + } else { + secondConnectionAttempt = true; + esp_spp_disconnect(param->srv_open.handle); + } + xEventGroupClearBits(_spp_event_group, SPP_DISCONNECTED); + xEventGroupSetBits(_spp_event_group, SPP_CONNECTED); + } else { + log_e("ESP_SPP_SRV_OPEN_EVT Failed!, status:%d", param->srv_open.status); + } + break; + + case ESP_SPP_CLOSE_EVT://Client connection closed + if ((param->close.async == false && param->close.status == ESP_SPP_SUCCESS) || param->close.async) { + log_i("ESP_SPP_CLOSE_EVT: %u", secondConnectionAttempt); + if(secondConnectionAttempt) { + secondConnectionAttempt = false; + } else { + _spp_client = 0; + xEventGroupSetBits(_spp_event_group, SPP_DISCONNECTED); + xEventGroupSetBits(_spp_event_group, SPP_CONGESTED); + } + xEventGroupClearBits(_spp_event_group, SPP_CONNECTED); + } else { + log_e("ESP_SPP_CLOSE_EVT failed!, status:%d", param->close.status); + } + break; + + case ESP_SPP_CONG_EVT://connection congestion status changed + if(param->cong.cong){ + xEventGroupClearBits(_spp_event_group, SPP_CONGESTED); + } else { + xEventGroupSetBits(_spp_event_group, SPP_CONGESTED); + } + log_v("ESP_SPP_CONG_EVT: %s", param->cong.cong?"CONGESTED":"FREE"); + break; + + case ESP_SPP_WRITE_EVT://write operation completed + if (param->write.status == ESP_SPP_SUCCESS) { + if(param->write.cong){ + xEventGroupClearBits(_spp_event_group, SPP_CONGESTED); + } + log_v("ESP_SPP_WRITE_EVT: %u %s", param->write.len, param->write.cong?"CONGESTED":""); + } else { + log_e("ESP_SPP_WRITE_EVT failed!, status:%d", param->write.status); + } + xSemaphoreGive(_spp_tx_done);//we can try to send another packet + break; + + case ESP_SPP_DATA_IND_EVT://connection received data + log_v("ESP_SPP_DATA_IND_EVT len=%d handle=%d", param->data_ind.len, param->data_ind.handle); + //esp_log_buffer_hex("",param->data_ind.data,param->data_ind.len); //for low level debug + //ets_printf("r:%u\n", param->data_ind.len); + + if(custom_data_callback){ + custom_data_callback(param->data_ind.data, param->data_ind.len); + } else if (_spp_rx_queue != NULL){ + for (int i = 0; i < param->data_ind.len; i++){ + if(xQueueSend(_spp_rx_queue, param->data_ind.data + i, (TickType_t)0) != pdTRUE){ + Serial.printf("Bluetooth RX buffer full! Discarding %u bytes. Consider increasing SPP RX buffer size.\r\n", param->data_ind.len - i); + break; + } + } + } + break; + + case ESP_SPP_DISCOVERY_COMP_EVT://discovery complete + log_i("ESP_SPP_DISCOVERY_COMP_EVT"); + if (param->disc_comp.status == ESP_SPP_SUCCESS) { + log_i("ESP_SPP_DISCOVERY_COMP_EVT: spp connect to remote"); + esp_spp_connect(ESP_SPP_SEC_AUTHENTICATE, ESP_SPP_ROLE_MASTER, param->disc_comp.scn[0], _peer_bd_addr); + } else { + log_e("ESP_SPP_DISCOVERY_COMP_EVT failed!, status:%d", param->disc_comp.status); + } + break; + + case ESP_SPP_OPEN_EVT://Client connection open + log_i("ESP_SPP_OPEN_EVT"); + if (!_spp_client){ + _spp_client = param->open.handle; + } else { + secondConnectionAttempt = true; + esp_spp_disconnect(param->open.handle); + } + xEventGroupClearBits(_spp_event_group, SPP_DISCONNECTED); + xEventGroupSetBits(_spp_event_group, SPP_CONNECTED); + xEventGroupSetBits(_spp_event_group, SPP_CONGESTED); + break; + + case ESP_SPP_START_EVT://server started + log_i("ESP_SPP_START_EVT"); + break; + + case ESP_SPP_CL_INIT_EVT://client initiated a connection + log_i("ESP_SPP_CL_INIT_EVT"); + break; + + default: + break; + } + if(custom_spp_callback)(*custom_spp_callback)(event, param); +} + +void BluetoothSerial::onData(BluetoothSerialDataCb cb){ + custom_data_callback = 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: { + 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++) { + 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)) { + log_i("ESP_BT_GAP_DISC_RES_EVT : EIR : %s : %d", peer_bdname, peer_bdname_len); + if (strlen(_remote_name) == peer_bdname_len + && strncmp(peer_bdname, _remote_name, peer_bdname_len) == 0) { + log_v("ESP_BT_GAP_DISC_RES_EVT : SPP_START_DISCOVERY_EIR : %s", peer_bdname, peer_bdname_len); + _isRemoteAddressSet = true; + memcpy(_peer_bd_addr, param->disc_res.bda, ESP_BD_ADDR_LEN); + esp_bt_gap_cancel_discovery(); + esp_spp_start_discovery(_peer_bd_addr); + } + } + break; + + case ESP_BT_GAP_DEV_PROP_BDNAME: + peer_bdname_len = param->disc_res.prop[i].len; + memcpy(peer_bdname, param->disc_res.prop[i].val, peer_bdname_len); + peer_bdname_len--; // len includes 0 terminator + log_v("ESP_BT_GAP_DISC_RES_EVT : BDNAME : %s : %d", peer_bdname, peer_bdname_len); + if (strlen(_remote_name) == peer_bdname_len + && strncmp(peer_bdname, _remote_name, peer_bdname_len) == 0) { + log_i("ESP_BT_GAP_DISC_RES_EVT : SPP_START_DISCOVERY_BDNAME : %s", peer_bdname); + _isRemoteAddressSet = true; + memcpy(_peer_bd_addr, param->disc_res.bda, ESP_BD_ADDR_LEN); + esp_bt_gap_cancel_discovery(); + esp_spp_start_discovery(_peer_bd_addr); + } + break; + + 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: + break; + } + if (_isRemoteAddressSet) + 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: 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: status = %d", param->rmt_srvc_rec.stat); + break; + + case ESP_BT_GAP_AUTH_CMPL_EVT: + if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) { + log_v("authentication success: %s", param->auth_cmpl.device_name); + if (auth_complete_callback) { + auth_complete_callback(true); + } + } else { + log_e("authentication failed, status:%d", param->auth_cmpl.stat); + if (auth_complete_callback) { + auth_complete_callback(false); + } + } + break; + + case ESP_BT_GAP_PIN_REQ_EVT: + // default pairing pins + log_i("ESP_BT_GAP_PIN_REQ_EVT min_16_digit:%d", param->pin_req.min_16_digit); + if (param->pin_req.min_16_digit) { + log_i("Input pin code: 0000 0000 0000 0000"); + esp_bt_pin_code_t pin_code; + memset(pin_code, '0', ESP_BT_PIN_CODE_LEN); + esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin_code); + } else { + log_i("Input pin code: 1234"); + esp_bt_pin_code_t pin_code; + memcpy(pin_code, "1234", 4); + esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin_code); + } + break; + + case ESP_BT_GAP_CFM_REQ_EVT: + log_i("ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d", param->cfm_req.num_val); + if (confirm_request_callback) { + memcpy(current_bd_addr, param->cfm_req.bda, sizeof(esp_bd_addr_t)); + confirm_request_callback(param->cfm_req.num_val); + } + else { + esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true); + } + break; + + case ESP_BT_GAP_KEY_NOTIF_EVT: + log_i("ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey); + break; + + case ESP_BT_GAP_KEY_REQ_EVT: + log_i("ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!"); + break; + + default: + break; + } +} + +//static bool _init_bt(const char *deviceName) +static bool _init_bt(const char *deviceName, uint16_t rxQueueSize, uint16_t txQueueSize) +{ + 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){ + log_e("SPP Event Group Create Failed!"); + return false; + } + xEventGroupClearBits(_spp_event_group, 0xFFFFFF); + xEventGroupSetBits(_spp_event_group, SPP_CONGESTED); + xEventGroupSetBits(_spp_event_group, SPP_DISCONNECTED); + } + if (_spp_rx_queue == NULL){ + //_spp_rx_queue = xQueueCreate(RX_QUEUE_SIZE, sizeof(uint8_t)); //initialize the queue + _spp_rx_queue = xQueueCreate(rxQueueSize, sizeof(uint8_t)); //initialize the queue + if (_spp_rx_queue == NULL){ + log_e("RX Queue Create Failed"); + return false; + } + } + if (_spp_tx_queue == NULL){ + //_spp_tx_queue = xQueueCreate(TX_QUEUE_SIZE, sizeof(spp_packet_t*)); //initialize the queue + _spp_tx_queue = xQueueCreate(txQueueSize, sizeof(spp_packet_t*)); //initialize the queue + if (_spp_tx_queue == NULL){ + log_e("TX Queue Create Failed"); + return false; + } + } + if(_spp_tx_done == NULL){ + _spp_tx_done = xSemaphoreCreateBinary(); + if (_spp_tx_done == NULL){ + log_e("TX Semaphore Create Failed"); + return false; + } + xSemaphoreTake(_spp_tx_done, 0); + } + + if(!_spp_task_handle){ + xTaskCreatePinnedToCore(_spp_tx_task, "spp_tx", 4096, NULL, 10, &_spp_task_handle, 0); + if(!_spp_task_handle){ + log_e("Network Event Task Start Failed!"); + return false; + } + } + + if (!btStarted() && !btStart()){ + log_e("initialize controller failed"); + return false; + } + + esp_bluedroid_status_t bt_state = esp_bluedroid_get_status(); + if (bt_state == ESP_BLUEDROID_STATUS_UNINITIALIZED){ + if (esp_bluedroid_init()) { + log_e("initialize bluedroid failed"); + return false; + } + } + + if (bt_state != ESP_BLUEDROID_STATUS_ENABLED){ + if (esp_bluedroid_enable()) { + log_e("enable bluedroid failed"); + return false; + } + } + + // Why only master need this? Slave need this during pairing as well +// if (_isMaster && esp_bt_gap_register_callback(esp_bt_gap_cb) != ESP_OK) { + if (esp_bt_gap_register_callback(esp_bt_gap_cb) != ESP_OK) { + log_e("gap register failed"); + return false; + } + + if (esp_spp_register_callback(esp_spp_cb) != ESP_OK){ + log_e("spp register failed"); + return false; + } + + if (esp_spp_init(ESP_SPP_MODE_CB) != ESP_OK){ + log_e("spp init failed"); + return false; + } + + // if (esp_bt_sleep_disable() != ESP_OK){ + // log_e("esp_bt_sleep_disable failed"); + // } + + log_i("device name set"); + esp_bt_dev_set_device_name(deviceName); + + if (_isPinSet) { + btSetPin(); + } + + if (_enableSSP) { + log_i("Simple Secure Pairing"); + esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE; + esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO; + esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t)); + } + + // the default BTA_DM_COD_LOUDSPEAKER does not work with the macOS BT stack + esp_bt_cod_t cod; + cod.major = 0b00001; + cod.minor = 0b000100; + cod.service = 0b00000010110; + if (esp_bt_gap_set_cod(cod, ESP_BT_INIT_COD) != ESP_OK) { + log_e("set cod failed"); + return false; + } + return true; +} + +static bool _stop_bt() +{ + if (btStarted()){ + if(_spp_client) + esp_spp_disconnect(_spp_client); + esp_spp_deinit(); + esp_bluedroid_disable(); + esp_bluedroid_deinit(); + btStop(); + } + _spp_client = 0; + if(_spp_task_handle){ + vTaskDelete(_spp_task_handle); + _spp_task_handle = NULL; + } + if(_spp_event_group){ + vEventGroupDelete(_spp_event_group); + _spp_event_group = NULL; + } + if(_spp_rx_queue){ + vQueueDelete(_spp_rx_queue); + //ToDo: clear RX queue when in packet mode + _spp_rx_queue = NULL; + } + if(_spp_tx_queue){ + spp_packet_t *packet = NULL; + while(xQueueReceive(_spp_tx_queue, &packet, 0) == pdTRUE){ + free(packet); + } + vQueueDelete(_spp_tx_queue); + _spp_tx_queue = NULL; + } + if (_spp_tx_done) { + vSemaphoreDelete(_spp_tx_done); + _spp_tx_done = NULL; + } + if (_bt_event_group) { + vEventGroupDelete(_bt_event_group); + _bt_event_group = NULL; + } + return true; +} + +static bool waitForConnect(int timeout) { + TickType_t xTicksToWait = timeout / portTICK_PERIOD_MS; + 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 + * + * */ + +BluetoothSerial::BluetoothSerial() +{ + local_name = "ESP32"; //default bluetooth name +} + +BluetoothSerial::~BluetoothSerial(void) +{ + _stop_bt(); +} + +//bool BluetoothSerial::begin(String localName, bool isMaster) +bool BluetoothSerial::begin(String localName, bool isMaster, uint16_t rxQueueSize, uint16_t txQueueSize) +{ + _isMaster = isMaster; + if (localName.length()){ + local_name = localName; + } + //return _init_bt(local_name.c_str()); + return _init_bt(local_name.c_str(), rxQueueSize, txQueueSize); +} + +int BluetoothSerial::available(void) +{ + if (_spp_rx_queue == NULL){ + return 0; + } + return uxQueueMessagesWaiting(_spp_rx_queue); +} + +int BluetoothSerial::peek(void) +{ + uint8_t c; + if (_spp_rx_queue && xQueuePeek(_spp_rx_queue, &c, 0)){ + return c; + } + return -1; +} + +bool BluetoothSerial::hasClient(void) +{ + return _spp_client > 0; +} + +int BluetoothSerial::read(void) +{ + + uint8_t c = 0; + if (_spp_rx_queue && xQueueReceive(_spp_rx_queue, &c, 0)){ + return c; + } + return -1; +} + +size_t BluetoothSerial::write(uint8_t c) +{ + return write(&c, 1); +} + +size_t BluetoothSerial::write(const uint8_t *buffer, size_t size) +{ + if (!_spp_client){ + return 0; + } + return (_spp_queue_packet((uint8_t *)buffer, size) == ESP_OK) ? size : 0; +} + +void BluetoothSerial::flush() +{ + if (_spp_tx_queue != NULL){ + while(uxQueueMessagesWaiting(_spp_tx_queue) > 0){ + delay(100); + } + } +} + +void BluetoothSerial::end() +{ + _stop_bt(); +} + +void BluetoothSerial::onConfirmRequest(ConfirmRequestCb cb) +{ + confirm_request_callback = cb; +} + +void BluetoothSerial::onAuthComplete(AuthCompleteCb cb) +{ + auth_complete_callback = cb; +} + +void BluetoothSerial::confirmReply(boolean confirm) +{ + esp_bt_gap_ssp_confirm_reply(current_bd_addr, confirm); +} + + +esp_err_t BluetoothSerial::register_callback(esp_spp_cb_t callback) +{ + custom_spp_callback = callback; + return ESP_OK; +} + +//Simple Secure Pairing +void BluetoothSerial::enableSSP() { + _enableSSP = true; +} +/* + * Set default parameters for Legacy Pairing + * Use fixed pin code +*/ +bool BluetoothSerial::setPin(const char *pin) { + bool isEmpty = !(pin && *pin); + if (isEmpty && !_isPinSet) { + return true; // nothing to do + } else if (!isEmpty){ + _pin_len = strlen(pin); + memcpy(_pin_code, pin, _pin_len); + } else { + _pin_len = 0; // resetting pin to none (default) + } + _pin_code[_pin_len] = 0; + _isPinSet = true; + if (isReady(false, READY_TIMEOUT)) { + btSetPin(); + } + return true; +} + +bool BluetoothSerial::connect(String remoteName) +{ + if (!isReady(true, READY_TIMEOUT)) return false; + if (remoteName && remoteName.length() < 1) { + log_e("No remote name is provided"); + return false; + } + disconnect(); + _isRemoteAddressSet = false; + strncpy(_remote_name, remoteName.c_str(), ESP_BT_GAP_MAX_BDNAME_LEN); + _remote_name[ESP_BT_GAP_MAX_BDNAME_LEN] = 0; + log_i("master : remoteName"); + // will first resolve name to address +#ifdef ESP_IDF_VERSION_MAJOR + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); +#else + esp_bt_gap_set_scan_mode(ESP_BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE); +#endif + if (esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, INQ_LEN, INQ_NUM_RSPS) == ESP_OK) { + return waitForConnect(SCAN_TIMEOUT); + } + return false; +} + +bool BluetoothSerial::connect(uint8_t remoteAddress[]) +{ + if (!isReady(true, READY_TIMEOUT)) return false; + if (!remoteAddress) { + log_e("No remote address is provided"); + return false; + } + disconnect(); + _remote_name[0] = 0; + _isRemoteAddressSet = true; + memcpy(_peer_bd_addr, remoteAddress, ESP_BD_ADDR_LEN); + log_i("master : remoteAddress"); + if (esp_spp_start_discovery(_peer_bd_addr) == ESP_OK) { + return waitForConnect(READY_TIMEOUT); + } + return false; +} + +bool BluetoothSerial::connect() +{ + if (!isReady(true, READY_TIMEOUT)) return false; + if (_isRemoteAddressSet){ + disconnect(); + // use resolved or set address first + log_i("master : remoteAddress"); + if (esp_spp_start_discovery(_peer_bd_addr) == ESP_OK) { + return waitForConnect(READY_TIMEOUT); + } + return false; + } else if (_remote_name[0]) { + disconnect(); + log_i("master : remoteName"); + // will resolve name to address first - it may take a while +#ifdef ESP_IDF_VERSION_MAJOR + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); +#else + esp_bt_gap_set_scan_mode(ESP_BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE); +#endif + if (esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, INQ_LEN, INQ_NUM_RSPS) == ESP_OK) { + return waitForConnect(SCAN_TIMEOUT); + } + return false; + } + log_e("Neither Remote name nor address was provided"); + return false; +} + +bool BluetoothSerial::disconnect() { + if (_spp_client) { + flush(); + log_i("disconnecting"); + if (esp_spp_disconnect(_spp_client) == ESP_OK) { + TickType_t xTicksToWait = READY_TIMEOUT / portTICK_PERIOD_MS; + return (xEventGroupWaitBits(_spp_event_group, SPP_DISCONNECTED, pdFALSE, pdTRUE, xTicksToWait) & SPP_DISCONNECTED) != 0; + } + } + return false; +} + +bool BluetoothSerial::unpairDevice(uint8_t remoteAddress[]) { + if (isReady(false, READY_TIMEOUT)) { + log_i("removing bonded device"); + return (esp_bt_gap_remove_bond_device(remoteAddress) == ESP_OK); + } + return false; +} + +bool BluetoothSerial::connected(int timeout) { + return waitForConnect(timeout); +} + +bool BluetoothSerial::isReady(bool checkMaster, int timeout) { + if (checkMaster && !_isMaster) { + log_e("Master mode is not active. Call begin(localName, true) to enable Master mode"); + return false; + } + if (!btStarted()) { + log_e("BT is not initialized. Call begin() first"); + return false; + } + TickType_t xTicksToWait = timeout / portTICK_PERIOD_MS; + 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; +} +#endif \ No newline at end of file diff --git a/lib/BluetoothSerial/BluetoothSerial.h b/lib/BluetoothSerial/BluetoothSerial.h new file mode 100644 index 0000000..937a26a --- /dev/null +++ b/lib/BluetoothSerial/BluetoothSerial.h @@ -0,0 +1,75 @@ +#ifndef _BLUETOOTH_SERIAL_H_ +#define _BLUETOOTH_SERIAL_H_ + +#include "sdkconfig.h" + +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BLUEDROID_ENABLED) + +#include "Arduino.h" +#include "Stream.h" +#include +#include +#include +#include "BTScan.h" + +typedef std::function BluetoothSerialDataCb; +typedef std::function ConfirmRequestCb; +typedef std::function AuthCompleteCb; +typedef std::function BTAdvertisedDeviceCb; + +class BluetoothSerial: public Stream +{ + public: + + BluetoothSerial(void); + ~BluetoothSerial(void); + + bool begin(String localName=String(), bool isMaster=false, uint16_t rxQueueSize = 512 * 2, uint16_t txQueueSize = 512); + bool begin(unsigned long baud){//compatibility + return begin(); + } + int available(void); + int peek(void); + bool hasClient(void); + int read(void); + size_t write(uint8_t c); + size_t write(const uint8_t *buffer, size_t size); + void flush(); + void end(void); + void onData(BluetoothSerialDataCb cb); + esp_err_t register_callback(esp_spp_cb_t callback); + + void onConfirmRequest(ConfirmRequestCb cb); + void onAuthComplete(AuthCompleteCb cb); + void confirmReply(boolean confirm); + + void enableSSP(); + bool setPin(const char *pin); + bool connect(String remoteName); + bool connect(uint8_t remoteAddress[]); + bool connect(); + bool connected(int timeout=0); + bool isReady(bool checkMaster=false, int timeout=0); + 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; + +}; + +#endif + +#endif \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index c07d713..82f15e2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -13,6 +13,10 @@ monitor_speed = 115200 platform = espressif32 board = esp32dev framework = arduino +; board_build.flash_size = 4MB +; board_build.maximum_ram_size = 327680 +; board_build.maximum_size = 4194304 +board_build.partitions = gnss_partitions_4mb.csv lib_deps = sparkfun/SparkFun u-blox GNSS Arduino Library@^2.2.13 adafruit/Adafruit SSD1306@^2.5.7 @@ -20,3 +24,4 @@ lib_deps = greiman/SdFat@^2.2.0 jchristensen/JC_Button@^2.1.2 fbiego/ESP32Time@^2.0.0 + avinabmalla/ESP32_BleSerial@^1.0.4 diff --git a/src/bluetoothSelect.h b/src/bluetoothSelect.h new file mode 100644 index 0000000..1d4a2b3 --- /dev/null +++ b/src/bluetoothSelect.h @@ -0,0 +1,175 @@ +#ifdef COMPILE_BT + +#include "BluetoothSerial.h" +#include + +class BTSerialInterface +{ + public: + virtual bool begin(String deviceName, bool isMaster, uint16_t rxQueueSize, uint16_t txQueueSize) = 0; + virtual void disconnect() = 0; + virtual void end() = 0; + virtual esp_err_t register_callback(esp_spp_cb_t callback) = 0; + virtual void setTimeout(unsigned long timeout) = 0; + + virtual int available() = 0; + // virtual bool hasClient() = 0; + virtual size_t readBytes(uint8_t *buffer, size_t bufferSize) = 0; + virtual int read() = 0; + + //virtual bool isCongested() = 0; + virtual size_t write(const uint8_t *buffer, size_t size) = 0; + virtual size_t write(uint8_t value) = 0; + virtual void flush() = 0; +}; + + +class BTClassicSerial : public virtual BTSerialInterface, public BluetoothSerial +{ + // Everything is already implemented in BluetoothSerial since the code was + // originally written using that class + public: + bool begin(String deviceName, bool isMaster, uint16_t rxQueueSize, uint16_t txQueueSize) + { + return BluetoothSerial::begin(deviceName, isMaster, rxQueueSize, txQueueSize); + } + + void disconnect() + { + BluetoothSerial::disconnect(); + } + + void end() + { + BluetoothSerial::end(); + } + + esp_err_t register_callback(esp_spp_cb_t callback) + { + return BluetoothSerial::register_callback(callback); + } + + void setTimeout(unsigned long timeout) + { + BluetoothSerial::setTimeout(timeout); + } + + int available() + { + return BluetoothSerial::available(); + } + + // bool hasClient() + // { + // return BluetoothSerial::hasClient(); + // } + + size_t readBytes(uint8_t *buffer, size_t bufferSize) + { + return BluetoothSerial::readBytes(buffer, bufferSize); + } + + int read() + { + return BluetoothSerial::read(); + } + + size_t write(const uint8_t *buffer, size_t size) + { + return BluetoothSerial::write(buffer, size); + } + + size_t write(uint8_t value) + { + return BluetoothSerial::write(value); + } + + void flush() + { + BluetoothSerial::flush(); + } +}; + +class BTLESerial : public virtual BTSerialInterface, public BleSerial +{ + public: + // Missing from BleSerial + bool begin(String deviceName, bool isMaster, uint16_t rxQueueSize, uint16_t txQueueSize) + { + BleSerial::begin(deviceName.c_str()); + return true; + } + + void disconnect() + { + Server->disconnect(Server->getConnId()); + } + + void end() + { + BleSerial::end(); + } + + esp_err_t register_callback(esp_spp_cb_t callback) + { + connectionCallback = callback; + return ESP_OK; + } + + void setTimeout(unsigned long timeout) + { + BleSerial::setTimeout(timeout); + } + + int available() + { + return BleSerial::available(); + } + + size_t readBytes(uint8_t *buffer, size_t bufferSize) + { + return BleSerial::readBytes(buffer, bufferSize); + } + + int read() + { + return BleSerial::read(); + } + + size_t write(const uint8_t *buffer, size_t size) + { + return BleSerial::write(buffer, size); + } + + size_t write(uint8_t value) + { + return BleSerial::write(value); + } + + void flush() + { + BleSerial::flush(); + } + + // override BLEServerCallbacks + void onConnect(BLEServer *pServer) + { + // bleConnected = true; Removed until PR is accepted + if(connectionCallback){ + connectionCallback(ESP_SPP_SRV_OPEN_EVT, nullptr); + } + } + + void onDisconnect(BLEServer *pServer) + { + // bleConnected = false; Removed until PR is accepted + esp_spp_cb_param_t* param = NULL; + connectionCallback(ESP_SPP_CLOSE_EVT, nullptr); + Server->startAdvertising(); + } + + private: + esp_spp_cb_t connectionCallback; +}; + +#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index e0bd026..0f2029f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -292,7 +292,7 @@ bool zedUartPassed = false; //Goes true during testing if ESP can communicate wi volatile int counter = 0; long lastTime = 0; -#include +#include "bluetoothSelect.h" // BluetoothSerial SerialBT; BTSerialInterface *bluetoothSerial; @@ -965,7 +965,7 @@ void loop() oled.setCursor(0,11); oled.printf("%d bytes",fileSize); - if(bluetoothSerial->hasClient()) + if(bluetoothState == BT_CONNECTED) { oled.setCursor(0,21); oled.println("BT connected"); @@ -1807,16 +1807,16 @@ void bluetoothStart() return; else if (settings.bluetoothRadioType == BLUETOOTH_RADIO_SPP) bluetoothSerial = new BTClassicSerial(); - // else if (settings.bluetoothRadioType == BLUETOOTH_RADIO_BLE) - // bluetoothSerial = new BTLESerial(); + else if (settings.bluetoothRadioType == BLUETOOTH_RADIO_BLE) + bluetoothSerial = new BTLESerial(); - if (bluetoothSerial->begin(deviceName) == false) + if (bluetoothSerial->begin(deviceName,false,settings.sppRxQueueSize, settings.sppTxQueueSize) == false) { Serial.println("An error occurred initializing Bluetooth"); - if (productVariant == RTK_SURVEYOR) - digitalWrite(pin_bluetoothStatusLED, LOW); - return; + // if (productVariant == RTK_SURVEYOR) + // digitalWrite(pin_bluetoothStatusLED, LOW); + // return; } //Set PIN to 1234 so we can connect to older BT devices, but not require a PIN for modern device pairing diff --git a/src/mybluetooth.h b/src/mybluetooth.h deleted file mode 100644 index 13bbcbe..0000000 --- a/src/mybluetooth.h +++ /dev/null @@ -1,76 +0,0 @@ -#include "BluetoothSerial.h" - -class BTSerialInterface -{ - public: - virtual bool begin(String deviceName) = 0; - virtual void disconnect() = 0; - virtual void end() = 0; - virtual esp_err_t register_callback(esp_spp_cb_t * callback) = 0; - virtual void setTimeout(unsigned long timeout) = 0; - - virtual int available() = 0; - virtual bool hasClient() = 0; - virtual size_t readBytes(uint8_t *buffer, size_t bufferSize) = 0; - - //virtual bool isCongested() = 0; - virtual size_t write(const uint8_t *buffer, size_t size) = 0; - virtual void flush() = 0; -}; - - -class BTClassicSerial : public virtual BTSerialInterface, public BluetoothSerial -{ - // Everything is already implemented in BluetoothSerial since the code was - // originally written using that class - public: - bool begin(String deviceName) - { - return BluetoothSerial::begin(deviceName); - } - - void disconnect() - { - BluetoothSerial::disconnect(); - } - - void end() - { - BluetoothSerial::end(); - } - - esp_err_t register_callback(esp_spp_cb_t * callback) - { - return BluetoothSerial::register_callback(callback); - } - - void setTimeout(unsigned long timeout) - { - BluetoothSerial::setTimeout(timeout); - } - - int available() - { - return BluetoothSerial::available(); - } - - bool hasClient() - { - return BluetoothSerial::hasClient(); - } - - size_t readBytes(uint8_t *buffer, size_t bufferSize) - { - return BluetoothSerial::readBytes(buffer, bufferSize); - } - - size_t write(const uint8_t *buffer, size_t size) - { - return BluetoothSerial::write(buffer, size); - } - - void flush() - { - BluetoothSerial::flush(); - } -}; diff --git a/src/settings.h b/src/settings.h index 7caba46..d8f1c86 100644 --- a/src/settings.h +++ b/src/settings.h @@ -189,7 +189,7 @@ typedef struct { uint16_t measurementRate = 250; //Elapsed ms between GNSS measurements. 25ms to 65535ms. Default 4Hz. uint16_t navigationRate = 1; //Ratio between number of measurements and navigation solutions. Default 1 for 4Hz (with measurementRate). bool enableI2Cdebug = false; //Turn on to display GNSS library debug messages - bool enableHeapReport = false; //Turn on to display free heap + bool enableHeapReport = true; //Turn on to display free heap bool enableTaskReports = false; //Turn on to display task high water marks muxConnectionType_e dataPortChannel = MUX_UBLOX_NMEA; //Mux default to ublox UART1 uint16_t spiFrequency = 16; //By default, use 16MHz SPI @@ -397,7 +397,8 @@ typedef struct { uint8_t espnowPeers[5][6]; //Max of 5 peers. Contains the MAC addresses (6 bytes) of paired units uint8_t espnowPeerCount; bool enableRtcmMessageChecking = false; - BluetoothRadioType_e bluetoothRadioType = BLUETOOTH_RADIO_SPP; + // BluetoothRadioType_e bluetoothRadioType = BLUETOOTH_RADIO_SPP; + BluetoothRadioType_e bluetoothRadioType = BLUETOOTH_RADIO_BLE; bool runLogTest = false; //When set to true, device will create a series of test logs } Settings; Settings settings;