From ce139796908e376a1093f8b1694f7b547d6ed0c6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 9 Jun 2023 07:41:09 +1200 Subject: [PATCH] Bluetooth Proxy: Raw bundled advertisements (#4924) --- esphome/components/api/api.proto | 22 ++- esphome/components/api/api_connection.cpp | 23 ++- esphome/components/api/api_connection.h | 13 +- esphome/components/api/api_pb2.cpp | 126 ++++++++++++- esphome/components/api/api_pb2.h | 31 +++- esphome/components/api/api_pb2_service.cpp | 9 + esphome/components/api/api_pb2_service.h | 3 + esphome/components/api/api_server.cpp | 105 ----------- esphome/components/api/api_server.h | 15 -- .../bluetooth_proxy/bluetooth_connection.cpp | 52 +++--- .../bluetooth_proxy/bluetooth_proxy.cpp | 170 ++++++++++++++---- .../bluetooth_proxy/bluetooth_proxy.h | 67 ++++++- esphome/components/esp32_ble/ble.cpp | 11 ++ esphome/components/esp32_ble/ble.h | 2 + .../esp32_ble_tracker/esp32_ble_tracker.cpp | 33 ++-- .../esp32_ble_tracker/esp32_ble_tracker.h | 3 + 16 files changed, 450 insertions(+), 235 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 34678fde0f..74d08195a0 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -206,7 +206,8 @@ message DeviceInfoResponse { uint32 webserver_port = 10; - uint32 bluetooth_proxy_version = 11; + uint32 legacy_bluetooth_proxy_version = 11; + uint32 bluetooth_proxy_feature_flags = 15; string manufacturer = 12; @@ -1130,6 +1131,8 @@ message SubscribeBluetoothLEAdvertisementsRequest { option (id) = 66; option (source) = SOURCE_CLIENT; option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint32 flags = 1; } message BluetoothServiceData { @@ -1154,6 +1157,23 @@ message BluetoothLEAdvertisementResponse { uint32 address_type = 7; } +message BluetoothLERawAdvertisement { + uint64 address = 1; + sint32 rssi = 2; + uint32 address_type = 3; + + bytes data = 4; +} + +message BluetoothLERawAdvertisementsResponse { + option (id) = 93; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + option (no_delay) = true; + + repeated BluetoothLERawAdvertisement advertisements = 1; +} + enum BluetoothDeviceRequestType { BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0; BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 3983c6403b..53e6ccd6dc 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -51,6 +51,14 @@ void APIConnection::start() { helper_->set_log_info(client_info_); } +APIConnection::~APIConnection() { +#ifdef USE_BLUETOOTH_PROXY + if (bluetooth_proxy::global_bluetooth_proxy->get_api_connection() == this) { + bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this); + } +#endif +} + void APIConnection::loop() { if (this->remove_) return; @@ -845,9 +853,13 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) { #endif #ifdef USE_BLUETOOTH_PROXY +void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->subscribe_api_connection(this, msg.flags); +} +void APIConnection::unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this); +} bool APIConnection::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg) { - if (!this->bluetooth_le_advertisement_subscription_) - return false; if (this->client_api_version_major_ < 1 || this->client_api_version_minor_ < 7) { BluetoothLEAdvertisementResponse resp = msg; for (auto &service : resp.service_data) { @@ -943,7 +955,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) { HelloResponse resp; resp.api_version_major = 1; - resp.api_version_minor = 8; + resp.api_version_minor = 9; resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; resp.name = App.get_name(); @@ -995,9 +1007,8 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { resp.webserver_port = USE_WEBSERVER_PORT; #endif #ifdef USE_BLUETOOTH_PROXY - resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() - ? bluetooth_proxy::ACTIVE_CONNECTIONS_VERSION - : bluetooth_proxy::PASSIVE_ONLY_VERSION; + resp.legacy_bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->get_legacy_version(); + resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags(); #endif #ifdef USE_VOICE_ASSISTANT resp.voice_assistant_version = voice_assistant::global_voice_assistant->get_version(); diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index d4e9cc656e..5398234f9f 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -16,7 +16,7 @@ namespace api { class APIConnection : public APIServerConnection { public: APIConnection(std::unique_ptr socket, APIServer *parent); - virtual ~APIConnection() = default; + virtual ~APIConnection(); void start(); void loop(); @@ -98,12 +98,8 @@ class APIConnection : public APIServerConnection { this->send_homeassistant_service_response(call); } #ifdef USE_BLUETOOTH_PROXY - void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override { - this->bluetooth_le_advertisement_subscription_ = true; - } - void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override { - this->bluetooth_le_advertisement_subscription_ = false; - } + void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; + void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override; bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg); void bluetooth_device_request(const BluetoothDeviceRequest &msg) override; @@ -211,9 +207,6 @@ class APIConnection : public APIServerConnection { uint32_t last_traffic_; bool sent_ping_{false}; bool service_call_subscription_{false}; -#ifdef USE_BLUETOOTH_PROXY - bool bluetooth_le_advertisement_subscription_{false}; -#endif #ifdef USE_VOICE_ASSISTANT bool voice_assistant_subscription_{false}; #endif diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 29e8e207dd..019e653a7f 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -617,7 +617,11 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { return true; } case 11: { - this->bluetooth_proxy_version = value.as_uint32(); + this->legacy_bluetooth_proxy_version = value.as_uint32(); + return true; + } + case 15: { + this->bluetooth_proxy_feature_flags = value.as_uint32(); return true; } case 14: { @@ -681,7 +685,8 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(8, this->project_name); buffer.encode_string(9, this->project_version); buffer.encode_uint32(10, this->webserver_port); - buffer.encode_uint32(11, this->bluetooth_proxy_version); + buffer.encode_uint32(11, this->legacy_bluetooth_proxy_version); + buffer.encode_uint32(15, this->bluetooth_proxy_feature_flags); buffer.encode_string(12, this->manufacturer); buffer.encode_string(13, this->friendly_name); buffer.encode_uint32(14, this->voice_assistant_version); @@ -731,8 +736,13 @@ void DeviceInfoResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" bluetooth_proxy_version: "); - sprintf(buffer, "%u", this->bluetooth_proxy_version); + out.append(" legacy_bluetooth_proxy_version: "); + sprintf(buffer, "%u", this->legacy_bluetooth_proxy_version); + out.append(buffer); + out.append("\n"); + + out.append(" bluetooth_proxy_feature_flags: "); + sprintf(buffer, "%u", this->bluetooth_proxy_feature_flags); out.append(buffer); out.append("\n"); @@ -5041,10 +5051,28 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const { out.append("}"); } #endif -void SubscribeBluetoothLEAdvertisementsRequest::encode(ProtoWriteBuffer buffer) const {} +bool SubscribeBluetoothLEAdvertisementsRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->flags = value.as_uint32(); + return true; + } + default: + return false; + } +} +void SubscribeBluetoothLEAdvertisementsRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint32(1, this->flags); +} #ifdef HAS_PROTO_MESSAGE_DUMP void SubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const { - out.append("SubscribeBluetoothLEAdvertisementsRequest {}"); + __attribute__((unused)) char buffer[64]; + out.append("SubscribeBluetoothLEAdvertisementsRequest {\n"); + out.append(" flags: "); + sprintf(buffer, "%u", this->flags); + out.append(buffer); + out.append("\n"); + out.append("}"); } #endif bool BluetoothServiceData::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -5197,6 +5225,92 @@ void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const { out.append("}"); } #endif +bool BluetoothLERawAdvertisement::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->rssi = value.as_sint32(); + return true; + } + case 3: { + this->address_type = value.as_uint32(); + return true; + } + default: + return false; + } +} +bool BluetoothLERawAdvertisement::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 4: { + this->data = value.as_string(); + return true; + } + default: + return false; + } +} +void BluetoothLERawAdvertisement::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_sint32(2, this->rssi); + buffer.encode_uint32(3, this->address_type); + buffer.encode_string(4, this->data); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothLERawAdvertisement::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothLERawAdvertisement {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" rssi: "); + sprintf(buffer, "%d", this->rssi); + out.append(buffer); + out.append("\n"); + + out.append(" address_type: "); + sprintf(buffer, "%u", this->address_type); + out.append(buffer); + out.append("\n"); + + out.append(" data: "); + out.append("'").append(this->data).append("'"); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothLERawAdvertisementsResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 1: { + this->advertisements.push_back(value.as_message()); + return true; + } + default: + return false; + } +} +void BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer buffer) const { + for (auto &it : this->advertisements) { + buffer.encode_message(1, it, true); + } +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothLERawAdvertisementsResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothLERawAdvertisementsResponse {\n"); + for (const auto &it : this->advertisements) { + out.append(" advertisements: "); + it.dump_to(out); + out.append("\n"); + } + out.append("}"); +} +#endif bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: { diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index cd1cfb595a..49307dfce0 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -287,7 +287,8 @@ class DeviceInfoResponse : public ProtoMessage { std::string project_name{}; std::string project_version{}; uint32_t webserver_port{0}; - uint32_t bluetooth_proxy_version{0}; + uint32_t legacy_bluetooth_proxy_version{0}; + uint32_t bluetooth_proxy_feature_flags{0}; std::string manufacturer{}; std::string friendly_name{}; uint32_t voice_assistant_version{0}; @@ -1247,12 +1248,14 @@ class MediaPlayerCommandRequest : public ProtoMessage { }; class SubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage { public: + uint32_t flags{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class BluetoothServiceData : public ProtoMessage { public: @@ -1286,6 +1289,32 @@ class BluetoothLEAdvertisementResponse : public ProtoMessage { bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; +class BluetoothLERawAdvertisement : public ProtoMessage { + public: + uint64_t address{0}; + int32_t rssi{0}; + uint32_t address_type{0}; + std::string data{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothLERawAdvertisementsResponse : public ProtoMessage { + public: + std::vector advertisements{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; +}; class BluetoothDeviceRequest : public ProtoMessage { public: uint64_t address{0}; diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index df36d0fdea..e14e22cdc1 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -339,6 +339,15 @@ bool APIServerConnectionBase::send_bluetooth_le_advertisement_response(const Blu } #endif #ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_le_raw_advertisements_response( + const BluetoothLERawAdvertisementsResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_le_raw_advertisements_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 93); +} +#endif +#ifdef USE_BLUETOOTH_PROXY #endif #ifdef USE_BLUETOOTH_PROXY bool APIServerConnectionBase::send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg) { diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 3808f128a4..8af7a53381 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -161,6 +161,9 @@ class APIServerConnectionBase : public ProtoService { #ifdef USE_BLUETOOTH_PROXY bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg); #endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_le_raw_advertisements_response(const BluetoothLERawAdvertisementsResponse &msg); +#endif #ifdef USE_BLUETOOTH_PROXY virtual void on_bluetooth_device_request(const BluetoothDeviceRequest &value){}; #endif diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 3dd47c4dd8..27c82f7ccc 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -291,112 +291,7 @@ void APIServer::send_homeassistant_service_call(const HomeassistantServiceRespon client->send_homeassistant_service_call(call); } } -#ifdef USE_BLUETOOTH_PROXY -void APIServer::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call) { - for (auto &client : this->clients_) { - client->send_bluetooth_le_advertisement(call); - } -} -void APIServer::send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) { - BluetoothDeviceConnectionResponse call; - call.address = address; - call.connected = connected; - call.mtu = mtu; - call.error = error; - for (auto &client : this->clients_) { - client->send_bluetooth_device_connection_response(call); - } -} - -void APIServer::send_bluetooth_device_pairing(uint64_t address, bool paired, esp_err_t error) { - BluetoothDevicePairingResponse call; - call.address = address; - call.paired = paired; - call.error = error; - - for (auto &client : this->clients_) { - client->send_bluetooth_device_pairing_response(call); - } -} - -void APIServer::send_bluetooth_device_unpairing(uint64_t address, bool success, esp_err_t error) { - BluetoothDeviceUnpairingResponse call; - call.address = address; - call.success = success; - call.error = error; - - for (auto &client : this->clients_) { - client->send_bluetooth_device_unpairing_response(call); - } -} - -void APIServer::send_bluetooth_device_clear_cache(uint64_t address, bool success, esp_err_t error) { - BluetoothDeviceClearCacheResponse call; - call.address = address; - call.success = success; - call.error = error; - - for (auto &client : this->clients_) { - client->send_bluetooth_device_clear_cache_response(call); - } -} - -void APIServer::send_bluetooth_connections_free(uint8_t free, uint8_t limit) { - BluetoothConnectionsFreeResponse call; - call.free = free; - call.limit = limit; - - for (auto &client : this->clients_) { - client->send_bluetooth_connections_free_response(call); - } -} - -void APIServer::send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call) { - for (auto &client : this->clients_) { - client->send_bluetooth_gatt_read_response(call); - } -} -void APIServer::send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call) { - for (auto &client : this->clients_) { - client->send_bluetooth_gatt_write_response(call); - } -} -void APIServer::send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call) { - for (auto &client : this->clients_) { - client->send_bluetooth_gatt_notify_data_response(call); - } -} -void APIServer::send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &call) { - for (auto &client : this->clients_) { - client->send_bluetooth_gatt_notify_response(call); - } -} -void APIServer::send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call) { - for (auto &client : this->clients_) { - client->send_bluetooth_gatt_get_services_response(call); - } -} -void APIServer::send_bluetooth_gatt_services_done(uint64_t address) { - BluetoothGATTGetServicesDoneResponse call; - call.address = address; - - for (auto &client : this->clients_) { - client->send_bluetooth_gatt_get_services_done_response(call); - } -} -void APIServer::send_bluetooth_gatt_error(uint64_t address, uint16_t handle, esp_err_t error) { - BluetoothGATTErrorResponse call; - call.address = address; - call.handle = handle; - call.error = error; - - for (auto &client : this->clients_) { - client->send_bluetooth_gatt_error_response(call); - } -} - -#endif APIServer::APIServer() { global_api_server = this; } void APIServer::subscribe_home_assistant_state(std::string entity_id, optional attribute, std::function f) { diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 79ba7b17f1..28e53f22e2 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -75,21 +75,6 @@ class APIServer : public Component, public Controller { void on_media_player_update(media_player::MediaPlayer *obj) override; #endif void send_homeassistant_service_call(const HomeassistantServiceResponse &call); -#ifdef USE_BLUETOOTH_PROXY - void send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call); - void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu = 0, esp_err_t error = ESP_OK); - void send_bluetooth_device_pairing(uint64_t address, bool paired, esp_err_t error = ESP_OK); - void send_bluetooth_device_unpairing(uint64_t address, bool success, esp_err_t error = ESP_OK); - void send_bluetooth_device_clear_cache(uint64_t address, bool success, esp_err_t error = ESP_OK); - void send_bluetooth_connections_free(uint8_t free, uint8_t limit); - void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call); - void send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call); - void send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call); - void send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &call); - void send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call); - void send_bluetooth_gatt_services_done(uint64_t address); - void send_bluetooth_gatt_error(uint64_t address, uint16_t handle, esp_err_t error); -#endif void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } #ifdef USE_HOMEASSISTANT_TIME void request_time(); diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index 9354ab36d6..26304325c1 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -1,6 +1,6 @@ #include "bluetooth_connection.h" -#include "esphome/components/api/api_server.h" +#include "esphome/components/api/api_pb2.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -20,24 +20,21 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga switch (event) { case ESP_GATTC_DISCONNECT_EVT: { - api::global_api_server->send_bluetooth_device_connection(this->address_, false, 0, param->disconnect.reason); + this->proxy_->send_device_connection(this->address_, false, 0, param->disconnect.reason); this->set_address(0); - api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(), - this->proxy_->get_bluetooth_connections_limit()); + this->proxy_->send_connections_free(); break; } case ESP_GATTC_OPEN_EVT: { if (param->open.conn_id != this->conn_id_) break; if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) { - api::global_api_server->send_bluetooth_device_connection(this->address_, false, 0, param->open.status); + this->proxy_->send_device_connection(this->address_, false, 0, param->open.status); this->set_address(0); - api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(), - this->proxy_->get_bluetooth_connections_limit()); + this->proxy_->send_connections_free(); } else if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) { - api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_); - api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(), - this->proxy_->get_bluetooth_connections_limit()); + this->proxy_->send_device_connection(this->address_, true, this->mtu_); + this->proxy_->send_connections_free(); } this->seen_mtu_or_services_ = false; break; @@ -52,9 +49,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga this->seen_mtu_or_services_ = true; break; } - api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_); - api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(), - this->proxy_->get_bluetooth_connections_limit()); + this->proxy_->send_device_connection(this->address_, true, this->mtu_); + this->proxy_->send_connections_free(); break; } case ESP_GATTC_SEARCH_CMPL_EVT: { @@ -67,9 +63,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga this->seen_mtu_or_services_ = true; break; } - api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_); - api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(), - this->proxy_->get_bluetooth_connections_limit()); + this->proxy_->send_device_connection(this->address_, true, this->mtu_); + this->proxy_->send_connections_free(); break; } case ESP_GATTC_READ_DESCR_EVT: @@ -79,7 +74,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga if (param->read.status != ESP_GATT_OK) { ESP_LOGW(TAG, "[%d] [%s] Error reading char/descriptor at handle 0x%2X, status=%d", this->connection_index_, this->address_str_.c_str(), param->read.handle, param->read.status); - api::global_api_server->send_bluetooth_gatt_error(this->address_, param->read.handle, param->read.status); + this->proxy_->send_gatt_error(this->address_, param->read.handle, param->read.status); break; } api::BluetoothGATTReadResponse resp; @@ -89,7 +84,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga for (uint16_t i = 0; i < param->read.value_len; i++) { resp.data.push_back(param->read.value[i]); } - api::global_api_server->send_bluetooth_gatt_read_response(resp); + this->proxy_->get_api_connection()->send_bluetooth_gatt_read_response(resp); break; } case ESP_GATTC_WRITE_CHAR_EVT: @@ -99,13 +94,13 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga if (param->write.status != ESP_GATT_OK) { ESP_LOGW(TAG, "[%d] [%s] Error writing char/descriptor at handle 0x%2X, status=%d", this->connection_index_, this->address_str_.c_str(), param->write.handle, param->write.status); - api::global_api_server->send_bluetooth_gatt_error(this->address_, param->write.handle, param->write.status); + this->proxy_->send_gatt_error(this->address_, param->write.handle, param->write.status); break; } api::BluetoothGATTWriteResponse resp; resp.address = this->address_; resp.handle = param->write.handle; - api::global_api_server->send_bluetooth_gatt_write_response(resp); + this->proxy_->get_api_connection()->send_bluetooth_gatt_write_response(resp); break; } case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: { @@ -113,28 +108,26 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga ESP_LOGW(TAG, "[%d] [%s] Error unregistering notifications for handle 0x%2X, status=%d", this->connection_index_, this->address_str_.c_str(), param->unreg_for_notify.handle, param->unreg_for_notify.status); - api::global_api_server->send_bluetooth_gatt_error(this->address_, param->unreg_for_notify.handle, - param->unreg_for_notify.status); + this->proxy_->send_gatt_error(this->address_, param->unreg_for_notify.handle, param->unreg_for_notify.status); break; } api::BluetoothGATTNotifyResponse resp; resp.address = this->address_; resp.handle = param->unreg_for_notify.handle; - api::global_api_server->send_bluetooth_gatt_notify_response(resp); + this->proxy_->get_api_connection()->send_bluetooth_gatt_notify_response(resp); break; } case ESP_GATTC_REG_FOR_NOTIFY_EVT: { if (param->reg_for_notify.status != ESP_GATT_OK) { ESP_LOGW(TAG, "[%d] [%s] Error registering notifications for handle 0x%2X, status=%d", this->connection_index_, this->address_str_.c_str(), param->reg_for_notify.handle, param->reg_for_notify.status); - api::global_api_server->send_bluetooth_gatt_error(this->address_, param->reg_for_notify.handle, - param->reg_for_notify.status); + this->proxy_->send_gatt_error(this->address_, param->reg_for_notify.handle, param->reg_for_notify.status); break; } api::BluetoothGATTNotifyResponse resp; resp.address = this->address_; resp.handle = param->reg_for_notify.handle; - api::global_api_server->send_bluetooth_gatt_notify_response(resp); + this->proxy_->get_api_connection()->send_bluetooth_gatt_notify_response(resp); break; } case ESP_GATTC_NOTIFY_EVT: { @@ -149,7 +142,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga for (uint16_t i = 0; i < param->notify.value_len; i++) { resp.data.push_back(param->notify.value[i]); } - api::global_api_server->send_bluetooth_gatt_notify_data_response(resp); + this->proxy_->get_api_connection()->send_bluetooth_gatt_notify_data_response(resp); break; } default: @@ -166,10 +159,9 @@ void BluetoothConnection::gap_event_handler(esp_gap_ble_cb_event_t event, esp_bl if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0) break; if (param->ble_security.auth_cmpl.success) { - api::global_api_server->send_bluetooth_device_pairing(this->address_, true); + this->proxy_->send_device_pairing(this->address_, true); } else { - api::global_api_server->send_bluetooth_device_pairing(this->address_, false, - param->ble_security.auth_cmpl.fail_reason); + this->proxy_->send_device_pairing(this->address_, false, param->ble_security.auth_cmpl.fail_reason); } break; default: diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index 76950c944e..b633fe2430 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -1,11 +1,10 @@ #include "bluetooth_proxy.h" #include "esphome/core/log.h" +#include "esphome/core/macros.h" #ifdef USE_ESP32 -#include "esphome/components/api/api_server.h" - namespace esphome { namespace bluetooth_proxy { @@ -27,15 +26,39 @@ std::vector get_128bit_uuid_vec(esp_bt_uuid_t uuid_source) { BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; } bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { - if (!api::global_api_server->is_connected()) + if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || this->raw_advertisements_) return false; + ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(), device.get_rssi()); this->send_api_packet_(device); - return true; } +bool BluetoothProxy::parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) { + if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || !this->raw_advertisements_) + return false; + + api::BluetoothLERawAdvertisementsResponse resp; + for (size_t i = 0; i < count; i++) { + auto &result = advertisements[i]; + api::BluetoothLERawAdvertisement adv; + adv.address = esp32_ble::ble_addr_to_uint64(result.bda); + adv.rssi = result.rssi; + adv.address_type = result.ble_addr_type; + + uint8_t length = result.adv_data_len + result.scan_rsp_len; + adv.data.reserve(length); + for (uint16_t i = 0; i < length; i++) { + adv.data.push_back(result.ble_adv[i]); + } + + resp.advertisements.push_back(std::move(adv)); + } + ESP_LOGV(TAG, "Proxying %d packets", count); + this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp); + return true; +} void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) { api::BluetoothLEAdvertisementResponse resp; resp.address = device.address_uint64(); @@ -58,7 +81,7 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi manufacturer_data.data.assign(data.data.begin(), data.data.end()); resp.manufacturer_data.push_back(std::move(manufacturer_data)); } - api::global_api_server->send_bluetooth_le_advertisement(resp); + this->api_connection_->send_bluetooth_le_advertisement(resp); } void BluetoothProxy::dump_config() { @@ -81,7 +104,7 @@ int BluetoothProxy::get_bluetooth_connections_free() { } void BluetoothProxy::loop() { - if (!api::global_api_server->is_connected()) { + if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) { for (auto *connection : this->connections_) { if (connection->get_address() != 0) { connection->disconnect(); @@ -92,7 +115,7 @@ void BluetoothProxy::loop() { for (auto *connection : this->connections_) { if (connection->send_service_ == connection->service_count_) { connection->send_service_ = DONE_SENDING_SERVICES; - api::global_api_server->send_bluetooth_gatt_services_done(connection->get_address()); + this->send_gatt_services_done(connection->get_address()); if (connection->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE || connection->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) { connection->release_services(); @@ -170,7 +193,7 @@ void BluetoothProxy::loop() { service_resp.characteristics.push_back(std::move(characteristic_resp)); } resp.services.push_back(std::move(service_resp)); - api::global_api_server->send_bluetooth_gatt_services(resp); + this->api_connection_->send_bluetooth_gatt_get_services_response(resp); } } } @@ -208,16 +231,15 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest auto *connection = this->get_connection_(msg.address, true); if (connection == nullptr) { ESP_LOGW(TAG, "No free connections available"); - api::global_api_server->send_bluetooth_device_connection(msg.address, false); + this->send_device_connection(msg.address, false); return; } if (connection->state() == espbt::ClientState::CONNECTED || connection->state() == espbt::ClientState::ESTABLISHED) { ESP_LOGW(TAG, "[%d] [%s] Connection already established", connection->get_connection_index(), connection->address_str().c_str()); - api::global_api_server->send_bluetooth_device_connection(msg.address, true); - api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(), - this->get_bluetooth_connections_limit()); + this->send_device_connection(msg.address, true); + this->send_connections_free(); return; } else if (connection->state() == espbt::ClientState::SEARCHING) { ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, already searching for device", @@ -263,25 +285,22 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest } else { connection->set_state(espbt::ClientState::SEARCHING); } - api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(), - this->get_bluetooth_connections_limit()); + this->send_connections_free(); break; } case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: { auto *connection = this->get_connection_(msg.address, false); if (connection == nullptr) { - api::global_api_server->send_bluetooth_device_connection(msg.address, false); - api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(), - this->get_bluetooth_connections_limit()); + this->send_device_connection(msg.address, false); + this->send_connections_free(); return; } if (connection->state() != espbt::ClientState::IDLE) { connection->disconnect(); } else { connection->set_address(0); - api::global_api_server->send_bluetooth_device_connection(msg.address, false); - api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(), - this->get_bluetooth_connections_limit()); + this->send_device_connection(msg.address, false); + this->send_connections_free(); } break; } @@ -291,10 +310,10 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest if (!connection->is_paired()) { auto err = connection->pair(); if (err != ESP_OK) { - api::global_api_server->send_bluetooth_device_pairing(msg.address, false, err); + this->send_device_pairing(msg.address, false, err); } } else { - api::global_api_server->send_bluetooth_device_pairing(msg.address, true); + this->send_device_pairing(msg.address, true); } } break; @@ -303,14 +322,20 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest esp_bd_addr_t address; uint64_to_bd_addr(msg.address, address); esp_err_t ret = esp_ble_remove_bond_device(address); - api::global_api_server->send_bluetooth_device_unpairing(msg.address, ret == ESP_OK, ret); + this->send_device_pairing(msg.address, ret == ESP_OK, ret); break; } case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE: { esp_bd_addr_t address; uint64_to_bd_addr(msg.address, address); esp_err_t ret = esp_ble_gattc_cache_clean(address); - api::global_api_server->send_bluetooth_device_clear_cache(msg.address, ret == ESP_OK, ret); + api::BluetoothDeviceClearCacheResponse call; + call.address = msg.address; + call.success = ret == ESP_OK; + call.error = ret; + + this->api_connection_->send_bluetooth_device_clear_cache_response(call); + break; } } @@ -320,13 +345,13 @@ void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &ms auto *connection = this->get_connection_(msg.address, false); if (connection == nullptr) { ESP_LOGW(TAG, "Cannot read GATT characteristic, not connected"); - api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED); + this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED); return; } auto err = connection->read_characteristic(msg.handle); if (err != ESP_OK) { - api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err); + this->send_gatt_error(msg.address, msg.handle, err); } } @@ -334,13 +359,13 @@ void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest & auto *connection = this->get_connection_(msg.address, false); if (connection == nullptr) { ESP_LOGW(TAG, "Cannot write GATT characteristic, not connected"); - api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED); + this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED); return; } auto err = connection->write_characteristic(msg.handle, msg.data, msg.response); if (err != ESP_OK) { - api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err); + this->send_gatt_error(msg.address, msg.handle, err); } } @@ -348,13 +373,13 @@ void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTRead auto *connection = this->get_connection_(msg.address, false); if (connection == nullptr) { ESP_LOGW(TAG, "Cannot read GATT descriptor, not connected"); - api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED); + this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED); return; } auto err = connection->read_descriptor(msg.handle); if (err != ESP_OK) { - api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err); + this->send_gatt_error(msg.address, msg.handle, err); } } @@ -362,13 +387,13 @@ void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWri auto *connection = this->get_connection_(msg.address, false); if (connection == nullptr) { ESP_LOGW(TAG, "Cannot write GATT descriptor, not connected"); - api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED); + this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED); return; } auto err = connection->write_descriptor(msg.handle, msg.data, true); if (err != ESP_OK) { - api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err); + this->send_gatt_error(msg.address, msg.handle, err); } } @@ -376,12 +401,12 @@ void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetSer auto *connection = this->get_connection_(msg.address, false); if (connection == nullptr || !connection->connected()) { ESP_LOGW(TAG, "Cannot get GATT services, not connected"); - api::global_api_server->send_bluetooth_gatt_error(msg.address, 0, ESP_GATT_NOT_CONNECTED); + this->send_gatt_error(msg.address, 0, ESP_GATT_NOT_CONNECTED); return; } if (!connection->service_count_) { ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->connection_index_, connection->address_str().c_str()); - api::global_api_server->send_bluetooth_gatt_services_done(msg.address); + this->send_gatt_services_done(msg.address); return; } if (connection->send_service_ == @@ -393,16 +418,89 @@ void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest auto *connection = this->get_connection_(msg.address, false); if (connection == nullptr) { ESP_LOGW(TAG, "Cannot notify GATT characteristic, not connected"); - api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED); + this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED); return; } auto err = connection->notify_characteristic(msg.handle, msg.enable); if (err != ESP_OK) { - api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err); + this->send_gatt_error(msg.address, msg.handle, err); } } +void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection, uint32_t flags) { + if (this->api_connection_ != nullptr) { + ESP_LOGE(TAG, "Only one API subscription is allowed at a time"); + return; + } + this->api_connection_ = api_connection; + this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS; +} + +void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connection) { + if (this->api_connection_ != api_connection) { + ESP_LOGV(TAG, "API connection is not subscribed"); + return; + } + this->api_connection_ = nullptr; + this->raw_advertisements_ = false; +} + +void BluetoothProxy::send_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) { + if (this->api_connection_ == nullptr) + return; + api::BluetoothDeviceConnectionResponse call; + call.address = address; + call.connected = connected; + call.mtu = mtu; + call.error = error; + this->api_connection_->send_bluetooth_device_connection_response(call); +} +void BluetoothProxy::send_connections_free() { + if (this->api_connection_ == nullptr) + return; + api::BluetoothConnectionsFreeResponse call; + call.free = this->get_bluetooth_connections_free(); + call.limit = this->get_bluetooth_connections_limit(); + this->api_connection_->send_bluetooth_connections_free_response(call); +} + +void BluetoothProxy::send_gatt_services_done(uint64_t address) { + if (this->api_connection_ == nullptr) + return; + api::BluetoothGATTGetServicesDoneResponse call; + call.address = address; + this->api_connection_->send_bluetooth_gatt_get_services_done_response(call); +} + +void BluetoothProxy::send_gatt_error(uint64_t address, uint16_t handle, esp_err_t error) { + if (this->api_connection_ == nullptr) + return; + api::BluetoothGATTErrorResponse call; + call.address = address; + call.handle = handle; + call.error = error; + this->api_connection_->send_bluetooth_gatt_error_response(call); +} + +void BluetoothProxy::send_device_pairing(uint64_t address, bool paired, esp_err_t error) { + api::BluetoothDevicePairingResponse call; + call.address = address; + call.paired = paired; + call.error = error; + + this->api_connection_->send_bluetooth_device_pairing_response(call); +} + +void BluetoothProxy::send_device_unpairing(uint64_t address, bool success, esp_err_t error) { + api::BluetoothDeviceUnpairingResponse call; + call.address = address; + call.success = success; + call.error = error; + + this->api_connection_->send_bluetooth_device_unpairing_response(call); +} + BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace bluetooth_proxy diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index a582abc8a3..97b6396b55 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -5,6 +5,7 @@ #include #include +#include "esphome/components/api/api_connection.h" #include "esphome/components/api/api_pb2.h" #include "esphome/components/esp32_ble_client/ble_client_base.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" @@ -21,10 +22,33 @@ static const esp_err_t ESP_GATT_NOT_CONNECTED = -1; using namespace esp32_ble_client; +// Legacy versions: +// Version 1: Initial version without active connections +// Version 2: Support for active connections +// Version 3: New connection API +// Version 4: Pairing support +// Version 5: Cache clear support +static const uint32_t LEGACY_ACTIVE_CONNECTIONS_VERSION = 5; +static const uint32_t LEGACY_PASSIVE_ONLY_VERSION = 1; + +enum BluetoothProxyFeature : uint32_t { + FEATURE_PASSIVE_SCAN = 1 << 0, + FEATURE_ACTIVE_CONNECTIONS = 1 << 1, + FEATURE_REMOTE_CACHING = 1 << 2, + FEATURE_PAIRING = 1 << 3, + FEATURE_CACHE_CLEARING = 1 << 4, + FEATURE_RAW_ADVERTISEMENTS = 1 << 5, +}; + +enum BluetoothProxySubscriptionFlag : uint32_t { + SUBSCRIPTION_RAW_ADVERTISEMENTS = 1 << 0, +}; + class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component { public: BluetoothProxy(); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; + bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) override; void dump_config() override; void loop() override; @@ -44,6 +68,18 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com int get_bluetooth_connections_free(); int get_bluetooth_connections_limit() { return this->connections_.size(); } + void subscribe_api_connection(api::APIConnection *api_connection, uint32_t flags); + void unsubscribe_api_connection(api::APIConnection *api_connection); + api::APIConnection *get_api_connection() { return this->api_connection_; } + + void send_device_connection(uint64_t address, bool connected, uint16_t mtu = 0, esp_err_t error = ESP_OK); + void send_connections_free(); + void send_gatt_services_done(uint64_t address); + void send_gatt_error(uint64_t address, uint16_t handle, esp_err_t error); + void send_device_pairing(uint64_t address, bool paired, esp_err_t error = ESP_OK); + void send_device_unpairing(uint64_t address, bool success, esp_err_t error = ESP_OK); + void send_device_clear_cache(uint64_t address, bool success, esp_err_t error = ESP_OK); + static void uint64_to_bd_addr(uint64_t address, esp_bd_addr_t bd_addr) { bd_addr[0] = (address >> 40) & 0xff; bd_addr[1] = (address >> 32) & 0xff; @@ -56,6 +92,27 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com void set_active(bool active) { this->active_ = active; } bool has_active() { return this->active_; } + uint32_t get_legacy_version() const { + if (this->active_) { + return LEGACY_ACTIVE_CONNECTIONS_VERSION; + } + return LEGACY_PASSIVE_ONLY_VERSION; + } + + uint32_t get_feature_flags() const { + uint32_t flags = 0; + flags |= BluetoothProxyFeature::FEATURE_PASSIVE_SCAN; + flags |= BluetoothProxyFeature::FEATURE_RAW_ADVERTISEMENTS; + if (this->active_) { + flags |= BluetoothProxyFeature::FEATURE_ACTIVE_CONNECTIONS; + flags |= BluetoothProxyFeature::FEATURE_REMOTE_CACHING; + flags |= BluetoothProxyFeature::FEATURE_PAIRING; + flags |= BluetoothProxyFeature::FEATURE_CACHE_CLEARING; + } + + return flags; + } + protected: void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device); @@ -64,18 +121,12 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com bool active_; std::vector connections_{}; + api::APIConnection *api_connection_{nullptr}; + bool raw_advertisements_{false}; }; extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -// Version 1: Initial version without active connections -// Version 2: Support for active connections -// Version 3: New connection API -// Version 4: Pairing support -// Version 5: Cache clear support -static const uint32_t ACTIVE_CONNECTIONS_VERSION = 5; -static const uint32_t PASSIVE_ONLY_VERSION = 1; - } // namespace bluetooth_proxy } // namespace esphome diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 21ec005e07..6c9124447a 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -244,6 +244,17 @@ void ESP32BLE::dump_config() { } } +uint64_t ble_addr_to_uint64(const esp_bd_addr_t address) { + uint64_t u = 0; + u |= uint64_t(address[0] & 0xFF) << 40; + u |= uint64_t(address[1] & 0xFF) << 32; + u |= uint64_t(address[2] & 0xFF) << 24; + u |= uint64_t(address[3] & 0xFF) << 16; + u |= uint64_t(address[4] & 0xFF) << 8; + u |= uint64_t(address[5] & 0xFF) << 0; + return u; +} + ESP32BLE *global_ble = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace esp32_ble diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 11ae826544..cde17da425 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -18,6 +18,8 @@ namespace esphome { namespace esp32_ble { +uint64_t ble_addr_to_uint64(const esp_bd_addr_t address); + // NOLINTNEXTLINE(modernize-use-using) typedef struct { void *peer_device; diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 26687ba9cc..0f6c4117d2 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -34,17 +34,6 @@ static const char *const TAG = "esp32_ble_tracker"; ESP32BLETracker *global_esp32_ble_tracker = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -uint64_t ble_addr_to_uint64(const esp_bd_addr_t address) { - uint64_t u = 0; - u |= uint64_t(address[0] & 0xFF) << 40; - u |= uint64_t(address[1] & 0xFF) << 32; - u |= uint64_t(address[2] & 0xFF) << 24; - u |= uint64_t(address[3] & 0xFF) << 16; - u |= uint64_t(address[4] & 0xFF) << 8; - u |= uint64_t(address[5] & 0xFF) << 0; - return u; -} - float ESP32BLETracker::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } void ESP32BLETracker::setup() { @@ -114,10 +103,20 @@ void ESP32BLETracker::loop() { if (this->scan_result_index_ && // if it looks like we have a scan result we will take the lock xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) { uint32_t index = this->scan_result_index_; - if (index) { - if (index >= ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) { - ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up."); - } + if (index >= ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) { + ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up."); + } + + bool bulk_parsed = false; + + for (auto *listener : this->listeners_) { + bulk_parsed |= listener->parse_devices(this->scan_result_buffer_, this->scan_result_index_); + } + for (auto *client : this->clients_) { + bulk_parsed |= client->parse_devices(this->scan_result_buffer_, this->scan_result_index_); + } + + if (!bulk_parsed) { for (size_t i = 0; i < index; i++) { ESPBTDevice device; device.parse_scan_rst(this->scan_result_buffer_[i]); @@ -141,8 +140,8 @@ void ESP32BLETracker::loop() { this->print_bt_device_info(device); } } - this->scan_result_index_ = 0; } + this->scan_result_index_ = 0; xSemaphoreGive(this->scan_result_lock_); } @@ -585,7 +584,7 @@ std::string ESPBTDevice::address_str() const { this->address_[3], this->address_[4], this->address_[5]); return mac; } -uint64_t ESPBTDevice::address_uint64() const { return ble_addr_to_uint64(this->address_); } +uint64_t ESPBTDevice::address_uint64() const { return esp32_ble::ble_addr_to_uint64(this->address_); } void ESP32BLETracker::dump_config() { ESP_LOGCONFIG(TAG, "BLE Tracker:"); diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 798f68f2bd..43e88fbf2b 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -113,6 +113,9 @@ class ESPBTDeviceListener { public: virtual void on_scan_end() {} virtual bool parse_device(const ESPBTDevice &device) = 0; + virtual bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) { + return false; + }; void set_parent(ESP32BLETracker *parent) { parent_ = parent; } protected: