mirror of
https://github.com/esphome/esphome.git
synced 2026-09-30 16:30:22 +00:00
Stream in place through a backend cut-through
This commit is contained in:
@@ -87,12 +87,10 @@ struct GattServiceTable {
|
||||
// - disconnect: also cancels a connect in progress.
|
||||
// - notify_characteristic: local registration only; the CCCD write is the
|
||||
// API client's responsibility (a plain write_descriptor).
|
||||
// - get_service_table(first_service): backend-owned transient storage,
|
||||
// valid until the next call or release_services() (idempotent release).
|
||||
// The services array is always complete; characteristics/descriptors need
|
||||
// only cover services from first_service through at least one full
|
||||
// MAX_PACKET_SIZE batch, with that window's index ranges rebased into the
|
||||
// returned arrays - backends bound their streaming peak to one batch.
|
||||
// - get_service_table/release_services: backend-owned transient storage,
|
||||
// released after streaming (release is idempotent). A backend may instead
|
||||
// provide its own service streamer (stream_service_batch on the concrete
|
||||
// type, detected by the consumer at compile time) and keep the table empty.
|
||||
// - completions: connect and disconnect land in on_connection_state,
|
||||
// discover_services in on_service_discovery_done, pair in
|
||||
// on_pairing_result, reads in on_read_result, notify_characteristic in
|
||||
@@ -111,7 +109,7 @@ concept BLEGattConnectionContract = requires(T conn, Sink *sink, const uint8_t *
|
||||
{ conn.notify_characteristic(uint16_t{}, true) } -> std::same_as<int>;
|
||||
{ conn.pair() } -> std::same_as<int>;
|
||||
{ conn.update_connection_params(uint16_t{}, uint16_t{}, uint16_t{}, uint16_t{}) } -> std::same_as<int>;
|
||||
{ conn.get_service_table(uint16_t{}) } -> std::same_as<GattServiceTable>;
|
||||
{ conn.get_service_table() } -> std::same_as<GattServiceTable>;
|
||||
{ conn.release_services() } -> std::same_as<void>;
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "bluetooth_connection.h"
|
||||
#include "bluetooth_connection_hub.h"
|
||||
|
||||
#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h"
|
||||
|
||||
#include "esphome/components/ble_device_base/ble_client_state.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
@@ -214,45 +216,7 @@ int BluedroidGattClient::update_connection_params(uint16_t min_interval, uint16_
|
||||
return this->update_conn_params_(min_interval, max_interval, latency, timeout, "custom");
|
||||
}
|
||||
|
||||
ble_device_base::GattServiceTable BluedroidGattClient::get_service_table(uint16_t first_service) {
|
||||
ble_device_base::GattServiceTable table{};
|
||||
if (this->services_ == nullptr || this->services_released_) {
|
||||
return table;
|
||||
}
|
||||
table.services = this->services_;
|
||||
table.service_count = this->service_total_;
|
||||
if (first_service >= this->service_total_) {
|
||||
// Terminal pump: the wrapper only reads service_count.
|
||||
return table;
|
||||
}
|
||||
if (!this->build_window_(first_service, table)) {
|
||||
// Zero-length ranges on a non-empty service trip the wrapper's
|
||||
// out-of-bounds abort, so a walk failure can never stream a truncated
|
||||
// database as authoritative.
|
||||
table.characteristics = nullptr;
|
||||
table.descriptors = nullptr;
|
||||
table.characteristic_count = 0;
|
||||
table.descriptor_count = 0;
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
void BluedroidGattClient::release_services() {
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
if (this->services_ != nullptr) {
|
||||
allocator.deallocate(reinterpret_cast<uint8_t *>(this->services_), 0);
|
||||
this->services_ = nullptr;
|
||||
}
|
||||
if (this->window_ != nullptr) {
|
||||
allocator.deallocate(this->window_, 0);
|
||||
this->window_ = nullptr;
|
||||
this->window_cap_ = 0;
|
||||
}
|
||||
if (this->elems_ != nullptr) {
|
||||
allocator.deallocate(this->elems_, 0);
|
||||
this->elems_ = nullptr;
|
||||
this->elems_cap_ = 0;
|
||||
}
|
||||
this->service_total_ = 0;
|
||||
#ifndef CONFIG_BT_GATTC_CACHE_NVS_FLASH
|
||||
// Only the cache clean makes the stack's database unsafe to walk.
|
||||
@@ -308,164 +272,150 @@ void BluedroidGattClient::log_gattc_warning_(const char *operation, int code) {
|
||||
ESP_LOGW(TAG, "[%d] %s failed, status=%d", this->connection_index_, operation, code);
|
||||
}
|
||||
|
||||
// ---- table materialization ----
|
||||
// ---- service streaming ----
|
||||
|
||||
uint8_t *BluedroidGattClient::ensure_capacity_(uint8_t *&buf, size_t &cap, size_t needed) {
|
||||
if (needed <= cap) {
|
||||
return buf;
|
||||
}
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
if (buf != nullptr) {
|
||||
allocator.deallocate(buf, 0);
|
||||
}
|
||||
buf = allocator.allocate(needed);
|
||||
cap = buf != nullptr ? needed : 0;
|
||||
return buf;
|
||||
}
|
||||
|
||||
bool BluedroidGattClient::materialize_services_() {
|
||||
if (this->services_released_) {
|
||||
ESP_LOGW(TAG, "[%d] Services released, cannot walk the GATT cache", this->connection_index_);
|
||||
return false;
|
||||
}
|
||||
void BluedroidGattClient::handle_search_cmpl_() {
|
||||
// Step down from the fast discovery params.
|
||||
this->update_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT, "medium");
|
||||
uint16_t primary = 0;
|
||||
uint16_t secondary = 0;
|
||||
esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_PRIMARY_SERVICE, 0x0001, 0xFFFF, 0,
|
||||
&primary);
|
||||
esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_SECONDARY_SERVICE, 0x0001, 0xFFFF, 0,
|
||||
&secondary);
|
||||
const uint16_t total = primary + secondary;
|
||||
if (total == 0) {
|
||||
this->service_total_ = 0;
|
||||
return true;
|
||||
this->service_total_ = primary + secondary;
|
||||
if (this->listener_ != nullptr) {
|
||||
this->listener_->on_service_discovery_done(0);
|
||||
}
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
this->services_ = reinterpret_cast<ble_device_base::GattService *>(
|
||||
allocator.allocate(total * sizeof(ble_device_base::GattService)));
|
||||
if (this->services_ == nullptr) {
|
||||
ESP_LOGE(TAG, "[%d] Service list allocation failed", this->connection_index_);
|
||||
return false;
|
||||
}
|
||||
uint16_t filled = 0;
|
||||
for (uint16_t s = 0; s < total; s++) {
|
||||
esp_gattc_service_elem_t service_result;
|
||||
uint16_t count = 1;
|
||||
if (esp_ble_gattc_get_service(this->gattc_if_, this->conn_id_, nullptr, &service_result, &count, s) !=
|
||||
ESP_GATT_OK ||
|
||||
count == 0) {
|
||||
break;
|
||||
}
|
||||
auto &service = this->services_[filled];
|
||||
service.uuid = esp32_ble_tracker::ESPBTUUID::from_uuid(service_result.uuid);
|
||||
service.start_handle = service_result.start_handle;
|
||||
service.end_handle = service_result.end_handle;
|
||||
service.first_characteristic = 0;
|
||||
uint16_t char_count = 0;
|
||||
esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_CHARACTERISTIC,
|
||||
service_result.start_handle, service_result.end_handle, 0, &char_count);
|
||||
service.characteristic_count = char_count;
|
||||
filled++;
|
||||
}
|
||||
this->service_total_ = filled;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluedroidGattClient::build_window_(uint16_t first_service, ble_device_base::GattServiceTable &out) {
|
||||
if (this->services_released_) {
|
||||
return false;
|
||||
void BluedroidGattClient::stream_service_batch(BluetoothConnection &conn) {
|
||||
if (this->services_released_ || conn.send_service_ >= this->service_total_) {
|
||||
conn.send_service_ = DONE_SENDING_SERVICES;
|
||||
conn.proxy_->send_gatt_services_done(conn.address_);
|
||||
this->release_services();
|
||||
return;
|
||||
}
|
||||
// Cover at least the services the wrapper can admit into one batch. Sizing
|
||||
// with the smaller (efficient-UUID) estimate makes the window a superset of
|
||||
// the admitted set in either UUID mode.
|
||||
uint16_t window_end = first_service;
|
||||
size_t est = 0;
|
||||
uint16_t chars_total = 0;
|
||||
uint16_t descs_bound = 0;
|
||||
uint16_t elems_max = 0;
|
||||
while (window_end < this->service_total_) {
|
||||
const auto &svc = this->services_[window_end];
|
||||
uint16_t n_attrs = 0;
|
||||
if (esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_ALL, svc.start_handle, svc.end_handle,
|
||||
0, &n_attrs) != ESP_GATT_OK) {
|
||||
return false;
|
||||
|
||||
// The subscriber vanished mid-stream: park the cursor at done WITHOUT
|
||||
// sending services-done (a resubscribing client gets silence and its 30 s
|
||||
// timeout, never an authoritative partial list).
|
||||
auto *api_conn = conn.proxy_->get_api_connection();
|
||||
if (api_conn == nullptr) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] API connection lost while streaming services", conn.connection_index_, conn.address_str_);
|
||||
conn.send_service_ = DONE_SENDING_SERVICES;
|
||||
this->release_services();
|
||||
return;
|
||||
}
|
||||
|
||||
bool use_efficient_uuids = conn.proxy_->client_supports_efficient_uuids();
|
||||
api::BluetoothGATTGetServicesResponse resp;
|
||||
resp.address = conn.address_;
|
||||
size_t current_size = resp.calculate_size();
|
||||
int16_t batch_start = conn.send_service_;
|
||||
|
||||
while (conn.send_service_ < this->service_total_) {
|
||||
esp_gattc_service_elem_t service_result;
|
||||
uint16_t svc_count = 1;
|
||||
if (esp_ble_gattc_get_service(this->gattc_if_, this->conn_id_, nullptr, &service_result, &svc_count,
|
||||
conn.send_service_) != ESP_GATT_OK ||
|
||||
svc_count == 0) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] Service walk failed (service %d), aborting stream", conn.connection_index_,
|
||||
conn.address_str_, conn.send_service_);
|
||||
conn.send_service_ = DONE_SENDING_SERVICES;
|
||||
return;
|
||||
}
|
||||
chars_total += svc.characteristic_count;
|
||||
if (n_attrs > 1 + svc.characteristic_count) {
|
||||
descs_bound += n_attrs - 1 - svc.characteristic_count;
|
||||
uint16_t total_char_count = 0;
|
||||
if (esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_CHARACTERISTIC,
|
||||
service_result.start_handle, service_result.end_handle, 0,
|
||||
&total_char_count) != ESP_GATT_OK) {
|
||||
conn.send_service_ = DONE_SENDING_SERVICES;
|
||||
return;
|
||||
}
|
||||
if (n_attrs > elems_max) {
|
||||
elems_max = n_attrs;
|
||||
}
|
||||
est += estimate_service_size(svc.characteristic_count, true);
|
||||
window_end++;
|
||||
if (est > MAX_PACKET_SIZE) {
|
||||
|
||||
// If this service likely won't fit, send the current batch 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) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t chr_bytes = chars_total * sizeof(ble_device_base::GattCharacteristic);
|
||||
if (this->ensure_capacity_(this->window_, this->window_cap_,
|
||||
chr_bytes + descs_bound * sizeof(ble_device_base::GattDescriptor)) == nullptr ||
|
||||
this->ensure_capacity_(this->elems_, this->elems_cap_, elems_max * sizeof(esp_gattc_db_elem_t)) == nullptr) {
|
||||
ESP_LOGE(TAG, "[%d] Service window allocation failed", this->connection_index_);
|
||||
return false;
|
||||
}
|
||||
auto *chars = reinterpret_cast<ble_device_base::GattCharacteristic *>(this->window_);
|
||||
auto *descs = reinterpret_cast<ble_device_base::GattDescriptor *>(this->window_ + chr_bytes);
|
||||
auto *elems = reinterpret_cast<esp_gattc_db_elem_t *>(this->elems_);
|
||||
resp.services.emplace_back();
|
||||
auto &service_resp = resp.services.back();
|
||||
fill_gatt_uuid(service_resp.uuid, service_resp.short_uuid,
|
||||
esp32_ble_tracker::ESPBTUUID::from_uuid(service_result.uuid), use_efficient_uuids);
|
||||
service_resp.handle = service_result.start_handle;
|
||||
|
||||
uint16_t chr_index = 0;
|
||||
uint16_t dsc_index = 0;
|
||||
for (uint16_t s = first_service; s < window_end; s++) {
|
||||
auto &svc = this->services_[s];
|
||||
svc.first_characteristic = chr_index;
|
||||
uint16_t filled_chars = 0;
|
||||
uint16_t count = elems_max;
|
||||
if (esp_ble_gattc_get_db(this->gattc_if_, this->conn_id_, svc.start_handle, svc.end_handle, elems, &count) !=
|
||||
ESP_GATT_OK) {
|
||||
return false;
|
||||
}
|
||||
// The snapshot is handle-ordered: descriptors follow their characteristic.
|
||||
ble_device_base::GattCharacteristic *chr = nullptr;
|
||||
for (uint16_t i = 0; i < count; i++) {
|
||||
const auto &elem = elems[i];
|
||||
if (elem.type == ESP_GATT_DB_CHARACTERISTIC) {
|
||||
// Bounded by the pass-1 count: a misbehaving peripheral can return
|
||||
// more entries than it reported.
|
||||
if (filled_chars >= svc.characteristic_count || chr_index >= chars_total) {
|
||||
if (total_char_count > 0) {
|
||||
service_resp.characteristics.init(total_char_count);
|
||||
uint16_t char_offset = 0;
|
||||
esp_gattc_char_elem_t char_result;
|
||||
// Bounded by the count query: a misbehaving peripheral can make the
|
||||
// enumeration return more entries than it reported.
|
||||
while (char_offset < total_char_count) {
|
||||
uint16_t cc = 1;
|
||||
auto char_status = esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, service_result.start_handle,
|
||||
service_result.end_handle, &char_result, &cc, char_offset);
|
||||
if (char_status == ESP_GATT_INVALID_OFFSET || char_status == ESP_GATT_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
chr = &chars[chr_index++];
|
||||
filled_chars++;
|
||||
chr->uuid = esp32_ble_tracker::ESPBTUUID::from_uuid(elem.uuid);
|
||||
chr->value_handle = elem.attribute_handle;
|
||||
chr->end_handle = elem.attribute_handle;
|
||||
chr->properties = elem.properties;
|
||||
chr->first_descriptor = dsc_index;
|
||||
chr->descriptor_count = 0;
|
||||
} else if (elem.type == ESP_GATT_DB_DESCRIPTOR && chr != nullptr && dsc_index < descs_bound) {
|
||||
descs[dsc_index].uuid = esp32_ble_tracker::ESPBTUUID::from_uuid(elem.uuid);
|
||||
descs[dsc_index].handle = elem.attribute_handle;
|
||||
dsc_index++;
|
||||
chr->descriptor_count++;
|
||||
if (char_status != ESP_GATT_OK || cc == 0) {
|
||||
if (char_status != ESP_GATT_OK) {
|
||||
conn.send_service_ = DONE_SENDING_SERVICES;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
service_resp.characteristics.emplace_back();
|
||||
auto &characteristic_resp = service_resp.characteristics.back();
|
||||
fill_gatt_uuid(characteristic_resp.uuid, characteristic_resp.short_uuid,
|
||||
esp32_ble_tracker::ESPBTUUID::from_uuid(char_result.uuid), use_efficient_uuids);
|
||||
characteristic_resp.handle = char_result.char_handle;
|
||||
characteristic_resp.properties = char_result.properties;
|
||||
|
||||
uint16_t total_desc_count = 0;
|
||||
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 (total_desc_count > 0) {
|
||||
characteristic_resp.descriptors.init(total_desc_count);
|
||||
uint16_t desc_offset = 0;
|
||||
esp_gattc_descr_elem_t desc_result;
|
||||
while (desc_offset < total_desc_count) {
|
||||
uint16_t dc = 1;
|
||||
auto desc_status = esp_ble_gattc_get_all_descr(this->gattc_if_, this->conn_id_, char_result.char_handle,
|
||||
&desc_result, &dc, desc_offset);
|
||||
if (desc_status == ESP_GATT_INVALID_OFFSET || desc_status == ESP_GATT_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
if (desc_status != ESP_GATT_OK || dc == 0) {
|
||||
if (desc_status != ESP_GATT_OK) {
|
||||
conn.send_service_ = DONE_SENDING_SERVICES;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
characteristic_resp.descriptors.emplace_back();
|
||||
auto &descriptor_resp = characteristic_resp.descriptors.back();
|
||||
fill_gatt_uuid(descriptor_resp.uuid, descriptor_resp.short_uuid,
|
||||
esp32_ble_tracker::ESPBTUUID::from_uuid(desc_result.uuid), use_efficient_uuids);
|
||||
descriptor_resp.handle = desc_result.handle;
|
||||
desc_offset++;
|
||||
}
|
||||
}
|
||||
char_offset++;
|
||||
}
|
||||
}
|
||||
svc.characteristic_count = filled_chars;
|
||||
|
||||
if (close_service_batch(resp, current_size, conn.send_service_, conn.connection_index_, conn.address_str_) !=
|
||||
BatchClose::CONTINUE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
out.characteristics = chars;
|
||||
out.descriptors = descs;
|
||||
out.characteristic_count = chr_index;
|
||||
out.descriptor_count = dsc_index;
|
||||
return true;
|
||||
}
|
||||
|
||||
void BluedroidGattClient::handle_search_cmpl_() {
|
||||
// Step down from the fast discovery params.
|
||||
this->update_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT, "medium");
|
||||
bool ok = this->materialize_services_();
|
||||
if (this->listener_ != nullptr) {
|
||||
this->listener_->on_service_discovery_done(ok ? 0 : ble_device_base::GATT_ERR_NO_MEMORY);
|
||||
// On a failed send, rewind the cursor so the batch is retried instead of
|
||||
// silently skipped.
|
||||
if (!api_conn->send_message(resp)) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Failed to send service batch, retrying", conn.connection_index_, conn.address_str_);
|
||||
conn.send_service_ = batch_start;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,9 +66,16 @@ class BluedroidGattClient final : public Component {
|
||||
int notify_characteristic(uint16_t handle, bool enable);
|
||||
int pair();
|
||||
int update_connection_params(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout);
|
||||
ble_device_base::GattServiceTable get_service_table(uint16_t first_service);
|
||||
// Never called: this backend streams in place (stream_service_batch), so
|
||||
// the table stays empty. Satisfies the contract concept.
|
||||
ble_device_base::GattServiceTable get_service_table() { return {}; }
|
||||
void release_services();
|
||||
|
||||
/// In-place service streamer (the wrapper detects and prefers it): builds
|
||||
/// one api response batch directly from Bluedroid's cached database, so the
|
||||
/// streaming peak is the response itself - the old esp32 model.
|
||||
void stream_service_batch(BluetoothConnection &conn);
|
||||
|
||||
void set_connection_type(esp32_ble_tracker::ConnectionType ct) { this->connection_type_ = ct; }
|
||||
bool disconnect_pending() const { return this->shim_.disconnect_pending(); }
|
||||
void cancel_pending_disconnect() { this->shim_.cancel_pending_disconnect(); }
|
||||
@@ -85,9 +92,6 @@ class BluedroidGattClient final : public Component {
|
||||
void handle_open_evt_(esp_ble_gattc_cb_param_t *param);
|
||||
void handle_disconnect_evt_(esp_ble_gattc_cb_param_t *param);
|
||||
void handle_search_cmpl_();
|
||||
bool materialize_services_();
|
||||
bool build_window_(uint16_t first_service, ble_device_base::GattServiceTable &out);
|
||||
uint8_t *ensure_capacity_(uint8_t *&buf, size_t &cap, size_t needed);
|
||||
void unconditional_disconnect_();
|
||||
void set_idle_();
|
||||
void set_disconnecting_();
|
||||
@@ -100,16 +104,6 @@ class BluedroidGattClient final : public Component {
|
||||
// Group 1: pointers / composed objects
|
||||
BluedroidTrackerShim shim_{this};
|
||||
BluetoothConnection *listener_{nullptr};
|
||||
// The full services array (small) lives for the whole streaming window;
|
||||
// characteristics/descriptors are materialized per batch into grow-only
|
||||
// scratch buffers so the peak stays bounded to one batch (the old
|
||||
// streamer's crash-driven budget). All freed by release_services().
|
||||
ble_device_base::GattService *services_{nullptr};
|
||||
uint8_t *window_{nullptr};
|
||||
uint8_t *elems_{nullptr};
|
||||
size_t window_cap_{0};
|
||||
size_t elems_cap_{0};
|
||||
|
||||
// Group 2: 4-byte types
|
||||
int gattc_if_{ESP_GATT_IF_NONE};
|
||||
uint32_t disconnecting_started_{0};
|
||||
@@ -120,13 +114,13 @@ class BluedroidGattClient final : public Component {
|
||||
// Group 4: 2-byte types
|
||||
uint16_t conn_id_{0xFFFF};
|
||||
uint16_t mtu_{23};
|
||||
uint16_t service_total_{0};
|
||||
|
||||
// Group 5: 1-byte types
|
||||
// Stored narrow (the enum is 4 bytes); widened at the esp_ble_gattc_open call.
|
||||
uint8_t remote_addr_type_{0};
|
||||
esp32_ble_tracker::ConnectionType connection_type_{esp32_ble_tracker::ConnectionType::V3_WITHOUT_CACHE};
|
||||
uint8_t connection_index_;
|
||||
uint8_t service_total_{0};
|
||||
// Set only when release_services() cleans the stack's GATT cache, which no
|
||||
// walk may then touch (Bluedroid asserts rather than erroring).
|
||||
bool services_released_{false};
|
||||
|
||||
@@ -45,7 +45,7 @@ class StubGattBackend {
|
||||
int update_connection_params(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout) {
|
||||
return ble_device_base::GATT_ERR_NOT_CONNECTED;
|
||||
}
|
||||
ble_device_base::GattServiceTable get_service_table(uint16_t first_service) { return {}; }
|
||||
ble_device_base::GattServiceTable get_service_table() { return {}; }
|
||||
void release_services() {}
|
||||
};
|
||||
|
||||
|
||||
@@ -325,7 +325,7 @@ conn_err_t BluetoothConnection::update_connection_params(uint16_t min_interval,
|
||||
// ---- Service streaming ----
|
||||
|
||||
void BluetoothConnection::send_service_for_discovery_() {
|
||||
auto table = this->backend_->get_service_table(this->send_service_);
|
||||
auto table = this->backend_->get_service_table();
|
||||
if (this->send_service_ >= table.service_count) {
|
||||
this->send_service_ = DONE_SENDING_SERVICES;
|
||||
this->proxy_->send_gatt_services_done(this->address_);
|
||||
|
||||
@@ -78,7 +78,7 @@ class BluetoothConnection final {
|
||||
/// loop() and has the same 10 s safety net in its base class).
|
||||
void process_pending_services() {
|
||||
if (this->send_service_ >= 0) {
|
||||
this->send_service_for_discovery_();
|
||||
this->stream_pending_(this->backend_);
|
||||
}
|
||||
this->check_disconnect_timeout_();
|
||||
}
|
||||
@@ -94,8 +94,21 @@ class BluetoothConnection final {
|
||||
|
||||
protected:
|
||||
friend class bluetooth_proxy::BluetoothProxy;
|
||||
// The Bluedroid backend streams services in place from its stack cache.
|
||||
friend class BluedroidGattClient;
|
||||
|
||||
void start_connect_();
|
||||
// A backend providing its own streamer (see the contract doc) builds the
|
||||
// response in place from its stack cache; the rest use the table streamer.
|
||||
// Template so the discarded branch is not odr-checked against backends
|
||||
// that lack the method.
|
||||
template<typename Backend> void stream_pending_(Backend *backend) {
|
||||
if constexpr (requires { backend->stream_service_batch(*this); }) {
|
||||
backend->stream_service_batch(*this);
|
||||
} else {
|
||||
this->send_service_for_discovery_();
|
||||
}
|
||||
}
|
||||
void send_service_for_discovery_();
|
||||
void check_disconnect_timeout_();
|
||||
void reset_connection_(conn_err_t reason);
|
||||
|
||||
@@ -801,7 +801,7 @@ void RP2GattClient::finish_discovery_(int error) {
|
||||
}
|
||||
}
|
||||
|
||||
ble_device_base::GattServiceTable RP2GattClient::get_service_table(uint16_t first_service) {
|
||||
ble_device_base::GattServiceTable RP2GattClient::get_service_table() {
|
||||
ble_device_base::GattServiceTable table;
|
||||
if (this->arena_ != nullptr) {
|
||||
table.services = this->arena_->services;
|
||||
|
||||
@@ -93,7 +93,7 @@ class RP2GattClient final : public Component, public Parented<rp2040_ble::RP2040
|
||||
int notify_characteristic(uint16_t handle, bool enable);
|
||||
int pair();
|
||||
int update_connection_params(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout);
|
||||
ble_device_base::GattServiceTable get_service_table(uint16_t first_service);
|
||||
ble_device_base::GattServiceTable get_service_table();
|
||||
void release_services();
|
||||
|
||||
protected:
|
||||
|
||||
@@ -49,7 +49,7 @@ class MinimalConnection {
|
||||
int update_connection_params(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout) {
|
||||
return 0;
|
||||
}
|
||||
GattServiceTable get_service_table(uint16_t first_service) { return {}; }
|
||||
GattServiceTable get_service_table() { return {}; }
|
||||
void release_services() {}
|
||||
|
||||
protected:
|
||||
@@ -70,7 +70,7 @@ TEST(BleGattClientContract, MinimalImplementerCompilesAndRoutesEvents) {
|
||||
EXPECT_EQ(connection.read_characteristic(1), GATT_ERR_NOT_CONNECTED);
|
||||
|
||||
// A default table is empty and safe to walk.
|
||||
GattServiceTable table = connection.get_service_table(0);
|
||||
GattServiceTable table = connection.get_service_table();
|
||||
EXPECT_EQ(table.service_count, 0);
|
||||
EXPECT_EQ(table.characteristic_count, 0);
|
||||
EXPECT_EQ(table.descriptor_count, 0);
|
||||
|
||||
Reference in New Issue
Block a user