From ed443c6153c56eedd5671b7fa545806c5350ff1c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 4 Oct 2022 10:45:06 +1300 Subject: [PATCH] Bluetooth Proxy active connections (#3817) --- CODEOWNERS | 1 + .../airthings_wave_mini.cpp | 6 +- .../airthings_wave_plus.cpp | 6 +- esphome/components/am43/am43.cpp | 10 +- esphome/components/am43/cover/am43_cover.cpp | 21 +- esphome/components/anova/anova.cpp | 22 +- esphome/components/api/api.proto | 177 +++- esphome/components/api/api_connection.cpp | 72 +- esphome/components/api/api_connection.h | 31 +- esphome/components/api/api_pb2.cpp | 764 +++++++++++++++++- esphome/components/api/api_pb2.h | 231 +++++- esphome/components/api/api_pb2_service.cpp | 285 ++++++- esphome/components/api/api_pb2_service.h | 95 ++- esphome/components/api/api_server.cpp | 45 ++ esphome/components/api/api_server.h | 6 + esphome/components/bedjet/bedjet_hub.cpp | 48 +- esphome/components/bedjet/bedjet_hub.h | 2 - esphome/components/ble_client/__init__.py | 7 +- esphome/components/ble_client/automation.cpp | 6 +- esphome/components/ble_client/automation.h | 3 +- esphome/components/ble_client/ble_client.cpp | 409 +--------- esphome/components/ble_client/ble_client.h | 86 +- .../components/ble_client/sensor/automation.h | 3 +- .../ble_client/sensor/ble_sensor.cpp | 12 +- .../ble_client/text_sensor/automation.h | 3 +- .../text_sensor/ble_text_sensor.cpp | 12 +- .../components/bluetooth_proxy/__init__.py | 16 +- .../bluetooth_proxy/bluetooth_proxy.cpp | 323 +++++++- .../bluetooth_proxy/bluetooth_proxy.h | 39 +- .../components/esp32_ble_client/__init__.py | 12 + .../esp32_ble_client/ble_characteristic.cpp | 83 ++ .../esp32_ble_client/ble_characteristic.h | 35 + .../esp32_ble_client/ble_client_base.cpp | 324 ++++++++ .../esp32_ble_client/ble_client_base.h | 72 ++ .../esp32_ble_client/ble_descriptor.h | 25 + .../esp32_ble_client/ble_service.cpp | 66 ++ .../components/esp32_ble_client/ble_service.h | 32 + .../components/esp32_ble_tracker/__init__.py | 2 +- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 37 +- .../esp32_ble_tracker/esp32_ble_tracker.h | 5 +- esphome/components/esp32_ble_tracker/queue.h | 10 +- .../display/pvvx_display.cpp | 5 +- .../radon_eye_rd200/radon_eye_rd200.cpp | 12 +- esphome/const.py | 1 + 44 files changed, 2818 insertions(+), 644 deletions(-) create mode 100644 esphome/components/esp32_ble_client/__init__.py create mode 100644 esphome/components/esp32_ble_client/ble_characteristic.cpp create mode 100644 esphome/components/esp32_ble_client/ble_characteristic.h create mode 100644 esphome/components/esp32_ble_client/ble_client_base.cpp create mode 100644 esphome/components/esp32_ble_client/ble_client_base.h create mode 100644 esphome/components/esp32_ble_client/ble_descriptor.h create mode 100644 esphome/components/esp32_ble_client/ble_service.cpp create mode 100644 esphome/components/esp32_ble_client/ble_service.h diff --git a/CODEOWNERS b/CODEOWNERS index 69e30027a9..d95ebcce59 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -70,6 +70,7 @@ esphome/components/ektf2232/* @jesserockz esphome/components/ens210/* @itn3rd77 esphome/components/esp32/* @esphome/core esphome/components/esp32_ble/* @jesserockz +esphome/components/esp32_ble_client/* @jesserockz esphome/components/esp32_ble_server/* @jesserockz esphome/components/esp32_camera_web_server/* @ayufan esphome/components/esp32_can/* @Sympatron diff --git a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp index 2e7a1fb024..40873ec005 100644 --- a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp +++ b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp @@ -38,7 +38,7 @@ void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt } case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent()->conn_id) + if (param->read.conn_id != this->parent()->get_conn_id()) break; if (param->read.status != ESP_GATT_OK) { ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); @@ -88,8 +88,8 @@ void AirthingsWaveMini::update() { } void AirthingsWaveMini::request_read_values_() { - auto status = - esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_, + ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); } diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp index 02ed33b87a..11f86307fe 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp @@ -38,7 +38,7 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt } case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent()->conn_id) + if (param->read.conn_id != this->parent()->get_conn_id()) break; if (param->read.status != ESP_GATT_OK) { ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); @@ -109,8 +109,8 @@ void AirthingsWavePlus::update() { } void AirthingsWavePlus::request_read_values_() { - auto status = - esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_, + ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); } diff --git a/esphome/components/am43/am43.cpp b/esphome/components/am43/am43.cpp index 1c4bad64c3..9a0e5999d2 100644 --- a/esphome/components/am43/am43.cpp +++ b/esphome/components/am43/am43.cpp @@ -76,9 +76,9 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i if (this->current_sensor_ > 0) { if (this->illuminance_ != nullptr) { auto *packet = this->encoder_->get_light_level_request(); - auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, - packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, - ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), + this->char_handle_, packet->length, packet->data, + ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); @@ -102,8 +102,8 @@ void Am43::update() { if (this->battery_ != nullptr) { auto *packet = this->encoder_->get_battery_level_request(); auto status = - esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, - packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); } diff --git a/esphome/components/am43/cover/am43_cover.cpp b/esphome/components/am43/cover/am43_cover.cpp index 39089e73c0..ba8e350732 100644 --- a/esphome/components/am43/cover/am43_cover.cpp +++ b/esphome/components/am43/cover/am43_cover.cpp @@ -27,8 +27,8 @@ void Am43Component::loop() { if (this->node_state == espbt::ClientState::ESTABLISHED && !this->logged_in_) { auto *packet = this->encoder_->get_send_pin_request(this->pin_); auto status = - esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, - packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); ESP_LOGI(TAG, "[%s] Logging into AM43", this->get_name().c_str()); if (status) { ESP_LOGW(TAG, "[%s] Error writing set_pin to device, error = %d", this->get_name().c_str(), status); @@ -54,8 +54,8 @@ void Am43Component::control(const CoverCall &call) { if (call.get_stop()) { auto *packet = this->encoder_->get_stop_request(); auto status = - esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, - packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) ESP_LOGW(TAG, "[%s] Error writing stop command to device, error = %d", this->get_name().c_str(), status); } @@ -66,8 +66,8 @@ void Am43Component::control(const CoverCall &call) { pos = 1 - pos; auto *packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100)); auto status = - esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, - packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) ESP_LOGW(TAG, "[%s] Error writing set_position command to device, error = %d", this->get_name().c_str(), status); } @@ -92,7 +92,8 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } this->char_handle_ = chr->handle; - auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle); + auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(), + chr->handle); if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status); } @@ -122,9 +123,9 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ if (this->decoder_->pin_ok_) { ESP_LOGI(TAG, "[%s] AM43 pin accepted.", this->get_name().c_str()); auto *packet = this->encoder_->get_position_request(); - auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, - packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, - ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), + this->char_handle_, packet->length, packet->data, + ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) ESP_LOGW(TAG, "[%s] Error writing set_position to device, error = %d", this->get_name().c_str(), status); } else { diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index 6c8316d338..cafd30149d 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -34,15 +34,17 @@ void Anova::control(const ClimateCall &call) { ESP_LOGW(TAG, "Unsupported mode: %d", mode); return; } - auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, - pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + auto status = + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); } if (call.get_target_temperature().has_value()) { auto *pkt = this->codec_->get_set_target_temp_request(*call.get_target_temperature()); - auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, - pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + auto status = + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); } @@ -65,7 +67,8 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ } this->char_handle_ = chr->handle; - auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle); + auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(), + chr->handle); if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status); } @@ -112,8 +115,8 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ } if (pkt != nullptr) { auto status = - esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, pkt->length, - pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); @@ -137,8 +140,9 @@ void Anova::update() { auto *pkt = this->codec_->get_read_device_status_request(); if (this->current_request_ == 0) this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c'); - auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, - pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + auto status = + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); this->current_request_++; diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index c6cde8b038..9fa77d2daa 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -27,7 +27,6 @@ service APIConnection { rpc subscribe_logs (SubscribeLogsRequest) returns (void) {} rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {} rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {} - rpc subscribe_bluetooth_le_advertisements (SubscribeBluetoothLEAdvertisementsRequest) returns (void) {} rpc get_time (GetTimeRequest) returns (GetTimeResponse) { option (needs_authentication) = false; } @@ -44,6 +43,16 @@ service APIConnection { rpc button_command (ButtonCommandRequest) returns (void) {} rpc lock_command (LockCommandRequest) returns (void) {} rpc media_player_command (MediaPlayerCommandRequest) returns (void) {} + + rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {} + rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {} + rpc bluetooth_gatt_get_services(BluetoothGATTGetServicesRequest) returns (void) {} + rpc bluetooth_gatt_read(BluetoothGATTReadRequest) returns (void) {} + rpc bluetooth_gatt_write(BluetoothGATTWriteRequest) returns (void) {} + rpc bluetooth_gatt_read_descriptor(BluetoothGATTReadDescriptorRequest) returns (void) {} + rpc bluetooth_gatt_write_descriptor(BluetoothGATTWriteDescriptorRequest) returns (void) {} + rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {} + rpc subscribe_bluetooth_connections_free(SubscribeBluetoothConnectionsFreeRequest) returns (BluetoothConnectionsFreeResponse) {} } @@ -78,6 +87,8 @@ message HelloRequest { // Not strictly necessary to send but nice for debugging // purposes. string client_info = 1; + uint32 api_version_major = 2; + uint32 api_version_minor = 3; } // Confirmation of successful connection request. @@ -192,7 +203,7 @@ message DeviceInfoResponse { uint32 webserver_port = 10; - bool has_bluetooth_proxy = 11; + uint32 bluetooth_proxy_version = 11; } message ListEntitiesRequest { @@ -1111,7 +1122,8 @@ message SubscribeBluetoothLEAdvertisementsRequest { message BluetoothServiceData { string uuid = 1; - repeated uint32 data = 2 [packed=false]; + repeated uint32 legacy_data = 2 [deprecated = true]; + bytes data = 3; // Changed in proto version 1.7 } message BluetoothLEAdvertisementResponse { option (id) = 67; @@ -1127,3 +1139,162 @@ message BluetoothLEAdvertisementResponse { repeated BluetoothServiceData service_data = 5; repeated BluetoothServiceData manufacturer_data = 6; } + +enum BluetoothDeviceRequestType { + BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0; + BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1; + BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2; + BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3; +} + +message BluetoothDeviceRequest { + option (id) = 68; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + BluetoothDeviceRequestType request_type = 2; +} + +message BluetoothDeviceConnectionResponse { + option (id) = 69; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + bool connected = 2; + uint32 mtu = 3; + int32 error = 4; +} + +message BluetoothGATTGetServicesRequest { + option (id) = 70; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; +} + +message BluetoothGATTDescriptor { + repeated uint64 uuid = 1; + uint32 handle = 2; +} + +message BluetoothGATTCharacteristic { + repeated uint64 uuid = 1; + uint32 handle = 2; + uint32 properties = 3; + repeated BluetoothGATTDescriptor descriptors = 4; +} + +message BluetoothGATTService { + repeated uint64 uuid = 1; + uint32 handle = 2; + repeated BluetoothGATTCharacteristic characteristics = 3; +} + +message BluetoothGATTGetServicesResponse { + option (id) = 71; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + repeated BluetoothGATTService services = 2; +} + +message BluetoothGATTGetServicesDoneResponse { + option (id) = 72; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; +} + +message BluetoothGATTReadRequest { + option (id) = 73; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; +} + +message BluetoothGATTReadResponse { + option (id) = 74; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; + + bytes data = 3; + +} + +message BluetoothGATTWriteRequest { + option (id) = 75; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; + bool response = 3; + + bytes data = 4; +} + +message BluetoothGATTReadDescriptorRequest { + option (id) = 76; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; +} + +message BluetoothGATTWriteDescriptorRequest { + option (id) = 77; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; + + bytes data = 3; +} + +message BluetoothGATTNotifyRequest { + option (id) = 78; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; + bool enable = 3; +} + +message BluetoothGATTNotifyDataResponse { + option (id) = 79; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; + + bytes data = 3; +} + +message SubscribeBluetoothConnectionsFreeRequest { + option (id) = 80; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; +} + +message BluetoothConnectionsFreeResponse { + option (id) = 81; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint32 free = 1; + uint32 limit = 2; +} diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index a3b000c778..1dbf0abec4 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1,10 +1,10 @@ #include "api_connection.h" -#include "esphome/core/entity_base.h" -#include "esphome/core/log.h" -#include "esphome/components/network/util.h" -#include "esphome/core/version.h" -#include "esphome/core/hal.h" #include +#include "esphome/components/network/util.h" +#include "esphome/core/entity_base.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "esphome/core/version.h" #ifdef USE_DEEP_SLEEP #include "esphome/components/deep_sleep/deep_sleep_component.h" @@ -12,6 +12,9 @@ #ifdef USE_HOMEASSISTANT_TIME #include "esphome/components/homeassistant/time/homeassistant_time.h" #endif +#ifdef USE_BLUETOOTH_PROXY +#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h" +#endif namespace esphome { namespace api { @@ -823,6 +826,56 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) { } #endif +#ifdef USE_BLUETOOTH_PROXY +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) { + service.legacy_data.assign(service.data.begin(), service.data.end()); + service.data.clear(); + } + for (auto &manufacturer_data : resp.manufacturer_data) { + manufacturer_data.legacy_data.assign(manufacturer_data.data.begin(), manufacturer_data.data.end()); + manufacturer_data.data.clear(); + } + return this->send_bluetooth_le_advertisement_response(resp); + } + return this->send_bluetooth_le_advertisement_response(msg); +} +void APIConnection::bluetooth_device_request(const BluetoothDeviceRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->bluetooth_device_request(msg); +} +void APIConnection::bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_read(msg); +} +void APIConnection::bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_write(msg); +} +void APIConnection::bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_read_descriptor(msg); +} +void APIConnection::bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_write_descriptor(msg); +} +void APIConnection::bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_send_services(msg); +} + +void APIConnection::bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_notify(msg); +} + +BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_free( + const SubscribeBluetoothConnectionsFreeRequest &msg) { + BluetoothConnectionsFreeResponse resp; + resp.free = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_free(); + resp.limit = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_limit(); + return resp; +} +#endif + bool APIConnection::send_log_message(int level, const char *tag, const char *line) { if (this->log_subscription_ < level) return false; @@ -840,11 +893,14 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin HelloResponse APIConnection::hello(const HelloRequest &msg) { this->client_info_ = msg.client_info + " (" + this->helper_->getpeername() + ")"; this->helper_->set_log_info(client_info_); - ESP_LOGV(TAG, "Hello from client: '%s'", this->client_info_.c_str()); + this->client_api_version_major_ = msg.api_version_major; + this->client_api_version_minor_ = msg.api_version_minor; + ESP_LOGV(TAG, "Hello from client: '%s' | API Version %d.%d", this->client_info_.c_str(), + this->client_api_version_major_, this->client_api_version_minor_); HelloResponse resp; resp.api_version_major = 1; - resp.api_version_minor = 6; + resp.api_version_minor = 7; resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; resp.name = App.get_name(); @@ -888,7 +944,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { resp.webserver_port = USE_WEBSERVER_PORT; #endif #ifdef USE_BLUETOOTH_PROXY - resp.has_bluetooth_proxy = true; + resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? 2 : 1; #endif return resp; } diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index dcf8bacad2..028fb80175 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -1,15 +1,11 @@ #pragma once -#include "esphome/core/component.h" -#include "esphome/core/application.h" +#include "api_frame_helper.h" #include "api_pb2.h" #include "api_pb2_service.h" #include "api_server.h" -#include "api_frame_helper.h" - -#ifdef USE_BLUETOOTH_PROXY -#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h" -#endif +#include "esphome/core/application.h" +#include "esphome/core/component.h" namespace esphome { namespace api { @@ -99,11 +95,18 @@ class APIConnection : public APIServerConnection { this->send_homeassistant_service_response(call); } #ifdef USE_BLUETOOTH_PROXY - bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call) { - if (!this->bluetooth_le_advertisement_subscription_) - return false; - return this->send_bluetooth_le_advertisement_response(call); - } + bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg); + + void bluetooth_device_request(const BluetoothDeviceRequest &msg) override; + void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) override; + void bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) override; + void bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) override; + void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) override; + void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) override; + void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override; + BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free( + const SubscribeBluetoothConnectionsFreeRequest &msg) override; + #endif #ifdef USE_HOMEASSISTANT_TIME void send_time_request() { @@ -181,6 +184,8 @@ class APIConnection : public APIServerConnection { std::unique_ptr helper_; std::string client_info_; + uint32_t client_api_version_major_{0}; + uint32_t client_api_version_minor_{0}; #ifdef USE_ESP32_CAMERA esp32_camera::CameraImageReader image_reader_; #endif @@ -190,7 +195,7 @@ class APIConnection : public APIServerConnection { uint32_t last_traffic_; bool sent_ping_{false}; bool service_call_subscription_{false}; - bool bluetooth_le_advertisement_subscription_{true}; + bool bluetooth_le_advertisement_subscription_{false}; bool next_close_ = false; APIServer *parent_; InitialStateIterator initial_state_iterator_; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 13969fce76..73d8044cde 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -340,6 +340,35 @@ template<> const char *proto_enum_to_string(enums::Me return "UNKNOWN"; } } +template<> +const char *proto_enum_to_string(enums::BluetoothDeviceRequestType value) { + switch (value) { + case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: + return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT"; + case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: + return "BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT"; + case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR: + return "BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR"; + case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR: + return "BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR"; + default: + return "UNKNOWN"; + } +} +bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 2: { + this->api_version_major = value.as_uint32(); + return true; + } + case 3: { + this->api_version_minor = value.as_uint32(); + return true; + } + default: + return false; + } +} bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -350,7 +379,11 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) return false; } } -void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); } +void HelloRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_string(1, this->client_info); + buffer.encode_uint32(2, this->api_version_major); + buffer.encode_uint32(3, this->api_version_minor); +} #ifdef HAS_PROTO_MESSAGE_DUMP void HelloRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; @@ -358,6 +391,16 @@ void HelloRequest::dump_to(std::string &out) const { out.append(" client_info: "); out.append("'").append(this->client_info).append("'"); out.append("\n"); + + out.append(" api_version_major: "); + sprintf(buffer, "%u", this->api_version_major); + out.append(buffer); + out.append("\n"); + + out.append(" api_version_minor: "); + sprintf(buffer, "%u", this->api_version_minor); + out.append(buffer); + out.append("\n"); out.append("}"); } #endif @@ -496,7 +539,7 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { return true; } case 11: { - this->has_bluetooth_proxy = value.as_bool(); + this->bluetooth_proxy_version = value.as_uint32(); return true; } default: @@ -548,7 +591,7 @@ 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_bool(11, this->has_bluetooth_proxy); + buffer.encode_uint32(11, this->bluetooth_proxy_version); } #ifdef HAS_PROTO_MESSAGE_DUMP void DeviceInfoResponse::dump_to(std::string &out) const { @@ -595,8 +638,9 @@ void DeviceInfoResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" has_bluetooth_proxy: "); - out.append(YESNO(this->has_bluetooth_proxy)); + out.append(" bluetooth_proxy_version: "); + sprintf(buffer, "%u", this->bluetooth_proxy_version); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -4872,7 +4916,7 @@ void SubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const bool BluetoothServiceData::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { - this->data.push_back(value.as_uint32()); + this->legacy_data.push_back(value.as_uint32()); return true; } default: @@ -4885,15 +4929,20 @@ bool BluetoothServiceData::decode_length(uint32_t field_id, ProtoLengthDelimited this->uuid = value.as_string(); return true; } + case 3: { + this->data = value.as_string(); + return true; + } default: return false; } } void BluetoothServiceData::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->uuid); - for (auto &it : this->data) { + for (auto &it : this->legacy_data) { buffer.encode_uint32(2, it, true); } + buffer.encode_string(3, this->data); } #ifdef HAS_PROTO_MESSAGE_DUMP void BluetoothServiceData::dump_to(std::string &out) const { @@ -4903,12 +4952,16 @@ void BluetoothServiceData::dump_to(std::string &out) const { out.append("'").append(this->uuid).append("'"); out.append("\n"); - for (const auto &it : this->data) { - out.append(" data: "); + for (const auto &it : this->legacy_data) { + out.append(" legacy_data: "); sprintf(buffer, "%u", it); out.append(buffer); out.append("\n"); } + + out.append(" data: "); + out.append("'").append(this->data).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -5000,6 +5053,699 @@ void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const { out.append("}"); } #endif +bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->request_type = value.as_enum(); + return true; + } + default: + return false; + } +} +void BluetoothDeviceRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_enum(2, this->request_type); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothDeviceRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothDeviceRequest {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" request_type: "); + out.append(proto_enum_to_string(this->request_type)); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothDeviceConnectionResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->connected = value.as_bool(); + return true; + } + case 3: { + this->mtu = value.as_uint32(); + return true; + } + case 4: { + this->error = value.as_int32(); + return true; + } + default: + return false; + } +} +void BluetoothDeviceConnectionResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_bool(2, this->connected); + buffer.encode_uint32(3, this->mtu); + buffer.encode_int32(4, this->error); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothDeviceConnectionResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothDeviceConnectionResponse {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" connected: "); + out.append(YESNO(this->connected)); + out.append("\n"); + + out.append(" mtu: "); + sprintf(buffer, "%u", this->mtu); + out.append(buffer); + out.append("\n"); + + out.append(" error: "); + sprintf(buffer, "%d", this->error); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTGetServicesRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + default: + return false; + } +} +void BluetoothGATTGetServicesRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); } +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTGetServicesRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTGetServicesRequest {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTDescriptor::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->uuid.push_back(value.as_uint64()); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +void BluetoothGATTDescriptor::encode(ProtoWriteBuffer buffer) const { + for (auto &it : this->uuid) { + buffer.encode_uint64(1, it, true); + } + buffer.encode_uint32(2, this->handle); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTDescriptor::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTDescriptor {\n"); + for (const auto &it : this->uuid) { + out.append(" uuid: "); + sprintf(buffer, "%llu", it); + out.append(buffer); + out.append("\n"); + } + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTCharacteristic::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->uuid.push_back(value.as_uint64()); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + case 3: { + this->properties = value.as_uint32(); + return true; + } + default: + return false; + } +} +bool BluetoothGATTCharacteristic::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 4: { + this->descriptors.push_back(value.as_message()); + return true; + } + default: + return false; + } +} +void BluetoothGATTCharacteristic::encode(ProtoWriteBuffer buffer) const { + for (auto &it : this->uuid) { + buffer.encode_uint64(1, it, true); + } + buffer.encode_uint32(2, this->handle); + buffer.encode_uint32(3, this->properties); + for (auto &it : this->descriptors) { + buffer.encode_message(4, it, true); + } +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTCharacteristic::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTCharacteristic {\n"); + for (const auto &it : this->uuid) { + out.append(" uuid: "); + sprintf(buffer, "%llu", it); + out.append(buffer); + out.append("\n"); + } + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + + out.append(" properties: "); + sprintf(buffer, "%u", this->properties); + out.append(buffer); + out.append("\n"); + + for (const auto &it : this->descriptors) { + out.append(" descriptors: "); + it.dump_to(out); + out.append("\n"); + } + out.append("}"); +} +#endif +bool BluetoothGATTService::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->uuid.push_back(value.as_uint64()); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +bool BluetoothGATTService::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 3: { + this->characteristics.push_back(value.as_message()); + return true; + } + default: + return false; + } +} +void BluetoothGATTService::encode(ProtoWriteBuffer buffer) const { + for (auto &it : this->uuid) { + buffer.encode_uint64(1, it, true); + } + buffer.encode_uint32(2, this->handle); + for (auto &it : this->characteristics) { + buffer.encode_message(3, it, true); + } +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTService::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTService {\n"); + for (const auto &it : this->uuid) { + out.append(" uuid: "); + sprintf(buffer, "%llu", it); + out.append(buffer); + out.append("\n"); + } + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + + for (const auto &it : this->characteristics) { + out.append(" characteristics: "); + it.dump_to(out); + out.append("\n"); + } + out.append("}"); +} +#endif +bool BluetoothGATTGetServicesResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + default: + return false; + } +} +bool BluetoothGATTGetServicesResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 2: { + this->services.push_back(value.as_message()); + return true; + } + default: + return false; + } +} +void BluetoothGATTGetServicesResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + for (auto &it : this->services) { + buffer.encode_message(2, it, true); + } +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTGetServicesResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTGetServicesResponse {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + for (const auto &it : this->services) { + out.append(" services: "); + it.dump_to(out); + out.append("\n"); + } + out.append("}"); +} +#endif +bool BluetoothGATTGetServicesDoneResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + default: + return false; + } +} +void BluetoothGATTGetServicesDoneResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTGetServicesDoneResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTGetServicesDoneResponse {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTReadRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +void BluetoothGATTReadRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTReadRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTReadRequest {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTReadResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +bool BluetoothGATTReadResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 3: { + this->data = value.as_string(); + return true; + } + default: + return false; + } +} +void BluetoothGATTReadResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); + buffer.encode_string(3, this->data); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTReadResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTReadResponse {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + + out.append(" data: "); + out.append("'").append(this->data).append("'"); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + case 3: { + this->response = value.as_bool(); + return true; + } + default: + return false; + } +} +bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 4: { + this->data = value.as_string(); + return true; + } + default: + return false; + } +} +void BluetoothGATTWriteRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); + buffer.encode_bool(3, this->response); + buffer.encode_string(4, this->data); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTWriteRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTWriteRequest {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + + out.append(" response: "); + out.append(YESNO(this->response)); + out.append("\n"); + + out.append(" data: "); + out.append("'").append(this->data).append("'"); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTReadDescriptorRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +void BluetoothGATTReadDescriptorRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTReadDescriptorRequest {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTWriteDescriptorRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 3: { + this->data = value.as_string(); + return true; + } + default: + return false; + } +} +void BluetoothGATTWriteDescriptorRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); + buffer.encode_string(3, this->data); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTWriteDescriptorRequest {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + + out.append(" data: "); + out.append("'").append(this->data).append("'"); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTNotifyRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + case 3: { + this->enable = value.as_bool(); + return true; + } + default: + return false; + } +} +void BluetoothGATTNotifyRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); + buffer.encode_bool(3, this->enable); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTNotifyRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTNotifyRequest {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + + out.append(" enable: "); + out.append(YESNO(this->enable)); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTNotifyDataResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +bool BluetoothGATTNotifyDataResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 3: { + this->data = value.as_string(); + return true; + } + default: + return false; + } +} +void BluetoothGATTNotifyDataResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); + buffer.encode_string(3, this->data); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTNotifyDataResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTNotifyDataResponse {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + + out.append(" data: "); + out.append("'").append(this->data).append("'"); + out.append("\n"); + out.append("}"); +} +#endif +void SubscribeBluetoothConnectionsFreeRequest::encode(ProtoWriteBuffer buffer) const {} +#ifdef HAS_PROTO_MESSAGE_DUMP +void SubscribeBluetoothConnectionsFreeRequest::dump_to(std::string &out) const { + out.append("SubscribeBluetoothConnectionsFreeRequest {}"); +} +#endif +bool BluetoothConnectionsFreeResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->free = value.as_uint32(); + return true; + } + case 2: { + this->limit = value.as_uint32(); + return true; + } + default: + return false; + } +} +void BluetoothConnectionsFreeResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint32(1, this->free); + buffer.encode_uint32(2, this->limit); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothConnectionsFreeResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothConnectionsFreeResponse {\n"); + out.append(" free: "); + sprintf(buffer, "%u", this->free); + out.append(buffer); + out.append("\n"); + + out.append(" limit: "); + sprintf(buffer, "%u", this->limit); + 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 2093f93ee7..325e9a23c3 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -155,12 +155,20 @@ enum MediaPlayerCommand : uint32_t { MEDIA_PLAYER_COMMAND_MUTE = 3, MEDIA_PLAYER_COMMAND_UNMUTE = 4, }; +enum BluetoothDeviceRequestType : uint32_t { + BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0, + BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1, + BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2, + BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3, +}; } // namespace enums class HelloRequest : public ProtoMessage { public: std::string client_info{}; + uint32_t api_version_major{0}; + uint32_t api_version_minor{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -168,6 +176,7 @@ class HelloRequest : public ProtoMessage { protected: bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class HelloResponse : public ProtoMessage { public: @@ -263,7 +272,7 @@ class DeviceInfoResponse : public ProtoMessage { std::string project_name{}; std::string project_version{}; uint32_t webserver_port{0}; - bool has_bluetooth_proxy{false}; + uint32_t bluetooth_proxy_version{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1227,7 +1236,8 @@ class SubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage { class BluetoothServiceData : public ProtoMessage { public: std::string uuid{}; - std::vector data{}; + std::vector legacy_data{}; + std::string data{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1254,6 +1264,223 @@ 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 BluetoothDeviceRequest : public ProtoMessage { + public: + uint64_t address{0}; + enums::BluetoothDeviceRequestType request_type{}; + 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 BluetoothDeviceConnectionResponse : public ProtoMessage { + public: + uint64_t address{0}; + bool connected{false}; + uint32_t mtu{0}; + 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 BluetoothGATTGetServicesRequest : public ProtoMessage { + public: + uint64_t address{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 BluetoothGATTDescriptor : public ProtoMessage { + public: + std::vector uuid{}; + uint32_t handle{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 BluetoothGATTCharacteristic : public ProtoMessage { + public: + std::vector uuid{}; + uint32_t handle{0}; + uint32_t properties{0}; + std::vector descriptors{}; + 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 BluetoothGATTService : public ProtoMessage { + public: + std::vector uuid{}; + uint32_t handle{0}; + std::vector characteristics{}; + 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 BluetoothGATTGetServicesResponse : public ProtoMessage { + public: + uint64_t address{0}; + std::vector services{}; + 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 BluetoothGATTGetServicesDoneResponse : public ProtoMessage { + public: + uint64_t address{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 BluetoothGATTReadRequest : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{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 BluetoothGATTReadResponse : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{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 BluetoothGATTWriteRequest : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{0}; + bool response{false}; + 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 BluetoothGATTReadDescriptorRequest : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{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 BluetoothGATTWriteDescriptorRequest : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{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 BluetoothGATTNotifyRequest : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{0}; + bool enable{false}; + 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 BluetoothGATTNotifyDataResponse : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{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 SubscribeBluetoothConnectionsFreeRequest : public ProtoMessage { + public: + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: +}; +class BluetoothConnectionsFreeResponse : public ProtoMessage { + public: + uint32_t free{0}; + uint32_t limit{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 6932675c41..7bfe1acf48 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -336,6 +336,71 @@ bool APIServerConnectionBase::send_bluetooth_le_advertisement_response(const Blu return this->send_message_(msg, 67); } #endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_device_connection_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 69); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_gatt_get_services_response(const BluetoothGATTGetServicesResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_gatt_get_services_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 71); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_gatt_get_services_done_response( + const BluetoothGATTGetServicesDoneResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_gatt_get_services_done_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 72); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_gatt_read_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 74); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_gatt_notify_data_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 79); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_connections_free_response(const BluetoothConnectionsFreeResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_connections_free_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 81); +} +#endif bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { switch (msg_type) { case 1: { @@ -612,6 +677,94 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, this->on_subscribe_bluetooth_le_advertisements_request(msg); break; } + case 68: { +#ifdef USE_BLUETOOTH_PROXY + BluetoothDeviceRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_bluetooth_device_request: %s", msg.dump().c_str()); +#endif + this->on_bluetooth_device_request(msg); +#endif + break; + } + case 70: { +#ifdef USE_BLUETOOTH_PROXY + BluetoothGATTGetServicesRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_bluetooth_gatt_get_services_request: %s", msg.dump().c_str()); +#endif + this->on_bluetooth_gatt_get_services_request(msg); +#endif + break; + } + case 73: { +#ifdef USE_BLUETOOTH_PROXY + BluetoothGATTReadRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_bluetooth_gatt_read_request: %s", msg.dump().c_str()); +#endif + this->on_bluetooth_gatt_read_request(msg); +#endif + break; + } + case 75: { +#ifdef USE_BLUETOOTH_PROXY + BluetoothGATTWriteRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_bluetooth_gatt_write_request: %s", msg.dump().c_str()); +#endif + this->on_bluetooth_gatt_write_request(msg); +#endif + break; + } + case 76: { +#ifdef USE_BLUETOOTH_PROXY + BluetoothGATTReadDescriptorRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_bluetooth_gatt_read_descriptor_request: %s", msg.dump().c_str()); +#endif + this->on_bluetooth_gatt_read_descriptor_request(msg); +#endif + break; + } + case 77: { +#ifdef USE_BLUETOOTH_PROXY + BluetoothGATTWriteDescriptorRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_bluetooth_gatt_write_descriptor_request: %s", msg.dump().c_str()); +#endif + this->on_bluetooth_gatt_write_descriptor_request(msg); +#endif + break; + } + case 78: { +#ifdef USE_BLUETOOTH_PROXY + BluetoothGATTNotifyRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_bluetooth_gatt_notify_request: %s", msg.dump().c_str()); +#endif + this->on_bluetooth_gatt_notify_request(msg); +#endif + break; + } + case 80: { +#ifdef USE_BLUETOOTH_PROXY + SubscribeBluetoothConnectionsFreeRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_subscribe_bluetooth_connections_free_request: %s", msg.dump().c_str()); +#endif + this->on_subscribe_bluetooth_connections_free_request(msg); +#endif + break; + } default: return false; } @@ -708,18 +861,6 @@ void APIServerConnection::on_subscribe_home_assistant_states_request(const Subsc } this->subscribe_home_assistant_states(msg); } -void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( - const SubscribeBluetoothLEAdvertisementsRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; - } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->subscribe_bluetooth_le_advertisements(msg); -} void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) { if (!this->is_connection_setup()) { this->on_no_setup_connection(); @@ -884,6 +1025,126 @@ void APIServerConnection::on_media_player_command_request(const MediaPlayerComma this->media_player_command(msg); } #endif +void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( + const SubscribeBluetoothLEAdvertisementsRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->subscribe_bluetooth_le_advertisements(msg); +} +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->bluetooth_device_request(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->bluetooth_gatt_get_services(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->bluetooth_gatt_read(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->bluetooth_gatt_write(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->bluetooth_gatt_read_descriptor(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->bluetooth_gatt_write_descriptor(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->bluetooth_gatt_notify(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_subscribe_bluetooth_connections_free_request( + const SubscribeBluetoothConnectionsFreeRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + BluetoothConnectionsFreeResponse ret = this->subscribe_bluetooth_connections_free(msg); + if (!this->send_bluetooth_connections_free_response(ret)) { + this->on_fatal_error(); + } +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 49426d1bbc..c7ef1468d8 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -158,6 +158,48 @@ class APIServerConnectionBase : public ProtoService { const SubscribeBluetoothLEAdvertisementsRequest &value){}; #ifdef USE_BLUETOOTH_PROXY bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_bluetooth_device_request(const BluetoothDeviceRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_gatt_get_services_response(const BluetoothGATTGetServicesResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_gatt_get_services_done_response(const BluetoothGATTGetServicesDoneResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_connections_free_response(const BluetoothConnectionsFreeResponse &msg); #endif protected: bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; @@ -175,7 +217,6 @@ class APIServerConnection : public APIServerConnectionBase { virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0; virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0; virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0; - virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0; virtual void execute_service(const ExecuteServiceRequest &msg) = 0; #ifdef USE_COVER @@ -210,6 +251,32 @@ class APIServerConnection : public APIServerConnectionBase { #endif #ifdef USE_MEDIA_PLAYER virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0; +#endif + virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_device_request(const BluetoothDeviceRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free( + const SubscribeBluetoothConnectionsFreeRequest &msg) = 0; #endif protected: void on_hello_request(const HelloRequest &msg) override; @@ -222,7 +289,6 @@ class APIServerConnection : public APIServerConnectionBase { void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override; void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override; void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override; - void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; void on_get_time_request(const GetTimeRequest &msg) override; void on_execute_service_request(const ExecuteServiceRequest &msg) override; #ifdef USE_COVER @@ -257,6 +323,31 @@ class APIServerConnection : public APIServerConnectionBase { #endif #ifdef USE_MEDIA_PLAYER void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override; +#endif + void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_device_request(const BluetoothDeviceRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &msg) override; #endif }; diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 5851594955..965f08aa15 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -297,6 +297,51 @@ void APIServer::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementRe 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_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_notify_data_response(const BluetoothGATTNotifyDataResponse &call) { + for (auto &client : this->clients_) { + client->send_bluetooth_gatt_notify_data_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); + } +} #endif APIServer::APIServer() { global_api_server = this; } void APIServer::subscribe_home_assistant_state(std::string entity_id, optional attribute, diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index dc892b2088..72ab55432c 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -75,6 +75,12 @@ class APIServer : public Component, public Controller { 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, 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_notify_data_response(const BluetoothGATTNotifyDataResponse &call); + void send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call); + void send_bluetooth_gatt_services_done(uint64_t address); #endif void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } #ifdef USE_HOMEASSISTANT_TIME diff --git a/esphome/components/bedjet/bedjet_hub.cpp b/esphome/components/bedjet/bedjet_hub.cpp index fd383eb6be..f90ca5cf54 100644 --- a/esphome/components/bedjet/bedjet_hub.cpp +++ b/esphome/components/bedjet/bedjet_hub.cpp @@ -128,9 +128,9 @@ uint8_t BedJetHub::write_bedjet_packet_(BedjetPacket *pkt) { } return -1; } - auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_cmd_, - pkt->data_length + 1, (uint8_t *) &pkt->command, ESP_GATT_WRITE_TYPE_NO_RSP, - ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), + this->char_handle_cmd_, pkt->data_length + 1, (uint8_t *) &pkt->command, + ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); return status; } @@ -138,13 +138,13 @@ uint8_t BedJetHub::write_bedjet_packet_(BedjetPacket *pkt) { uint8_t BedJetHub::set_notify_(const bool enable) { uint8_t status; if (enable) { - status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, + status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(), this->char_handle_status_); if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status); } } else { - status = esp_ble_gattc_unregister_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, + status = esp_ble_gattc_unregister_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(), this->char_handle_status_); if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_unregister_for_notify failed, status=%d", this->get_name().c_str(), status); @@ -204,8 +204,8 @@ bool BedJetHub::discover_characteristics_() { result = false; } else { this->char_handle_name_ = chr->handle; - auto status = esp_ble_gattc_read_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_name_, - ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_read_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), + this->char_handle_name_, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGI(TAG, "[%s] Unable to read name characteristic: %d", this->get_name().c_str(), status); } @@ -230,22 +230,6 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga this->dispatch_state_(false); break; } - case ESP_GATTC_OPEN_EVT: { - // FIXME: bug in BLEClient - this->parent_->conn_id = param->open.conn_id; - this->open_conn_id_ = param->open.conn_id; - break; - } - - case ESP_GATTC_CONNECT_EVT: { - if (this->parent_->conn_id != param->connect.conn_id && this->open_conn_id_ != 0xff) { - // FIXME: bug in BLEClient - ESP_LOGW(TAG, "[%s] CONNECT_EVT unexpected conn_id; open=%d, parent=%d, param=%d", this->get_name().c_str(), - this->open_conn_id_, this->parent_->conn_id, param->connect.conn_id); - this->parent_->conn_id = this->open_conn_id_; - } - break; - } case ESP_GATTC_SEARCH_CMPL_EVT: { auto result = this->discover_characteristics_(); @@ -301,7 +285,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga break; } case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent_->conn_id) + if (param->read.conn_id != this->parent_->get_conn_id()) break; if (param->read.status != ESP_GATT_OK) { ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); @@ -358,9 +342,9 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga if (this->processing_) break; - if (param->notify.conn_id != this->parent_->conn_id) { + if (param->notify.conn_id != this->parent_->get_conn_id()) { ESP_LOGW(TAG, "[%s] Received notify event for unexpected parent conn: expect %x, got %x", - this->get_name().c_str(), this->parent_->conn_id, param->notify.conn_id); + this->get_name().c_str(), this->parent_->get_conn_id(), param->notify.conn_id); // FIXME: bug in BLEClient holding wrong conn_id. } @@ -394,7 +378,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga if (needs_extra) { // This means the packet was partial, so read the status characteristic to get the second part. // Ideally this will complete quickly. We won't process additional notification events until it does. - auto status = esp_ble_gattc_read_char(this->parent_->gattc_if, this->parent_->conn_id, + auto status = esp_ble_gattc_read_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_status_, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGI(TAG, "[%s] Unable to read extended status packet", this->get_name().c_str()); @@ -438,15 +422,15 @@ uint8_t BedJetHub::write_notify_config_descriptor_(bool enable) { // NOTE: BLEClient uses `uint8_t*` of length 1, but BLE spec requires 16 bits. uint16_t notify_en = enable ? 1 : 0; - auto status = - esp_ble_gattc_write_char_descr(this->parent_->gattc_if, this->parent_->conn_id, handle, sizeof(notify_en), - (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_write_char_descr(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), handle, + sizeof(notify_en), (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status); return status; } ESP_LOGD(TAG, "[%s] wrote notify=%s to status config 0x%04x, for conn %d", this->get_name().c_str(), - enable ? "true" : "false", handle, this->parent_->conn_id); + enable ? "true" : "false", handle, this->parent_->get_conn_id()); return ESP_GATT_OK; } @@ -500,7 +484,7 @@ void BedJetHub::update() { this->dispatch_status_(); } void BedJetHub::dump_config() { ESP_LOGCONFIG(TAG, "BedJet Hub '%s'", this->get_name().c_str()); ESP_LOGCONFIG(TAG, " ble_client.app_id: %d", this->parent()->app_id); - ESP_LOGCONFIG(TAG, " ble_client.conn_id: %d", this->parent()->conn_id); + ESP_LOGCONFIG(TAG, " ble_client.conn_id: %d", this->parent()->get_conn_id()); LOG_UPDATE_INTERVAL(this) ESP_LOGCONFIG(TAG, " Child components (%d):", this->children_.size()); for (auto *child : this->children_) { diff --git a/esphome/components/bedjet/bedjet_hub.h b/esphome/components/bedjet/bedjet_hub.h index c7583b78e9..f1479710a7 100644 --- a/esphome/components/bedjet/bedjet_hub.h +++ b/esphome/components/bedjet/bedjet_hub.h @@ -167,8 +167,6 @@ class BedJetHub : public esphome::ble_client::BLEClientNode, public PollingCompo uint16_t char_handle_status_; uint16_t config_descr_status_; - uint8_t open_conn_id_ = -1; - uint8_t write_notify_config_descriptor_(bool enable); }; diff --git a/esphome/components/ble_client/__init__.py b/esphome/components/ble_client/__init__.py index 4b7d5f5b8a..0f1f60e62b 100644 --- a/esphome/components/ble_client/__init__.py +++ b/esphome/components/ble_client/__init__.py @@ -1,6 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import esp32_ble_tracker +from esphome.components import esp32_ble_tracker, esp32_ble_client from esphome.const import ( CONF_CHARACTERISTIC_UUID, CONF_ID, @@ -14,13 +14,12 @@ from esphome.const import ( ) from esphome import automation +AUTO_LOAD = ["esp32_ble_client"] CODEOWNERS = ["@buxtronix"] DEPENDENCIES = ["esp32_ble_tracker"] ble_client_ns = cg.esphome_ns.namespace("ble_client") -BLEClient = ble_client_ns.class_( - "BLEClient", cg.Component, esp32_ble_tracker.ESPBTClient -) +BLEClient = ble_client_ns.class_("BLEClient", esp32_ble_client.BLEClientBase) BLEClientNode = ble_client_ns.class_("BLEClientNode") BLEClientNodeConstRef = BLEClientNode.operator("ref").operator("const") # Triggers diff --git a/esphome/components/ble_client/automation.cpp b/esphome/components/ble_client/automation.cpp index 6918ab31b4..395950dd26 100644 --- a/esphome/components/ble_client/automation.cpp +++ b/esphome/components/ble_client/automation.cpp @@ -1,8 +1,8 @@ #include "automation.h" +#include #include #include -#include #include "esphome/core/log.h" @@ -31,8 +31,8 @@ void BLEWriterClientNode::write(const std::vector &value) { } ESP_LOGVV(TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str()); esp_err_t err = - esp_ble_gattc_write_char(this->parent()->gattc_if, this->parent()->conn_id, this->ble_char_handle_, value.size(), - const_cast(value.data()), write_type, ESP_GATT_AUTH_REQ_NONE); + esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->ble_char_handle_, + value.size(), const_cast(value.data()), write_type, ESP_GATT_AUTH_REQ_NONE); if (err != ESP_OK) { ESP_LOGE(TAG, "Error writing to characteristic: %s!", esp_err_to_name(err)); } diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index 38e64ebd76..ba5f78109e 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -28,7 +28,8 @@ class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode { void loop() override {} void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override { - if (event == ESP_GATTC_DISCONNECT_EVT && memcmp(param->disconnect.remote_bda, this->parent_->remote_bda, 6) == 0) + if (event == ESP_GATTC_DISCONNECT_EVT && + memcmp(param->disconnect.remote_bda, this->parent_->get_remote_bda(), 6) == 0) this->trigger(); if (event == ESP_GATTC_SEARCH_CMPL_EVT) this->node_state = espbt::ClientState::ESTABLISHED; diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index 5f58d8273f..3f86df32f5 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -1,8 +1,9 @@ -#include "esphome/core/log.h" +#include "ble_client.h" +#include "esphome/components/esp32_ble_client/ble_client_base.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/core/application.h" #include "esphome/core/helpers.h" -#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#include "ble_client.h" +#include "esphome/core/log.h" #ifdef USE_ESP32 @@ -11,22 +12,13 @@ namespace ble_client { static const char *const TAG = "ble_client"; -float BLEClient::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } - void BLEClient::setup() { - auto ret = esp_ble_gattc_app_register(this->app_id); - if (ret) { - ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret); - this->mark_failed(); - } - this->set_states_(espbt::ClientState::IDLE); + BLEClientBase::setup(); this->enabled = true; } void BLEClient::loop() { - if (this->state() == espbt::ClientState::DISCOVERED) { - this->connect(); - } + BLEClientBase::loop(); for (auto *node : this->nodes_) node->loop(); } @@ -39,34 +31,7 @@ void BLEClient::dump_config() { bool BLEClient::parse_device(const espbt::ESPBTDevice &device) { if (!this->enabled) return false; - if (device.address_uint64() != this->address) - return false; - if (this->state() != espbt::ClientState::IDLE) - return false; - - ESP_LOGD(TAG, "Found device at MAC address [%s]", device.address_str().c_str()); - this->set_states_(espbt::ClientState::DISCOVERED); - - auto addr = device.address_uint64(); - this->remote_bda[0] = (addr >> 40) & 0xFF; - this->remote_bda[1] = (addr >> 32) & 0xFF; - this->remote_bda[2] = (addr >> 24) & 0xFF; - this->remote_bda[3] = (addr >> 16) & 0xFF; - this->remote_bda[4] = (addr >> 8) & 0xFF; - this->remote_bda[5] = (addr >> 0) & 0xFF; - this->remote_addr_type = device.get_address_type(); - return true; -} - -std::string BLEClient::address_str() const { - char buf[20]; - sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x", (uint8_t)(this->address >> 40) & 0xff, - (uint8_t)(this->address >> 32) & 0xff, (uint8_t)(this->address >> 24) & 0xff, - (uint8_t)(this->address >> 16) & 0xff, (uint8_t)(this->address >> 8) & 0xff, - (uint8_t)(this->address >> 0) & 0xff); - std::string ret; - ret = buf; - return ret; + return BLEClientBase::parse_device(device); } void BLEClient::set_enabled(bool enabled) { @@ -74,7 +39,7 @@ void BLEClient::set_enabled(bool enabled) { return; if (!enabled && this->state() != espbt::ClientState::IDLE) { ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str()); - auto ret = esp_ble_gattc_close(this->gattc_if, this->conn_id); + auto ret = esp_ble_gattc_close(this->gattc_if_, this->conn_id_); if (ret) { ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s status=%d", this->address_str().c_str(), ret); } @@ -82,125 +47,12 @@ void BLEClient::set_enabled(bool enabled) { this->enabled = enabled; } -void BLEClient::connect() { - ESP_LOGI(TAG, "Attempting BLE connection to %s", this->address_str().c_str()); - auto ret = esp_ble_gattc_open(this->gattc_if, this->remote_bda, this->remote_addr_type, true); - if (ret) { - ESP_LOGW(TAG, "esp_ble_gattc_open error, address=%s status=%d", this->address_str().c_str(), ret); - this->set_states_(espbt::ClientState::IDLE); - } else { - this->set_states_(espbt::ClientState::CONNECTING); - } -} - void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if, esp_ble_gattc_cb_param_t *param) { - if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id) - return; - if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if) - return; - bool all_established = this->all_nodes_established_(); - switch (event) { - case ESP_GATTC_REG_EVT: { - if (param->reg.status == ESP_GATT_OK) { - ESP_LOGV(TAG, "gattc registered app id %d", this->app_id); - this->gattc_if = esp_gattc_if; - } else { - ESP_LOGE(TAG, "gattc app registration failed id=%d code=%d", param->reg.app_id, param->reg.status); - } - break; - } - case ESP_GATTC_OPEN_EVT: { - ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str()); - this->conn_id = param->open.conn_id; - if (param->open.status != ESP_GATT_OK) { - ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status); - this->set_states_(espbt::ClientState::IDLE); - break; - } - break; - } - case ESP_GATTC_CONNECT_EVT: { - ESP_LOGV(TAG, "[%s] ESP_GATTC_CONNECT_EVT", this->address_str().c_str()); - if (this->conn_id != param->connect.conn_id) { - ESP_LOGD(TAG, "[%s] Unexpected conn_id in CONNECT_EVT: param conn=%d, open conn=%d", - this->address_str().c_str(), param->connect.conn_id, this->conn_id); - } - auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if, param->connect.conn_id); - if (ret) { - ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%x", ret); - } - break; - } - case ESP_GATTC_CFG_MTU_EVT: { - if (param->cfg_mtu.status != ESP_GATT_OK) { - ESP_LOGW(TAG, "cfg_mtu to %s failed, mtu %d, status %d", this->address_str().c_str(), param->cfg_mtu.mtu, - param->cfg_mtu.status); - this->set_states_(espbt::ClientState::IDLE); - break; - } - ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu); - esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr); - break; - } - case ESP_GATTC_DISCONNECT_EVT: { - if (memcmp(param->disconnect.remote_bda, this->remote_bda, 6) != 0) { - return; - } - ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->address_str().c_str(), param->disconnect.reason); - for (auto &svc : this->services_) - delete svc; // NOLINT(cppcoreguidelines-owning-memory) - this->services_.clear(); - this->set_states_(espbt::ClientState::IDLE); - break; - } - case ESP_GATTC_SEARCH_RES_EVT: { - BLEService *ble_service = new BLEService(); // NOLINT(cppcoreguidelines-owning-memory) - ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid); - ble_service->start_handle = param->search_res.start_handle; - ble_service->end_handle = param->search_res.end_handle; - ble_service->client = this; - this->services_.push_back(ble_service); - break; - } - case ESP_GATTC_SEARCH_CMPL_EVT: { - ESP_LOGV(TAG, "[%s] ESP_GATTC_SEARCH_CMPL_EVT", this->address_str().c_str()); - for (auto &svc : this->services_) { - ESP_LOGI(TAG, "Service UUID: %s", svc->uuid.to_string().c_str()); - ESP_LOGI(TAG, " start_handle: 0x%x end_handle: 0x%x", svc->start_handle, svc->end_handle); - svc->parse_characteristics(); - } - this->set_states_(espbt::ClientState::CONNECTED); - this->set_state(espbt::ClientState::ESTABLISHED); - break; - } - case ESP_GATTC_REG_FOR_NOTIFY_EVT: { - auto *descr = this->get_config_descriptor(param->reg_for_notify.handle); - if (descr == nullptr) { - ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", param->reg_for_notify.handle); - break; - } - if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 || - descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) { - ESP_LOGW(TAG, "Handle 0x%x (uuid %s) is not a client config char uuid", param->reg_for_notify.handle, - descr->uuid.to_string().c_str()); - break; - } - uint16_t notify_en = 1; - auto status = - esp_ble_gattc_write_char_descr(this->gattc_if, this->conn_id, descr->handle, sizeof(notify_en), - (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE); - if (status) { - ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status); - } - break; - } + BLEClientBase::gattc_event_handler(event, esp_gattc_if, param); - default: - break; - } for (auto *node : this->nodes_) node->gattc_event_handler(event, esp_gattc_if, param); @@ -212,237 +64,20 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es } } -void BLEClient::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { - switch (event) { - // This event is sent by the server when it requests security - case ESP_GAP_BLE_SEC_REQ_EVT: - ESP_LOGV(TAG, "ESP_GAP_BLE_SEC_REQ_EVT %x", 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: - esp_bd_addr_t bd_addr; - memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); - ESP_LOGI(TAG, "auth complete. remote BD_ADDR: %s", format_hex(bd_addr, 6).c_str()); - if (!param->ble_security.auth_cmpl.success) { - ESP_LOGE(TAG, "auth fail reason = 0x%x", param->ble_security.auth_cmpl.fail_reason); - } else { - ESP_LOGV(TAG, "auth success. address type = %d auth mode = %d", param->ble_security.auth_cmpl.addr_type, - param->ble_security.auth_cmpl.auth_mode); - } - break; - // There are other events we'll want to implement at some point to support things like pass key - // https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md - default: - break; +void BLEClient::set_state(espbt::ClientState state) { + BLEClientBase::set_state(state); + for (auto &node : nodes_) + node->node_state = state; +} + +bool BLEClient::all_nodes_established_() { + if (this->state() != espbt::ClientState::ESTABLISHED) + return false; + for (auto &node : nodes_) { + if (node->node_state != espbt::ClientState::ESTABLISHED) + return false; } -} - -// Parse GATT values into a float for a sensor. -// Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/ -float BLEClient::parse_char_value(uint8_t *value, uint16_t length) { - // A length of one means a single octet value. - if (length == 0) - return 0; - if (length == 1) - return (float) ((uint8_t) value[0]); - - switch (value[0]) { - case 0x1: // boolean. - case 0x2: // 2bit. - case 0x3: // nibble. - case 0x4: // uint8. - return (float) ((uint8_t) value[1]); - case 0x5: // uint12. - case 0x6: // uint16. - if (length > 2) { - return (float) ((uint16_t)(value[1] << 8) + (uint16_t) value[2]); - } - case 0x7: // uint24. - if (length > 3) { - return (float) ((uint32_t)(value[1] << 16) + (uint32_t)(value[2] << 8) + (uint32_t)(value[3])); - } - case 0x8: // uint32. - if (length > 4) { - return (float) ((uint32_t)(value[1] << 24) + (uint32_t)(value[2] << 16) + (uint32_t)(value[3] << 8) + - (uint32_t)(value[4])); - } - case 0xC: // int8. - return (float) ((int8_t) value[1]); - case 0xD: // int12. - case 0xE: // int16. - if (length > 2) { - return (float) ((int16_t)(value[1] << 8) + (int16_t) value[2]); - } - case 0xF: // int24. - if (length > 3) { - return (float) ((int32_t)(value[1] << 16) + (int32_t)(value[2] << 8) + (int32_t)(value[3])); - } - case 0x10: // int32. - if (length > 4) { - return (float) ((int32_t)(value[1] << 24) + (int32_t)(value[2] << 16) + (int32_t)(value[3] << 8) + - (int32_t)(value[4])); - } - } - ESP_LOGW(TAG, "Cannot parse characteristic value of type 0x%x length %d", value[0], length); - return NAN; -} - -BLEService *BLEClient::get_service(espbt::ESPBTUUID uuid) { - for (auto *svc : this->services_) { - if (svc->uuid == uuid) - return svc; - } - return nullptr; -} - -BLEService *BLEClient::get_service(uint16_t uuid) { return this->get_service(espbt::ESPBTUUID::from_uint16(uuid)); } - -BLECharacteristic *BLEClient::get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr) { - auto *svc = this->get_service(service); - if (svc == nullptr) - return nullptr; - return svc->get_characteristic(chr); -} - -BLECharacteristic *BLEClient::get_characteristic(uint16_t service, uint16_t chr) { - return this->get_characteristic(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr)); -} - -BLEDescriptor *BLEClient::get_config_descriptor(uint16_t handle) { - for (auto &svc : this->services_) { - for (auto &chr : svc->characteristics) { - if (chr->handle == handle) { - for (auto &desc : chr->descriptors) { - if (desc->uuid == espbt::ESPBTUUID::from_uint16(0x2902)) - return desc; - } - } - } - } - return nullptr; -} - -BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) { - for (auto &chr : this->characteristics) { - if (chr->uuid == uuid) - return chr; - } - return nullptr; -} - -BLECharacteristic *BLEService::get_characteristic(uint16_t uuid) { - return this->get_characteristic(espbt::ESPBTUUID::from_uint16(uuid)); -} - -BLEDescriptor *BLEClient::get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr) { - auto *svc = this->get_service(service); - if (svc == nullptr) - return nullptr; - auto *ch = svc->get_characteristic(chr); - if (ch == nullptr) - return nullptr; - return ch->get_descriptor(descr); -} - -BLEDescriptor *BLEClient::get_descriptor(uint16_t service, uint16_t chr, uint16_t descr) { - return this->get_descriptor(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr), - espbt::ESPBTUUID::from_uint16(descr)); -} - -BLEService::~BLEService() { - for (auto &chr : this->characteristics) - delete chr; // NOLINT(cppcoreguidelines-owning-memory) -} - -void BLEService::parse_characteristics() { - uint16_t offset = 0; - esp_gattc_char_elem_t result; - - while (true) { - uint16_t count = 1; - esp_gatt_status_t status = esp_ble_gattc_get_all_char( - this->client->gattc_if, this->client->conn_id, this->start_handle, this->end_handle, &result, &count, offset); - if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) { - break; - } - if (status != ESP_GATT_OK) { - ESP_LOGW(TAG, "esp_ble_gattc_get_all_char error, status=%d", status); - break; - } - if (count == 0) { - break; - } - - BLECharacteristic *characteristic = new BLECharacteristic(); // NOLINT(cppcoreguidelines-owning-memory) - characteristic->uuid = espbt::ESPBTUUID::from_uuid(result.uuid); - characteristic->properties = result.properties; - characteristic->handle = result.char_handle; - characteristic->service = this; - this->characteristics.push_back(characteristic); - ESP_LOGI(TAG, " characteristic %s, handle 0x%x, properties 0x%x", characteristic->uuid.to_string().c_str(), - characteristic->handle, characteristic->properties); - characteristic->parse_descriptors(); - offset++; - } -} - -BLECharacteristic::~BLECharacteristic() { - for (auto &desc : this->descriptors) - delete desc; // NOLINT(cppcoreguidelines-owning-memory) -} - -void BLECharacteristic::parse_descriptors() { - uint16_t offset = 0; - esp_gattc_descr_elem_t result; - - while (true) { - uint16_t count = 1; - esp_gatt_status_t status = esp_ble_gattc_get_all_descr( - this->service->client->gattc_if, this->service->client->conn_id, this->handle, &result, &count, offset); - if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) { - break; - } - if (status != ESP_GATT_OK) { - ESP_LOGW(TAG, "esp_ble_gattc_get_all_descr error, status=%d", status); - break; - } - if (count == 0) { - break; - } - - BLEDescriptor *desc = new BLEDescriptor(); // NOLINT(cppcoreguidelines-owning-memory) - desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid); - desc->handle = result.handle; - desc->characteristic = this; - this->descriptors.push_back(desc); - ESP_LOGV(TAG, " descriptor %s, handle 0x%x", desc->uuid.to_string().c_str(), desc->handle); - offset++; - } -} - -BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) { - for (auto &desc : this->descriptors) { - if (desc->uuid == uuid) - return desc; - } - return nullptr; -} -BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) { - return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid)); -} - -void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) { - auto *client = this->service->client; - auto status = esp_ble_gattc_write_char(client->gattc_if, client->conn_id, this->handle, new_val_size, new_val, - write_type, ESP_GATT_AUTH_REQ_NONE); - if (status) { - ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status); - } -} - -void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) { - write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP); + return true; } } // namespace ble_client diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index 5ed8f219d1..0f8373ab1f 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -1,8 +1,9 @@ #pragma once +#include "esphome/components/esp32_ble_client/ble_client_base.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" -#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #ifdef USE_ESP32 @@ -18,9 +19,9 @@ namespace ble_client { namespace espbt = esphome::esp32_ble_tracker; +using namespace esp32_ble_client; + class BLEClient; -class BLEService; -class BLECharacteristic; class BLEClientNode { public: @@ -42,57 +43,15 @@ class BLEClientNode { uint64_t address_; }; -class BLEDescriptor { - public: - espbt::ESPBTUUID uuid; - uint16_t handle; - - BLECharacteristic *characteristic; -}; - -class BLECharacteristic { - public: - ~BLECharacteristic(); - espbt::ESPBTUUID uuid; - uint16_t handle; - esp_gatt_char_prop_t properties; - std::vector descriptors; - void parse_descriptors(); - BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid); - BLEDescriptor *get_descriptor(uint16_t uuid); - void write_value(uint8_t *new_val, int16_t new_val_size); - void write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type); - BLEService *service; -}; - -class BLEService { - public: - ~BLEService(); - espbt::ESPBTUUID uuid; - uint16_t start_handle; - uint16_t end_handle; - std::vector characteristics; - BLEClient *client; - void parse_characteristics(); - BLECharacteristic *get_characteristic(espbt::ESPBTUUID uuid); - BLECharacteristic *get_characteristic(uint16_t uuid); -}; - -class BLEClient : public espbt::ESPBTClient, public Component { +class BLEClient : public BLEClientBase { public: void setup() override; void dump_config() override; void loop() override; - float get_setup_priority() const override; void 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; bool parse_device(const espbt::ESPBTDevice &device) override; - void on_scan_end() override {} - void connect() override; - - void set_address(uint64_t address) { this->address = address; } void set_enabled(bool enabled); @@ -102,43 +61,14 @@ class BLEClient : public espbt::ESPBTClient, public Component { this->nodes_.push_back(node); } - BLEService *get_service(espbt::ESPBTUUID uuid); - BLEService *get_service(uint16_t uuid); - BLECharacteristic *get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr); - BLECharacteristic *get_characteristic(uint16_t service, uint16_t chr); - BLEDescriptor *get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr); - BLEDescriptor *get_descriptor(uint16_t service, uint16_t chr, uint16_t descr); - // Get the configuration descriptor for the given characteristic handle. - BLEDescriptor *get_config_descriptor(uint16_t handle); - - float parse_char_value(uint8_t *value, uint16_t length); - - int gattc_if; - esp_bd_addr_t remote_bda; - esp_ble_addr_type_t remote_addr_type; - uint16_t conn_id; - uint64_t address; bool enabled; - std::string address_str() const; + + void set_state(espbt::ClientState state) override; protected: - void set_states_(espbt::ClientState st) { - this->set_state(st); - for (auto &node : nodes_) - node->node_state = st; - } - bool all_nodes_established_() { - if (this->state() != espbt::ClientState::ESTABLISHED) - return false; - for (auto &node : nodes_) { - if (node->node_state != espbt::ClientState::ESTABLISHED) - return false; - } - return true; - } + bool all_nodes_established_(); std::vector nodes_; - std::vector services_; }; } // namespace ble_client diff --git a/esphome/components/ble_client/sensor/automation.h b/esphome/components/ble_client/sensor/automation.h index 2baaafe2ec..d830165d30 100644 --- a/esphome/components/ble_client/sensor/automation.h +++ b/esphome/components/ble_client/sensor/automation.h @@ -19,7 +19,8 @@ class BLESensorNotifyTrigger : public Trigger, public BLESensor { break; } case ESP_GATTC_NOTIFY_EVT: { - if (param->notify.conn_id != this->sensor_->parent()->conn_id || param->notify.handle != this->sensor_->handle) + if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() || + param->notify.handle != this->sensor_->handle) break; this->trigger(this->sensor_->parent()->parse_char_value(param->notify.value, param->notify.value_len)); } diff --git a/esphome/components/ble_client/sensor/ble_sensor.cpp b/esphome/components/ble_client/sensor/ble_sensor.cpp index ee5afd3f7b..a05efad60b 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_sensor.cpp @@ -63,8 +63,8 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga this->handle = descr->handle; } if (this->notify_) { - auto status = - esp_ble_gattc_register_for_notify(this->parent()->gattc_if, this->parent()->remote_bda, chr->handle); + auto status = esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(), + this->parent()->get_remote_bda(), chr->handle); if (status) { ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status); } @@ -74,7 +74,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga break; } case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent()->conn_id) + if (param->read.conn_id != this->parent()->get_conn_id()) break; if (param->read.status != ESP_GATT_OK) { ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); @@ -87,7 +87,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga break; } case ESP_GATTC_NOTIFY_EVT: { - if (param->notify.conn_id != this->parent()->conn_id || param->notify.handle != this->handle) + if (param->notify.conn_id != this->parent()->get_conn_id() || param->notify.handle != this->handle) break; ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(), param->notify.handle, param->notify.value[0]); @@ -122,8 +122,8 @@ void BLESensor::update() { return; } - auto status = - esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle, + ESP_GATT_AUTH_REQ_NONE); if (status) { this->status_set_warning(); this->publish_state(NAN); diff --git a/esphome/components/ble_client/text_sensor/automation.h b/esphome/components/ble_client/text_sensor/automation.h index be85892c5a..c504c35a58 100644 --- a/esphome/components/ble_client/text_sensor/automation.h +++ b/esphome/components/ble_client/text_sensor/automation.h @@ -19,7 +19,8 @@ class BLETextSensorNotifyTrigger : public Trigger, public BLETextSe break; } case ESP_GATTC_NOTIFY_EVT: { - if (param->notify.conn_id != this->sensor_->parent()->conn_id || param->notify.handle != this->sensor_->handle) + if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() || + param->notify.handle != this->sensor_->handle) break; this->trigger(this->sensor_->parse_data(param->notify.value, param->notify.value_len)); } diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp index 1a71cd6cd8..1a304593c7 100644 --- a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp @@ -66,8 +66,8 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ this->handle = descr->handle; } if (this->notify_) { - auto status = - esp_ble_gattc_register_for_notify(this->parent()->gattc_if, this->parent()->remote_bda, chr->handle); + auto status = esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(), + this->parent()->get_remote_bda(), chr->handle); if (status) { ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status); } @@ -77,7 +77,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; } case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent()->conn_id) + if (param->read.conn_id != this->parent()->get_conn_id()) break; if (param->read.status != ESP_GATT_OK) { ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); @@ -90,7 +90,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; } case ESP_GATTC_NOTIFY_EVT: { - if (param->notify.conn_id != this->parent()->conn_id || param->notify.handle != this->handle) + if (param->notify.conn_id != this->parent()->get_conn_id() || param->notify.handle != this->handle) break; ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(), param->notify.handle, param->notify.value[0]); @@ -121,8 +121,8 @@ void BLETextSensor::update() { return; } - auto status = - esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle, + ESP_GATT_AUTH_REQ_NONE); if (status) { this->status_set_warning(); this->publish_state(EMPTY); diff --git a/esphome/components/bluetooth_proxy/__init__.py b/esphome/components/bluetooth_proxy/__init__.py index a6a5a4391b..b5acea89dd 100644 --- a/esphome/components/bluetooth_proxy/__init__.py +++ b/esphome/components/bluetooth_proxy/__init__.py @@ -1,19 +1,23 @@ -from esphome.components import esp32_ble_tracker +from esphome.components import esp32_ble_tracker, esp32_ble_client import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID +from esphome.const import CONF_ACTIVE, CONF_ID -DEPENDENCIES = ["esp32", "esp32_ble_tracker"] +AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"] +DEPENDENCIES = ["esp32"] CODEOWNERS = ["@jesserockz"] bluetooth_proxy_ns = cg.esphome_ns.namespace("bluetooth_proxy") -BluetoothProxy = bluetooth_proxy_ns.class_("BluetoothProxy", cg.Component) +BluetoothProxy = bluetooth_proxy_ns.class_( + "BluetoothProxy", esp32_ble_client.BLEClientBase +) CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(BluetoothProxy), + cv.Optional(CONF_ACTIVE, default=False): cv.boolean, } ).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) @@ -22,6 +26,8 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - await esp32_ble_tracker.register_ble_device(var, config) + cg.add(var.set_active(config[CONF_ACTIVE])) + + await esp32_ble_tracker.register_client(var, config) cg.add_define("USE_BLUETOOTH_PROXY") diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index 41871295ce..96fee39f95 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -1,22 +1,39 @@ #include "bluetooth_proxy.h" -#ifdef USE_API -#include "esphome/components/api/api_pb2.h" -#include "esphome/components/api/api_server.h" -#endif // USE_API #include "esphome/core/log.h" #ifdef USE_ESP32 +#ifdef USE_API +#include "esphome/components/api/api_server.h" +#endif + namespace esphome { namespace bluetooth_proxy { static const char *const TAG = "bluetooth_proxy"; +BluetoothProxy::BluetoothProxy() { + global_bluetooth_proxy = this; + this->address_ = 0; +} + bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { 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); + + this->address_type_map_[device.address_uint64()] = device.get_address_type(); + + if (this->address_ == 0) + return true; + + if (this->state_ == espbt::ClientState::DISCOVERED) { + ESP_LOGV(TAG, "Connecting to address %s", this->address_str().c_str()); + return true; + } + + BLEClientBase::parse_device(device); return true; } @@ -35,23 +52,309 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi for (auto &data : device.get_service_datas()) { api::BluetoothServiceData service_data; service_data.uuid = data.uuid.to_string(); - for (auto d : data.data) - service_data.data.push_back(d); - resp.service_data.push_back(service_data); + service_data.data.assign(data.data.begin(), data.data.end()); + resp.service_data.push_back(std::move(service_data)); } for (auto &data : device.get_manufacturer_datas()) { api::BluetoothServiceData manufacturer_data; manufacturer_data.uuid = data.uuid.to_string(); - for (auto d : data.data) - manufacturer_data.data.push_back(d); - resp.manufacturer_data.push_back(manufacturer_data); + 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); #endif } +void BluetoothProxy::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) { + BLEClientBase::gattc_event_handler(event, gattc_if, param); + switch (event) { + case ESP_GATTC_DISCONNECT_EVT: { +#ifdef USE_API + api::global_api_server->send_bluetooth_device_connection(this->address_, false, this->mtu_, + param->disconnect.reason); + api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(), + this->get_bluetooth_connections_limit()); +#endif + this->address_ = 0; + } + case ESP_GATTC_OPEN_EVT: { + if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) { +#ifdef USE_API + api::global_api_server->send_bluetooth_device_connection(this->address_, false, this->mtu_, param->open.status); + +#endif + break; + } + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: { +#ifdef USE_API + api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_); + api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(), + this->get_bluetooth_connections_limit()); +#endif + break; + } + case ESP_GATTC_READ_DESCR_EVT: + case ESP_GATTC_READ_CHAR_EVT: { + if (param->read.conn_id != this->conn_id_) + break; + if (param->read.status != ESP_GATT_OK) { + ESP_LOGW(TAG, "Error reading char/descriptor at handle %d, status=%d", param->read.handle, param->read.status); + break; + } +#ifdef USE_API + api::BluetoothGATTReadResponse resp; + resp.address = this->address_; + resp.handle = param->read.handle; + resp.data.reserve(param->read.value_len); + 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); +#endif + break; + } + case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.conn_id != this->conn_id_) + break; + ESP_LOGV(TAG, "ESP_GATTC_NOTIFY_EVT: handle=0x%x", param->notify.handle); +#ifdef USE_API + api::BluetoothGATTNotifyDataResponse resp; + resp.address = this->address_; + resp.handle = param->notify.handle; + resp.data.reserve(param->notify.value_len); + 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); +#endif + break; + } + default: + break; + } +} + void BluetoothProxy::dump_config() { ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); } +void BluetoothProxy::loop() { + BLEClientBase::loop(); +#ifdef USE_API + if (this->state_ != espbt::ClientState::IDLE && !api::global_api_server->is_connected()) { + ESP_LOGI(TAG, "[%s] Disconnecting.", this->address_str().c_str()); + auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_); + if (err != ERR_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s err=%d", this->address_str().c_str(), err); + } + } + + if (this->send_service_ == this->services_.size()) { + this->send_service_ = -1; + api::global_api_server->send_bluetooth_gatt_services_done(this->address_); + } else if (this->send_service_ >= 0) { + auto &service = this->services_[this->send_service_]; + api::BluetoothGATTGetServicesResponse resp; + resp.address = this->address_; + api::BluetoothGATTService service_resp; + service_resp.uuid = {service->uuid.get_128bit_high(), service->uuid.get_128bit_low()}; + service_resp.handle = service->start_handle; + for (auto &characteristic : service->characteristics) { + api::BluetoothGATTCharacteristic characteristic_resp; + characteristic_resp.uuid = {characteristic->uuid.get_128bit_high(), characteristic->uuid.get_128bit_low()}; + characteristic_resp.handle = characteristic->handle; + characteristic_resp.properties = characteristic->properties; + for (auto &descriptor : characteristic->descriptors) { + api::BluetoothGATTDescriptor descriptor_resp; + descriptor_resp.uuid = {descriptor->uuid.get_128bit_high(), descriptor->uuid.get_128bit_low()}; + descriptor_resp.handle = descriptor->handle; + characteristic_resp.descriptors.push_back(std::move(descriptor_resp)); + } + 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->send_service_++; + } +#endif +} + +#ifdef USE_API +void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest &msg) { + switch (msg.request_type) { + case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: { + this->address_ = msg.address; + if (this->address_type_map_.find(this->address_) != this->address_type_map_.end()) { + // Utilise the address type cache + this->remote_addr_type_ = this->address_type_map_[this->address_]; + } else { + this->remote_addr_type_ = BLE_ADDR_TYPE_PUBLIC; + } + this->remote_bda_[0] = (this->address_ >> 40) & 0xFF; + this->remote_bda_[1] = (this->address_ >> 32) & 0xFF; + this->remote_bda_[2] = (this->address_ >> 24) & 0xFF; + this->remote_bda_[3] = (this->address_ >> 16) & 0xFF; + this->remote_bda_[4] = (this->address_ >> 8) & 0xFF; + this->remote_bda_[5] = (this->address_ >> 0) & 0xFF; + this->set_state(espbt::ClientState::DISCOVERED); + esp_ble_gap_stop_scanning(); + break; + } + case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: { + if (this->state() != espbt::ClientState::IDLE) { + ESP_LOGI(TAG, "[%s] Disconnecting.", this->address_str().c_str()); + auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_); + if (err != ERR_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s err=%d", this->address_str().c_str(), err); + } + } + break; + } + case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR: + case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR: + break; + } +} + +void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) { + if (this->state_ != espbt::ClientState::ESTABLISHED) { + ESP_LOGW(TAG, "Cannot read GATT characteristic, not connected."); + return; + } + if (this->address_ != msg.address) { + ESP_LOGW(TAG, "Address mismatch for read GATT characteristic request"); + return; + } + + auto *characteristic = this->get_characteristic(msg.handle); + if (characteristic == nullptr) { + ESP_LOGW(TAG, "Cannot read GATT characteristic, not found."); + return; + } + + ESP_LOGV(TAG, "Reading GATT characteristic %s", characteristic->uuid.to_string().c_str()); + + esp_err_t err = + esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, characteristic->handle, ESP_GATT_AUTH_REQ_NONE); + if (err != ERR_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_read_char error, err=%d", err); + } +} + +void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg) { + if (this->state_ != espbt::ClientState::ESTABLISHED) { + ESP_LOGW(TAG, "Cannot write GATT characteristic, not connected."); + return; + } + if (this->address_ != msg.address) { + ESP_LOGW(TAG, "Address mismatch for write GATT characteristic request"); + return; + } + + auto *characteristic = this->get_characteristic(msg.handle); + if (characteristic == nullptr) { + ESP_LOGW(TAG, "Cannot write GATT characteristic, not found."); + return; + } + + ESP_LOGV(TAG, "Writing GATT characteristic %s", characteristic->uuid.to_string().c_str()); + characteristic->write_value((uint8_t *) msg.data.data(), msg.data.size(), + msg.response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP); +} + +void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg) { + if (this->state_ != espbt::ClientState::ESTABLISHED) { + ESP_LOGW(TAG, "Cannot read GATT characteristic descriptor, not connected."); + return; + } + if (this->address_ != msg.address) { + ESP_LOGW(TAG, "Address mismatch for read GATT characteristic descriptor request"); + return; + } + + auto *descriptor = this->get_descriptor(msg.handle); + if (descriptor == nullptr) { + ESP_LOGW(TAG, "Cannot read GATT characteristic descriptor, not found."); + return; + } + + ESP_LOGV(TAG, "Reading GATT characteristic descriptor %s -> %s", descriptor->characteristic->uuid.to_string().c_str(), + descriptor->uuid.to_string().c_str()); + + esp_err_t err = + esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, ESP_GATT_AUTH_REQ_NONE); + if (err != ERR_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_read_char error, err=%d", err); + } +} + +void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg) { + if (this->state_ != espbt::ClientState::ESTABLISHED) { + ESP_LOGW(TAG, "Cannot write GATT characteristic descriptor, not connected."); + return; + } + if (this->address_ != msg.address) { + ESP_LOGW(TAG, "Address mismatch for write GATT characteristic descriptor request"); + return; + } + + auto *descriptor = this->get_descriptor(msg.handle); + if (descriptor == nullptr) { + ESP_LOGW(TAG, "Cannot write GATT characteristic descriptor, not found."); + return; + } + + ESP_LOGV(TAG, "Writing GATT characteristic descriptor %s -> %s", descriptor->characteristic->uuid.to_string().c_str(), + descriptor->uuid.to_string().c_str()); + + esp_err_t err = + esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, msg.data.size(), + (uint8_t *) msg.data.data(), ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + if (err != ERR_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, err=%d", err); + } +} + +void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg) { + if (this->address_ != msg.address) { + ESP_LOGW(TAG, "Address mismatch for service list request"); + return; + } + this->send_service_ = 0; +} + +void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg) { + if (this->address_ != msg.address) { + ESP_LOGW(TAG, "Address mismatch for notify"); + return; + } + + auto *characteristic = this->get_characteristic(msg.handle); + + if (characteristic == nullptr) { + ESP_LOGW(TAG, "Cannot notify GATT characteristic, not found."); + return; + } + + esp_err_t err; + if (msg.enable) { + err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, err=%d", err); + } + } else { + err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_unregister_for_notify failed, err=%d", err); + } + } +} + +#endif + +BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + } // namespace bluetooth_proxy } // namespace esphome diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index 9a936747c0..8ff43aff3f 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -4,22 +4,59 @@ #include +#include "esphome/components/esp32_ble_client/ble_client_base.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/core/automation.h" #include "esphome/core/component.h" +#include "esphome/core/defines.h" + +#include + +#ifdef USE_API +#include "esphome/components/api/api_pb2.h" +#endif // USE_API namespace esphome { namespace bluetooth_proxy { -class BluetoothProxy : public Component, public esp32_ble_tracker::ESPBTDeviceListener { +using namespace esp32_ble_client; + +class BluetoothProxy : public BLEClientBase { public: + BluetoothProxy(); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; void dump_config() override; + void loop() override; + + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; + +#ifdef USE_API + void bluetooth_device_request(const api::BluetoothDeviceRequest &msg); + void bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg); + void bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg); + void bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg); + void bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg); + void bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg); + void bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg); +#endif + + int get_bluetooth_connections_free() { return this->state_ == espbt::ClientState::IDLE ? 1 : 0; } + int get_bluetooth_connections_limit() { return 1; } + + void set_active(bool active) { this->active_ = active; } + bool has_active() { return this->active_; } protected: void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device); + + std::map address_type_map_; + int16_t send_service_{-1}; + bool active_; }; +extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + } // namespace bluetooth_proxy } // namespace esphome diff --git a/esphome/components/esp32_ble_client/__init__.py b/esphome/components/esp32_ble_client/__init__.py new file mode 100644 index 0000000000..94a5576d0b --- /dev/null +++ b/esphome/components/esp32_ble_client/__init__.py @@ -0,0 +1,12 @@ +import esphome.codegen as cg + +from esphome.components import esp32_ble_tracker + +AUTO_LOAD = ["esp32_ble_tracker"] +CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["esp32"] + +esp32_ble_client_ns = cg.esphome_ns.namespace("esp32_ble_client") +BLEClientBase = esp32_ble_client_ns.class_( + "BLEClientBase", esp32_ble_tracker.ESPBTClient, cg.Component +) diff --git a/esphome/components/esp32_ble_client/ble_characteristic.cpp b/esphome/components/esp32_ble_client/ble_characteristic.cpp new file mode 100644 index 0000000000..873833368c --- /dev/null +++ b/esphome/components/esp32_ble_client/ble_characteristic.cpp @@ -0,0 +1,83 @@ +#include "ble_characteristic.h" +#include "ble_client_base.h" +#include "ble_service.h" + +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace esp32_ble_client { + +static const char *const TAG = "esp32_ble_client.characteristic"; + +BLECharacteristic::~BLECharacteristic() { + for (auto &desc : this->descriptors) + delete desc; // NOLINT(cppcoreguidelines-owning-memory) +} + +void BLECharacteristic::parse_descriptors() { + uint16_t offset = 0; + esp_gattc_descr_elem_t result; + + while (true) { + uint16_t count = 1; + esp_gatt_status_t status = + esp_ble_gattc_get_all_descr(this->service->client->get_gattc_if(), this->service->client->get_conn_id(), + this->handle, &result, &count, offset); + if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) { + break; + } + if (status != ESP_GATT_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_get_all_descr error, status=%d", status); + break; + } + if (count == 0) { + break; + } + + BLEDescriptor *desc = new BLEDescriptor(); // NOLINT(cppcoreguidelines-owning-memory) + desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid); + desc->handle = result.handle; + desc->characteristic = this; + this->descriptors.push_back(desc); + ESP_LOGV(TAG, " descriptor %s, handle 0x%x", desc->uuid.to_string().c_str(), desc->handle); + offset++; + } +} + +BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) { + for (auto &desc : this->descriptors) { + if (desc->uuid == uuid) + return desc; + } + return nullptr; +} +BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) { + return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid)); +} +BLEDescriptor *BLECharacteristic::get_descriptor_by_handle(uint16_t handle) { + for (auto &desc : this->descriptors) { + if (desc->handle == handle) + return desc; + } + return nullptr; +} + +void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) { + auto *client = this->service->client; + auto status = esp_ble_gattc_write_char(client->get_gattc_if(), client->get_conn_id(), this->handle, new_val_size, + new_val, write_type, ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status); + } +} + +void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) { + write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP); +} + +} // namespace esp32_ble_client +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_client/ble_characteristic.h b/esphome/components/esp32_ble_client/ble_characteristic.h new file mode 100644 index 0000000000..ffa9227cc4 --- /dev/null +++ b/esphome/components/esp32_ble_client/ble_characteristic.h @@ -0,0 +1,35 @@ +#pragma once + +#ifdef USE_ESP32 + +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +#include "ble_descriptor.h" + +namespace esphome { +namespace esp32_ble_client { + +namespace espbt = esphome::esp32_ble_tracker; + +class BLEService; + +class BLECharacteristic { + public: + ~BLECharacteristic(); + espbt::ESPBTUUID uuid; + uint16_t handle; + esp_gatt_char_prop_t properties; + std::vector descriptors; + void parse_descriptors(); + BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid); + BLEDescriptor *get_descriptor(uint16_t uuid); + BLEDescriptor *get_descriptor_by_handle(uint16_t handle); + void write_value(uint8_t *new_val, int16_t new_val_size); + void write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type); + BLEService *service; +}; + +} // namespace esp32_ble_client +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp new file mode 100644 index 0000000000..0e81c9aca8 --- /dev/null +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -0,0 +1,324 @@ +#include "ble_client_base.h" + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace esp32_ble_client { + +static const char *const TAG = "esp32_ble_client"; + +void BLEClientBase::setup() { + auto ret = esp_ble_gattc_app_register(this->app_id); + if (ret) { + ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret); + this->mark_failed(); + } + this->set_state(espbt::ClientState::IDLE); +} + +void BLEClientBase::loop() { + if (this->state_ == espbt::ClientState::DISCOVERED) { + this->connect(); + } +} + +float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } + +bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) { + if (device.address_uint64() != this->address_) + return false; + if (this->state_ != espbt::ClientState::IDLE) + return false; + + ESP_LOGD(TAG, "Found device at MAC address [%s]", device.address_str().c_str()); + this->set_state(espbt::ClientState::DISCOVERED); + + auto addr = device.address_uint64(); + this->remote_bda_[0] = (addr >> 40) & 0xFF; + this->remote_bda_[1] = (addr >> 32) & 0xFF; + this->remote_bda_[2] = (addr >> 24) & 0xFF; + this->remote_bda_[3] = (addr >> 16) & 0xFF; + this->remote_bda_[4] = (addr >> 8) & 0xFF; + this->remote_bda_[5] = (addr >> 0) & 0xFF; + this->remote_addr_type_ = device.get_address_type(); + return true; +} + +std::string BLEClientBase::address_str() const { + return str_snprintf("%02x:%02x:%02x:%02x:%02x:%02x", 17, (uint8_t)(this->address_ >> 40) & 0xff, + (uint8_t)(this->address_ >> 32) & 0xff, (uint8_t)(this->address_ >> 24) & 0xff, + (uint8_t)(this->address_ >> 16) & 0xff, (uint8_t)(this->address_ >> 8) & 0xff, + (uint8_t)(this->address_ >> 0) & 0xff); +} + +void BLEClientBase::connect() { + ESP_LOGI(TAG, "Attempting BLE connection to %s", this->address_str().c_str()); + auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true); + if (ret) { + ESP_LOGW(TAG, "esp_ble_gattc_open error, address=%s status=%d", this->address_str().c_str(), ret); + this->set_state(espbt::ClientState::IDLE); + } else { + this->set_state(espbt::ClientState::CONNECTING); + } +} + +void BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if, + esp_ble_gattc_cb_param_t *param) { + if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id) + return; + if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if_) + return; + + switch (event) { + case ESP_GATTC_REG_EVT: { + if (param->reg.status == ESP_GATT_OK) { + ESP_LOGV(TAG, "gattc registered app id %d", this->app_id); + this->gattc_if_ = esp_gattc_if; + } else { + ESP_LOGE(TAG, "gattc app registration failed id=%d code=%d", param->reg.app_id, param->reg.status); + } + break; + } + case ESP_GATTC_OPEN_EVT: { + ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str()); + this->conn_id_ = param->open.conn_id; + if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) { + ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status); + this->set_state(espbt::ClientState::IDLE); + break; + } + break; + } + case ESP_GATTC_CONNECT_EVT: { + ESP_LOGV(TAG, "[%s] ESP_GATTC_CONNECT_EVT", this->address_str().c_str()); + if (this->conn_id_ != param->connect.conn_id) { + ESP_LOGD(TAG, "[%s] Unexpected conn_id in CONNECT_EVT: param conn=%d, open conn=%d", + this->address_str().c_str(), param->connect.conn_id, this->conn_id_); + } + auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->connect.conn_id); + if (ret) { + ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%x", ret); + } + break; + } + case ESP_GATTC_CFG_MTU_EVT: { + if (param->cfg_mtu.status != ESP_GATT_OK) { + ESP_LOGW(TAG, "cfg_mtu to %s failed, mtu %d, status %d", this->address_str().c_str(), param->cfg_mtu.mtu, + param->cfg_mtu.status); + this->set_state(espbt::ClientState::IDLE); + break; + } + ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu); + this->mtu_ = param->cfg_mtu.mtu; + esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr); + break; + } + case ESP_GATTC_DISCONNECT_EVT: { + if (memcmp(param->disconnect.remote_bda, this->remote_bda_, 6) != 0) { + return; + } + ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->address_str().c_str(), param->disconnect.reason); + for (auto &svc : this->services_) + delete svc; // NOLINT(cppcoreguidelines-owning-memory) + this->services_.clear(); + this->set_state(espbt::ClientState::IDLE); + break; + } + case ESP_GATTC_SEARCH_RES_EVT: { + BLEService *ble_service = new BLEService(); // NOLINT(cppcoreguidelines-owning-memory) + ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid); + ble_service->start_handle = param->search_res.start_handle; + ble_service->end_handle = param->search_res.end_handle; + ble_service->client = this; + this->services_.push_back(ble_service); + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: { + ESP_LOGV(TAG, "[%s] ESP_GATTC_SEARCH_CMPL_EVT", this->address_str().c_str()); + for (auto &svc : this->services_) { + ESP_LOGI(TAG, "Service UUID: %s", svc->uuid.to_string().c_str()); + ESP_LOGI(TAG, " start_handle: 0x%x end_handle: 0x%x", svc->start_handle, svc->end_handle); + svc->parse_characteristics(); + } + this->set_state(espbt::ClientState::CONNECTED); + this->state_ = espbt::ClientState::ESTABLISHED; + break; + } + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + auto *descr = this->get_config_descriptor(param->reg_for_notify.handle); + if (descr == nullptr) { + ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", param->reg_for_notify.handle); + break; + } + if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 || + descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) { + ESP_LOGW(TAG, "Handle 0x%x (uuid %s) is not a client config char uuid", param->reg_for_notify.handle, + descr->uuid.to_string().c_str()); + break; + } + uint16_t notify_en = 1; + auto status = + esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, descr->handle, sizeof(notify_en), + (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status); + } + break; + } + + default: + break; + } +} + +void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { + switch (event) { + // This event is sent by the server when it requests security + case ESP_GAP_BLE_SEC_REQ_EVT: + ESP_LOGV(TAG, "ESP_GAP_BLE_SEC_REQ_EVT %x", 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: + esp_bd_addr_t bd_addr; + memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); + ESP_LOGI(TAG, "auth complete. remote BD_ADDR: %s", format_hex(bd_addr, 6).c_str()); + if (!param->ble_security.auth_cmpl.success) { + ESP_LOGE(TAG, "auth fail reason = 0x%x", param->ble_security.auth_cmpl.fail_reason); + } else { + ESP_LOGV(TAG, "auth success. address type = %d auth mode = %d", param->ble_security.auth_cmpl.addr_type, + param->ble_security.auth_cmpl.auth_mode); + } + break; + // There are other events we'll want to implement at some point to support things like pass key + // https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md + default: + break; + } +} + +// Parse GATT values into a float for a sensor. +// Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/ +float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) { + // A length of one means a single octet value. + if (length == 0) + return 0; + if (length == 1) + return (float) ((uint8_t) value[0]); + + switch (value[0]) { + case 0x1: // boolean. + case 0x2: // 2bit. + case 0x3: // nibble. + case 0x4: // uint8. + return (float) ((uint8_t) value[1]); + case 0x5: // uint12. + case 0x6: // uint16. + if (length > 2) { + return (float) encode_uint16(value[1], value[2]); + } + case 0x7: // uint24. + if (length > 3) { + return (float) encode_uint24(value[1], value[2], value[3]); + } + case 0x8: // uint32. + if (length > 4) { + return (float) encode_uint32(value[1], value[2], value[3], value[4]); + } + case 0xC: // int8. + return (float) ((int8_t) value[1]); + case 0xD: // int12. + case 0xE: // int16. + if (length > 2) { + return (float) ((int16_t)(value[1] << 8) + (int16_t) value[2]); + } + case 0xF: // int24. + if (length > 3) { + return (float) ((int32_t)(value[1] << 16) + (int32_t)(value[2] << 8) + (int32_t)(value[3])); + } + case 0x10: // int32. + if (length > 4) { + return (float) ((int32_t)(value[1] << 24) + (int32_t)(value[2] << 16) + (int32_t)(value[3] << 8) + + (int32_t)(value[4])); + } + } + ESP_LOGW(TAG, "Cannot parse characteristic value of type 0x%x length %d", value[0], length); + return NAN; +} + +BLEService *BLEClientBase::get_service(espbt::ESPBTUUID uuid) { + for (auto *svc : this->services_) { + if (svc->uuid == uuid) + return svc; + } + return nullptr; +} + +BLEService *BLEClientBase::get_service(uint16_t uuid) { return this->get_service(espbt::ESPBTUUID::from_uint16(uuid)); } + +BLECharacteristic *BLEClientBase::get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr) { + auto *svc = this->get_service(service); + if (svc == nullptr) + return nullptr; + return svc->get_characteristic(chr); +} + +BLECharacteristic *BLEClientBase::get_characteristic(uint16_t service, uint16_t chr) { + return this->get_characteristic(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr)); +} + +BLECharacteristic *BLEClientBase::get_characteristic(uint16_t handle) { + for (auto *svc : this->services_) { + for (auto *chr : svc->characteristics) { + if (chr->handle == handle) + return chr; + } + } + return nullptr; +} + +BLEDescriptor *BLEClientBase::get_config_descriptor(uint16_t handle) { + auto *chr = this->get_characteristic(handle); + if (chr != nullptr) { + for (auto &desc : chr->descriptors) { + if (desc->uuid == espbt::ESPBTUUID::from_uint16(0x2902)) + return desc; + } + } + return nullptr; +} + +BLEDescriptor *BLEClientBase::get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr) { + auto *svc = this->get_service(service); + if (svc == nullptr) + return nullptr; + auto *ch = svc->get_characteristic(chr); + if (ch == nullptr) + return nullptr; + return ch->get_descriptor(descr); +} + +BLEDescriptor *BLEClientBase::get_descriptor(uint16_t service, uint16_t chr, uint16_t descr) { + return this->get_descriptor(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr), + espbt::ESPBTUUID::from_uint16(descr)); +} + +BLEDescriptor *BLEClientBase::get_descriptor(uint16_t handle) { + for (auto *svc : this->services_) { + for (auto *chr : svc->characteristics) { + for (auto *desc : chr->descriptors) { + if (desc->handle == handle) + return desc; + } + } + } + return nullptr; +} + +} // namespace esp32_ble_client +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_client/ble_client_base.h b/esphome/components/esp32_ble_client/ble_client_base.h new file mode 100644 index 0000000000..eba70fa571 --- /dev/null +++ b/esphome/components/esp32_ble_client/ble_client_base.h @@ -0,0 +1,72 @@ +#pragma once + +#ifdef USE_ESP32 + +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/core/component.h" + +#include "ble_service.h" + +#include +#include + +#include +#include +#include +#include + +namespace esphome { +namespace esp32_ble_client { + +namespace espbt = esphome::esp32_ble_tracker; + +class BLEClientBase : public espbt::ESPBTClient, public Component { + public: + void setup() override; + void loop() override; + float get_setup_priority() const override; + + bool parse_device(const espbt::ESPBTDevice &device) override; + void on_scan_end() override {} + void 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; + void connect() override; + + void set_address(uint64_t address) { this->address_ = address; } + std::string address_str() const; + + BLEService *get_service(espbt::ESPBTUUID uuid); + BLEService *get_service(uint16_t uuid); + BLECharacteristic *get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr); + BLECharacteristic *get_characteristic(uint16_t service, uint16_t chr); + BLECharacteristic *get_characteristic(uint16_t handle); + BLEDescriptor *get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr); + BLEDescriptor *get_descriptor(uint16_t service, uint16_t chr, uint16_t descr); + BLEDescriptor *get_descriptor(uint16_t handle); + // Get the configuration descriptor for the given characteristic handle. + BLEDescriptor *get_config_descriptor(uint16_t handle); + + float parse_char_value(uint8_t *value, uint16_t length); + + int get_gattc_if() const { return this->gattc_if_; } + uint8_t *get_remote_bda() { return this->remote_bda_; } + esp_ble_addr_type_t get_remote_addr_type() const { return this->remote_addr_type_; } + uint16_t get_conn_id() const { return this->conn_id_; } + uint64_t get_address() const { return this->address_; } + + protected: + int gattc_if_; + esp_bd_addr_t remote_bda_; + esp_ble_addr_type_t remote_addr_type_; + uint16_t conn_id_; + uint64_t address_; + uint16_t mtu_{23}; + + std::vector services_; +}; + +} // namespace esp32_ble_client +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_client/ble_descriptor.h b/esphome/components/esp32_ble_client/ble_descriptor.h new file mode 100644 index 0000000000..c05430144f --- /dev/null +++ b/esphome/components/esp32_ble_client/ble_descriptor.h @@ -0,0 +1,25 @@ +#pragma once + +#ifdef USE_ESP32 + +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +namespace esphome { +namespace esp32_ble_client { + +namespace espbt = esphome::esp32_ble_tracker; + +class BLECharacteristic; + +class BLEDescriptor { + public: + espbt::ESPBTUUID uuid; + uint16_t handle; + + BLECharacteristic *characteristic; +}; + +} // namespace esp32_ble_client +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_client/ble_service.cpp b/esphome/components/esp32_ble_client/ble_service.cpp new file mode 100644 index 0000000000..1d81eaa556 --- /dev/null +++ b/esphome/components/esp32_ble_client/ble_service.cpp @@ -0,0 +1,66 @@ +#include "ble_service.h" +#include "ble_client_base.h" + +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace esp32_ble_client { + +static const char *const TAG = "esp32_ble_client.service"; + +BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) { + for (auto &chr : this->characteristics) { + if (chr->uuid == uuid) + return chr; + } + return nullptr; +} + +BLECharacteristic *BLEService::get_characteristic(uint16_t uuid) { + return this->get_characteristic(espbt::ESPBTUUID::from_uint16(uuid)); +} + +BLEService::~BLEService() { + for (auto &chr : this->characteristics) + delete chr; // NOLINT(cppcoreguidelines-owning-memory) +} + +void BLEService::parse_characteristics() { + uint16_t offset = 0; + esp_gattc_char_elem_t result; + + while (true) { + uint16_t count = 1; + esp_gatt_status_t status = + esp_ble_gattc_get_all_char(this->client->get_gattc_if(), this->client->get_conn_id(), this->start_handle, + this->end_handle, &result, &count, offset); + if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) { + break; + } + if (status != ESP_GATT_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_get_all_char error, status=%d", status); + break; + } + if (count == 0) { + break; + } + + BLECharacteristic *characteristic = new BLECharacteristic(); // NOLINT(cppcoreguidelines-owning-memory) + characteristic->uuid = espbt::ESPBTUUID::from_uuid(result.uuid); + characteristic->properties = result.properties; + characteristic->handle = result.char_handle; + characteristic->service = this; + this->characteristics.push_back(characteristic); + ESP_LOGI(TAG, " characteristic %s, handle 0x%x, properties 0x%x", characteristic->uuid.to_string().c_str(), + characteristic->handle, characteristic->properties); + characteristic->parse_descriptors(); + offset++; + } +} + +} // namespace esp32_ble_client +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_client/ble_service.h b/esphome/components/esp32_ble_client/ble_service.h new file mode 100644 index 0000000000..04f2212e0e --- /dev/null +++ b/esphome/components/esp32_ble_client/ble_service.h @@ -0,0 +1,32 @@ +#pragma once + +#ifdef USE_ESP32 + +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +#include "ble_characteristic.h" + +namespace esphome { +namespace esp32_ble_client { + +namespace espbt = esphome::esp32_ble_tracker; + +class BLEClientBase; + +class BLEService { + public: + ~BLEService(); + espbt::ESPBTUUID uuid; + uint16_t start_handle; + uint16_t end_handle; + std::vector characteristics; + BLEClientBase *client; + void parse_characteristics(); + BLECharacteristic *get_characteristic(espbt::ESPBTUUID uuid); + BLECharacteristic *get_characteristic(uint16_t uuid); +}; + +} // namespace esp32_ble_client +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 6d7868d4c5..0d7abe32f9 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -3,6 +3,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation from esphome.const import ( + CONF_ACTIVE, CONF_ID, CONF_INTERVAL, CONF_DURATION, @@ -22,7 +23,6 @@ DEPENDENCIES = ["esp32"] CONF_ESP32_BLE_ID = "esp32_ble_id" CONF_SCAN_PARAMETERS = "scan_parameters" CONF_WINDOW = "window" -CONF_ACTIVE = "active" CONF_CONTINUOUS = "continuous" CONF_ON_SCAN_END = "on_scan_end" esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker") diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 47c1f6022e..4aaa6dfa32 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -518,28 +518,39 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const { } esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; } std::string ESPBTUUID::to_string() const { - char sbuf[64]; switch (this->uuid_.len) { case ESP_UUID_LEN_16: - sprintf(sbuf, "0x%02X%02X", this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff); - break; + return str_snprintf("0x%02X%02X", 6, this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff); case ESP_UUID_LEN_32: - 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; + return str_snprintf("0x%02X%02X%02X%02X", 10, this->uuid_.uuid.uuid32 >> 24, + (this->uuid_.uuid.uuid32 >> 16 & 0xff), (this->uuid_.uuid.uuid32 >> 8 & 0xff), + this->uuid_.uuid.uuid32 & 0xff); default: case ESP_UUID_LEN_128: - char *bpos = sbuf; + std::string buf; for (int8_t i = 15; i >= 0; i--) { - sprintf(bpos, "%02X", this->uuid_.uuid.uuid128[i]); - bpos += 2; + buf += str_snprintf("%02X", 2, this->uuid_.uuid.uuid128[i]); if (i == 6 || i == 8 || i == 10 || i == 12) - sprintf(bpos++, "-"); + buf += "-"; } - sbuf[47] = '\0'; - break; + return buf; } - return sbuf; + return ""; +} + +uint64_t ESPBTUUID::get_128bit_high() const { + esp_bt_uuid_t uuid = this->as_128bit().get_uuid(); + return ((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) | + ((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) | + ((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) | + ((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]); +} +uint64_t ESPBTUUID::get_128bit_low() const { + esp_bt_uuid_t uuid = this->as_128bit().get_uuid(); + return ((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) | + ((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) | + ((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) | + ((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0]); } ESPBLEiBeacon::ESPBLEiBeacon(const uint8_t *data) { memcpy(&this->beacon_data_, data, sizeof(beacon_data_)); } diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 29d0c81542..45abcd63fa 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -41,6 +41,9 @@ class ESPBTUUID { std::string to_string() const; + uint64_t get_128bit_high() const; + uint64_t get_128bit_low() const; + protected: esp_bt_uuid_t uuid_; }; @@ -158,7 +161,7 @@ class ESPBTClient : public ESPBTDeviceListener { esp_ble_gattc_cb_param_t *param) = 0; virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0; virtual void connect() = 0; - void set_state(ClientState st) { this->state_ = st; } + virtual void set_state(ClientState st) { this->state_ = st; } ClientState state() const { return state_; } int app_id; diff --git a/esphome/components/esp32_ble_tracker/queue.h b/esphome/components/esp32_ble_tracker/queue.h index f09b2ca8d7..f3a2b3cb3c 100644 --- a/esphome/components/esp32_ble_tracker/queue.h +++ b/esphome/components/esp32_ble_tracker/queue.h @@ -72,13 +72,13 @@ class BLEEvent { // Need to also make a copy of relevant event data. switch (e) { case ESP_GATTC_NOTIFY_EVT: - memcpy(this->event_.gattc.data, p->notify.value, p->notify.value_len); - this->event_.gattc.gattc_param.notify.value = this->event_.gattc.data; + this->data.assign(p->notify.value, p->notify.value + p->notify.value_len); + this->event_.gattc.gattc_param.notify.value = this->data.data(); break; case ESP_GATTC_READ_CHAR_EVT: case ESP_GATTC_READ_DESCR_EVT: - memcpy(this->event_.gattc.data, p->read.value, p->read.value_len); - this->event_.gattc.gattc_param.read.value = this->event_.gattc.data; + this->data.assign(p->read.value, p->read.value + p->read.value_len); + this->event_.gattc.gattc_param.read.value = this->data.data(); break; default: break; @@ -96,9 +96,9 @@ class BLEEvent { esp_gattc_cb_event_t gattc_event; esp_gatt_if_t gattc_if; esp_ble_gattc_cb_param_t gattc_param; - uint8_t data[64]; } gattc; } event_; + std::vector data{}; uint8_t type_; // 0=gap 1=gattc }; diff --git a/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp b/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp index 21638ef7e4..384537e5d7 100644 --- a/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp +++ b/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp @@ -102,8 +102,9 @@ void PVVXDisplay::send_to_setup_char_(uint8_t *blk, size_t size) { ESP_LOGW(TAG, "[%s] Not connected to BLE client.", this->parent_->address_str().c_str()); return; } - auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, size, blk, - ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + auto status = + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, size, + blk, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); } else { diff --git a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp index 6bb17f0508..3959178b94 100644 --- a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp +++ b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp @@ -50,7 +50,7 @@ void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent()->conn_id) + if (param->read.conn_id != this->parent()->get_conn_id()) break; if (param->read.status != ESP_GATT_OK) { ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); @@ -146,17 +146,17 @@ void RadonEyeRD200::update() { void RadonEyeRD200::write_query_message_() { ESP_LOGV(TAG, "writing 0x50 to write service"); int request = 0x50; - auto status = esp_ble_gattc_write_char_descr(this->parent()->gattc_if, this->parent()->conn_id, this->write_handle_, - sizeof(request), (uint8_t *) &request, ESP_GATT_WRITE_TYPE_NO_RSP, - ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_write_char_descr(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), + this->write_handle_, sizeof(request), (uint8_t *) &request, + ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "Error sending write request for sensor, status=%d", status); } } void RadonEyeRD200::request_read_values_() { - auto status = esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->read_handle_, - ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), + this->read_handle_, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); } diff --git a/esphome/const.py b/esphome/const.py index 22f030e84e..d12708cdd6 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -23,6 +23,7 @@ CONF_ACCURACY = "accuracy" CONF_ACCURACY_DECIMALS = "accuracy_decimals" CONF_ACTION_ID = "action_id" CONF_ACTION_STATE_TOPIC = "action_state_topic" +CONF_ACTIVE = "active" CONF_ACTIVE_POWER = "active_power" CONF_ADDRESS = "address" CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id"