1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-06 21:32:21 +01:00

BLE client support on ESP32 (#1177)

Co-authored-by: Ben Buxton <bb@cactii.net>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
buxtronix
2021-05-03 09:10:50 +10:00
committed by GitHub
parent 07db9319ad
commit 02aa75f68c
19 changed files with 1342 additions and 8 deletions

View File

@@ -26,6 +26,7 @@ CONF_WINDOW = "window"
CONF_ACTIVE = "active"
esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker")
ESP32BLETracker = esp32_ble_tracker_ns.class_("ESP32BLETracker", cg.Component)
ESPBTClient = esp32_ble_tracker_ns.class_("ESPBTClient")
ESPBTDeviceListener = esp32_ble_tracker_ns.class_("ESPBTDeviceListener")
ESPBTDevice = esp32_ble_tracker_ns.class_("ESPBTDevice")
ESPBTDeviceConstRef = ESPBTDevice.operator("ref").operator("const")
@@ -220,3 +221,10 @@ def register_ble_device(var, config):
paren = yield cg.get_variable(config[CONF_ESP32_BLE_ID])
cg.add(paren.register_listener(var))
yield var
@coroutine
def register_client(var, config):
paren = yield cg.get_variable(config[CONF_ESP32_BLE_ID])
cg.add(paren.register_client(var))
yield var

View File

@@ -48,7 +48,23 @@ void ESP32BLETracker::setup() {
}
void ESP32BLETracker::loop() {
if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
BLEEvent *ble_event = this->ble_events_.pop();
while (ble_event != nullptr) {
if (ble_event->type_)
this->real_gattc_event_handler(ble_event->event_.gattc.gattc_event, ble_event->event_.gattc.gattc_if,
&ble_event->event_.gattc.gattc_param);
else
this->real_gap_event_handler(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param);
delete ble_event;
ble_event = this->ble_events_.pop();
}
bool connecting = false;
for (auto *client : this->clients_) {
if (client->state() == ClientState::Connecting || client->state() == ClientState::Discovered)
connecting = true;
}
if (!connecting && xSemaphoreTake(this->scan_end_lock_, 0L)) {
xSemaphoreGive(this->scan_end_lock_);
global_esp32_ble_tracker->start_scan(false);
}
@@ -69,6 +85,17 @@ void ESP32BLETracker::loop() {
if (listener->parse_device(device))
found = true;
for (auto *client : this->clients_)
if (client->parse_device(device)) {
found = true;
if (client->state() == ClientState::Discovered) {
esp_ble_gap_stop_scanning();
if (xSemaphoreTake(this->scan_end_lock_, 10L / portTICK_PERIOD_MS)) {
xSemaphoreGive(this->scan_end_lock_);
}
}
}
if (!found) {
this->print_bt_device_info(device);
}
@@ -122,6 +149,11 @@ bool ESP32BLETracker::ble_setup() {
ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", err);
return false;
}
err = esp_ble_gattc_register_callback(ESP32BLETracker::gattc_event_handler);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gattc_register_callback failed: %d", err);
return false;
}
// Empty name
esp_ble_gap_set_device_name("");
@@ -166,7 +198,17 @@ void ESP32BLETracker::start_scan(bool first) {
});
}
void ESP32BLETracker::register_client(ESPBTClient *client) {
client->app_id = ++this->app_id_;
this->clients_.push_back(client);
}
void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
BLEEvent *gap_event = new BLEEvent(event, param);
global_esp32_ble_tracker->ble_events_.push(gap_event);
}
void ESP32BLETracker::real_gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
switch (event) {
case ESP_GAP_BLE_SCAN_RESULT_EVT:
global_esp32_ble_tracker->gap_scan_result(param->scan_rst);
@@ -177,6 +219,9 @@ void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_ga
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
global_esp32_ble_tracker->gap_scan_start_complete(param->scan_start_cmpl);
break;
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
global_esp32_ble_tracker->gap_scan_stop_complete(param->scan_stop_cmpl);
break;
default:
break;
}
@@ -190,6 +235,10 @@ void ESP32BLETracker::gap_scan_start_complete(const esp_ble_gap_cb_param_t::ble_
this->scan_start_failed_ = param.status;
}
void ESP32BLETracker::gap_scan_stop_complete(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param) {
xSemaphoreGive(this->scan_end_lock_);
}
void ESP32BLETracker::gap_scan_result(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param) {
if (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
if (xSemaphoreTake(this->scan_result_lock_, 0L)) {
@@ -203,6 +252,19 @@ void ESP32BLETracker::gap_scan_result(const esp_ble_gap_cb_param_t::ble_scan_res
}
}
void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) {
BLEEvent *gattc_event = new BLEEvent(event, gattc_if, param);
global_esp32_ble_tracker->ble_events_.push(gattc_event);
}
void ESP32BLETracker::real_gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) {
for (auto *client : global_esp32_ble_tracker->clients_) {
client->gattc_event_handler(event, gattc_if, param);
}
}
ESPBTUUID::ESPBTUUID() : uuid_() {}
ESPBTUUID ESPBTUUID::from_uint16(uint16_t uuid) {
ESPBTUUID ret;
@@ -223,6 +285,15 @@ ESPBTUUID ESPBTUUID::from_raw(const uint8_t *data) {
ret.uuid_.uuid.uuid128[i] = data[i];
return ret;
}
ESPBTUUID ESPBTUUID::from_uuid(esp_bt_uuid_t uuid) {
ESPBTUUID ret;
ret.uuid_.len = uuid.len;
ret.uuid_.uuid.uuid16 = uuid.uuid.uuid16;
ret.uuid_.uuid.uuid32 = uuid.uuid.uuid32;
for (size_t i = 0; i < ESP_UUID_LEN_128; i++)
ret.uuid_.uuid.uuid128[i] = uuid.uuid.uuid128[i];
return ret;
}
ESPBTUUID ESPBTUUID::as_128bit() const {
if (this->uuid_.len == ESP_UUID_LEN_128) {
return *this;
@@ -289,16 +360,21 @@ std::string ESPBTUUID::to_string() {
char sbuf[64];
switch (this->uuid_.len) {
case ESP_UUID_LEN_16:
sprintf(sbuf, "%02X:%02X", this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff);
sprintf(sbuf, "0x%02X%02X", this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff);
break;
case ESP_UUID_LEN_32:
sprintf(sbuf, "%02X:%02X:%02X:%02X", this->uuid_.uuid.uuid32 >> 24, (this->uuid_.uuid.uuid32 >> 16 & 0xff),
sprintf(sbuf, "0x%02X%02X%02X%02X", this->uuid_.uuid.uuid32 >> 24, (this->uuid_.uuid.uuid32 >> 16 & 0xff),
(this->uuid_.uuid.uuid32 >> 8 & 0xff), this->uuid_.uuid.uuid32 & 0xff);
break;
default:
case ESP_UUID_LEN_128:
for (uint8_t i = 0; i < 16; i++)
sprintf(sbuf + i * 3, "%02X:", this->uuid_.uuid.uuid128[i]);
char *bpos = sbuf;
for (int8_t i = 15; i >= 0; i--) {
sprintf(bpos, "%02X", this->uuid_.uuid.uuid128[i]);
bpos += 2;
if (i == 3 || i == 5 || i == 7 || i == 9)
sprintf(bpos++, "-");
}
sbuf[47] = '\0';
break;
}

View File

@@ -2,12 +2,14 @@
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "queue.h"
#ifdef ARDUINO_ARCH_ESP32
#include <string>
#include <array>
#include <esp_gap_ble_api.h>
#include <esp_gattc_api.h>
#include <esp_bt_defs.h>
namespace esphome {
@@ -23,6 +25,8 @@ class ESPBTUUID {
static ESPBTUUID from_raw(const uint8_t *data);
static ESPBTUUID from_uuid(esp_bt_uuid_t uuid);
ESPBTUUID as_128bit() const;
bool contains(uint8_t data1, uint8_t data2) const;
@@ -135,6 +139,32 @@ class ESPBTDeviceListener {
ESP32BLETracker *parent_{nullptr};
};
enum class ClientState {
// Connection is idle, no device detected.
Idle,
// Device advertisement found.
Discovered,
// Connection in progress.
Connecting,
// Initial connection established.
Connected,
// The client and sub-clients have completed setup.
Established,
};
class ESPBTClient : public ESPBTDeviceListener {
public:
virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) = 0;
virtual void connect() = 0;
void set_state(ClientState st) { this->state_ = st; }
ClientState state() const { return state_; }
int app_id;
protected:
ClientState state_;
};
class ESP32BLETracker : public Component {
public:
void set_scan_duration(uint32_t scan_duration) { scan_duration_ = scan_duration; }
@@ -153,6 +183,8 @@ class ESP32BLETracker : public Component {
this->listeners_.push_back(listener);
}
void register_client(ESPBTClient *client);
void print_bt_device_info(const ESPBTDevice &device);
protected:
@@ -162,16 +194,26 @@ class ESP32BLETracker : public Component {
void start_scan(bool first);
/// Callback that will handle all GAP events and redistribute them to other callbacks.
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
void real_gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
/// Called when a `ESP_GAP_BLE_SCAN_RESULT_EVT` event is received.
void gap_scan_result(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param);
/// Called when a `ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT` event is received.
void gap_scan_set_param_complete(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param &param);
/// Called when a `ESP_GAP_BLE_SCAN_START_COMPLETE_EVT` event is received.
void gap_scan_start_complete(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param &param);
/// Called when a `ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT` event is received.
void gap_scan_stop_complete(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param);
int app_id_;
/// Callback that will handle all GATTC events and redistribute them to other callbacks.
static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
void real_gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
/// Vector of addresses that have already been printed in print_bt_device_info
std::vector<uint64_t> already_discovered_;
std::vector<ESPBTDeviceListener *> listeners_;
/// Client parameters.
std::vector<ESPBTClient *> clients_;
/// A structure holding the ESP BLE scan parameters.
esp_ble_scan_params_t scan_params_;
/// The interval in seconds to perform scans.
@@ -185,6 +227,8 @@ class ESP32BLETracker : public Component {
esp_ble_gap_cb_param_t::ble_scan_result_evt_param scan_result_buffer_[16];
esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS};
esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS};
Queue<BLEEvent> ble_events_;
};
extern ESP32BLETracker *global_esp32_ble_tracker;

View File

@@ -0,0 +1,96 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include <queue>
#include <mutex>
#ifdef ARDUINO_ARCH_ESP32
#include <esp_gap_ble_api.h>
#include <esp_gattc_api.h>
/*
* BLE events come in from a separate Task (thread) in the ESP32 stack. Rather
* than trying to deal wth various locking strategies, all incoming GAP and GATT
* events will simply be placed on a semaphore guarded queue. The next time the
* component runs loop(), these events are popped off the queue and handed at
* this safer time.
*/
namespace esphome {
namespace esp32_ble_tracker {
template<class T> class Queue {
public:
Queue() { m = xSemaphoreCreateMutex(); }
void push(T *element) {
if (element == nullptr)
return;
if (xSemaphoreTake(m, 5L / portTICK_PERIOD_MS)) {
q.push(element);
xSemaphoreGive(m);
}
}
T *pop() {
T *element = nullptr;
if (xSemaphoreTake(m, 5L / portTICK_PERIOD_MS)) {
if (!q.empty()) {
element = q.front();
q.pop();
}
xSemaphoreGive(m);
}
return element;
}
protected:
std::queue<T *> q;
SemaphoreHandle_t m;
};
// Received GAP and GATTC events are only queued, and get processed in the main loop().
// This class stores each event in a single type.
class BLEEvent {
public:
BLEEvent(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
this->event_.gap.gap_event = e;
memcpy(&this->event_.gap.gap_param, p, sizeof(esp_ble_gap_cb_param_t));
this->type_ = 0;
};
BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
this->event_.gattc.gattc_event = e;
this->event_.gattc.gattc_if = i;
memcpy(&this->event_.gattc.gattc_param, p, sizeof(esp_ble_gattc_cb_param_t));
// Need to also make a copy of notify event data.
if (e == ESP_GATTC_NOTIFY_EVT) {
memcpy(this->event_.gattc.notify_data, p->notify.value, p->notify.value_len);
this->event_.gattc.gattc_param.notify.value = this->event_.gattc.notify_data;
}
this->type_ = 1;
};
union {
struct gap_event {
esp_gap_ble_cb_event_t gap_event;
esp_ble_gap_cb_param_t gap_param;
} gap;
struct gattc_event {
esp_gattc_cb_event_t gattc_event;
esp_gatt_if_t gattc_if;
esp_ble_gattc_cb_param_t gattc_param;
uint8_t notify_data[64];
} gattc;
} event_;
uint8_t type_; // 0=gap 1=gattc
};
} // namespace esp32_ble_tracker
} // namespace esphome
#endif