mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Merge branch 'btp_uuids_dynamic_services' into integration
This commit is contained in:
		| @@ -37,6 +37,35 @@ static void fill_gatt_uuid(std::array<uint64_t, 2> &uuid_128, uint32_t &short_uu | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Constants for size estimation | ||||
| static constexpr uint8_t SERVICE_OVERHEAD_LEGACY = 25;     // UUID(20) + handle(4) + overhead(1) | ||||
| static constexpr uint8_t SERVICE_OVERHEAD_EFFICIENT = 10;  // UUID(6) + handle(4) | ||||
| static constexpr uint8_t CHAR_SIZE_128BIT = 35;            // UUID(20) + handle(4) + props(4) + overhead(7) | ||||
| static constexpr uint8_t DESC_SIZE_128BIT = 25;            // UUID(20) + handle(4) + overhead(1) | ||||
| static constexpr uint8_t DESC_SIZE_16BIT = 10;             // UUID(6) + handle(4) | ||||
| static constexpr uint8_t DESC_PER_CHAR = 1;                // Assume 1 descriptor per characteristic | ||||
|  | ||||
| // Helper to estimate service size before fetching all data | ||||
| static size_t estimate_service_size(uint16_t char_count, bool use_efficient_uuids) { | ||||
|   size_t service_overhead = use_efficient_uuids ? SERVICE_OVERHEAD_EFFICIENT : SERVICE_OVERHEAD_LEGACY; | ||||
|   // Always assume 128-bit UUIDs for characteristics to be safe | ||||
|   size_t char_size = CHAR_SIZE_128BIT; | ||||
|   // Assume one 128-bit descriptor per characteristic | ||||
|   size_t desc_size = DESC_SIZE_128BIT * DESC_PER_CHAR; | ||||
|  | ||||
|   return service_overhead + (char_size + desc_size) * char_count; | ||||
| } | ||||
|  | ||||
| // Helper to calculate actual service size | ||||
| static size_t get_service_size(api::BluetoothGATTService &service) { | ||||
|   api::ProtoSize service_size; | ||||
|   service.calculate_size(service_size); | ||||
|   size_t size = service_size.get_size(); | ||||
|   ESP_LOGV(TAG, "Service size calculation: uuid[0]=%llx uuid[1]=%llx short_uuid=%u handle=%u -> size=%d", | ||||
|            service.uuid[0], service.uuid[1], service.short_uuid, service.handle, size); | ||||
|   return size; | ||||
| } | ||||
|  | ||||
| bool BluetoothConnection::supports_efficient_uuids_() const { | ||||
|   auto *api_conn = this->proxy_->get_api_connection(); | ||||
|   return api_conn && api_conn->client_supports_api_version(1, 12); | ||||
| @@ -95,16 +124,23 @@ void BluetoothConnection::send_service_for_discovery_() { | ||||
|   // Check if client supports efficient UUIDs | ||||
|   bool use_efficient_uuids = this->supports_efficient_uuids_(); | ||||
|  | ||||
|   // Prepare response for up to 3 services | ||||
|   // Prepare response | ||||
|   api::BluetoothGATTGetServicesResponse resp; | ||||
|   resp.address = this->address_; | ||||
|  | ||||
|   // Process up to 3 services in this iteration | ||||
|   uint8_t services_to_process = | ||||
|       std::min(MAX_SERVICES_PER_BATCH, static_cast<uint8_t>(this->service_count_ - this->send_service_)); | ||||
|   resp.services.reserve(services_to_process); | ||||
|   // Dynamic batching based on actual size | ||||
|   static constexpr size_t MAX_PACKET_SIZE = | ||||
|       1360;  // Conservative MTU limit for API messages (accounts for WPA3 overhead) | ||||
|  | ||||
|   for (int service_idx = 0; service_idx < services_to_process; service_idx++) { | ||||
|   // Keep running total of actual message size | ||||
|   size_t current_size = 0; | ||||
|   api::ProtoSize size; | ||||
|   resp.calculate_size(size); | ||||
|   current_size = size.get_size(); | ||||
|   ESP_LOGV(TAG, "[%d] [%s] Starting batch with base size: %d, send_service_: %d", this->connection_index_, | ||||
|            this->address_str().c_str(), current_size, this->send_service_); | ||||
|  | ||||
|   while (this->send_service_ < this->service_count_) { | ||||
|     esp_gattc_service_elem_t service_result; | ||||
|     uint16_t service_count = 1; | ||||
|     esp_gatt_status_t service_status = esp_ble_gattc_get_service(this->gattc_if_, this->conn_id_, nullptr, | ||||
| @@ -118,15 +154,7 @@ void BluetoothConnection::send_service_for_discovery_() { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     this->send_service_++; | ||||
|     resp.services.emplace_back(); | ||||
|     auto &service_resp = resp.services.back(); | ||||
|  | ||||
|     fill_gatt_uuid(service_resp.uuid, service_resp.short_uuid, service_result.uuid, use_efficient_uuids); | ||||
|  | ||||
|     service_resp.handle = service_result.start_handle; | ||||
|  | ||||
|     // Get the number of characteristics directly with one call | ||||
|     // Get the number of characteristics BEFORE adding to response | ||||
|     uint16_t total_char_count = 0; | ||||
|     esp_gatt_status_t char_count_status = | ||||
|         esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_CHARACTERISTIC, | ||||
| @@ -139,91 +167,145 @@ void BluetoothConnection::send_service_for_discovery_() { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     if (total_char_count == 0) { | ||||
|       // No characteristics, continue to next service | ||||
|       continue; | ||||
|     // If this service likely won't fit, send current batch (unless it's the first) | ||||
|     size_t estimated_size = estimate_service_size(total_char_count, use_efficient_uuids); | ||||
|     if (!resp.services.empty() && (current_size + estimated_size > MAX_PACKET_SIZE)) { | ||||
|       // This service likely won't fit, send current batch | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     // Reserve space and process characteristics | ||||
|     service_resp.characteristics.reserve(total_char_count); | ||||
|     uint16_t char_offset = 0; | ||||
|     esp_gattc_char_elem_t char_result; | ||||
|     while (true) {  // characteristics | ||||
|       uint16_t char_count = 1; | ||||
|       esp_gatt_status_t char_status = | ||||
|           esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, service_result.start_handle, | ||||
|                                      service_result.end_handle, &char_result, &char_count, char_offset); | ||||
|       if (char_status == ESP_GATT_INVALID_OFFSET || char_status == ESP_GATT_NOT_FOUND) { | ||||
|         break; | ||||
|       } | ||||
|       if (char_status != ESP_GATT_OK) { | ||||
|         ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_, | ||||
|                  this->address_str().c_str(), char_status); | ||||
|         this->send_service_ = DONE_SENDING_SERVICES; | ||||
|         return; | ||||
|       } | ||||
|       if (char_count == 0) { | ||||
|         break; | ||||
|       } | ||||
|     // Now add the service since we know it will likely fit | ||||
|     resp.services.emplace_back(); | ||||
|     auto &service_resp = resp.services.back(); | ||||
|  | ||||
|       service_resp.characteristics.emplace_back(); | ||||
|       auto &characteristic_resp = service_resp.characteristics.back(); | ||||
|     fill_gatt_uuid(service_resp.uuid, service_resp.short_uuid, service_result.uuid, use_efficient_uuids); | ||||
|  | ||||
|       fill_gatt_uuid(characteristic_resp.uuid, characteristic_resp.short_uuid, char_result.uuid, use_efficient_uuids); | ||||
|     service_resp.handle = service_result.start_handle; | ||||
|  | ||||
|       characteristic_resp.handle = char_result.char_handle; | ||||
|       characteristic_resp.properties = char_result.properties; | ||||
|       char_offset++; | ||||
|     ESP_LOGV(TAG, "[%d] [%s] Service UUID: %llx,%llx short:%u handle:%u", this->connection_index_, | ||||
|              this->address_str().c_str(), service_resp.uuid[0], service_resp.uuid[1], service_resp.short_uuid, | ||||
|              service_resp.handle); | ||||
|  | ||||
|       // Get the number of descriptors directly with one call | ||||
|       uint16_t total_desc_count = 0; | ||||
|       esp_gatt_status_t desc_count_status = esp_ble_gattc_get_attr_count( | ||||
|           this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, 0, 0, char_result.char_handle, &total_desc_count); | ||||
|  | ||||
|       if (desc_count_status != ESP_GATT_OK) { | ||||
|         ESP_LOGE(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d", this->connection_index_, | ||||
|                  this->address_str().c_str(), char_result.char_handle, desc_count_status); | ||||
|         this->send_service_ = DONE_SENDING_SERVICES; | ||||
|         return; | ||||
|       } | ||||
|       if (total_desc_count == 0) { | ||||
|         // No descriptors, continue to next characteristic | ||||
|         continue; | ||||
|       } | ||||
|  | ||||
|       // Reserve space and process descriptors | ||||
|       characteristic_resp.descriptors.reserve(total_desc_count); | ||||
|       uint16_t desc_offset = 0; | ||||
|       esp_gattc_descr_elem_t desc_result; | ||||
|       while (true) {  // descriptors | ||||
|         uint16_t desc_count = 1; | ||||
|         esp_gatt_status_t desc_status = esp_ble_gattc_get_all_descr( | ||||
|             this->gattc_if_, this->conn_id_, char_result.char_handle, &desc_result, &desc_count, desc_offset); | ||||
|         if (desc_status == ESP_GATT_INVALID_OFFSET || desc_status == ESP_GATT_NOT_FOUND) { | ||||
|     if (total_char_count > 0) { | ||||
|       // Reserve space and process characteristics | ||||
|       service_resp.characteristics.reserve(total_char_count); | ||||
|       uint16_t char_offset = 0; | ||||
|       esp_gattc_char_elem_t char_result; | ||||
|       while (true) {  // characteristics | ||||
|         uint16_t char_count = 1; | ||||
|         esp_gatt_status_t char_status = | ||||
|             esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, service_result.start_handle, | ||||
|                                        service_result.end_handle, &char_result, &char_count, char_offset); | ||||
|         if (char_status == ESP_GATT_INVALID_OFFSET || char_status == ESP_GATT_NOT_FOUND) { | ||||
|           break; | ||||
|         } | ||||
|         if (desc_status != ESP_GATT_OK) { | ||||
|           ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d", this->connection_index_, | ||||
|                    this->address_str().c_str(), desc_status); | ||||
|         if (char_status != ESP_GATT_OK) { | ||||
|           ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_, | ||||
|                    this->address_str().c_str(), char_status); | ||||
|           this->send_service_ = DONE_SENDING_SERVICES; | ||||
|           return; | ||||
|         } | ||||
|         if (desc_count == 0) { | ||||
|           break;  // No more descriptors | ||||
|         if (char_count == 0) { | ||||
|           break; | ||||
|         } | ||||
|  | ||||
|         characteristic_resp.descriptors.emplace_back(); | ||||
|         auto &descriptor_resp = characteristic_resp.descriptors.back(); | ||||
|         service_resp.characteristics.emplace_back(); | ||||
|         auto &characteristic_resp = service_resp.characteristics.back(); | ||||
|  | ||||
|         fill_gatt_uuid(descriptor_resp.uuid, descriptor_resp.short_uuid, desc_result.uuid, use_efficient_uuids); | ||||
|         fill_gatt_uuid(characteristic_resp.uuid, characteristic_resp.short_uuid, char_result.uuid, use_efficient_uuids); | ||||
|  | ||||
|         descriptor_resp.handle = desc_result.handle; | ||||
|         desc_offset++; | ||||
|         characteristic_resp.handle = char_result.char_handle; | ||||
|         characteristic_resp.properties = char_result.properties; | ||||
|         char_offset++; | ||||
|  | ||||
|         // Get the number of descriptors directly with one call | ||||
|         uint16_t total_desc_count = 0; | ||||
|         esp_gatt_status_t desc_count_status = esp_ble_gattc_get_attr_count( | ||||
|             this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, 0, 0, char_result.char_handle, &total_desc_count); | ||||
|  | ||||
|         if (desc_count_status != ESP_GATT_OK) { | ||||
|           ESP_LOGE(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d", | ||||
|                    this->connection_index_, this->address_str().c_str(), char_result.char_handle, desc_count_status); | ||||
|           this->send_service_ = DONE_SENDING_SERVICES; | ||||
|           return; | ||||
|         } | ||||
|         if (total_desc_count == 0) { | ||||
|           // No descriptors, continue to next characteristic | ||||
|           continue; | ||||
|         } | ||||
|  | ||||
|         // Reserve space and process descriptors | ||||
|         characteristic_resp.descriptors.reserve(total_desc_count); | ||||
|         uint16_t desc_offset = 0; | ||||
|         esp_gattc_descr_elem_t desc_result; | ||||
|         while (true) {  // descriptors | ||||
|           uint16_t desc_count = 1; | ||||
|           esp_gatt_status_t desc_status = esp_ble_gattc_get_all_descr( | ||||
|               this->gattc_if_, this->conn_id_, char_result.char_handle, &desc_result, &desc_count, desc_offset); | ||||
|           if (desc_status == ESP_GATT_INVALID_OFFSET || desc_status == ESP_GATT_NOT_FOUND) { | ||||
|             break; | ||||
|           } | ||||
|           if (desc_status != ESP_GATT_OK) { | ||||
|             ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d", this->connection_index_, | ||||
|                      this->address_str().c_str(), desc_status); | ||||
|             this->send_service_ = DONE_SENDING_SERVICES; | ||||
|             return; | ||||
|           } | ||||
|           if (desc_count == 0) { | ||||
|             break;  // No more descriptors | ||||
|           } | ||||
|  | ||||
|           characteristic_resp.descriptors.emplace_back(); | ||||
|           auto &descriptor_resp = characteristic_resp.descriptors.back(); | ||||
|  | ||||
|           fill_gatt_uuid(descriptor_resp.uuid, descriptor_resp.short_uuid, desc_result.uuid, use_efficient_uuids); | ||||
|  | ||||
|           descriptor_resp.handle = desc_result.handle; | ||||
|           desc_offset++; | ||||
|         } | ||||
|       } | ||||
|     }  // end if (total_char_count > 0) | ||||
|  | ||||
|     // Calculate the actual size of just this service | ||||
|     size_t service_size = get_service_size(service_resp) + 1;  // +1 for field tag | ||||
|  | ||||
|     // Check if adding this service would exceed the limit | ||||
|     if (current_size + service_size > MAX_PACKET_SIZE) { | ||||
|       // We would go over - pop the last service if we have more than one | ||||
|       if (resp.services.size() > 1) { | ||||
|         resp.services.pop_back(); | ||||
|         ESP_LOGD(TAG, "[%d] [%s] Service %d would exceed limit (current: %d + service: %d > %d), sending current batch", | ||||
|                  this->connection_index_, this->address_str().c_str(), this->send_service_, current_size, service_size, | ||||
|                  MAX_PACKET_SIZE); | ||||
|         // Don't increment send_service_ - we'll retry this service in next batch | ||||
|       } else { | ||||
|         // This single service is too large, but we have to send it anyway | ||||
|         current_size += service_size; | ||||
|         ESP_LOGV(TAG, "[%d] [%s] Service %d is too large (%d bytes) but sending anyway", this->connection_index_, | ||||
|                  this->address_str().c_str(), this->send_service_, service_size); | ||||
|         // Increment so we don't get stuck | ||||
|         this->send_service_++; | ||||
|       } | ||||
|       // Send what we have | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     // Now we know we're keeping this service, add its size | ||||
|     current_size += service_size; | ||||
|  | ||||
|     // Log the difference between estimate and actual size | ||||
|     int size_diff = (int) service_size - (int) estimated_size; | ||||
|     ESP_LOGV(TAG, "[%d] [%s] Service %d actual: %d, estimated: %d, diff: %+d", this->connection_index_, | ||||
|              this->address_str().c_str(), this->send_service_, service_size, estimated_size, size_diff); | ||||
|     ESP_LOGV(TAG, "[%d] [%s] Total size now: %d", this->connection_index_, this->address_str().c_str(), current_size); | ||||
|  | ||||
|     // Successfully added this service, increment counter | ||||
|     this->send_service_++; | ||||
|   } | ||||
|  | ||||
|   // Send the message with 1-3 services | ||||
|   // Send the message with dynamically batched services | ||||
|   ESP_LOGD(TAG, "[%d] [%s] Sending batch with %d services, total size %d", this->connection_index_, | ||||
|            this->address_str().c_str(), resp.services.size(), current_size); | ||||
|   api_conn->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -22,7 +22,6 @@ namespace esphome::bluetooth_proxy { | ||||
|  | ||||
| static const esp_err_t ESP_GATT_NOT_CONNECTED = -1; | ||||
| static const int DONE_SENDING_SERVICES = -2; | ||||
| static const uint8_t MAX_SERVICES_PER_BATCH = 3; | ||||
|  | ||||
| using namespace esp32_ble_client; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user