5#if defined(USE_RP2040_BLE) && defined(USE_BLE_GATT_CLIENT)
11#include <BluetoothLock.h>
18static const char *
const TAG =
"bluetooth_connection.rp2";
20using ble_device_base::ESPBTUUID;
21using ble_device_base::GATT_ERR_NOT_CONNECTED;
22using ble_device_base::GATT_ERR_NO_MEMORY;
28static constexpr uint32_t CONNECT_TIMEOUT_MS = 20000;
32static constexpr uint32_t CONNECT_CANCEL_TIMEOUT_MS = 2000;
35static constexpr uint32_t CONNECT_RETRY_INTERVAL_MS = 50;
37static constexpr uint32_t WRITE_NO_RSP_TIMEOUT_MS = 500;
40static constexpr uint8_t HCI_REASON_CONNECTION_TIMEOUT = 0x08;
47static constexpr uint16_t CONN_SCAN_INTERVAL = 96;
48static constexpr uint16_t CONN_SCAN_WINDOW = 48;
49static constexpr uint16_t CONN_CE_MIN = 16;
50static constexpr uint16_t CONN_CE_MAX = 48;
52using ble_device_base::FAST_CONN_TIMEOUT;
53using ble_device_base::FAST_MAX_CONN_INTERVAL;
54using ble_device_base::FAST_MIN_CONN_INTERVAL;
55using ble_device_base::MEDIUM_CONN_TIMEOUT;
56using ble_device_base::MEDIUM_MAX_CONN_INTERVAL;
57using ble_device_base::MEDIUM_MIN_CONN_INTERVAL;
67static ESPBTUUID uuid_from_btstack(uint16_t uuid16,
const uint8_t uuid128[16]) {
78 if (!this->
event_pool_.warm() || !this->notify_pool_.warm()) {
79 ESP_LOGE(TAG,
"GATT event pool warm-up failed");
87 ESP_LOGE(TAG,
"GATT client registry full");
108#ifdef USE_OTA_STATE_LISTENER
115#ifdef USE_OTA_STATE_LISTENER
142 if (
type != HCI_EVENT_PACKET) {
145 uint8_t event_type = hci_event_packet_get_type(packet);
146 switch (event_type) {
147 case HCI_EVENT_META_GAP: {
148 if (hci_event_gap_meta_get_subevent_code(packet) != GAP_SUBEVENT_LE_CONNECTION_COMPLETE) {
151 uint8_t
status = gap_subevent_le_connection_complete_get_status(packet);
152 hci_con_handle_t con_handle = gap_subevent_le_connection_complete_get_connection_handle(packet);
154 gap_subevent_le_connection_complete_get_peer_address(packet, peer);
161 static constexpr bd_addr_t ZERO_ADDR = {};
162 if (inst !=
nullptr && memcmp(peer, ZERO_ADDR,
sizeof(bd_addr_t)) != 0 &&
163 memcmp(inst->
peer_addr_, peer,
sizeof(bd_addr_t)) != 0) {
174 gap_disconnect(con_handle);
179 if (inst ==
nullptr) {
184 gap_disconnect(con_handle);
197 case HCI_EVENT_DISCONNECTION_COMPLETE: {
201 if (inst !=
nullptr) {
212 if (
type != HCI_EVENT_PACKET) {
215 switch (hci_event_packet_get_type(packet)) {
216 case SM_EVENT_JUST_WORKS_REQUEST:
220 sm_just_works_confirm(sm_event_just_works_request_get_handle(packet));
222 case SM_EVENT_PAIRING_COMPLETE: {
224 if (inst !=
nullptr) {
229 case SM_EVENT_REENCRYPTION_COMPLETE: {
233 if (inst !=
nullptr) {
244 if (
type != HCI_EVENT_PACKET) {
247 uint8_t event_type = hci_event_packet_get_type(packet);
250 hci_con_handle_t con_handle;
251 switch (event_type) {
253 con_handle = gatt_event_mtu_get_handle(packet);
255 case GATT_EVENT_SERVICE_QUERY_RESULT:
256 con_handle = gatt_event_service_query_result_get_handle(packet);
258 case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
259 con_handle = gatt_event_characteristic_query_result_get_handle(packet);
261 case GATT_EVENT_ALL_CHARACTERISTIC_DESCRIPTORS_QUERY_RESULT:
262 con_handle = gatt_event_all_characteristic_descriptors_query_result_get_handle(packet);
264 case GATT_EVENT_LONG_CHARACTERISTIC_VALUE_QUERY_RESULT:
265 con_handle = gatt_event_long_characteristic_value_query_result_get_handle(packet);
267 case GATT_EVENT_LONG_CHARACTERISTIC_DESCRIPTOR_QUERY_RESULT:
268 con_handle = gatt_event_long_characteristic_descriptor_query_result_get_handle(packet);
270 case GATT_EVENT_NOTIFICATION:
271 con_handle = gatt_event_notification_get_handle(packet);
273 case GATT_EVENT_INDICATION:
274 con_handle = gatt_event_indication_get_handle(packet);
276 case GATT_EVENT_QUERY_COMPLETE:
277 con_handle = gatt_event_query_complete_get_handle(packet);
283 if (inst !=
nullptr) {
289 switch (event_type) {
293 case GATT_EVENT_QUERY_COMPLETE:
296 case GATT_EVENT_SERVICE_QUERY_RESULT: {
297 if (this->
arena_ ==
nullptr) {
304 gatt_client_service_t service;
305 gatt_event_service_query_result_get_service(packet, &service);
307 dst.
uuid = uuid_from_btstack(service.uuid16, service.uuid128);
308 dst.start_handle = service.start_group_handle;
309 dst.end_handle = service.end_group_handle;
310 dst.first_characteristic = 0;
311 dst.characteristic_count = 0;
315 case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT: {
316 if (this->
arena_ ==
nullptr) {
319 if (this->
char_count_ >= RP2_GATT_MAX_CHARACTERISTICS) {
323 gatt_client_characteristic_t characteristic;
324 gatt_event_characteristic_query_result_get_characteristic(packet, &characteristic);
326 dst.
uuid = uuid_from_btstack(characteristic.uuid16, characteristic.uuid128);
327 dst.value_handle = characteristic.value_handle;
328 dst.end_handle = characteristic.end_handle;
329 dst.properties =
static_cast<uint8_t
>(characteristic.properties);
330 dst.first_descriptor = 0;
331 dst.descriptor_count = 0;
335 case GATT_EVENT_ALL_CHARACTERISTIC_DESCRIPTORS_QUERY_RESULT: {
336 if (this->
arena_ ==
nullptr) {
339 if (this->
desc_count_ >= RP2_GATT_MAX_DESCRIPTORS) {
343 gatt_client_characteristic_descriptor_t descriptor;
344 gatt_event_all_characteristic_descriptors_query_result_get_characteristic_descriptor(packet, &descriptor);
346 dst.
uuid = uuid_from_btstack(descriptor.uuid16, descriptor.uuid128);
347 dst.handle = descriptor.handle;
351 case GATT_EVENT_LONG_CHARACTERISTIC_VALUE_QUERY_RESULT:
353 this->
assemble_blob_irq_(gatt_event_long_characteristic_value_query_result_get_value_offset(packet),
354 gatt_event_long_characteristic_value_query_result_get_value(packet),
355 gatt_event_long_characteristic_value_query_result_get_value_length(packet));
357 case GATT_EVENT_LONG_CHARACTERISTIC_DESCRIPTOR_QUERY_RESULT:
358 this->
assemble_blob_irq_(gatt_event_long_characteristic_descriptor_query_result_get_descriptor_offset(packet),
359 gatt_event_long_characteristic_descriptor_query_result_get_descriptor(packet),
360 gatt_event_long_characteristic_descriptor_query_result_get_descriptor_length(packet));
362 case GATT_EVENT_NOTIFICATION:
364 gatt_event_notification_get_value(packet),
365 gatt_event_notification_get_value_length(packet));
367 case GATT_EVENT_INDICATION:
369 this->
enqueue_notify_irq_(gatt_event_indication_get_value_handle(packet), gatt_event_indication_get_value(packet),
370 gatt_event_indication_get_value_length(packet));
379 if (offset >= RP2_GATT_MAX_ATTR_LEN) {
382 if (
len > RP2_GATT_MAX_ATTR_LEN - offset) {
383 len = RP2_GATT_MAX_ATTR_LEN - offset;
393 if (event ==
nullptr) {
399 event->status = status;
400 event->value = value;
407 if (event ==
nullptr) {
413 event->len = len > RP2_GATT_MAX_ATTR_LEN ? RP2_GATT_MAX_ATTR_LEN :
len;
414 memcpy(event->data, data, event->len);
438 uint16_t dropped = this->
event_queue_.get_and_reset_dropped_count();
442 ESP_LOGE(TAG,
"[%u] Dropped %u GATT control events, disconnecting", this->
engine_index_, dropped);
445 uint16_t notify_dropped = this->
notify_queue_.get_and_reset_dropped_count();
446 if (notify_dropped > 0) {
447 ESP_LOGW(TAG,
"[%u] Dropped %u GATT notifications (queue full)", this->
engine_index_, notify_dropped);
454 ESP_LOGW(TAG,
"[%u] Connect timeout (queued)", this->
engine_index_);
466 uint32_t budget = cancel_in_flight ? CONNECT_CANCEL_TIMEOUT_MS : CONNECT_TIMEOUT_MS;
470 bool cancel_sent =
false;
477 link_up = this->con_handle_ != HCI_CON_HANDLE_INVALID;
484 gap_connect_cancel();
487 this->connect_cancel_attempted_ =
true;
497 }
else if (cancel_sent) {
508 ESP_LOGW(TAG,
"[%u] Disconnect timeout, forcing idle", this->
engine_index_);
514 bool timed_out =
false;
528 this->
event_queue_.empty() && this->notify_queue_.empty())) {
535 switch (event.
type) {
544 this->
mtu_ =
event.value;
571 uint8_t
status = gatt_client_write_value_of_characteristic_without_response(self->con_handle_, self->op_handle_,
572 self->op_len_, self->op_buffer_);
573 if ((
status == GATT_CLIENT_BUSY ||
status == BTSTACK_ACL_BUFFERS_FULL) &&
574 gatt_client_request_to_write_without_response(&self->can_write_registration_, self->con_handle_) == 0) {
593 ESP_LOGW(TAG,
"[%u] Connect failed, status=0x%02x", this->
engine_index_, status);
610 if (disc_status != 0) {
617 ESP_LOGV(TAG,
"[%u] Link up, handle=0x%04x, negotiating MTU", this->
engine_index_, con_handle);
697 ESP_LOGV(TAG,
"[%u] Disconnected, reason=0x%02x", this->
engine_index_, reason);
717 if ((att_status == ATT_ERROR_INVALID_OFFSET || att_status == ATT_ERROR_ATTRIBUTE_NOT_LONG) &&
742 return GATT_ERR_NOT_CONNECTED;
745 return GATT_CLIENT_IN_WRONG_STATE;
747 if (this->
arena_ ==
nullptr) {
754 if (this->
arena_ ==
nullptr) {
755 ESP_LOGE(TAG,
"[%u] Service table allocation failed", this->
engine_index_);
756 return ble_device_base::GATT_ERR_NO_MEMORY;
777 gatt_client_service_t btstack_service = {};
778 btstack_service.start_group_handle = service.
start_handle;
779 btstack_service.end_group_handle = service.end_handle;
788 gatt_client_characteristic_t btstack_characteristic = {};
790 btstack_characteristic.end_handle = chr.end_handle;
794 &btstack_characteristic);
798 if (this->
arena_ ==
nullptr) {
804 if (att_status != 0) {
861 ESP_LOGV(TAG,
"[%u] Discovery done (err=%d): %u services, %u characteristics, %u descriptors", this->
engine_index_,
866 error = ATT_ERROR_INSUFFICIENT_RESOURCES;
873 gap_update_connection_parameters(this->
con_handle_, MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0,
874 MEDIUM_CONN_TIMEOUT);
877 ESP_LOGE(TAG,
"Service table truncated (device exceeds %u services / %u characteristics / %u descriptors)",
878 RP2_GATT_MAX_SERVICES, RP2_GATT_MAX_CHARACTERISTICS, RP2_GATT_MAX_DESCRIPTORS);
888 if (this->
arena_ !=
nullptr) {
900 if (this->
arena_ !=
nullptr) {
905 this->
arena_->~ServiceArena();
921 return GATT_ERR_NOT_CONNECTED;
924 return GATT_CLIENT_IN_WRONG_STATE;
927 return GATT_ERR_NOT_CONNECTED;
932 this->
peer_addr_type_ = (addr_type & 1) != 0 ? BD_ADDR_TYPE_LE_RANDOM : BD_ADDR_TYPE_LE_PUBLIC;
967 status = ERROR_CODE_COMMAND_DISALLOWED;
976 gap_set_connection_parameters(CONN_SCAN_INTERVAL, CONN_SCAN_WINDOW,
977 cached ? MEDIUM_MIN_CONN_INTERVAL : FAST_MIN_CONN_INTERVAL,
978 cached ? MEDIUM_MAX_CONN_INTERVAL : FAST_MAX_CONN_INTERVAL, 0,
979 cached ? MEDIUM_CONN_TIMEOUT : FAST_CONN_TIMEOUT, CONN_CE_MIN, CONN_CE_MAX);
995 if (
status == ERROR_CODE_COMMAND_DISALLOWED) {
1000 ESP_LOGW(TAG,
"[%u] gap_connect failed, status=0x%02x", this->
engine_index_, status);
1007 return GATT_ERR_NOT_CONNECTED;
1015 if (this->
con_handle_ == HCI_CON_HANDLE_INVALID) {
1032 gap_connect_cancel();
1041 uint8_t
status = ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
1042 if (this->
con_handle_ != HCI_CON_HANDLE_INVALID) {
1048 ESP_LOGW(TAG,
"[%u] gap_disconnect failed, status=0x%02x", this->
engine_index_, status);
1071 return GATT_ERR_NOT_CONNECTED;
1074 return GATT_CLIENT_IN_WRONG_STATE;
1092 return GATT_ERR_NOT_CONNECTED;
1094 if (
len > RP2_GATT_MAX_ATTR_LEN) {
1095 return ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LENGTH;
1106 return GATT_CLIENT_BUSY;
1108 status = gatt_client_write_value_of_characteristic_without_response(this->
con_handle_, handle,
len,
1109 const_cast<uint8_t *
>(data));
1112 if (
status == GATT_CLIENT_BUSY ||
status == BTSTACK_ACL_BUFFERS_FULL) {
1126 if (req != 0 && req != ERROR_CODE_COMMAND_DISALLOWED) {
1143 return GATT_CLIENT_IN_WRONG_STATE;
1168 return GATT_ERR_NOT_CONNECTED;
1171 return GATT_CLIENT_IN_WRONG_STATE;
1177 uint8_t
status = gatt_client_read_long_characteristic_descriptor_using_descriptor_handle(
1188 return GATT_ERR_NOT_CONNECTED;
1191 return GATT_CLIENT_IN_WRONG_STATE;
1193 if (
len > RP2_GATT_MAX_ATTR_LEN) {
1194 return ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LENGTH;
1200 uint8_t
status = gatt_client_write_characteristic_descriptor_using_descriptor_handle(
1211 return GATT_ERR_NOT_CONNECTED;
1220 return GATT_ERR_NOT_CONNECTED;
1227 return GATT_ERR_NO_MEMORY;
1255 return GATT_ERR_NOT_CONNECTED;
1258 return gap_update_connection_parameters(this->
con_handle_, min_interval, max_interval, latency, timeout);
1262 uint8_t mac[MAC_ADDRESS_SIZE];
1268 for (
int i = 0; i < le_device_db_max_count(); i++) {
1271 le_device_db_info(i, &addr_type, addr,
nullptr);
1272 if (addr_type != BD_ADDR_TYPE_UNKNOWN && memcmp(addr, mac,
sizeof(bd_addr_t)) == 0) {
1273 le_device_db_remove(i);
1282 return GATT_NOT_CONNECTED;
void mark_failed()
Mark this component as failed.
void enable_loop_soon_any_context()
Thread and ISR-safe version of enable_loop() that can be called from any context.
void enable_loop()
Enable this component's loop.
void disable_loop()
Disable this component's loop.
rp2040_ble::RP2040BLE * parent_
An STL allocator that uses SPI or internal RAM.
void deallocate(T *p, size_t n)
static ESPBTUUID from_uint16(uint16_t uuid)
static ESPBTUUID from_raw_reversed(const uint8_t *data)
Construct from raw 16-byte big-endian UUID (reversed on store).
virtual void on_notify_state(uint16_t handle, bool enabled, int error)
virtual void on_notify_data(uint16_t handle, const uint8_t *data, uint16_t len)
virtual void on_write_result(uint16_t handle, int error)
virtual void on_service_discovery_done(int error)
virtual void on_pairing_result(int status)
virtual void on_connection_state(bool connected, uint16_t mtu, int error)
virtual void on_read_result(uint16_t handle, const uint8_t *data, uint16_t len, int error)
void enqueue_notify_irq_(uint16_t handle, const uint8_t *data, uint16_t len)
uint16_t disc_service_cursor_
static RP2GattClient * connect_owner
int read_characteristic(uint16_t handle)
uint8_t notify_subscription_count_
esphome::EventPool< RP2GattNotifyEvent, RP2_GATT_NOTIFY_QUEUE_SIZE - 1 > notify_pool_
btstack_context_callback_registration_t can_write_registration_
int issue_characteristic_query_(uint16_t service_index)
void advance_discovery_(uint8_t att_status)
bd_addr_type_t peer_addr_type_
void finish_discovery_(int error)
void handle_event_(const RP2GattEvent &event)
void finish_write_no_rsp_(uint8_t status)
uint32_t write_no_rsp_started_
void enqueue_event_irq_(RP2GattEvent::Type type, uint8_t status, uint16_t value)
void handle_connected_(uint8_t status, uint16_t con_handle)
void handle_gatt_event_irq_(uint8_t event_type, const uint8_t *packet)
static void sm_packet_handler(uint8_t type, uint16_t channel, uint8_t *packet, uint16_t size)
int write_characteristic(uint16_t handle, const uint8_t *data, uint16_t len, bool response)
static btstack_packet_callback_registration_t hci_event_registration
void cleanup_link_state_()
hci_con_handle_t con_handle_
static RP2GattClient * instance_for_con_handle(hci_con_handle_t con_handle)
static void can_write_no_rsp_trampoline(void *context)
void on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) override
DiscoveryPhase discovery_phase_
uint16_t disc_char_cursor_
int read_descriptor(uint16_t handle)
bool op_in_flight_() const
static uint8_t instance_count
static void hci_packet_handler(uint8_t type, uint16_t channel, uint8_t *packet, uint16_t size)
ble_device_base::GattServiceTable get_service_table()
int connect(uint64_t address, uint8_t addr_type)
std::array< uint16_t, RP2_GATT_MAX_NOTIFY_SUBSCRIPTIONS > notify_subscriptions_
uint32_t disconnecting_started_
esphome::LockFreeQueue< RP2GattNotifyEvent, RP2_GATT_NOTIFY_QUEUE_SIZE > notify_queue_
ble_device_base::GattClientListener * listener_
int update_connection_params(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout)
uint32_t connect_started_
int notify_characteristic(uint16_t handle, bool enable)
static btstack_packet_callback_registration_t sm_event_registration
bool notify_subscribed_(uint16_t handle) const
gatt_client_notification_t notification_registration_
int write_descriptor(uint16_t handle, const uint8_t *data, uint16_t len)
void handle_disconnected_(uint8_t reason)
static RP2GattClient * instances[ESPHOME_BLE_GATT_CLIENT_COUNT]
void handle_query_complete_(uint8_t att_status)
void assemble_blob_irq_(uint16_t offset, const uint8_t *data, uint16_t len)
float get_setup_priority() const override
void dump_config() override
int issue_descriptor_query_(uint16_t char_index)
static void gatt_packet_handler(uint8_t type, uint16_t channel, uint8_t *packet, uint16_t size)
void release_scan_inhibit_()
uint8_t op_buffer_[RP2_GATT_MAX_ATTR_LEN]
ble_device_base::ConnectionType connection_type_
esphome::LockFreeQueue< RP2GattEvent, RP2_GATT_EVENT_QUEUE_SIZE > event_queue_
esphome::EventPool< RP2GattEvent, RP2_GATT_EVENT_QUEUE_SIZE - 1 > event_pool_
uint32_t connect_retry_ms_
bool connect_cancel_attempted_
void fail_connection_(uint8_t reason)
void add_global_state_listener(OTAGlobalStateListener *listener)
void inhibit_scan()
Pause the physical scan for the duration of a GATT connect attempt (initiating and scanning contend f...
void release_scan_inhibit()
void uint64_to_mac_msb_first(uint64_t address, uint8_t out[6])
Unpack a uint64 BLE address into printable (MSB-first) byte order — the order bd_addr_t / esp_bd_addr...
conn_err_t unpair_device(uint64_t address)
ble_device_base::ESPBTUUID ESPBTUUID
OTAGlobalCallback * get_global_ota_callback()
constexpr float AFTER_BLUETOOTH
uint32_t IRAM_ATTR HOT millis()
uint16_t descriptor_count
uint16_t characteristic_count
Borrowed view of the backend-owned service table.
const GattDescriptor * descriptors
uint16_t descriptor_count
const GattCharacteristic * characteristics
const GattService * services
uint16_t characteristic_count
ble_device_base::GattService services[RP2_GATT_MAX_SERVICES]
ble_device_base::GattCharacteristic characteristics[RP2_GATT_MAX_CHARACTERISTICS]
ble_device_base::GattDescriptor descriptors[RP2_GATT_MAX_DESCRIPTORS]
uint8_t data[RP2_GATT_MAX_ATTR_LEN]
spi_device_handle_t handle