ESPHome 2026.8.0b4
Loading...
Searching...
No Matches
bluetooth_connection_hub.h
Go to the documentation of this file.
1// BluetoothConnection: drives the build's GATT backend (the
2// ble_device_base::BLEGattConnection alias) and translates its events into
3// the proxy's API messages. One wrapper for every platform; per-backend
4// differences live behind the alias and the streamer cut-through.
5
6#pragma once
7
9
10// The wrapper exists to serve the proxy's API surface; direct consumers
11// drive the backend themselves, so backend-only builds compile this header
12// empty.
13#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
14
18
20class BluetoothProxy;
21} // namespace esphome::bluetooth_proxy
22
24
27
37
39 public:
42 this->backend_ = backend;
43 backend->set_listener(this);
44 }
45
46 // ---- proxy dispatch surface ----
48 conn_err_t write_characteristic(uint16_t handle, const uint8_t *data, size_t length, bool response);
50 conn_err_t write_descriptor(uint16_t handle, const uint8_t *data, size_t length, bool response);
51 conn_err_t notify_characteristic(uint16_t handle, bool enable);
52 conn_err_t update_connection_params(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout);
53
56 this->latch_pending_error_(err);
57 this->send_service_ = DONE_SENDING_SERVICES;
58 this->disconnect();
59 }
60
64 void initiate_connection(uint8_t address_type);
65 void disconnect();
70 if (this->state_ == ClientState::DISCONNECTING && this->backend_->cancel_gatt_disconnect()) {
71 this->state_ = ClientState::CONNECTING;
72 return true;
73 }
74 return false;
75 }
76 bool is_paired() const { return this->paired_; }
77 void set_unpaired() { this->paired_ = false; }
78 conn_err_t pair() { return this->backend_->pair(); }
79
80 void set_address(uint64_t address);
81 uint64_t get_address() const { return this->address_; }
82 const char *address_str() const { return this->address_str_; }
83 uint8_t get_connection_index() const { return this->connection_index_; }
84
85 ClientState state() const { return this->state_; }
86 void set_state(ClientState st) { this->state_ = st; }
87 bool connected() const { return this->state_ == ClientState::ESTABLISHED; }
89 this->connection_type_ = ct;
90 // Both backends branch on the type before connecting (bluedroid picks
91 // prefer-params and the with-cache report at OPEN_EVT; rp2 picks the
92 // initiating parameters), so this must be set before the connect starts.
93 this->backend_->set_connection_type(ct);
94 }
95 // Latched at discovery completion rather than read from the backend table:
96 // streaming frees the table, and this must stay true for the connection's
97 // lifetime (a repeat GetServices is silently ignored, never answered with
98 // an authoritative empty database).
99 bool has_gatt_services() const { return this->services_discovered_; }
100
104 if (this->send_service_ >= 0) {
105 this->stream_pending_(this->backend_);
106 }
107 }
108
109 // ---- backend event listener (called directly by the backend, main loop) ----
110 void on_connection_state(bool connected, uint16_t mtu, int error) override;
111 void on_service_discovery_done(int error) override;
112 void on_read_result(uint16_t handle, const uint8_t *data, uint16_t len, int error) override;
113 void on_write_result(uint16_t handle, int error) override;
114 void on_notify_state(uint16_t handle, bool enabled, int error) override;
115 void on_notify_data(uint16_t handle, const uint8_t *data, uint16_t len) override;
116 void on_pairing_result(int status) override;
117
118 protected:
120 // The Bluedroid backend streams services in place from its stack cache.
122
125 if (this->pending_error_ == 0) {
126 this->pending_error_ = err;
127 }
128 }
129
133 void latch_pending_ack_(PendingAck kind, uint16_t handle, conn_err_t error = 0) {
134 this->pending_ack_retries_ = 0;
135 this->pending_ack_ = kind;
137 this->pending_ack_error_ = error;
138 }
144 if (this->has_pending_ack_() && this->pending_ack_handle_ == handle &&
145 (this->pending_ack_ == PendingAck::PENDING_ACK_ERROR || this->pending_ack_ == kind)) {
146 this->clear_pending_ack_();
147 }
148 }
153 void note_batch_stalled_();
161 void flush_owed_replies_();
165 this->batch_stalled_ = false;
166 this->connected_reply_owed_ = false;
167 }
169 bool try_send_ack_(PendingAck kind, uint16_t handle, conn_err_t error);
171 void send_ack_(PendingAck kind, uint16_t handle, conn_err_t error = 0);
174 void send_gatt_error_(uint16_t handle, conn_err_t error) {
175 this->send_ack_(PendingAck::PENDING_ACK_ERROR, handle, error);
176 }
178 void flush_pending_ack_();
180 void age_pending_ack_();
181 // A backend providing its own streamer (see the contract doc) builds the
182 // response in place from its stack cache; the rest use the table streamer.
183 // Template so the discarded branch is not odr-checked against backends
184 // that lack the method.
185 template<typename Backend> void stream_pending_(Backend *backend) {
186 if constexpr (requires { backend->stream_service_batch(*this); }) {
187 backend->stream_service_batch(*this);
188 } else {
190 }
191 }
196 this->batch_stalled_ = false;
197 if (this->send_service_ >= 0) {
198 this->backend_->release_services();
199 this->send_service_ = DONE_SENDING_SERVICES;
200 } else if (this->send_service_ == SERVICES_DONE_PENDING) {
201 this->send_service_ = DONE_SENDING_SERVICES;
202 }
203 }
209 void send_services_done_();
211 void age_services_done_();
212 void reset_connection_(conn_err_t reason);
213 conn_err_t check_connected_op_(const char *action, const char *type) const;
214 void log_gatt_operation_error_(const char *operation, uint16_t handle, int status);
215
216 // Memory optimized layout for 32-bit systems
217 // Group 1: Pointers (4 bytes each, naturally aligned)
220
221 // Group 2: 2-byte types. Exactly 4 bytes, so address_ below stays
222 // 8-aligned with no padding (the vptr makes Group 1 12 bytes, not 8).
223 int16_t send_service_{INIT_SENDING_SERVICES};
224 uint16_t mtu_{ble_device_base::DEFAULT_ATT_MTU};
225
226 // Group 3: 8-byte and 4-byte types
227 uint64_t address_{0};
229 // Full width: the GATT error domain is open-ended (ble_gatt_client.h) and
230 // forwarded untranslated, so narrowing would corrupt platform codes.
232
233 // Group 4: Arrays
234 char address_str_[MAC_ADDRESS_PRETTY_BUFFER_SIZE]{};
235 // Parked here rather than in Group 2: address_str_ ends 2-aligned, so this
236 // uses tail slack instead of pushing address_ out by 6 bytes of padding.
238
239 // Group 5: bit-packed tail. The first two bytes were already full, so the
240 // first added bit forced a third and took the 8-aligned object 48 -> 56;
241 // the handle, error and retry counter ride in that padding. Four bitfield
242 // bits left; another byte-sized member costs 8 per slot.
243 static_assert(static_cast<uint8_t>(ClientState::ESTABLISHED) < (1 << 3), "state_ bitfield too narrow");
244 static_assert(static_cast<uint8_t>(ConnectionType::V3_WITHOUT_CACHE) < (1 << 2),
245 "connection_type_ bitfield too narrow");
246 // Ordered so neither byte's fields straddle a storage unit: 3+5 and
247 // 4+2+1+1 fill the first two tail bytes exactly.
248 ClientState state_ : 3 {ClientState::IDLE};
249 static_assert(SERVICES_DONE_RETRY_LIMIT < (1 << 5), "counter bitfield too narrow");
250 uint8_t services_done_retries_ : 5 {0};
251 uint8_t connection_index_ : 4 {0};
252 ConnectionType connection_type_ : 2 {ConnectionType::V1};
253 bool paired_ : 1 {false};
254 bool services_discovered_ : 1 {false};
255 static_assert(static_cast<uint8_t>(PendingAck::PENDING_ACK_ERROR) < (1 << 2), "pending_ack_ bitfield too narrow");
258 bool batch_stalled_ : 1 {false};
260 bool connected_reply_owed_ : 1 {false};
261 // Plain byte after the bitfields: takes the padding byte instead of
262 // straddling pending_ack_'s storage unit and growing the object.
263 static_assert(PENDING_ACK_RETRY_LIMIT <= 0xFF, "retry counter too narrow");
265};
266
267// Pins the grouping above: pending_ack_handle_ in Group 2 instead would pad
268// address_ out and reach 64. 32-bit only; the host unit tests build 64-bit.
269static_assert(sizeof(void *) != 4 || sizeof(BluetoothConnection) <= 56,
270 "BluetoothConnection layout regressed on a 32-bit target");
271
272} // namespace esphome::bluetooth_connection
273
274#endif // USE_BLUETOOTH_PROXY_CONNECTIONS
uint8_t address
Definition bl0906.h:4
uint8_t status
Definition bl0942.h:8
The event surface a backend delivers completions through - the one place with genuine runtime polymor...
bool try_send_ack_(PendingAck kind, uint16_t handle, conn_err_t error)
Sole construction site for these replies, shared by send and retry.
void on_read_result(uint16_t handle, const uint8_t *data, uint16_t len, int error) override
void initiate_connection(uint8_t address_type)
Start connecting with the API address type (BLE_ADDR_TYPE_* code space).
void latch_pending_ack_(PendingAck kind, uint16_t handle, conn_err_t error=0)
Latch a refused reply for the proxy drain.
conn_err_t write_descriptor(uint16_t handle, const uint8_t *data, size_t length, bool response)
bool connected_reply_owed_
An owed connected=true reply; the proxy's paced drain re-offers it.
void supersede_pending_ack_(uint16_t handle, PendingAck kind)
Drop an owed reply this re-ask makes stale.
void latch_pending_error_(conn_err_t err)
First cause wins: a later, less specific error must not overwrite it.
void abort_service_stream(conn_err_t err)
Streamer abort: latch the GATT cause, park the cursor, tear down.
void age_pending_ack_()
Advance the retry budget and abandon at the limit, without sending.
void set_backend(ble_device_base::BLEGattConnection *backend)
Wire the platform backend. Called from codegen before setup.
void send_gatt_error_(uint16_t handle, conn_err_t error)
Report a rejected request.
conn_err_t check_connected_op_(const char *action, const char *type) const
void age_services_done_()
Advance the retry budget and abandon at the limit, without sending.
void process_pending_services()
Stream any pending service-discovery batch (proxy loop; the backend owns the disconnect safety timer)...
void log_gatt_operation_error_(const char *operation, uint16_t handle, int status)
void on_write_result(uint16_t handle, int error) override
bool batch_stalled_
Set while a refused batch is retrying, so only the first one warns.
void flush_pending_ack_()
Re-offer the owed reply; clears on success, stays owed on a refusal.
void on_notify_state(uint16_t handle, bool enabled, int error) override
bool cancel_teardown()
A connect request racing a scheduled teardown: true when the backend had not started closing - the in...
conn_err_t notify_characteristic(uint16_t handle, bool enable)
void on_connection_state(bool connected, uint16_t mtu, int error) override
void send_services_done_()
Send services-done and settle the cursor: DONE when it lands (or no subscriber), SERVICES_DONE_PENDIN...
void park_service_stream_()
Park the stream without services-done and free any held table: an interrupted stream must never be de...
void flush_owed_replies_()
Re-offer everything this slot owes.
void note_batch_stalled_()
Warn on the stall's leading edge only.
conn_err_t write_characteristic(uint16_t handle, const uint8_t *data, size_t length, bool response)
void on_notify_data(uint16_t handle, const uint8_t *data, uint16_t len) override
void send_ack_(PendingAck kind, uint16_t handle, conn_err_t error=0)
First attempt: send, and latch it for the drain if the API refuses.
void send_connected_reply_()
Send the connected=true reply, latching it if the API refuses.
conn_err_t update_connection_params(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout)
void clear_owed_flags_()
Drop everything this slot owes, in one write to the shared tail byte.
uint16_t type
ESPHOME_BLE_GATT_CONNECTION_TYPE BLEGattConnection
PendingAck
A refused GATT reply owed to the current subscriber.
const void size_t len
Definition hal.h:64
uint16_t length
Definition tt21100.cpp:0
spi_device_handle_t handle