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