ESPHome 2026.8.0b4
Loading...
Searching...
No Matches
bluetooth_connection_rp2.h
Go to the documentation of this file.
1// RP2 (Pico W / Pico 2 W) GATT client backend over BTstack.
2//
3// The build's ble_device_base::BLEGattConnection backend (bound by alias in
4// bluetooth_connection_gatt_backend.h) for the hub BluetoothConnection wrapper. BTstack packet handlers run in the
5// CYW43 async-context low-priority IRQ (or on the main-loop stack during BluetoothLock release), so handlers only copy
6// into per-instance lock-free queues/storage; loop() drains them and drives the state machine. Every BTstack call
7// issued from the main loop is wrapped in BluetoothLock.
8
9#pragma once
10
12
13#if defined(USE_RP2040_BLE) && defined(USE_BLE_GATT_CLIENT)
14
21
22#ifdef USE_OTA_STATE_LISTENER
24#endif
25
26#include <btstack.h>
27
28#include <array>
29#include <cstdint>
30
32
33// Caps for the transient service table. Sized generously for real devices
34// (typical peripherals expose < 8 services / < 30 characteristics); a peer
35// exceeding a cap fails discovery with INSUFFICIENT_RESOURCES rather than
36// streaming an incomplete database a V3 client would cache permanently.
37static constexpr uint16_t RP2_GATT_MAX_SERVICES = 16;
38static constexpr uint16_t RP2_GATT_MAX_CHARACTERISTICS = 96;
39static constexpr uint16_t RP2_GATT_MAX_DESCRIPTORS = 96;
40
41// Concurrent notify subscriptions per connection (enable fails with
42// GATT_ERR_NO_MEMORY when exceeded; real clients subscribe to a handful).
43static constexpr uint8_t RP2_GATT_MAX_NOTIFY_SUBSCRIPTIONS = 16;
44
45// ATT spec maximum attribute value length; bounds the op buffer and
46// notification payloads.
47static constexpr uint16_t RP2_GATT_MAX_ATTR_LEN = 512;
48
49// Control events from the BTstack handlers to loop().
51 enum Type : uint8_t {
52 CONNECTED, // status + con_handle (value)
53 DISCONNECTED, // status = HCI reason
54 MTU_EXCHANGED, // value = negotiated MTU
55 QUERY_COMPLETE, // status = ATT status of the finished query
56 WRITE_NO_RSP_DONE, // status = result of the deferred write
57 PAIRING_RESULT, // status = SM pairing status (0 = bonded)
58 };
60 uint8_t status;
61 uint16_t value;
62 void release() {}
63};
64
65// One notification/indication from the peer.
67 uint16_t handle;
68 uint16_t len;
69 uint8_t data[RP2_GATT_MAX_ATTR_LEN];
70 void release() {}
71};
72
73static constexpr uint8_t RP2_GATT_EVENT_QUEUE_SIZE = 8;
74// Depth 4: the queue is drained every main-loop iteration and each slot is a
75// full 512 B ATT payload, so depth buys burst tolerance at ~516 B per slot.
76static constexpr uint8_t RP2_GATT_NOTIFY_QUEUE_SIZE = 4;
77
78class RP2GattClient final : public Component,
79 public Parented<rp2040_ble::RP2040BLE>
80#ifdef USE_OTA_STATE_LISTENER
81 ,
83#endif
84{
85 public:
86 void setup() override;
87 void loop() override;
88 void dump_config() override;
89 float get_setup_priority() const override;
90
91 void set_listener(ble_device_base::GattClientListener *listener) { this->listener_ = listener; }
92
93 // ---- ble_device_base::BLEGattConnection contract ----
94 int connect(uint64_t address, uint8_t addr_type);
95 int gatt_disconnect();
96 // Teardown starts inside gatt_disconnect() on this backend; nothing is
97 // ever scheduled, so there is nothing to cancel.
98 bool cancel_gatt_disconnect() { return false; }
100 int read_characteristic(uint16_t handle);
101 int write_characteristic(uint16_t handle, const uint8_t *data, uint16_t len, bool response);
102 int read_descriptor(uint16_t handle);
103 int write_descriptor(uint16_t handle, const uint8_t *data, uint16_t len);
104 int notify_characteristic(uint16_t handle, bool enable);
105 int pair();
106 int update_connection_params(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout);
108 // Cached connections initiate at MEDIUM parameters (esp32 parity); FAST is
109 // reserved for the discovery phase of uncached connects.
111 void release_services();
112
113#ifdef USE_OTA_STATE_LISTENER
114 // Drop the connection while an OTA runs (esp32 parity): an active link
115 // competes with the transfer for the shared radio.
116 void on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) override;
117#endif
118
119 protected:
120 // Link/engine state. Discovery and GATT ops have their own cursors below —
121 // the link stays READY while they run.
122 enum class EngineState : uint8_t {
123 IDLE,
124 CONNECT_PENDING, // queued: another engine owns the stack-wide create-connection
125 CONNECTING, // gap_connect issued, waiting for connection complete
126 MTU_EXCHANGE, // link up, waiting for GATT_EVENT_MTU
127 READY, // on_connection_state(true) delivered
129 };
130
132
134
135 // The whole table in one transient allocation (RAMAllocator, checked),
136 // freed after streaming.
141 };
142
143 // BTstack packet handlers (IRQ context: copy-and-enqueue only).
144 static void hci_packet_handler(uint8_t type, uint16_t channel, uint8_t *packet, uint16_t size);
145 static void gatt_packet_handler(uint8_t type, uint16_t channel, uint8_t *packet, uint16_t size);
146 static void sm_packet_handler(uint8_t type, uint16_t channel, uint8_t *packet, uint16_t size);
147 static RP2GattClient *instance_for_con_handle(hci_con_handle_t con_handle);
148
149 void handle_gatt_event_irq_(uint8_t event_type, const uint8_t *packet);
150 void enqueue_event_irq_(RP2GattEvent::Type type, uint8_t status, uint16_t value);
151 void enqueue_notify_irq_(uint16_t handle, const uint8_t *data, uint16_t len);
152 void assemble_blob_irq_(uint16_t offset, const uint8_t *data, uint16_t len);
153
154 // Main-loop state machine.
155 void handle_event_(const RP2GattEvent &event);
156 void handle_connected_(uint8_t status, uint16_t con_handle);
157 void handle_disconnected_(uint8_t reason);
158 void handle_query_complete_(uint8_t att_status);
159 void advance_discovery_(uint8_t att_status);
160 int issue_characteristic_query_(uint16_t service_index);
161 int issue_descriptor_query_(uint16_t char_index);
162 void finish_discovery_(int error);
163 void fail_connection_(uint8_t reason);
164 int try_gap_connect_();
165 void cleanup_link_state_();
166 bool notify_subscribed_(uint16_t handle) const;
167 static void can_write_no_rsp_trampoline(void *context);
168 void finish_write_no_rsp_(uint8_t status);
170 bool op_in_flight_() const {
172 }
173
174 // Group 1: containers / large storage
178 esphome::EventPool<RP2GattEvent, RP2_GATT_EVENT_QUEUE_SIZE - 1> event_pool_;
180 esphome::EventPool<RP2GattNotifyEvent, RP2_GATT_NOTIFY_QUEUE_SIZE - 1> notify_pool_;
181
182 // Shared buffer for the single outstanding GATT op: write payloads (BTstack
183 // keeps the caller's pointer until the request is sent) and read results
184 // (written from the handler, read after QUERY_COMPLETE is drained).
185 uint8_t op_buffer_[RP2_GATT_MAX_ATTR_LEN];
186
187 // BTstack registrations
188 gatt_client_notification_t notification_registration_{};
189 btstack_context_callback_registration_t can_write_registration_{};
190
191 // Group 3: 4-byte types
193 uint32_t connect_retry_ms_{0}; // last CONNECT_PENDING gap_connect attempt
196 // Unscoped C enum, so int-sized: lives with the 4-byte members to keep the
197 // padding at the tail.
198 bd_addr_type_t peer_addr_type_{BD_ADDR_TYPE_LE_PUBLIC};
199
200 // Group 4: 2-byte types (table counters written from the handler during
201 // discovery, read from the main loop after the phase's QUERY_COMPLETE)
202 hci_con_handle_t con_handle_{HCI_CON_HANDLE_INVALID};
203 uint16_t mtu_{ble_device_base::DEFAULT_ATT_MTU};
204 uint16_t op_handle_{0};
205 uint16_t op_len_{0};
206 uint16_t service_count_{0};
207 uint16_t char_count_{0};
208 uint16_t desc_count_{0};
210 uint16_t disc_char_cursor_{0};
211
212 // Group 5: arrays / 1-byte types
213 // Subscribed notify handles; the loop() drain filters the wildcard
214 // listener's deliveries on this list (esp32 parity for enable=false).
215 std::array<uint16_t, RP2_GATT_MAX_NOTIFY_SUBSCRIPTIONS> notify_subscriptions_{};
217 uint8_t engine_index_{0}; // position in instances[]; tags log lines per slot
218 bd_addr_t peer_addr_{}; // MSB-first, as gap_connect expects
223 bool truncated_{false};
224 // One cancel attempt per connect: the second timeout escalates to failure.
226 // A disconnect request raced an in-flight connect; finish teardown on link-up.
227 bool cancel_requested_{false};
228 // This engine's own hold on the shared scan inhibit, so the pairing stays
229 // one-to-one per connection even with multiple slots.
231
232 // Instance registry for routing BTstack events (IRQ context) to engines.
233 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
234 static RP2GattClient *instances[ESPHOME_BLE_GATT_CLIENT_COUNT];
235 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
236 static uint8_t instance_count;
237 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
238 static btstack_packet_callback_registration_t hci_event_registration;
239 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
240 static btstack_packet_callback_registration_t sm_event_registration;
241 // The engine whose gap_connect is in flight: BTstack allows one outgoing LE
242 // create-connection stack-wide, and gap_connect_cancel is global, so only
243 // the owner may cancel. Written under BluetoothLock from the main loop,
244 // cleared in the BTstack context when the procedure resolves.
245 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
247};
248
249} // namespace esphome::bluetooth_connection
250
251#endif // USE_RP2040_BLE && USE_BLE_GATT_CLIENT
uint8_t address
Definition bl0906.h:4
Helper class to easily give an object a parent of type T.
Definition helpers.h:1907
The event surface a backend delivers completions through - the one place with genuine runtime polymor...
void enqueue_notify_irq_(uint16_t handle, const uint8_t *data, uint16_t len)
esphome::EventPool< RP2GattNotifyEvent, RP2_GATT_NOTIFY_QUEUE_SIZE - 1 > notify_pool_
btstack_context_callback_registration_t can_write_registration_
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
static RP2GattClient * instance_for_con_handle(hci_con_handle_t con_handle)
void on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) override
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_
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)
static btstack_packet_callback_registration_t sm_event_registration
int write_descriptor(uint16_t handle, const uint8_t *data, uint16_t len)
static RP2GattClient * instances[ESPHOME_BLE_GATT_CLIENT_COUNT]
void assemble_blob_irq_(uint16_t offset, const uint8_t *data, uint16_t len)
static void gatt_packet_handler(uint8_t type, uint16_t channel, uint8_t *packet, uint16_t size)
void set_connection_type(ble_device_base::ConnectionType ct)
void set_listener(ble_device_base::GattClientListener *listener)
esphome::LockFreeQueue< RP2GattEvent, RP2_GATT_EVENT_QUEUE_SIZE > event_queue_
esphome::EventPool< RP2GattEvent, RP2_GATT_EVENT_QUEUE_SIZE - 1 > event_pool_
Listener interface for global OTA state changes (includes OTA component pointer).
uint16_t type
bool state
Definition fan.h:2
const void size_t len
Definition hal.h:64
uint16_t size
Definition helpers.cpp:25
static void uint32_t
Borrowed view of the backend-owned service table.
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]
spi_device_handle_t handle