ESPHome 2025.10.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 if (this->api_connection_ != nullptr) {
33 }
34 });
35}
36
47
49 ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, state: %s", connection->get_connection_index(),
50 connection->address_str().c_str(), espbt::client_state_to_string(state));
51}
52
53void BluetoothProxy::log_connection_info_(BluetoothConnection *connection, const char *message) {
54 ESP_LOGI(TAG, "[%d] [%s] Connecting %s", connection->get_connection_index(), connection->address_str().c_str(),
55 message);
56}
57
58void BluetoothProxy::log_not_connected_gatt_(const char *action, const char *type) {
59 ESP_LOGW(TAG, "Cannot %s GATT %s, not connected", action, type);
60}
61
62void BluetoothProxy::handle_gatt_not_connected_(uint64_t address, uint16_t handle, const char *action,
63 const char *type) {
64 this->log_not_connected_gatt_(action, type);
65 this->send_gatt_error(address, handle, ESP_GATT_NOT_CONNECTED);
66}
67
68#ifdef USE_ESP32_BLE_DEVICE
70 // This method should never be called since bluetooth_proxy always uses raw advertisements
71 // but we need to provide an implementation to satisfy the virtual method requirement
72 return false;
73}
74#endif
75
76bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) {
77 if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr)
78 return false;
79
80 auto &advertisements = this->response_.advertisements;
81
82 for (size_t i = 0; i < count; i++) {
83 auto &result = scan_results[i];
84 uint8_t length = result.adv_data_len + result.scan_rsp_len;
85
86 // Fill in the data directly at current position
87 auto &adv = advertisements[this->response_.advertisements_len];
88 adv.address = esp32_ble::ble_addr_to_uint64(result.bda);
89 adv.rssi = result.rssi;
90 adv.address_type = result.ble_addr_type;
91 adv.data_len = length;
92 std::memcpy(adv.data, result.ble_adv, length);
93
95
96 ESP_LOGV(TAG, "Queuing raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0],
97 result.bda[1], result.bda[2], result.bda[3], result.bda[4], result.bda[5], length, result.rssi);
98
99 // Flush if we have reached BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE
100 if (this->response_.advertisements_len >= BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE) {
102 }
103 }
104
105 return true;
106}
107
110 this->api_connection_ == nullptr)
111 return;
112
113 // Send the message
115
116 ESP_LOGV(TAG, "Sent batch of %u BLE advertisements", this->response_.advertisements_len);
117
118 // Reset the length for the next batch
120}
121
123 ESP_LOGCONFIG(TAG,
124 "Bluetooth Proxy:\n"
125 " Active: %s\n"
126 " Connections: %d",
127 YESNO(this->active_), this->connection_count_);
128}
129
131 if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) {
132 for (uint8_t i = 0; i < this->connection_count_; i++) {
133 auto *connection = this->connections_[i];
134 if (connection->get_address() != 0 && !connection->disconnect_pending()) {
135 connection->disconnect();
136 }
137 }
138 return;
139 }
140
141 // Flush any pending BLE advertisements that have been accumulated but not yet sent
142 uint32_t now = App.get_loop_component_start_time();
143
144 // Flush accumulated advertisements every 100ms
145 if (now - this->last_advertisement_flush_time_ >= 100) {
148 }
149}
150
154
156 for (uint8_t i = 0; i < this->connection_count_; i++) {
157 auto *connection = this->connections_[i];
158 if (connection->get_address() == address)
159 return connection;
160 }
161
162 if (!reserve)
163 return nullptr;
164
165 for (uint8_t i = 0; i < this->connection_count_; i++) {
166 auto *connection = this->connections_[i];
167 if (connection->get_address() == 0) {
168 connection->send_service_ = INIT_SENDING_SERVICES;
169 connection->set_address(address);
170 // All connections must start at INIT
171 // We only set the state if we allocate the connection
172 // to avoid a race where multiple connection attempts
173 // are made.
174 connection->set_state(espbt::ClientState::INIT);
175 return connection;
176 }
177 }
178
179 return nullptr;
180}
181
183 switch (msg.request_type) {
186 auto *connection = this->get_connection_(msg.address, true);
187 if (connection == nullptr) {
188 ESP_LOGW(TAG, "No free connections available");
189 this->send_device_connection(msg.address, false);
190 return;
191 }
192 if (!msg.has_address_type) {
193 ESP_LOGE(TAG, "[%d] [%s] Missing address type in connect request", connection->get_connection_index(),
194 connection->address_str().c_str());
195 this->send_device_connection(msg.address, false);
196 return;
197 }
198 if (connection->state() == espbt::ClientState::CONNECTED ||
199 connection->state() == espbt::ClientState::ESTABLISHED) {
200 this->log_connection_request_ignored_(connection, connection->state());
201 this->send_device_connection(msg.address, true);
202 this->send_connections_free();
203 return;
204 } else if (connection->state() == espbt::ClientState::CONNECTING) {
205 if (connection->disconnect_pending()) {
206 ESP_LOGW(TAG, "[%d] [%s] Connection request while pending disconnect, cancelling pending disconnect",
207 connection->get_connection_index(), connection->address_str().c_str());
208 connection->cancel_pending_disconnect();
209 return;
210 }
211 this->log_connection_request_ignored_(connection, connection->state());
212 return;
213 } else if (connection->state() != espbt::ClientState::INIT) {
214 this->log_connection_request_ignored_(connection, connection->state());
215 return;
216 }
218 connection->set_connection_type(espbt::ConnectionType::V3_WITH_CACHE);
219 this->log_connection_info_(connection, "v3 with cache");
220 } else { // BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE
221 connection->set_connection_type(espbt::ConnectionType::V3_WITHOUT_CACHE);
222 this->log_connection_info_(connection, "v3 without cache");
223 }
224 uint64_to_bd_addr(msg.address, connection->remote_bda_);
225 connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
226 connection->set_state(espbt::ClientState::DISCOVERED);
227 this->send_connections_free();
228 break;
229 }
231 auto *connection = this->get_connection_(msg.address, false);
232 if (connection == nullptr) {
233 this->send_device_connection(msg.address, false);
234 this->send_connections_free();
235 return;
236 }
237 if (connection->state() != espbt::ClientState::IDLE) {
238 connection->disconnect();
239 } else {
240 connection->set_address(0);
241 this->send_device_connection(msg.address, false);
242 this->send_connections_free();
243 }
244 break;
245 }
247 auto *connection = this->get_connection_(msg.address, false);
248 if (connection != nullptr) {
249 if (!connection->is_paired()) {
250 auto err = connection->pair();
251 if (err != ESP_OK) {
252 this->send_device_pairing(msg.address, false, err);
253 }
254 } else {
255 this->send_device_pairing(msg.address, true);
256 }
257 }
258 break;
259 }
261 esp_bd_addr_t address;
263 esp_err_t ret = esp_ble_remove_bond_device(address);
264 this->send_device_pairing(msg.address, ret == ESP_OK, ret);
265 break;
266 }
268 esp_bd_addr_t address;
270 esp_err_t ret = esp_ble_gattc_cache_clean(address);
272 call.address = msg.address;
273 call.success = ret == ESP_OK;
274 call.error = ret;
275
277
278 break;
279 }
281 ESP_LOGE(TAG, "V1 connections removed");
282 this->send_device_connection(msg.address, false);
283 break;
284 }
285 }
286}
287
289 auto *connection = this->get_connection_(msg.address, false);
290 if (connection == nullptr) {
291 this->handle_gatt_not_connected_(msg.address, msg.handle, "read", "characteristic");
292 return;
293 }
294
295 auto err = connection->read_characteristic(msg.handle);
296 if (err != ESP_OK) {
297 this->send_gatt_error(msg.address, msg.handle, err);
298 }
299}
300
302 auto *connection = this->get_connection_(msg.address, false);
303 if (connection == nullptr) {
304 this->handle_gatt_not_connected_(msg.address, msg.handle, "write", "characteristic");
305 return;
306 }
307
308 auto err = connection->write_characteristic(msg.handle, msg.data, msg.data_len, msg.response);
309 if (err != ESP_OK) {
310 this->send_gatt_error(msg.address, msg.handle, err);
311 }
312}
313
315 auto *connection = this->get_connection_(msg.address, false);
316 if (connection == nullptr) {
317 this->handle_gatt_not_connected_(msg.address, msg.handle, "read", "descriptor");
318 return;
319 }
320
321 auto err = connection->read_descriptor(msg.handle);
322 if (err != ESP_OK) {
323 this->send_gatt_error(msg.address, msg.handle, err);
324 }
325}
326
328 auto *connection = this->get_connection_(msg.address, false);
329 if (connection == nullptr) {
330 this->handle_gatt_not_connected_(msg.address, msg.handle, "write", "descriptor");
331 return;
332 }
333
334 auto err = connection->write_descriptor(msg.handle, msg.data, msg.data_len, true);
335 if (err != ESP_OK) {
336 this->send_gatt_error(msg.address, msg.handle, err);
337 }
338}
339
341 auto *connection = this->get_connection_(msg.address, false);
342 if (connection == nullptr || !connection->connected()) {
343 this->handle_gatt_not_connected_(msg.address, 0, "get", "services");
344 return;
345 }
346 if (!connection->service_count_) {
347 ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->connection_index_, connection->address_str().c_str());
349 return;
350 }
351 if (connection->send_service_ == INIT_SENDING_SERVICES) // Start sending services if not started yet
352 connection->send_service_ = 0;
353}
354
356 auto *connection = this->get_connection_(msg.address, false);
357 if (connection == nullptr) {
358 this->handle_gatt_not_connected_(msg.address, msg.handle, "notify", "characteristic");
359 return;
360 }
361
362 auto err = connection->notify_characteristic(msg.handle, msg.enable);
363 if (err != ESP_OK) {
364 this->send_gatt_error(msg.address, msg.handle, err);
365 }
366}
367
369 if (this->api_connection_ != nullptr) {
370 ESP_LOGE(TAG, "Only one API subscription is allowed at a time");
371 return;
372 }
373 this->api_connection_ = api_connection;
375
377}
378
380 if (this->api_connection_ != api_connection) {
381 ESP_LOGV(TAG, "API connection is not subscribed");
382 return;
383 }
384 this->api_connection_ = nullptr;
386}
387
388void BluetoothProxy::send_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) {
389 if (this->api_connection_ == nullptr)
390 return;
393 call.connected = connected;
394 call.mtu = mtu;
395 call.error = error;
397}
399 if (this->api_connection_ != nullptr) {
401 }
402}
403
407
415
416void BluetoothProxy::send_gatt_error(uint64_t address, uint16_t handle, esp_err_t error) {
417 if (this->api_connection_ == nullptr)
418 return;
421 call.handle = handle;
422 call.error = error;
424}
425
426void BluetoothProxy::send_device_pairing(uint64_t address, bool paired, esp_err_t error) {
429 call.paired = paired;
430 call.error = error;
431
433}
434
435void BluetoothProxy::send_device_unpairing(uint64_t address, bool success, esp_err_t error) {
438 call.success = success;
439 call.error = error;
440
442}
443
445 if (this->parent_->get_scan_active() == active) {
446 return;
447 }
448 ESP_LOGD(TAG, "Setting scanner mode to %s", active ? "active" : "passive");
449 this->parent_->set_scan_active(active);
450 this->parent_->stop_scan();
452 true); // Set this to true to automatically start scanning again when it has cleaned up.
453}
454
455BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
456
457} // namespace esphome::bluetooth_proxy
458
459#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)
static constexpr uint8_t MESSAGE_TYPE
Definition api_pb2.h:2169
enums::BluetoothDeviceRequestType request_type
Definition api_pb2.h:1831
static constexpr uint8_t MESSAGE_TYPE
Definition api_pb2.h:2135
std::array< BluetoothLERawAdvertisement, BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE > advertisements
Definition api_pb2.h:1813
enums::BluetoothScannerMode mode
Definition api_pb2.h:2242
enums::BluetoothScannerState state
Definition api_pb2.h:2241
static constexpr uint8_t MESSAGE_TYPE
Definition api_pb2.h:2236
enums::BluetoothScannerMode configured_mode
Definition api_pb2.h:2243
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)
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)
const std::string & address_str() const
void add_scanner_state_callback(std::function< void(ScannerState)> &&callback)
uint16_t type
uint16_t flags
bool state
Definition fan.h:0
@ BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR
Definition api_pb2.h:183
@ BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE
Definition api_pb2.h:184
@ BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT
Definition api_pb2.h:180
@ BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR
Definition api_pb2.h:182
@ BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE
Definition api_pb2.h:185
@ BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE
Definition api_pb2.h:186
@ BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT
Definition api_pb2.h:181
@ BLUETOOTH_SCANNER_MODE_PASSIVE
Definition api_pb2.h:197
@ BLUETOOTH_SCANNER_MODE_ACTIVE
Definition api_pb2.h:198
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:575
Application App
Global storage of Application pointer - only one Application can exist.
uint16_t length
Definition tt21100.cpp:0