ESPHome 2026.1.0-dev
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 <cstring>
7
8#ifdef USE_ESP32
9
11
12static const char *const TAG = "bluetooth_proxy";
13
14// BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE is defined during code generation
15// It sets the batch size for BLE advertisements to maximize WiFi efficiency
16
17// Verify BLE advertisement data array size matches the BLE specification (31 bytes adv + 31 bytes scan response)
18static_assert(sizeof(((api::BluetoothLERawAdvertisement *) nullptr)->data) == 62,
19 "BLE advertisement data array size mismatch");
20
22
24 this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS;
25 this->connections_free_response_.free = BLUETOOTH_PROXY_MAX_CONNECTIONS;
26
27 // Capture the configured scan mode from YAML before any API changes
29
31}
32
38
49
51 ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, state: %s", connection->get_connection_index(),
53}
54
56 ESP_LOGI(TAG, "[%d] [%s] Connecting %s", connection->get_connection_index(), connection->address_str(), message);
57}
58
59void BluetoothProxy::log_not_connected_gatt_(const char *action, const char *type) {
60 ESP_LOGW(TAG, "Cannot %s GATT %s, not connected", action, type);
61}
62
63void BluetoothProxy::handle_gatt_not_connected_(uint64_t address, uint16_t handle, const char *action,
64 const char *type) {
65 this->log_not_connected_gatt_(action, type);
66 this->send_gatt_error(address, handle, ESP_GATT_NOT_CONNECTED);
67}
68
69#ifdef USE_ESP32_BLE_DEVICE
71 // This method should never be called since bluetooth_proxy always uses raw advertisements
72 // but we need to provide an implementation to satisfy the virtual method requirement
73 return false;
74}
75#endif
76
77bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) {
78 if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr)
79 return false;
80
81 auto &advertisements = this->response_.advertisements;
82
83 for (size_t i = 0; i < count; i++) {
84 auto &result = scan_results[i];
85 uint8_t length = result.adv_data_len + result.scan_rsp_len;
86
87 // Fill in the data directly at current position
88 auto &adv = advertisements[this->response_.advertisements_len];
89 adv.address = esp32_ble::ble_addr_to_uint64(result.bda);
90 adv.rssi = result.rssi;
91 adv.address_type = result.ble_addr_type;
92 adv.data_len = length;
93 std::memcpy(adv.data, result.ble_adv, length);
94
96
97 ESP_LOGV(TAG, "Queuing raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0],
98 result.bda[1], result.bda[2], result.bda[3], result.bda[4], result.bda[5], length, result.rssi);
99
100 // Flush if we have reached BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE
101 if (this->response_.advertisements_len >= BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE) {
103 }
104 }
105
106 return true;
107}
108
111 this->api_connection_ == nullptr)
112 return;
113
114 // Send the message
116
117 ESP_LOGV(TAG, "Sent batch of %u BLE advertisements", this->response_.advertisements_len);
118
119 // Reset the length for the next batch
121}
122
124 ESP_LOGCONFIG(TAG,
125 "Bluetooth Proxy:\n"
126 " Active: %s\n"
127 " Connections: %d",
128 YESNO(this->active_), this->connection_count_);
129}
130
132 if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) {
133 for (uint8_t i = 0; i < this->connection_count_; i++) {
134 auto *connection = this->connections_[i];
135 if (connection->get_address() != 0 && !connection->disconnect_pending()) {
136 connection->disconnect();
137 }
138 }
139 return;
140 }
141
142 // Flush any pending BLE advertisements that have been accumulated but not yet sent
143 uint32_t now = App.get_loop_component_start_time();
144
145 // Flush accumulated advertisements every 100ms
146 if (now - this->last_advertisement_flush_time_ >= 100) {
149 }
150}
151
155
157 for (uint8_t i = 0; i < this->connection_count_; i++) {
158 auto *connection = this->connections_[i];
159 uint64_t conn_addr = connection->get_address();
160
161 if (conn_addr == address)
162 return connection;
163
164 if (reserve && conn_addr == 0) {
165 connection->send_service_ = INIT_SENDING_SERVICES;
166 connection->set_address(address);
167 // All connections must start at INIT
168 // We only set the state if we allocate the connection
169 // to avoid a race where multiple connection attempts
170 // are made.
171 connection->set_state(espbt::ClientState::INIT);
172 return connection;
173 }
174 }
175 return nullptr;
176}
177
179 switch (msg.request_type) {
182 auto *connection = this->get_connection_(msg.address, true);
183 if (connection == nullptr) {
184 ESP_LOGW(TAG, "No free connections available");
185 this->send_device_connection(msg.address, false);
186 return;
187 }
188 if (!msg.has_address_type) {
189 ESP_LOGE(TAG, "[%d] [%s] Missing address type in connect request", connection->get_connection_index(),
190 connection->address_str());
191 this->send_device_connection(msg.address, false);
192 return;
193 }
194 if (connection->state() == espbt::ClientState::CONNECTED ||
195 connection->state() == espbt::ClientState::ESTABLISHED) {
196 this->log_connection_request_ignored_(connection, connection->state());
197 this->send_device_connection(msg.address, true);
198 this->send_connections_free();
199 return;
200 } else if (connection->state() == espbt::ClientState::CONNECTING) {
201 if (connection->disconnect_pending()) {
202 ESP_LOGW(TAG, "[%d] [%s] Connection request while pending disconnect, cancelling pending disconnect",
203 connection->get_connection_index(), connection->address_str());
204 connection->cancel_pending_disconnect();
205 return;
206 }
207 this->log_connection_request_ignored_(connection, connection->state());
208 return;
209 } else if (connection->state() != espbt::ClientState::INIT) {
210 this->log_connection_request_ignored_(connection, connection->state());
211 return;
212 }
214 connection->set_connection_type(espbt::ConnectionType::V3_WITH_CACHE);
215 this->log_connection_info_(connection, "v3 with cache");
216 } else { // BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE
217 connection->set_connection_type(espbt::ConnectionType::V3_WITHOUT_CACHE);
218 this->log_connection_info_(connection, "v3 without cache");
219 }
220 uint64_to_bd_addr(msg.address, connection->remote_bda_);
221 connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
222 connection->set_state(espbt::ClientState::DISCOVERED);
223 this->send_connections_free();
224 break;
225 }
227 auto *connection = this->get_connection_(msg.address, false);
228 if (connection == nullptr) {
229 this->send_device_connection(msg.address, false);
230 this->send_connections_free();
231 return;
232 }
233 if (connection->state() != espbt::ClientState::IDLE) {
234 connection->disconnect();
235 } else {
236 connection->set_address(0);
237 this->send_device_connection(msg.address, false);
238 this->send_connections_free();
239 }
240 break;
241 }
243 auto *connection = this->get_connection_(msg.address, false);
244 if (connection != nullptr) {
245 if (!connection->is_paired()) {
246 auto err = connection->pair();
247 if (err != ESP_OK) {
248 this->send_device_pairing(msg.address, false, err);
249 }
250 } else {
251 this->send_device_pairing(msg.address, true);
252 }
253 }
254 break;
255 }
257 esp_bd_addr_t address;
259 esp_err_t ret = esp_ble_remove_bond_device(address);
260 this->send_device_pairing(msg.address, ret == ESP_OK, ret);
261 break;
262 }
264 esp_bd_addr_t address;
266 esp_err_t ret = esp_ble_gattc_cache_clean(address);
268 call.address = msg.address;
269 call.success = ret == ESP_OK;
270 call.error = ret;
271
273
274 break;
275 }
277 ESP_LOGE(TAG, "V1 connections removed");
278 this->send_device_connection(msg.address, false);
279 break;
280 }
281 }
282}
283
285 auto *connection = this->get_connection_(msg.address, false);
286 if (connection == nullptr) {
287 this->handle_gatt_not_connected_(msg.address, msg.handle, "read", "characteristic");
288 return;
289 }
290
291 auto err = connection->read_characteristic(msg.handle);
292 if (err != ESP_OK) {
293 this->send_gatt_error(msg.address, msg.handle, err);
294 }
295}
296
298 auto *connection = this->get_connection_(msg.address, false);
299 if (connection == nullptr) {
300 this->handle_gatt_not_connected_(msg.address, msg.handle, "write", "characteristic");
301 return;
302 }
303
304 auto err = connection->write_characteristic(msg.handle, msg.data, msg.data_len, msg.response);
305 if (err != ESP_OK) {
306 this->send_gatt_error(msg.address, msg.handle, err);
307 }
308}
309
311 auto *connection = this->get_connection_(msg.address, false);
312 if (connection == nullptr) {
313 this->handle_gatt_not_connected_(msg.address, msg.handle, "read", "descriptor");
314 return;
315 }
316
317 auto err = connection->read_descriptor(msg.handle);
318 if (err != ESP_OK) {
319 this->send_gatt_error(msg.address, msg.handle, err);
320 }
321}
322
324 auto *connection = this->get_connection_(msg.address, false);
325 if (connection == nullptr) {
326 this->handle_gatt_not_connected_(msg.address, msg.handle, "write", "descriptor");
327 return;
328 }
329
330 auto err = connection->write_descriptor(msg.handle, msg.data, msg.data_len, true);
331 if (err != ESP_OK) {
332 this->send_gatt_error(msg.address, msg.handle, err);
333 }
334}
335
337 auto *connection = this->get_connection_(msg.address, false);
338 if (connection == nullptr || !connection->connected()) {
339 this->handle_gatt_not_connected_(msg.address, 0, "get", "services");
340 return;
341 }
342 if (!connection->service_count_) {
343 ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->connection_index_, connection->address_str());
345 return;
346 }
347 if (connection->send_service_ == INIT_SENDING_SERVICES) // Start sending services if not started yet
348 connection->send_service_ = 0;
349}
350
352 auto *connection = this->get_connection_(msg.address, false);
353 if (connection == nullptr) {
354 this->handle_gatt_not_connected_(msg.address, msg.handle, "notify", "characteristic");
355 return;
356 }
357
358 auto err = connection->notify_characteristic(msg.handle, msg.enable);
359 if (err != ESP_OK) {
360 this->send_gatt_error(msg.address, msg.handle, err);
361 }
362}
363
365 if (this->api_connection_ != nullptr) {
366 ESP_LOGE(TAG, "Only one API subscription is allowed at a time");
367 return;
368 }
369 this->api_connection_ = api_connection;
371
373}
374
376 if (this->api_connection_ != api_connection) {
377 ESP_LOGV(TAG, "API connection is not subscribed");
378 return;
379 }
380 this->api_connection_ = nullptr;
382}
383
384void BluetoothProxy::send_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) {
385 if (this->api_connection_ == nullptr)
386 return;
389 call.connected = connected;
390 call.mtu = mtu;
391 call.error = error;
393}
395 if (this->api_connection_ != nullptr) {
397 }
398}
399
403
411
412void BluetoothProxy::send_gatt_error(uint64_t address, uint16_t handle, esp_err_t error) {
413 if (this->api_connection_ == nullptr)
414 return;
417 call.handle = handle;
418 call.error = error;
420}
421
422void BluetoothProxy::send_device_pairing(uint64_t address, bool paired, esp_err_t error) {
425 call.paired = paired;
426 call.error = error;
427
429}
430
431void BluetoothProxy::send_device_unpairing(uint64_t address, bool success, esp_err_t error) {
434 call.success = success;
435 call.error = error;
436
438}
439
441 if (this->parent_->get_scan_active() == active) {
442 return;
443 }
444 ESP_LOGD(TAG, "Setting scanner mode to %s", active ? "active" : "passive");
445 this->parent_->set_scan_active(active);
446 this->parent_->stop_scan();
448 true); // Set this to true to automatically start scanning again when it has cleaned up.
449}
450
451BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
452
453} // namespace esphome::bluetooth_proxy
454
455#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 ProtoMessage &msg, uint8_t message_type)
bool is_connected(bool state_subscription_only=false) const
static constexpr uint8_t MESSAGE_TYPE
Definition api_pb2.h:2337
enums::BluetoothDeviceRequestType request_type
Definition api_pb2.h:1999
static constexpr uint8_t MESSAGE_TYPE
Definition api_pb2.h:2303
std::array< BluetoothLERawAdvertisement, BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE > advertisements
Definition api_pb2.h:1981
enums::BluetoothScannerMode mode
Definition api_pb2.h:2410
enums::BluetoothScannerState state
Definition api_pb2.h:2409
static constexpr uint8_t MESSAGE_TYPE
Definition api_pb2.h:2404
enums::BluetoothScannerMode configured_mode
Definition api_pb2.h:2411
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_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:0
@ BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR
Definition api_pb2.h:209
@ BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE
Definition api_pb2.h:210
@ BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT
Definition api_pb2.h:206
@ BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR
Definition api_pb2.h:208
@ BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE
Definition api_pb2.h:211
@ BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE
Definition api_pb2.h:212
@ BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT
Definition api_pb2.h:207
@ BLUETOOTH_SCANNER_MODE_PASSIVE
Definition api_pb2.h:223
@ BLUETOOTH_SCANNER_MODE_ACTIVE
Definition api_pb2.h:224
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.cpp:662
Application App
Global storage of Application pointer - only one Application can exist.
uint16_t length
Definition tt21100.cpp:0