ESPHome 2026.5.0-dev
Loading...
Searching...
No Matches
ble_nus.cpp
Go to the documentation of this file.
1#ifdef USE_ZEPHYR
2#include "ble_nus.h"
3#include <zephyr/kernel.h>
4#include <bluetooth/services/nus.h>
5#include "esphome/core/log.h"
6#ifdef USE_LOGGER
9#endif
10#include <zephyr/sys/ring_buffer.h>
11
13
14// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables)
16RING_BUF_DECLARE(global_ble_tx_ring_buf, ESPHOME_BLE_NUS_TX_RING_BUFFER_SIZE);
17#ifdef ESPHOME_BLE_NUS_RX_RING_BUFFER_SIZE
18RING_BUF_DECLARE(global_ble_rx_ring_buf, ESPHOME_BLE_NUS_RX_RING_BUFFER_SIZE);
19#endif
20// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
21
22static const char *const TAG = "ble_nus";
23
24void BLENUS::write_array(const uint8_t *data, size_t len) {
25 if (atomic_get(&this->tx_status_) == TX_DISABLED) {
26 return;
27 }
28 auto sent = ring_buf_put(&global_ble_tx_ring_buf, data, len);
29 if (sent < len) {
30 ESP_LOGE(TAG, "TX dropping %u bytes", len - sent);
31 return;
32 }
33#ifdef USE_UART_DEBUGGER
34 for (size_t i = 0; i < len; i++) {
35 this->debug_callback_.call(uart::UART_DIRECTION_TX, data[i]);
36 }
37#endif
38}
39
40bool BLENUS::peek_byte(uint8_t *data) {
41#ifdef ESPHOME_BLE_NUS_RX_RING_BUFFER_SIZE
42 if (this->has_peek_) {
43 *data = this->peek_buffer_;
44 return true;
45 }
46
47 if (this->read_byte(&this->peek_buffer_)) {
48 *data = this->peek_buffer_;
49 this->has_peek_ = true;
50 return true;
51 }
52
53 return false;
54#else
55 return false;
56#endif
57}
58
59bool BLENUS::read_array(uint8_t *data, size_t len) {
60#ifdef ESPHOME_BLE_NUS_RX_RING_BUFFER_SIZE
61 if (len == 0) {
62 return true;
63 }
64 if (this->available() < len) {
65 return false;
66 }
67
68 // First, use the peek buffer if available
69 if (this->has_peek_) {
70#ifdef USE_UART_DEBUGGER
72#endif
73 data[0] = this->peek_buffer_;
74 this->has_peek_ = false;
75 data++;
76 if (--len == 0) { // Decrement len first, then check it...
77 return true; // No more to read
78 }
79 }
80
81 if (ring_buf_get(&global_ble_rx_ring_buf, data, len) != len) {
82 ESP_LOGE(TAG, "UART BLE unexpected size");
83 return false;
84 }
85#ifdef USE_UART_DEBUGGER
86 for (size_t i = 0; i < len; i++) {
87 this->debug_callback_.call(uart::UART_DIRECTION_RX, data[i]);
88 }
89#endif
90 return true;
91#else
92 return false;
93#endif
94}
95
97#ifdef ESPHOME_BLE_NUS_RX_RING_BUFFER_SIZE
98 uint32_t size = ring_buf_size_get(&global_ble_rx_ring_buf);
99 ESP_LOGVV(TAG, "UART BLE available %u", size);
100 return size + (this->has_peek_ ? 1 : 0);
101#else
102 return 0;
103#endif
104}
105
107 constexpr uint32_t timeout_500ms = 500;
108 uint32_t start = millis();
109 while (atomic_get(&this->tx_status_) != TX_DISABLED && !ring_buf_is_empty(&global_ble_tx_ring_buf)) {
110 if (millis() - start > timeout_500ms) {
111 ESP_LOGW(TAG, "Flush timeout");
113 }
114 delay(1);
115 }
117}
118
119void BLENUS::connected(bt_conn *conn, uint8_t err) {
120 if (err == 0) {
121 global_ble_nus->conn_.store(bt_conn_ref(conn));
123 }
124}
125
126void BLENUS::disconnected(bt_conn *conn, uint8_t reason) {
127 if (global_ble_nus->conn_) {
128 bt_conn_unref(global_ble_nus->conn_.load());
129 // Connection array is global static.
130 // Reference can be kept even if disconnected.
131 global_ble_nus->connected_ = false;
132 }
133}
134
135void BLENUS::tx_callback(bt_conn *conn) {
136 atomic_cas(&global_ble_nus->tx_status_, TX_BUSY, TX_ENABLED);
137 ESP_LOGVV(TAG, "Sent operation completed");
138}
139
140void BLENUS::send_enabled_callback(bt_nus_send_status status) {
141 switch (status) {
142 case BT_NUS_SEND_STATUS_ENABLED:
143 atomic_set(&global_ble_nus->tx_status_, TX_ENABLED);
144#ifdef USE_LOGGER
147 }
148#endif
149 ESP_LOGD(TAG, "NUS notification has been enabled");
150 break;
151 case BT_NUS_SEND_STATUS_DISABLED:
152 atomic_set(&global_ble_nus->tx_status_, TX_DISABLED);
153 ESP_LOGD(TAG, "NUS notification has been disabled");
154 break;
155 }
156}
157void BLENUS::rx_callback(bt_conn *conn, const uint8_t *const data, uint16_t len) {
158 ESP_LOGV(TAG, "Received %d bytes.", len);
159#ifdef ESPHOME_BLE_NUS_RX_RING_BUFFER_SIZE
160 auto recv_len = ring_buf_put(&global_ble_rx_ring_buf, data, len);
161 if (recv_len < len) {
162 ESP_LOGE(TAG, "RX dropping %u bytes", len - recv_len);
163 }
164#endif
165}
167#ifdef ESPHOME_BLE_NUS_RX_RING_BUFFER_SIZE
168 this->rx_buffer_size_ = ESPHOME_BLE_NUS_RX_RING_BUFFER_SIZE;
169#endif
170 bt_nus_cb callbacks = {
171 .received = rx_callback,
172 .sent = tx_callback,
173 .send_enabled = send_enabled_callback,
174 };
175
176 bt_nus_init(&callbacks);
177
178 static bt_conn_cb conn_callbacks = {
179 .connected = BLENUS::connected,
180 .disconnected = BLENUS::disconnected,
181 };
182
183 bt_conn_cb_register(&conn_callbacks);
184
185 global_ble_nus = this;
186#ifdef USE_LOGGER
187 if (logger::global_logger != nullptr && this->expose_log_) {
189 this, [](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) {
190 static_cast<BLENUS *>(self)->on_log(level, tag, message, message_len);
191 });
192 }
193#endif
194}
195
196#ifdef USE_LOGGER
197void BLENUS::on_log(uint8_t level, const char *tag, const char *message, size_t message_len) {
198 (void) level;
199 (void) tag;
200 this->write_array(reinterpret_cast<const uint8_t *>(message), message_len);
201 const char c = '\n';
202 this->write_array(reinterpret_cast<const uint8_t *>(&c), 1);
203}
204#endif
205
207 uint32_t mtu = 0;
208 bt_conn *conn = this->conn_.load();
209 if (conn && this->connected_) {
210 mtu = bt_nus_get_mtu(conn);
211 }
212 ESP_LOGCONFIG(TAG,
213 "ble nus:\n"
214 " log: %s\n"
215 " connected: %s\n"
216 " MTU: %u",
217 YESNO(this->expose_log_), YESNO(this->connected_.load()), mtu);
218}
219
221 if (ring_buf_is_empty(&global_ble_tx_ring_buf)) {
222 return;
223 }
224
225 if (!atomic_cas(&this->tx_status_, TX_ENABLED, TX_BUSY)) {
226 if (atomic_get(&this->tx_status_) == TX_DISABLED) {
227 ring_buf_reset(&global_ble_tx_ring_buf);
228 }
229 return;
230 }
231
232 bt_conn *conn = this->conn_.load();
233 if (conn) {
234 conn = bt_conn_ref(conn);
235 }
236
237 if (nullptr == conn) {
238 atomic_cas(&this->tx_status_, TX_BUSY, TX_ENABLED);
239 return;
240 }
241
242 uint32_t req_len = bt_nus_get_mtu(conn);
243
244 uint8_t *buf;
245 uint32_t size = ring_buf_get_claim(&global_ble_tx_ring_buf, &buf, req_len);
246
247 int err, err2;
248
249 err = bt_nus_send(conn, buf, size);
250 err2 = ring_buf_get_finish(&global_ble_tx_ring_buf, size);
251 if (err2) {
252 // It should no happen.
253 ESP_LOGE(TAG, "Size %u exceeds valid bytes in the ring buffer (%d error)", size, err2);
254 }
255 if (err == 0) {
256 ESP_LOGVV(TAG, "Sent %d bytes", size);
257 } else {
258 ESP_LOGE(TAG, "Failed to send %d bytes (%d error)", size, err);
259 atomic_cas(&this->tx_status_, TX_BUSY, TX_ENABLED);
260 }
261 bt_conn_unref(conn);
262}
263
264} // namespace esphome::ble_nus
265#endif
uint8_t status
Definition bl0942.h:8
static void disconnected(bt_conn *conn, uint8_t reason)
Definition ble_nus.cpp:126
static void rx_callback(bt_conn *conn, const uint8_t *data, uint16_t len)
Definition ble_nus.cpp:157
size_t available() override
Definition ble_nus.cpp:96
static void connected(bt_conn *conn, uint8_t err)
Definition ble_nus.cpp:119
uart::UARTFlushResult flush() override
Definition ble_nus.cpp:106
void setup() override
Definition ble_nus.cpp:166
bool read_array(uint8_t *data, size_t len) override
Definition ble_nus.cpp:59
void loop() override
Definition ble_nus.cpp:220
std::atomic< bt_conn * > conn_
Definition ble_nus.h:43
static void tx_callback(bt_conn *conn)
Definition ble_nus.cpp:135
void write_array(const uint8_t *data, size_t len) override
Definition ble_nus.cpp:24
bool peek_byte(uint8_t *data) override
Definition ble_nus.cpp:40
void on_log(uint8_t level, const char *tag, const char *message, size_t message_len)
Definition ble_nus.cpp:197
void dump_config() override
Definition ble_nus.cpp:206
std::atomic< bool > connected_
Definition ble_nus.h:46
static void send_enabled_callback(bt_nus_send_status status)
Definition ble_nus.cpp:140
void add_log_callback(void *instance, void(*fn)(void *, uint8_t, const char *, const char *, size_t))
Register a log callback to receive log messages.
Definition logger.h:187
bool read_byte(uint8_t *data)
CallbackManager< void(UARTDirection, uint8_t)> debug_callback_
const char * message
Definition component.cpp:35
BLENUS * global_ble_nus
Definition ble_nus.cpp:15
RING_BUF_DECLARE(global_ble_tx_ring_buf, ESPHOME_BLE_NUS_TX_RING_BUFFER_SIZE)
Logger * global_logger
Definition logger.cpp:272
UARTFlushResult
Result of a flush() call.
@ UART_FLUSH_RESULT_SUCCESS
Confirmed: all bytes left the TX FIFO.
@ UART_FLUSH_RESULT_TIMEOUT
Confirmed: timed out before TX completed.
const char * tag
Definition log.h:74
std::string size_t len
Definition helpers.h:1045
uint16_t size
Definition helpers.cpp:25
void HOT delay(uint32_t ms)
Definition core.cpp:28
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:26
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t