ESPHome 2026.3.0
Loading...
Searching...
No Matches
bluetooth_proxy.cpp
Go to the documentation of this file.
1#include "bluetooth_proxy.h"
2
3#include "esphome/core/log.h"
6#include <algorithm>
7#include <cstring>
8#include <limits>
9
10#ifdef USE_ESP32
11
13
14static const char *const TAG = "bluetooth_proxy";
15
16// BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE is defined during code generation
17// It sets the batch size for BLE advertisements to maximize WiFi efficiency
18
19// Verify BLE advertisement data array size matches the BLE specification (31 bytes adv + 31 bytes scan response)
20static_assert(sizeof(((api::BluetoothLERawAdvertisement *) nullptr)->data) == 62,
21 "BLE advertisement data array size mismatch");
22
24
26 this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS;
27 this->connections_free_response_.free = BLUETOOTH_PROXY_MAX_CONNECTIONS;
28
29 // Capture the configured scan mode from YAML before any API changes
31
33}
34
40
51
53 ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, state: %s", connection->get_connection_index(),
55}
56
58 ESP_LOGI(TAG, "[%d] [%s] Connecting %s", connection->get_connection_index(), connection->address_str(), message);
59}
60
61void BluetoothProxy::log_not_connected_gatt_(const char *action, const char *type) {
62 ESP_LOGW(TAG, "Cannot %s GATT %s, not connected", action, type);
63}
64
65void BluetoothProxy::handle_gatt_not_connected_(uint64_t address, uint16_t handle, const char *action,
66 const char *type) {
67 this->log_not_connected_gatt_(action, type);
68 this->send_gatt_error(address, handle, ESP_GATT_NOT_CONNECTED);
69}
70
71#ifdef USE_ESP32_BLE_DEVICE
73 // This method should never be called since bluetooth_proxy always uses raw advertisements
74 // but we need to provide an implementation to satisfy the virtual method requirement
75 return false;
76}
77#endif
78
79bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) {
80 if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr)
81 return false;
82
83 auto &advertisements = this->response_.advertisements;
84
85 for (size_t i = 0; i < count; i++) {
86 auto &result = scan_results[i];
87 uint8_t length = result.adv_data_len + result.scan_rsp_len;
88
89 // Fill in the data directly at current position
90 auto &adv = advertisements[this->response_.advertisements_len];
91 adv.address = esp32_ble::ble_addr_to_uint64(result.bda);
92 adv.rssi = result.rssi;
93 adv.address_type = result.ble_addr_type;
94 adv.data_len = length;
95 std::memcpy(adv.data, result.ble_adv, length);
96
98
99 ESP_LOGV(TAG, "Queuing raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0],
100 result.bda[1], result.bda[2], result.bda[3], result.bda[4], result.bda[5], length, result.rssi);
101
102 // Flush if we have reached BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE
103 if (this->response_.advertisements_len >= BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE) {
105 }
106 }
107
108 return true;
109}
110
113 this->api_connection_ == nullptr)
114 return;
115
116 // Send the message
118
119 ESP_LOGV(TAG, "Sent batch of %u BLE advertisements", this->response_.advertisements_len);
120
121 // Reset the length for the next batch
123}
124
126 ESP_LOGCONFIG(TAG,
127 "Bluetooth Proxy:\n"
128 " Active: %s\n"
129 " Connections: %d",
130 YESNO(this->active_), this->connection_count_);
131}
132
134 if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) {
135 for (uint8_t i = 0; i < this->connection_count_; i++) {
136 auto *connection = this->connections_[i];
137 if (connection->get_address() != 0 && !connection->disconnect_pending()) {
138 connection->disconnect();
139 }
140 }
141 return;
142 }
143
144 // Flush any pending BLE advertisements that have been accumulated but not yet sent
146
147 // Flush accumulated advertisements every 100ms
148 if (now - this->last_advertisement_flush_time_ >= 100) {
151 }
152}
153
157
159 for (uint8_t i = 0; i < this->connection_count_; i++) {
160 auto *connection = this->connections_[i];
161 uint64_t conn_addr = connection->get_address();
162
163 if (conn_addr == address)
164 return connection;
165
166 if (reserve && conn_addr == 0) {
167 connection->send_service_ = INIT_SENDING_SERVICES;
168 connection->set_address(address);
169 // All connections must start at INIT
170 // We only set the state if we allocate the connection
171 // to avoid a race where multiple connection attempts
172 // are made.
173 connection->set_state(espbt::ClientState::INIT);
174 return connection;
175 }
176 }
177 return nullptr;
178}
179
181 switch (msg.request_type) {
184 auto *connection = this->get_connection_(msg.address, true);
185 if (connection == nullptr) {
186 ESP_LOGW(TAG, "No free connections available");
187 this->send_device_connection(msg.address, false);
188 return;
189 }
190 if (!msg.has_address_type) {
191 ESP_LOGE(TAG, "[%d] [%s] Missing address type in connect request", connection->get_connection_index(),
192 connection->address_str());
193 this->send_device_connection(msg.address, false);
194 return;
195 }
196 if (connection->state() == espbt::ClientState::CONNECTED ||
197 connection->state() == espbt::ClientState::ESTABLISHED) {
198 this->log_connection_request_ignored_(connection, connection->state());
199 this->send_device_connection(msg.address, true);
200 this->send_connections_free();
201 return;
202 } else if (connection->state() == espbt::ClientState::CONNECTING) {
203 if (connection->disconnect_pending()) {
204 ESP_LOGW(TAG, "[%d] [%s] Connection request while pending disconnect, cancelling pending disconnect",
205 connection->get_connection_index(), connection->address_str());
206 connection->cancel_pending_disconnect();
207 return;
208 }
209 this->log_connection_request_ignored_(connection, connection->state());
210 return;
211 } else if (connection->state() != espbt::ClientState::INIT) {
212 this->log_connection_request_ignored_(connection, connection->state());
213 return;
214 }
216 connection->set_connection_type(espbt::ConnectionType::V3_WITH_CACHE);
217 this->log_connection_info_(connection, "v3 with cache");
218 } else { // BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE
219 connection->set_connection_type(espbt::ConnectionType::V3_WITHOUT_CACHE);
220 this->log_connection_info_(connection, "v3 without cache");
221 }
222 uint64_to_bd_addr(msg.address, connection->remote_bda_);
223 connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
224 connection->set_state(espbt::ClientState::DISCOVERED);
225 this->send_connections_free();
226 break;
227 }
229 auto *connection = this->get_connection_(msg.address, false);
230 if (connection == nullptr) {
231 this->send_device_connection(msg.address, false);
232 this->send_connections_free();
233 return;
234 }
235 if (connection->state() != espbt::ClientState::IDLE) {
236 connection->disconnect();
237 } else {
238 connection->set_address(0);
239 this->send_device_connection(msg.address, false);
240 this->send_connections_free();
241 }
242 break;
243 }
245 auto *connection = this->get_connection_(msg.address, false);
246 if (connection != nullptr) {
247 if (!connection->is_paired()) {
248 auto err = connection->pair();
249 if (err != ESP_OK) {
250 this->send_device_pairing(msg.address, false, err);
251 }
252 } else {
253 this->send_device_pairing(msg.address, true);
254 }
255 }
256 break;
257 }
259 esp_bd_addr_t address;
261 esp_err_t ret = esp_ble_remove_bond_device(address);
262 this->send_device_pairing(msg.address, ret == ESP_OK, ret);
263 break;
264 }
266 esp_bd_addr_t address;
268 esp_err_t ret = esp_ble_gattc_cache_clean(address);
270 call.address = msg.address;
271 call.success = ret == ESP_OK;
272 call.error = ret;
273
274 this->api_connection_->send_message(call);
275
276 break;
277 }
279 ESP_LOGE(TAG, "V1 connections removed");
280 this->send_device_connection(msg.address, false);
281 break;
282 }
283 }
284}
285
287 auto *connection = this->get_connection_(msg.address, false);
288 if (connection == nullptr) {
289 this->handle_gatt_not_connected_(msg.address, msg.handle, "read", "characteristic");
290 return;
291 }
292
293 auto err = connection->read_characteristic(msg.handle);
294 if (err != ESP_OK) {
295 this->send_gatt_error(msg.address, msg.handle, err);
296 }
297}
298
300 auto *connection = this->get_connection_(msg.address, false);
301 if (connection == nullptr) {
302 this->handle_gatt_not_connected_(msg.address, msg.handle, "write", "characteristic");
303 return;
304 }
305
306 auto err = connection->write_characteristic(msg.handle, msg.data, msg.data_len, msg.response);
307 if (err != ESP_OK) {
308 this->send_gatt_error(msg.address, msg.handle, err);
309 }
310}
311
313 auto *connection = this->get_connection_(msg.address, false);
314 if (connection == nullptr) {
315 this->handle_gatt_not_connected_(msg.address, msg.handle, "read", "descriptor");
316 return;
317 }
318
319 auto err = connection->read_descriptor(msg.handle);
320 if (err != ESP_OK) {
321 this->send_gatt_error(msg.address, msg.handle, err);
322 }
323}
324
326 auto *connection = this->get_connection_(msg.address, false);
327 if (connection == nullptr) {
328 this->handle_gatt_not_connected_(msg.address, msg.handle, "write", "descriptor");
329 return;
330 }
331
332 auto err = connection->write_descriptor(msg.handle, msg.data, msg.data_len, true);
333 if (err != ESP_OK) {
334 this->send_gatt_error(msg.address, msg.handle, err);
335 }
336}
337
339 auto *connection = this->get_connection_(msg.address, false);
340 if (connection == nullptr || !connection->connected()) {
341 this->handle_gatt_not_connected_(msg.address, 0, "get", "services");
342 return;
343 }
344 if (!connection->service_count_) {
345 ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->connection_index_, connection->address_str());
347 return;
348 }
349 if (connection->send_service_ == INIT_SENDING_SERVICES) // Start sending services if not started yet
350 connection->send_service_ = 0;
351}
352
354 auto *connection = this->get_connection_(msg.address, false);
355 if (connection == nullptr) {
356 this->handle_gatt_not_connected_(msg.address, msg.handle, "notify", "characteristic");
357 return;
358 }
359
360 auto err = connection->notify_characteristic(msg.handle, msg.enable);
361 if (err != ESP_OK) {
362 this->send_gatt_error(msg.address, msg.handle, err);
363 }
364}
365
367 if (this->api_connection_ == nullptr)
368 return;
369
370 auto *connection = this->get_connection_(msg.address, false);
372 resp.address = msg.address;
373
374 if (connection == nullptr || !connection->connected()) {
375 ESP_LOGW(TAG, "[%d] [%s] Cannot set connection params, not connected",
376 connection ? static_cast<int>(connection->connection_index_) : -1,
377 connection ? connection->address_str() : "unknown");
378 resp.error = ESP_GATT_NOT_CONNECTED;
379 this->api_connection_->send_message(resp);
380 return;
381 }
382
383 // Protobuf fields are uint32_t to future-proof the API if BLE ever supports wider values;
384 // clamp to uint16_t since the current BLE spec defines these as 16-bit.
385 constexpr uint32_t max_val = std::numeric_limits<uint16_t>::max();
386 resp.error = connection->update_connection_params(static_cast<uint16_t>(std::min(msg.min_interval, max_val)),
387 static_cast<uint16_t>(std::min(msg.max_interval, max_val)),
388 static_cast<uint16_t>(std::min(msg.latency, max_val)),
389 static_cast<uint16_t>(std::min(msg.timeout, max_val)));
390 this->api_connection_->send_message(resp);
391}
392
394 if (this->api_connection_ != nullptr) {
395 ESP_LOGE(TAG, "Only one API subscription is allowed at a time");
396 return;
397 }
398 this->api_connection_ = api_connection;
400
402}
403
405 if (this->api_connection_ != api_connection) {
406 ESP_LOGV(TAG, "API connection is not subscribed");
407 return;
408 }
409 this->api_connection_ = nullptr;
411}
412
413void BluetoothProxy::send_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) {
414 if (this->api_connection_ == nullptr)
415 return;
418 call.connected = connected;
419 call.mtu = mtu;
420 call.error = error;
421 this->api_connection_->send_message(call);
422}
424 if (this->api_connection_ != nullptr) {
426 }
427}
428
432
440
441void BluetoothProxy::send_gatt_error(uint64_t address, uint16_t handle, esp_err_t error) {
442 if (this->api_connection_ == nullptr)
443 return;
446 call.handle = handle;
447 call.error = error;
448 this->api_connection_->send_message(call);
449}
450
451void BluetoothProxy::send_device_pairing(uint64_t address, bool paired, esp_err_t error) {
452 if (this->api_connection_ == nullptr)
453 return;
456 call.paired = paired;
457 call.error = error;
458
459 this->api_connection_->send_message(call);
460}
461
462void BluetoothProxy::send_device_unpairing(uint64_t address, bool success, esp_err_t error) {
463 if (this->api_connection_ == nullptr)
464 return;
467 call.success = success;
468 call.error = error;
469
470 this->api_connection_->send_message(call);
471}
472
474 if (this->parent_->get_scan_active() == active) {
475 return;
476 }
477 ESP_LOGD(TAG, "Setting scanner mode to %s", active ? "active" : "passive");
478 this->parent_->set_scan_active(active);
479 this->parent_->stop_scan();
481 true); // Set this to true to automatically start scanning again when it has cleaned up.
482}
483
484BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
485
486} // namespace esphome::bluetooth_proxy
487
488#endif // USE_ESP32
uint8_t address
Definition bl0906.h:4
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
bool send_message(const T &msg)
bool is_connected() const
Definition api_server.h:189
enums::BluetoothDeviceRequestType request_type
Definition api_pb2.h:1924
std::array< BluetoothLERawAdvertisement, BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE > advertisements
Definition api_pb2.h:1906
enums::BluetoothScannerMode mode
Definition api_pb2.h:2309
enums::BluetoothScannerState state
Definition api_pb2.h:2308
enums::BluetoothScannerMode configured_mode
Definition api_pb2.h:2310
void bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg)
void bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg)
void handle_gatt_not_connected_(uint64_t address, uint16_t handle, const char *action, const char *type)
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override
void send_device_connection(uint64_t address, bool connected, uint16_t mtu=0, esp_err_t error=ESP_OK)
void send_device_unpairing(uint64_t address, bool success, esp_err_t error=ESP_OK)
void send_device_pairing(uint64_t address, bool paired, esp_err_t error=ESP_OK)
bool parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) override
void log_not_connected_gatt_(const char *action, const char *type)
void bluetooth_device_request(const api::BluetoothDeviceRequest &msg)
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override
void bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg)
void send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state)
void subscribe_api_connection(api::APIConnection *api_connection, uint32_t flags)
void send_gatt_error(uint64_t address, uint16_t handle, esp_err_t error)
void unsubscribe_api_connection(api::APIConnection *api_connection)
api::BluetoothLERawAdvertisementsResponse response_
void log_connection_info_(BluetoothConnection *connection, const char *message)
BluetoothConnection * get_connection_(uint64_t address, bool reserve)
void bluetooth_set_connection_params(const api::BluetoothSetConnectionParamsRequest &msg)
void bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg)
void bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg)
void bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg)
void on_scanner_state(esp32_ble_tracker::ScannerState state) override
BLEScannerStateListener interface.
static void uint64_to_bd_addr(uint64_t address, esp_bd_addr_t bd_addr)
std::array< BluetoothConnection *, BLUETOOTH_PROXY_MAX_CONNECTIONS > connections_
api::BluetoothConnectionsFreeResponse connections_free_response_
void log_connection_request_ignored_(BluetoothConnection *connection, espbt::ClientState state)
void add_scanner_state_listener(BLEScannerStateListener *listener)
Add a listener for scanner state changes.
const char * message
Definition component.cpp:38
uint16_t type
uint16_t flags
bool state
Definition fan.h:2
@ BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR
Definition api_pb2.h:222
@ BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE
Definition api_pb2.h:223
@ BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT
Definition api_pb2.h:219
@ BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR
Definition api_pb2.h:221
@ BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE
Definition api_pb2.h:224
@ BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE
Definition api_pb2.h:225
@ BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT
Definition api_pb2.h:220
@ BLUETOOTH_SCANNER_MODE_PASSIVE
Definition api_pb2.h:236
@ BLUETOOTH_SCANNER_MODE_ACTIVE
Definition api_pb2.h:237
APIServer * global_api_server
BluetoothProxy * global_bluetooth_proxy
const char * client_state_to_string(ClientState state)
uint64_t ble_addr_to_uint64(const esp_bd_addr_t address)
Definition ble.h:38
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t
uint16_t length
Definition tt21100.cpp:0