ESPHome 2026.5.3
Loading...
Searching...
No Matches
bluetooth_proxy.cpp
Go to the documentation of this file.
1#include "bluetooth_proxy.h"
2
4#include "esphome/core/log.h"
7#include <algorithm>
8#include <cstring>
9#include <limits>
10
11#ifdef USE_ESP32
12
14
15static const char *const TAG = "bluetooth_proxy";
16
17// BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE is defined during code generation
18// It sets the batch size for BLE advertisements to maximize WiFi efficiency
19
20// Verify BLE advertisement data array size matches the BLE specification (31 bytes adv + 31 bytes scan response)
21static_assert(sizeof(((api::BluetoothLERawAdvertisement *) nullptr)->data) == 62,
22 "BLE advertisement data array size mismatch");
23
25
27 this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS;
28 this->connections_free_response_.free = BLUETOOTH_PROXY_MAX_CONNECTIONS;
29
30 // Capture the configured scan mode from YAML before any API changes
32
34}
35
41
52
54 ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, state: %s", connection->get_connection_index(),
56}
57
59 ESP_LOGI(TAG, "[%d] [%s] Connecting %s", connection->get_connection_index(), connection->address_str(), message);
60}
61
62void BluetoothProxy::log_not_connected_gatt_(const char *action, const char *type) {
63 ESP_LOGW(TAG, "Cannot %s GATT %s, not connected", action, type);
64}
65
66void BluetoothProxy::handle_gatt_not_connected_(uint64_t address, uint16_t handle, const char *action,
67 const char *type) {
68 this->log_not_connected_gatt_(action, type);
69 this->send_gatt_error(address, handle, ESP_GATT_NOT_CONNECTED);
70}
71
72#ifdef USE_ESP32_BLE_DEVICE
74 // This method should never be called since bluetooth_proxy always uses raw advertisements
75 // but we need to provide an implementation to satisfy the virtual method requirement
76 return false;
77}
78#endif
79
80bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) {
81 if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr)
82 return false;
83
84 auto &advertisements = this->response_.advertisements;
85
86 for (size_t i = 0; i < count; i++) {
87 auto &result = scan_results[i];
88 uint8_t length = result.adv_data_len + result.scan_rsp_len;
89
90 // Fill in the data directly at current position
91 auto &adv = advertisements[this->response_.advertisements_len];
92 adv.address = esp32_ble::ble_addr_to_uint64(result.bda);
93 adv.rssi = result.rssi;
94 adv.address_type = result.ble_addr_type;
95 adv.data_len = length;
96 std::memcpy(adv.data, result.ble_adv, length);
97
99
100 ESP_LOGV(TAG, "Queuing raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0],
101 result.bda[1], result.bda[2], result.bda[3], result.bda[4], result.bda[5], length, result.rssi);
102
103 // Flush if we have reached BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE
104 if (this->response_.advertisements_len >= BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE) {
106 }
107 }
108
109 return true;
110}
111
113 ESP_LOGV(TAG, "Sent batch of %u BLE advertisements", this->response_.advertisements_len);
114}
115
117 ESP_LOGCONFIG(TAG,
118 "Bluetooth Proxy:\n"
119 " Active: %s\n"
120 " Connections: %d",
121 YESNO(this->active_), this->connection_count_);
122}
123
125 // Run advertisement flush / connection cleanup every 100ms
127 if (now - this->last_advertisement_flush_time_ < 100)
128 return;
130
131 if (api::global_api_server->is_connected() && this->api_connection_ != nullptr) {
133 return;
134 }
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}
142
146
148 for (uint8_t i = 0; i < this->connection_count_; i++) {
149 auto *connection = this->connections_[i];
150 uint64_t conn_addr = connection->get_address();
151
152 if (conn_addr == address)
153 return connection;
154
155 if (reserve && conn_addr == 0) {
156 connection->send_service_ = INIT_SENDING_SERVICES;
157 connection->set_address(address);
158 // All connections must start at INIT
159 // We only set the state if we allocate the connection
160 // to avoid a race where multiple connection attempts
161 // are made.
162 connection->set_state(espbt::ClientState::INIT);
163 return connection;
164 }
165 }
166 return nullptr;
167}
168
170 switch (msg.request_type) {
173 auto *connection = this->get_connection_(msg.address, true);
174 if (connection == nullptr) {
175 ESP_LOGW(TAG, "No free connections available");
176 this->send_device_connection(msg.address, false);
177 return;
178 }
179 if (!msg.has_address_type) {
180 ESP_LOGE(TAG, "[%d] [%s] Missing address type in connect request", connection->get_connection_index(),
181 connection->address_str());
182 this->send_device_connection(msg.address, false);
183 return;
184 }
185 if (connection->state() == espbt::ClientState::CONNECTED ||
186 connection->state() == espbt::ClientState::ESTABLISHED) {
187 this->log_connection_request_ignored_(connection, connection->state());
188 this->send_device_connection(msg.address, true);
189 this->send_connections_free();
190 return;
191 } else if (connection->state() == espbt::ClientState::CONNECTING) {
192 if (connection->disconnect_pending()) {
193 ESP_LOGW(TAG, "[%d] [%s] Connection request while pending disconnect, cancelling pending disconnect",
194 connection->get_connection_index(), connection->address_str());
195 connection->cancel_pending_disconnect();
196 return;
197 }
198 this->log_connection_request_ignored_(connection, connection->state());
199 return;
200 } else if (connection->state() != espbt::ClientState::INIT) {
201 this->log_connection_request_ignored_(connection, connection->state());
202 return;
203 }
205 connection->set_connection_type(espbt::ConnectionType::V3_WITH_CACHE);
206 this->log_connection_info_(connection, "v3 with cache");
207 } else { // BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE
208 connection->set_connection_type(espbt::ConnectionType::V3_WITHOUT_CACHE);
209 this->log_connection_info_(connection, "v3 without cache");
210 }
211 connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
212 connection->set_state(espbt::ClientState::DISCOVERED);
213 this->send_connections_free();
214 break;
215 }
217 auto *connection = this->get_connection_(msg.address, false);
218 if (connection == nullptr) {
219 this->send_device_connection(msg.address, false);
220 this->send_connections_free();
221 return;
222 }
223 if (connection->state() != espbt::ClientState::IDLE) {
224 connection->disconnect();
225 } else {
226 connection->set_address(0);
227 this->send_device_connection(msg.address, false);
228 this->send_connections_free();
229 }
230 break;
231 }
233 auto *connection = this->get_connection_(msg.address, false);
234 if (connection != nullptr) {
235 if (!connection->is_paired()) {
236 auto err = connection->pair();
237 if (err != ESP_OK) {
238 this->send_device_pairing(msg.address, false, err);
239 }
240 } else {
241 this->send_device_pairing(msg.address, true);
242 }
243 }
244 break;
245 }
247 esp_bd_addr_t address;
249 esp_err_t ret = esp_ble_remove_bond_device(address);
250 this->send_device_pairing(msg.address, ret == ESP_OK, ret);
251 break;
252 }
254 esp_bd_addr_t address;
256 esp_err_t ret = esp_ble_gattc_cache_clean(address);
258 call.address = msg.address;
259 call.success = ret == ESP_OK;
260 call.error = ret;
261
262 this->api_connection_->send_message(call);
263
264 break;
265 }
267 ESP_LOGE(TAG, "V1 connections removed");
268 this->send_device_connection(msg.address, false);
269 break;
270 }
271 }
272}
273
275 auto *connection = this->get_connection_(msg.address, false);
276 if (connection == nullptr) {
277 this->handle_gatt_not_connected_(msg.address, msg.handle, "read", "characteristic");
278 return;
279 }
280
281 auto err = connection->read_characteristic(msg.handle);
282 if (err != ESP_OK) {
283 this->send_gatt_error(msg.address, msg.handle, err);
284 }
285}
286
288 auto *connection = this->get_connection_(msg.address, false);
289 if (connection == nullptr) {
290 this->handle_gatt_not_connected_(msg.address, msg.handle, "write", "characteristic");
291 return;
292 }
293
294 auto err = connection->write_characteristic(msg.handle, msg.data, msg.data_len, msg.response);
295 if (err != ESP_OK) {
296 this->send_gatt_error(msg.address, msg.handle, err);
297 }
298}
299
301 auto *connection = this->get_connection_(msg.address, false);
302 if (connection == nullptr) {
303 this->handle_gatt_not_connected_(msg.address, msg.handle, "read", "descriptor");
304 return;
305 }
306
307 auto err = connection->read_descriptor(msg.handle);
308 if (err != ESP_OK) {
309 this->send_gatt_error(msg.address, msg.handle, err);
310 }
311}
312
314 auto *connection = this->get_connection_(msg.address, false);
315 if (connection == nullptr) {
316 this->handle_gatt_not_connected_(msg.address, msg.handle, "write", "descriptor");
317 return;
318 }
319
320 auto err = connection->write_descriptor(msg.handle, msg.data, msg.data_len, true);
321 if (err != ESP_OK) {
322 this->send_gatt_error(msg.address, msg.handle, err);
323 }
324}
325
327 auto *connection = this->get_connection_(msg.address, false);
328 if (connection == nullptr || !connection->connected()) {
329 this->handle_gatt_not_connected_(msg.address, 0, "get", "services");
330 return;
331 }
332 if (!connection->service_count_) {
333 ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->connection_index_, connection->address_str());
335 return;
336 }
337 if (connection->send_service_ == INIT_SENDING_SERVICES) // Start sending services if not started yet
338 connection->send_service_ = 0;
339}
340
342 auto *connection = this->get_connection_(msg.address, false);
343 if (connection == nullptr) {
344 this->handle_gatt_not_connected_(msg.address, msg.handle, "notify", "characteristic");
345 return;
346 }
347
348 auto err = connection->notify_characteristic(msg.handle, msg.enable);
349 if (err != ESP_OK) {
350 this->send_gatt_error(msg.address, msg.handle, err);
351 }
352}
353
355 if (this->api_connection_ == nullptr)
356 return;
357
358 auto *connection = this->get_connection_(msg.address, false);
360 resp.address = msg.address;
361
362 if (connection == nullptr || !connection->connected()) {
363 ESP_LOGW(TAG, "[%d] [%s] Cannot set connection params, not connected",
364 connection ? static_cast<int>(connection->connection_index_) : -1,
365 connection ? connection->address_str() : "unknown");
366 resp.error = ESP_GATT_NOT_CONNECTED;
367 this->api_connection_->send_message(resp);
368 return;
369 }
370
371 // Protobuf fields are uint32_t to future-proof the API if BLE ever supports wider values;
372 // clamp to uint16_t since the current BLE spec defines these as 16-bit.
373 constexpr uint32_t max_val = std::numeric_limits<uint16_t>::max();
374 resp.error = connection->update_connection_params(static_cast<uint16_t>(std::min(msg.min_interval, max_val)),
375 static_cast<uint16_t>(std::min(msg.max_interval, max_val)),
376 static_cast<uint16_t>(std::min(msg.latency, max_val)),
377 static_cast<uint16_t>(std::min(msg.timeout, max_val)));
378 this->api_connection_->send_message(resp);
379}
380
382 if (this->api_connection_ != nullptr) {
383 ESP_LOGE(TAG, "Only one API subscription is allowed at a time");
384 return;
385 }
386 this->api_connection_ = api_connection;
388
390}
391
393 if (this->api_connection_ != api_connection) {
394 ESP_LOGV(TAG, "API connection is not subscribed");
395 return;
396 }
397 this->api_connection_ = nullptr;
399}
400
401void BluetoothProxy::send_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) {
402 if (this->api_connection_ == nullptr)
403 return;
406 call.connected = connected;
407 call.mtu = mtu;
408 call.error = error;
409 this->api_connection_->send_message(call);
410}
412 if (this->api_connection_ != nullptr) {
414 }
415}
416
420
428
429void BluetoothProxy::send_gatt_error(uint64_t address, uint16_t handle, esp_err_t error) {
430 if (this->api_connection_ == nullptr)
431 return;
434 call.handle = handle;
435 call.error = error;
436 this->api_connection_->send_message(call);
437}
438
439void BluetoothProxy::send_device_pairing(uint64_t address, bool paired, esp_err_t error) {
440 if (this->api_connection_ == nullptr)
441 return;
444 call.paired = paired;
445 call.error = error;
446
447 this->api_connection_->send_message(call);
448}
449
450void BluetoothProxy::send_device_unpairing(uint64_t address, bool success, esp_err_t error) {
451 if (this->api_connection_ == nullptr)
452 return;
455 call.success = success;
456 call.error = error;
457
458 this->api_connection_->send_message(call);
459}
460
462 if (this->parent_->get_scan_active() == active) {
463 return;
464 }
465 ESP_LOGD(TAG, "Setting scanner mode to %s", active ? "active" : "passive");
466 this->parent_->set_scan_active(active);
467 this->parent_->stop_scan();
469 true); // Set this to true to automatically start scanning again when it has cleaned up.
470}
471
472BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
473
474} // namespace esphome::bluetooth_proxy
475
476#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)
enums::BluetoothDeviceRequestType request_type
Definition api_pb2.h:1931
std::array< BluetoothLERawAdvertisement, BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE > advertisements
Definition api_pb2.h:1913
enums::BluetoothScannerMode mode
Definition api_pb2.h:2316
enums::BluetoothScannerState state
Definition api_pb2.h:2315
enums::BluetoothScannerMode configured_mode
Definition api_pb2.h:2317
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.
void flush_pending_advertisements_()
Caller must ensure api_connection_ is non-null and API server is connected.
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:35
uint16_t type
uint16_t flags
bool state
Definition fan.h:2
@ BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR
Definition api_pb2.h:229
@ BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE
Definition api_pb2.h:230
@ BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT
Definition api_pb2.h:226
@ BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR
Definition api_pb2.h:228
@ BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE
Definition api_pb2.h:231
@ BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE
Definition api_pb2.h:232
@ BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT
Definition api_pb2.h:227
@ BLUETOOTH_SCANNER_MODE_PASSIVE
Definition api_pb2.h:243
@ BLUETOOTH_SCANNER_MODE_ACTIVE
Definition api_pb2.h:244
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