ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
ble_server.cpp
Go to the documentation of this file.
1#ifdef USE_ZEPHYR
2#include "ble_server.h"
6#include "esphome/core/log.h"
7#include <zephyr/bluetooth/bluetooth.h>
8#include <zephyr/settings/settings.h>
9
11
12ESPHOME_LOG_TAG(TAG, "zephyr_ble_server");
13
14static k_work advertise_work; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
15
16BLEServer *global_ble_server; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
17
18// What the advertisement has left for the name: the flags take 3 bytes and the name header 2
19static constexpr size_t MAX_ADV_NAME_LEN = BT_GAP_ADV_MAX_ADV_DATA_LEN - 3 - 2;
20
21static const bt_data SD[] = {
22#ifdef USE_OTA
23 BT_DATA_BYTES(BT_DATA_UUID128_ALL, 0x84, 0xaa, 0x60, 0x74, 0x52, 0x8a, 0x8b, 0x86, 0xd3, 0x4c, 0xb7, 0x1d, 0x1d,
24 0xdc, 0x53, 0x8d),
25#endif
26};
27
28const bt_le_adv_param *const ADV_PARAM = BT_LE_ADV_CONN;
29
30static void advertise(k_work *work) {
31 int rc = bt_le_adv_stop();
32 if (rc) {
33 ESP_LOGE(TAG, "Advertising failed to stop (rc %d)", rc);
34 }
35
36 // Zephyr would cut the end of a long name, which is where the MAC suffix is
37 char name[MAX_ADV_NAME_LEN + 1];
38 const auto &app_name = App.get_name();
39 const size_t name_len =
40 truncate_name_to(name, sizeof(name), app_name.c_str(), app_name.length(),
41 App.is_name_add_mac_suffix_enabled() ? MAC_ADDRESS_SUFFIX_WITH_SEPARATOR_LEN : 0);
42 const uint8_t name_type = name_len < app_name.length() ? BT_DATA_NAME_SHORTENED : BT_DATA_NAME_COMPLETE;
43 static constexpr uint8_t FLAGS[] = {BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR};
44 const bt_data ad[] = {
45 BT_DATA(BT_DATA_FLAGS, FLAGS, sizeof(FLAGS)),
46 BT_DATA(name_type, name, static_cast<uint8_t>(name_len)),
47 };
48
49 rc = bt_le_adv_start(ADV_PARAM, ad, ARRAY_SIZE(ad), SD, ARRAY_SIZE(SD));
50 if (rc) {
51 ESP_LOGE(TAG, "Advertising failed to start (rc %d)", rc);
52 return;
53 }
54 ESP_LOGI(TAG, "Advertising successfully started");
55}
56
57void BLEServer::connected(bt_conn *conn, uint8_t err) {
58 char addr[BT_ADDR_LE_STR_LEN];
59 bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
60 if (err) {
61 ESP_LOGE(TAG, "Failed to connect to %s (%u)", addr, err);
62 return;
63 }
64 ESP_LOGI(TAG, "Connected %s", addr);
65#ifdef CONFIG_BT_SMP
66 if (bt_conn_set_security(conn, BT_SECURITY_L4)) {
67 ESP_LOGE(TAG, "Failed to set security");
68 }
69#endif
70 conn = bt_conn_ref(conn);
71 global_ble_server->defer([conn]() { global_ble_server->conn_ = conn; });
72}
73
74void BLEServer::disconnected(bt_conn *conn, uint8_t reason) {
75 char addr[BT_ADDR_LE_STR_LEN];
76
77 bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
78
79 ESP_LOGI(TAG, "Disconnected from %s (reason 0x%02x)", addr, reason);
82 bt_conn_unref(global_ble_server->conn_);
83 global_ble_server->conn_ = nullptr;
84 }
85 });
86 k_work_submit(&advertise_work);
87}
88
89#ifdef CONFIG_BT_SMP
90static void identity_resolved(bt_conn *conn, const bt_addr_le_t *rpa, const bt_addr_le_t *identity) {
91 char addr_identity[BT_ADDR_LE_STR_LEN];
92 char addr_rpa[BT_ADDR_LE_STR_LEN];
93
94 bt_addr_le_to_str(identity, addr_identity, sizeof(addr_identity));
95 bt_addr_le_to_str(rpa, addr_rpa, sizeof(addr_rpa));
96
97 ESP_LOGD(TAG, "Identity resolved %s -> %s", addr_rpa, addr_identity);
98}
99
100static void security_changed(bt_conn *conn, bt_security_t level, bt_security_err err) {
101 char addr[BT_ADDR_LE_STR_LEN];
102
103 bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
104
105 if (!err) {
106 ESP_LOGD(TAG, "Security changed: %s level %u", addr, level);
107 } else {
108 ESP_LOGE(TAG, "Security failed: %s level %u err %d", addr, level, err);
109 }
110}
111
112static void pairing_complete(bt_conn *conn, bool bonded) {
113 char addr[BT_ADDR_LE_STR_LEN];
114
115 bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
116
117 ESP_LOGD(TAG, "Pairing completed: %s, bonded: %d", addr, bonded);
118}
119
120static void pairing_failed(bt_conn *conn, bt_security_err reason) {
121 char addr[BT_ADDR_LE_STR_LEN];
122
123 bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
124
125 ESP_LOGE(TAG, "Pairing failed conn: %s, reason %d", addr, reason);
126
127 bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
128}
129
130static void bond_deleted(uint8_t id, const bt_addr_le_t *peer) {
131 char addr[BT_ADDR_LE_STR_LEN];
132
133 bt_addr_le_to_str(peer, addr, sizeof(addr));
134 ESP_LOGD(TAG, "Bond deleted for %s, id %u", addr, id);
135}
136
137static void auth_passkey_display(bt_conn *conn, unsigned int passkey) {
138 char addr[BT_ADDR_LE_STR_LEN];
139 char passkey_str[7];
140
141 bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
142
143 snprintk(passkey_str, 7, "%06u", passkey);
144
145 ESP_LOGI(TAG, "Passkey for %s: %s", addr, passkey_str);
146}
147
148static void conn_addr_str(bt_conn *conn, char *addr, size_t len) {
149 struct bt_conn_info info;
150
151 if (bt_conn_get_info(conn, &info) < 0) {
152 addr[0] = '\0';
153 return;
154 }
155
156 switch (info.type) {
157 case BT_CONN_TYPE_LE:
158 bt_addr_le_to_str(info.le.dst, addr, len);
159 break;
160 default:
161 ESP_LOGE(TAG, "Not implemented");
162 addr[0] = '\0';
163 break;
164 }
165}
166
167static void auth_cancel(bt_conn *conn) {
168 char addr[BT_ADDR_LE_STR_LEN];
169
170 conn_addr_str(conn, addr, sizeof(addr));
171
172 ESP_LOGI(TAG, "Pairing cancelled: %s", addr);
173}
174
175void BLEServer::auth_passkey_confirm(bt_conn *conn, unsigned int passkey) {
176 char addr[BT_ADDR_LE_STR_LEN];
177 char passkey_str[7];
178
179 bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
180
181 snprintk(passkey_str, 7, "%06u", passkey);
182
183 ESP_LOGI(TAG, "Confirm passkey for %s: %s", addr, passkey_str);
184 global_ble_server->defer([passkey]() { global_ble_server->passkey_cb_(passkey); });
185}
186
187static void auth_pairing_confirm(bt_conn *conn) {
188 /* Automatically confirm pairing request from the device side. */
189 auto err = bt_conn_auth_pairing_confirm(conn);
190 if (err) {
191 ESP_LOGE(TAG, "Can't confirm pairing (err: %d)", err);
192 return;
193 }
194
195 char addr[BT_ADDR_LE_STR_LEN];
196
197 bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
198
199 ESP_LOGI(TAG, "Pairing confirmed: %s", addr);
200}
201
202#endif
203
205 global_ble_server = this;
206 int err = 0;
207 k_work_init(&advertise_work, advertise);
208
209 static bt_conn_cb conn_callbacks = {
210 .connected = connected,
211 .disconnected = disconnected,
212#ifdef CONFIG_BT_SMP
213 .identity_resolved = identity_resolved,
214 .security_changed = security_changed,
215#endif
216 };
217
218 bt_conn_cb_register(&conn_callbacks);
219#ifdef CONFIG_BT_SMP
220 static struct bt_conn_auth_info_cb conn_auth_info_callbacks = {
221 .pairing_complete = pairing_complete, .pairing_failed = pairing_failed, .bond_deleted = bond_deleted};
222 err = bt_conn_auth_info_cb_register(&conn_auth_info_callbacks);
223 if (err) {
224 ESP_LOGE(TAG, "Failed to register authorization info callbacks.");
225 }
226 static struct bt_conn_auth_cb auth_cb = {
227 .passkey_display = auth_passkey_display,
228 .passkey_confirm = auth_passkey_confirm,
229 .cancel = auth_cancel,
230 .pairing_confirm = auth_pairing_confirm,
231 };
232 err = bt_conn_auth_cb_register(&auth_cb);
233 if (err) {
234 ESP_LOGE(TAG, "Failed to set auth handlers (%d)", err);
235 }
236#endif
237 // callback cannot be used to start scanning due to race conditions with BT_SETTINGS
238 err = bt_enable(nullptr);
239 if (err) {
240 ESP_LOGE(TAG, "Bluetooth enable failed: %d", err);
241 return;
242 }
243#ifdef CONFIG_BT_SETTINGS
244 err = settings_load();
245 if (err) {
246 ESP_LOGE(TAG, "Cannot load settings, err: %d", err);
247 }
248#endif
249 k_work_submit(&advertise_work);
250}
251
252#ifdef ESPHOME_LOG_HAS_DEBUG
253static const char *role_str(uint8_t role) {
254 switch (role) {
255 case BT_CONN_ROLE_CENTRAL:
256 return "Central";
257 case BT_CONN_ROLE_PERIPHERAL:
258 return "Peripheral";
259 default:
260 break;
261 }
262
263 return "Unknown";
264}
265
266static void connection_info(bt_conn *conn, void *user_data) {
267 char addr[BT_ADDR_LE_STR_LEN];
268 struct bt_conn_info info;
269
270 if (bt_conn_get_info(conn, &info) < 0) {
271 ESP_LOGE(TAG, "Unable to get info: conn %p", conn);
272 return;
273 }
274
275 switch (info.type) {
276 case BT_CONN_TYPE_LE:
277 bt_addr_le_to_str(info.le.dst, addr, sizeof(addr));
278 ESP_LOGD(TAG, " %u [LE][%s] %s: Interval %u latency %u timeout %u security L%u", info.id, role_str(info.role),
279 addr, info.le.interval, info.le.latency, info.le.timeout, info.security.level);
280 break;
281 default:
282 ESP_LOGE(TAG, "Not implemented");
283 break;
284 }
285}
286#ifdef CONFIG_BT_BONDABLE
287static void bond_info(const struct bt_bond_info *info, void *user_data) {
288 char addr[BT_ADDR_LE_STR_LEN];
289
290 bt_addr_le_to_str(&info->addr, addr, sizeof(addr));
291 ESP_LOGD(TAG, " Bond remote identity: %s", addr);
292}
293#endif
294#endif
295
297 ESP_LOGCONFIG(TAG,
298 "ble server:\n"
299 " connected: %s\n"
300 " name: %s\n"
301 " appearance: %u\n"
302 " ready: %s\n"
303#ifdef CONFIG_BT_SMP
304 " security manager: YES",
305#else
306 " security manager: NO",
307#endif
308 YESNO(this->conn_), bt_get_name(), bt_get_appearance(), YESNO(bt_is_ready()));
309
310#ifdef ESPHOME_LOG_HAS_DEBUG
311 bt_conn_foreach(BT_CONN_TYPE_ALL, connection_info, nullptr);
312#ifdef CONFIG_BT_BONDABLE
313 bt_foreach_bond(BT_ID_DEFAULT, bond_info, nullptr);
314#endif
315#endif
316}
317
319 if (this->conn_ == nullptr) {
320 ESP_LOGE(TAG, "Not connected");
321 return;
322 }
323 ESP_LOGD(TAG, "Numeric comparison %s", accept ? "accepted" : "rejected");
324 if (accept) {
325 bt_conn_auth_passkey_confirm(this->conn_);
326 } else {
327 bt_conn_auth_cancel(this->conn_);
328 }
329}
330
331} // namespace esphome::zephyr_ble_server
332
333#endif
const StringRef & get_name() const
Get the name of this Application set by pre_setup().
bool is_name_add_mac_suffix_enabled() const
void defer(const char *name, std::function< void()> &&f)
Defer a callback to the next loop() call with a const char* name.
static void disconnected(bt_conn *conn, uint8_t reason)
CallbackManager< void(uint32_t)> passkey_cb_
Definition ble_server.h:21
static void auth_passkey_confirm(bt_conn *conn, unsigned int passkey)
static void connected(bt_conn *conn, uint8_t err)
ESPHOME_LOG_TAG(TAG, "zephyr_ble_server")
const bt_le_adv_param *const ADV_PARAM
size_t truncate_name_to(char *buffer, size_t buffer_size, const char *name, size_t name_len, size_t keep_suffix_len)
Copy a name into a buffer, cutting it when it does not fit.
Definition helpers.cpp:297
const void size_t len
Definition hal.h:68
Application App
Global storage of Application pointer - only one Application can exist.