diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index eb639f2065..1cebdd0cbe 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1339,3 +1339,23 @@ message BluetoothGATTNotifyResponse { uint64 address = 1; uint32 handle = 2; } + +message BluetoothDevicePairingResponse { + option (id) = 85; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + bool paired = 2; + int32 error = 3; +} + +message BluetoothDeviceUnpairingResponse { + option (id) = 86; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + bool success = 2; + int32 error = 3; +} diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 487aa53193..40a5a230a5 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -953,7 +953,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() ? 3 : 1; + resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? 4 : 1; #endif return resp; } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 3fc1bfa95d..381f8b3c46 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -5974,6 +5974,92 @@ void BluetoothGATTNotifyResponse::dump_to(std::string &out) const { out.append("}"); } #endif +bool BluetoothDevicePairingResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->paired = value.as_bool(); + return true; + } + case 3: { + this->error = value.as_int32(); + return true; + } + default: + return false; + } +} +void BluetoothDevicePairingResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_bool(2, this->paired); + buffer.encode_int32(3, this->error); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothDevicePairingResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothDevicePairingResponse {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" paired: "); + out.append(YESNO(this->paired)); + out.append("\n"); + + out.append(" error: "); + sprintf(buffer, "%d", this->error); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothDeviceUnpairingResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->success = value.as_bool(); + return true; + } + case 3: { + this->error = value.as_int32(); + return true; + } + default: + return false; + } +} +void BluetoothDeviceUnpairingResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_bool(2, this->success); + buffer.encode_int32(3, this->error); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothDeviceUnpairingResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothDeviceUnpairingResponse {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" success: "); + out.append(YESNO(this->success)); + out.append("\n"); + + out.append(" error: "); + sprintf(buffer, "%d", this->error); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index e192892e72..e9025142e9 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1528,6 +1528,32 @@ class BluetoothGATTNotifyResponse : public ProtoMessage { protected: bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; +class BluetoothDevicePairingResponse : public ProtoMessage { + public: + uint64_t address{0}; + bool paired{false}; + int32_t error{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 BluetoothDeviceUnpairingResponse : public ProtoMessage { + public: + uint64_t address{0}; + bool success{false}; + int32_t error{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; +}; } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index b603ade9de..7ee9e56192 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -425,6 +425,22 @@ bool APIServerConnectionBase::send_bluetooth_gatt_notify_response(const Bluetoot return this->send_message_(msg, 84); } #endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_device_pairing_response(const BluetoothDevicePairingResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_device_pairing_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 85); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_device_unpairing_response(const BluetoothDeviceUnpairingResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_device_unpairing_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 86); +} +#endif bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { switch (msg_type) { case 1: { diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 3cb8b59ba5..f1879b2dba 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -209,6 +209,12 @@ class APIServerConnectionBase : public ProtoService { #endif #ifdef USE_BLUETOOTH_PROXY bool send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_device_pairing_response(const BluetoothDevicePairingResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_device_unpairing_response(const BluetoothDeviceUnpairingResponse &msg); #endif protected: bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index dbd732f466..6e28637241 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -309,6 +309,28 @@ void APIServer::send_bluetooth_device_connection(uint64_t address, bool connecte } } +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_connections_free(uint8_t free, uint8_t limit) { BluetoothConnectionsFreeResponse call; call.free = free; diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 8e69a77475..5f92e6b058 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -78,6 +78,8 @@ class APIServer : public Component, public Controller { #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_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); diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index 09611b6174..9354ab36d6 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -158,6 +158,25 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga return true; } +void BluetoothConnection::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { + BLEClientBase::gap_event_handler(event, param); + + switch (event) { + case ESP_GAP_BLE_AUTH_CMPL_EVT: + 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); + } else { + api::global_api_server->send_bluetooth_device_pairing(this->address_, false, + param->ble_security.auth_cmpl.fail_reason); + } + break; + default: + break; + } +} + esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) { if (!this->connected()) { ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT characteristic, not connected.", this->connection_index_, diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.h b/esphome/components/bluetooth_proxy/bluetooth_connection.h index fde074d17f..8b13f4d1c2 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.h +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.h @@ -13,6 +13,7 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase { public: bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override; + void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; esp_err_t read_characteristic(uint16_t handle); esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response); diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index 017e1bf83f..55fabf05ef 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -257,12 +257,7 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest 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; - connection->remote_bda_[2] = (msg.address >> 24) & 0xFF; - connection->remote_bda_[3] = (msg.address >> 16) & 0xFF; - connection->remote_bda_[4] = (msg.address >> 8) & 0xFF; - connection->remote_bda_[5] = (msg.address >> 0) & 0xFF; + uint64_to_bd_addr(msg.address, connection->remote_bda_); connection->set_remote_addr_type(static_cast(msg.address_type)); connection->set_state(espbt::ClientState::DISCOVERED); } else { @@ -290,9 +285,27 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest } break; } - case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR: - case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR: + case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR: { + auto *connection = this->get_connection_(msg.address, false); + if (connection != nullptr) { + if (!connection->is_paired()) { + auto err = connection->pair(); + if (err != ESP_OK) { + api::global_api_server->send_bluetooth_device_pairing(msg.address, false, err); + } + } else { + api::global_api_server->send_bluetooth_device_pairing(msg.address, true); + } + } break; + } + case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR: { + 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); + break; + } } } diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index 5d3b385bec..b99e9a8527 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -44,6 +44,15 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com int get_bluetooth_connections_free(); int get_bluetooth_connections_limit() { return this->connections_.size(); } + 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; + bd_addr[2] = (address >> 24) & 0xff; + bd_addr[3] = (address >> 16) & 0xff; + bd_addr[4] = (address >> 8) & 0xff; + bd_addr[5] = (address >> 0) & 0xff; + } + void set_active(bool active) { this->active_ = active; } bool has_active() { return this->active_; } diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index 2793a74c5a..9ca82c7239 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -62,6 +62,7 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) { void BLEClientBase::connect() { ESP_LOGI(TAG, "[%d] [%s] 0x%02x Attempting BLE connection", this->connection_index_, this->address_str_.c_str(), this->remote_addr_type_); + this->paired_ = false; auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true); if (ret) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_open error, status=%d", this->connection_index_, this->address_str_.c_str(), @@ -72,6 +73,8 @@ void BLEClientBase::connect() { } } +esp_err_t BLEClientBase::pair() { return esp_ble_set_encryption(this->remote_bda_, ESP_BLE_SEC_ENCRYPT); } + void BLEClientBase::disconnect() { if (this->state_ == espbt::ClientState::IDLE || this->state_ == espbt::ClientState::DISCONNECTING) return; @@ -247,11 +250,15 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_ switch (event) { // This event is sent by the server when it requests security case ESP_GAP_BLE_SEC_REQ_EVT: + if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0) + break; ESP_LOGV(TAG, "[%d] [%s] ESP_GAP_BLE_SEC_REQ_EVT %x", this->connection_index_, this->address_str_.c_str(), event); esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); break; // This event is sent once authentication has completed case ESP_GAP_BLE_AUTH_CMPL_EVT: + if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0) + break; esp_bd_addr_t bd_addr; memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); ESP_LOGI(TAG, "[%d] [%s] auth complete. remote BD_ADDR: %s", this->connection_index_, this->address_str_.c_str(), @@ -260,6 +267,7 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_ ESP_LOGE(TAG, "[%d] [%s] auth fail reason = 0x%x", this->connection_index_, this->address_str_.c_str(), param->ble_security.auth_cmpl.fail_reason); } else { + this->paired_ = true; ESP_LOGV(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_, this->address_str_.c_str(), param->ble_security.auth_cmpl.addr_type, param->ble_security.auth_cmpl.auth_mode); diff --git a/esphome/components/esp32_ble_client/ble_client_base.h b/esphome/components/esp32_ble_client/ble_client_base.h index 9adf239512..2879da4d8c 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.h +++ b/esphome/components/esp32_ble_client/ble_client_base.h @@ -33,6 +33,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { esp_ble_gattc_cb_param_t *param) override; void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; void connect() override; + esp_err_t pair(); void disconnect(); void release_services(); @@ -71,6 +72,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { void set_remote_addr_type(esp_ble_addr_type_t address_type) { this->remote_addr_type_ = address_type; } uint16_t get_conn_id() const { return this->conn_id_; } uint64_t get_address() const { return this->address_; } + bool is_paired() const { return this->paired_; } uint8_t get_connection_index() const { return this->connection_index_; } @@ -86,6 +88,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { uint8_t connection_index_; int16_t service_count_{0}; uint16_t mtu_{23}; + bool paired_{false}; espbt::ConnectionType connection_type_{espbt::ConnectionType::V1}; std::vector services_;