diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 3a6f514f91..e1bc7b0a57 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1150,6 +1150,8 @@ enum BluetoothDeviceRequestType { BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1; BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2; BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3; + BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE = 4; + BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5; } message BluetoothDeviceRequest { diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index d56f4e7a40..aac58587d1 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -950,7 +950,7 @@ 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() ? 2 : 1; + resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? 3 : 1; #endif return resp; } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index e82a77974e..1401cc00f7 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -6,6 +6,7 @@ namespace esphome { namespace api { +#ifdef HAS_PROTO_MESSAGE_DUMP template<> const char *proto_enum_to_string(enums::EntityCategory value) { switch (value) { case enums::ENTITY_CATEGORY_NONE: @@ -351,10 +352,15 @@ const char *proto_enum_to_string(enums::Bluet return "BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR"; case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR: return "BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR"; + case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE: + return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE"; + case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE: + return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE"; default: return "UNKNOWN"; } } +#endif bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 67ffa5c2dd..8a78f1ad03 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -160,6 +160,8 @@ enum BluetoothDeviceRequestType : uint32_t { BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1, BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2, BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3, + BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE = 4, + BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5, }; } // namespace enums diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index 488d56bc95..09611b6174 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -34,6 +34,10 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga this->set_address(0); api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(), this->proxy_->get_bluetooth_connections_limit()); + } 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->seen_mtu_or_services_ = false; break; diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index 567874e5a2..fe873665e8 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -80,6 +80,10 @@ void BluetoothProxy::loop() { if (connection->send_service_ == connection->services_.size()) { connection->send_service_ = -1; api::global_api_server->send_bluetooth_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(); + } } else if (connection->send_service_ >= 0) { auto &service = connection->services_[connection->send_service_]; api::BluetoothGATTGetServicesResponse resp; @@ -171,6 +175,8 @@ BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool rese void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest &msg) { switch (msg.request_type) { + case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE: + case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE: case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: { auto *connection = this->get_connection_(msg.address, true); if (connection == nullptr) { @@ -186,11 +192,43 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(), this->get_bluetooth_connections_limit()); return; + } else if (connection->state() == espbt::ClientState::SEARCHING) { + ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, already searching for device", + connection->get_connection_index(), connection->address_str().c_str()); + return; + } else if (connection->state() == espbt::ClientState::DISCOVERED) { + ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, device already discovered", + connection->get_connection_index(), connection->address_str().c_str()); + return; + } else if (connection->state() == espbt::ClientState::READY_TO_CONNECT) { + ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, waiting in line to connect", + connection->get_connection_index(), connection->address_str().c_str()); + return; + } else if (connection->state() == espbt::ClientState::CONNECTING) { + ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, already connecting", connection->get_connection_index(), + connection->address_str().c_str()); + return; + } else if (connection->state() == espbt::ClientState::DISCONNECTING) { + ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, device is disconnecting", + connection->get_connection_index(), connection->address_str().c_str()); + return; } else if (connection->state() != espbt::ClientState::INIT) { ESP_LOGW(TAG, "[%d] [%s] Connection already in progress", connection->get_connection_index(), connection->address_str().c_str()); return; } + if (msg.request_type == api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE) { + connection->set_connection_type(espbt::ConnectionType::V3_WITH_CACHE); + ESP_LOGI(TAG, "[%d] [%s] Connecting v3 with cache", connection->get_connection_index(), + connection->address_str().c_str()); + } else if (msg.request_type == api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE) { + connection->set_connection_type(espbt::ConnectionType::V3_WITHOUT_CACHE); + ESP_LOGI(TAG, "[%d] [%s] Connecting v3 without cache", connection->get_connection_index(), + connection->address_str().c_str()); + } else { + connection->set_connection_type(espbt::ConnectionType::V1); + ESP_LOGI(TAG, "[%d] [%s] Connecting v1", connection->get_connection_index(), connection->address_str().c_str()); + } if (msg.has_address_type) { connection->remote_bda_[0] = (msg.address >> 40) & 0xFF; connection->remote_bda_[1] = (msg.address >> 32) & 0xFF; diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index 065d56e5fb..74f030dafd 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -129,6 +129,11 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ this->set_state(espbt::ClientState::IDLE); break; } + if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) { + this->set_state(espbt::ClientState::CONNECTED); + this->state_ = espbt::ClientState::ESTABLISHED; + break; + } auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->open.conn_id); if (ret) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_send_mtu_req failed, status=%x", this->connection_index_, @@ -180,6 +185,12 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; } case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE || + this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) { + // Client is responsible for flipping the descriptor value + // when using the cache + break; + } esp_gattc_descr_elem_t desc_result; uint16_t count = 1; esp_gatt_status_t descr_status = diff --git a/esphome/components/esp32_ble_client/ble_client_base.h b/esphome/components/esp32_ble_client/ble_client_base.h index 7dd740b703..f536c24d98 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.h +++ b/esphome/components/esp32_ble_client/ble_client_base.h @@ -74,6 +74,8 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { uint8_t get_connection_index() const { return this->connection_index_; } + virtual void set_connection_type(espbt::ConnectionType ct) { this->connection_type_ = ct; } + protected: int gattc_if_; esp_bd_addr_t remote_bda_; @@ -83,6 +85,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { std::string address_str_{}; uint8_t connection_index_; uint16_t mtu_{23}; + espbt::ConnectionType connection_type_{espbt::ConnectionType::V1}; std::vector services_; }; diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 0e980e9923..9052e620cf 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -165,6 +165,18 @@ enum class ClientState { ESTABLISHED, }; +enum class ConnectionType { + // The default connection type, we hold all the services in ram + // for the duration of the connection. + V1, + // The client has a cache of the services and mtu so we should not + // fetch them again + V3_WITH_CACHE, + // The client does not need the services and mtu once we send them + // so we should wipe them from memory as soon as we send them + V3_WITHOUT_CACHE +}; + class ESPBTClient : public ESPBTDeviceListener { public: virtual bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,