ESPHome 2026.8.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 // ring_buf_put() performs a partial write when the buffer is nearly full, which would commit a
29 // truncated fragment and corrupt the stream. Only write when the whole payload fits, so the byte
30 // stream never contains a partial message.
31 if (ring_buf_space_get(&global_ble_tx_ring_buf) < len) {
32 ESP_LOGE(TAG, "TX dropping %u bytes", len);
33 return;
34 }
35 ring_buf_put(&global_ble_tx_ring_buf, data, len);
36#ifdef USE_UART_DEBUGGER
37 for (size_t i = 0; i < len; i++) {
38 this->debug_callback_.call(uart::UART_DIRECTION_TX, data[i]);
39 }
40#endif
41}
42
43bool BLENUS::peek_byte(uint8_t *data) {
44#ifdef ESPHOME_BLE_NUS_RX_RING_BUFFER_SIZE
45 if (this->has_peek_) {
46 *data = this->peek_buffer_;
47 return true;
48 }
49
50 if (this->read_byte(&this->peek_buffer_)) {
51 *data = this->peek_buffer_;
52 this->has_peek_ = true;
53 return true;
54 }
55
56 return false;
57#else
58 return false;
59#endif
60}
61
62bool BLENUS::read_array(uint8_t *data, size_t len) {
63#ifdef ESPHOME_BLE_NUS_RX_RING_BUFFER_SIZE
64 if (len == 0) {
65 return true;
66 }
67 if (this->available() < len) {
68 return false;
69 }
70
71 // First, use the peek buffer if available
72 if (this->has_peek_) {
73#ifdef USE_UART_DEBUGGER
75#endif
76 data[0] = this->peek_buffer_;
77 this->has_peek_ = false;
78 data++;
79 if (--len == 0) { // Decrement len first, then check it...
80 return true; // No more to read
81 }
82 }
83
84 if (ring_buf_get(&global_ble_rx_ring_buf, data, len) != len) {
85 ESP_LOGE(TAG, "UART BLE unexpected size");
86 return false;
87 }
88#ifdef USE_UART_DEBUGGER
89 for (size_t i = 0; i < len; i++) {
90 this->debug_callback_.call(uart::UART_DIRECTION_RX, data[i]);
91 }
92#endif
93 return true;
94#else
95 return false;
96#endif
97}
98
100#ifdef ESPHOME_BLE_NUS_RX_RING_BUFFER_SIZE
101 uint32_t size = ring_buf_size_get(&global_ble_rx_ring_buf);
102 ESP_LOGVV(TAG, "UART BLE available %u", size);
103 return size + (this->has_peek_ ? 1 : 0);
104#else
105 return 0;
106#endif
107}
108
110 constexpr uint32_t timeout_500ms = 500;
111 uint32_t start = millis();
112 while (atomic_get(&this->tx_status_) != TX_DISABLED && !ring_buf_is_empty(&global_ble_tx_ring_buf)) {
113 if (millis() - start > timeout_500ms) {
114 ESP_LOGW(TAG, "Flush timeout");
116 }
117 delay(1);
118 }
120}
121
122void BLENUS::connected(bt_conn *conn, uint8_t err) {
123 if (err == 0) {
124 global_ble_nus->conn_.store(bt_conn_ref(conn));
126 }
127}
128
129void BLENUS::disconnected(bt_conn *conn, uint8_t reason) {
130 if (global_ble_nus->conn_) {
131 bt_conn_unref(global_ble_nus->conn_.load());
132 // Connection array is global static.
133 // Reference can be kept even if disconnected.
134 global_ble_nus->connected_ = false;
135 }
136}
137
138void BLENUS::tx_callback(bt_conn *conn) {
139 atomic_cas(&global_ble_nus->tx_status_, TX_BUSY, TX_ENABLED);
140 ESP_LOGVV(TAG, "Sent operation completed");
141}
142
143void BLENUS::send_enabled_callback(bt_nus_send_status status) {
144 switch (status) {
145 case BT_NUS_SEND_STATUS_ENABLED:
146 atomic_set(&global_ble_nus->tx_status_, TX_ENABLED);
147#ifdef USE_LOGGER
150 }
151#endif
152 ESP_LOGD(TAG, "NUS notification has been enabled");
153 break;
154 case BT_NUS_SEND_STATUS_DISABLED:
155 atomic_set(&global_ble_nus->tx_status_, TX_DISABLED);
156 ESP_LOGD(TAG, "NUS notification has been disabled");
157 break;
158 }
159}
160void BLENUS::rx_callback(bt_conn *conn, const uint8_t *const data, uint16_t len) {
161 ESP_LOGV(TAG, "Received %d bytes.", len);
162#ifdef ESPHOME_BLE_NUS_RX_RING_BUFFER_SIZE
163 auto recv_len = ring_buf_put(&global_ble_rx_ring_buf, data, len);
164 if (recv_len < len) {
165 ESP_LOGE(TAG, "RX dropping %u bytes", len - recv_len);
166 }
167#endif
168}
170#ifdef ESPHOME_BLE_NUS_RX_RING_BUFFER_SIZE
171 this->rx_buffer_size_ = ESPHOME_BLE_NUS_RX_RING_BUFFER_SIZE;
172#endif
173 bt_nus_cb callbacks = {
174 .received = rx_callback,
175 .sent = tx_callback,
176 .send_enabled = send_enabled_callback,
177 };
178
179 bt_nus_init(&callbacks);
180
181 static bt_conn_cb conn_callbacks = {
182 .connected = BLENUS::connected,
183 .disconnected = BLENUS::disconnected,
184 };
185
186 bt_conn_cb_register(&conn_callbacks);
187
188 global_ble_nus = this;
189#ifdef USE_LOGGER
190 if (logger::global_logger != nullptr && this->expose_log_) {
192 this, [](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) {
193 static_cast<BLENUS *>(self)->on_log(level, tag, message, message_len);
194 });
195 }
196#endif
197}
198
199#ifdef USE_LOGGER
200void BLENUS::on_log(uint8_t level, const char *tag, const char *message, size_t message_len) {
201 (void) level;
202 (void) tag;
203 // make sure there is space for '\n' or entire message is dropped
204 if (ring_buf_space_get(&global_ble_tx_ring_buf) < message_len + 1) {
205 return;
206 }
207 this->write_array(reinterpret_cast<const uint8_t *>(message), message_len);
208 const char c = '\n';
209 this->write_array(reinterpret_cast<const uint8_t *>(&c), 1);
210}
211#endif
212
214 uint32_t mtu = 0;
215 bt_conn *conn = this->conn_.load();
216 if (conn && this->connected_) {
217 mtu = bt_nus_get_mtu(conn);
218 }
219 ESP_LOGCONFIG(TAG,
220 "ble nus:\n"
221 " log: %s\n"
222 " connected: %s\n"
223 " MTU: %u",
224 YESNO(this->expose_log_), YESNO(this->connected_.load()), mtu);
225}
226
228 if (ring_buf_is_empty(&global_ble_tx_ring_buf)) {
229 return;
230 }
231
232 if (!atomic_cas(&this->tx_status_, TX_ENABLED, TX_BUSY)) {
233 if (atomic_get(&this->tx_status_) == TX_DISABLED) {
234 ring_buf_reset(&global_ble_tx_ring_buf);
235 }
236 return;
237 }
238
239 bt_conn *conn = this->conn_.load();
240 if (conn) {
241 conn = bt_conn_ref(conn);
242 }
243
244 if (nullptr == conn) {
245 atomic_cas(&this->tx_status_, TX_BUSY, TX_ENABLED);
246 return;
247 }
248
249 uint32_t req_len = bt_nus_get_mtu(conn);
250
251 uint8_t *buf;
252 uint32_t size = ring_buf_get_claim(&global_ble_tx_ring_buf, &buf, req_len);
253
254 int err, err2;
255
256 err = bt_nus_send(conn, buf, size);
257 err2 = ring_buf_get_finish(&global_ble_tx_ring_buf, size);
258 if (err2) {
259 // It should no happen.
260 ESP_LOGE(TAG, "Size %u exceeds valid bytes in the ring buffer (%d error)", size, err2);
261 }
262 if (err == 0) {
263 ESP_LOGVV(TAG, "Sent %d bytes", size);
264 } else {
265 ESP_LOGE(TAG, "Failed to send %d bytes (%d error)", size, err);
266 atomic_cas(&this->tx_status_, TX_BUSY, TX_ENABLED);
267 }
268 bt_conn_unref(conn);
269}
270
271} // namespace esphome::ble_nus
272#endif
uint8_t status
Definition bl0942.h:8
static void disconnected(bt_conn *conn, uint8_t reason)
Definition ble_nus.cpp:129
static void rx_callback(bt_conn *conn, const uint8_t *data, uint16_t len)
Definition ble_nus.cpp:160
size_t available() override
Definition ble_nus.cpp:99
static void connected(bt_conn *conn, uint8_t err)
Definition ble_nus.cpp:122
uart::UARTFlushResult flush() override
Definition ble_nus.cpp:109
void setup() override
Definition ble_nus.cpp:169
bool read_array(uint8_t *data, size_t len) override
Definition ble_nus.cpp:62
void loop() override
Definition ble_nus.cpp:227
std::atomic< bt_conn * > conn_
Definition ble_nus.h:43
static void tx_callback(bt_conn *conn)
Definition ble_nus.cpp:138
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:43
void on_log(uint8_t level, const char *tag, const char *message, size_t message_len)
Definition ble_nus.cpp:200
void dump_config() override
Definition ble_nus.cpp:213
std::atomic< bool > connected_
Definition ble_nus.h:46
static void send_enabled_callback(bt_nus_send_status status)
Definition ble_nus.cpp:143
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 LogString * 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:279
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
const void size_t len
Definition hal.h:64
uint16_t size
Definition helpers.cpp:25
void HOT delay(uint32_t ms)
Definition hal.cpp:85
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:28
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t