ESPHome 2026.3.0
Loading...
Searching...
No Matches
ble_characteristic.cpp
Go to the documentation of this file.
2#include "ble_server.h"
3#include "ble_service.h"
4
6#include "esphome/core/log.h"
7
8#ifdef USE_ESP32
9
10namespace esphome {
11namespace esp32_ble_server {
12
13static const char *const TAG = "esp32_ble_server.characteristic";
14
16 for (auto *descriptor : this->descriptors_) {
17 delete descriptor; // NOLINT(cppcoreguidelines-owning-memory)
18 }
19}
20
21BLECharacteristic::BLECharacteristic(const ESPBTUUID uuid, uint32_t properties) : uuid_(uuid) {
22 this->properties_ = (esp_gatt_char_prop_t) 0;
23
24 this->set_broadcast_property((properties & PROPERTY_BROADCAST) != 0);
25 this->set_indicate_property((properties & PROPERTY_INDICATE) != 0);
26 this->set_notify_property((properties & PROPERTY_NOTIFY) != 0);
27 this->set_read_property((properties & PROPERTY_READ) != 0);
28 this->set_write_property((properties & PROPERTY_WRITE) != 0);
29 this->set_write_no_response_property((properties & PROPERTY_WRITE_NR) != 0);
30}
31
33
34void BLECharacteristic::set_value(std::vector<uint8_t> &&buffer) { this->value_ = std::move(buffer); }
35
36void BLECharacteristic::set_value(std::initializer_list<uint8_t> data) {
37 this->set_value(std::vector<uint8_t>(data)); // Delegate to move overload
38}
39
40void BLECharacteristic::set_value(const std::string &buffer) {
41 this->set_value(std::vector<uint8_t>(buffer.begin(), buffer.end())); // Delegate to move overload
42}
43
45 if (this->service_ == nullptr || this->service_->get_server() == nullptr ||
46 this->service_->get_server()->get_connected_client_count() == 0)
47 return;
48
49 const uint16_t *clients = this->service_->get_server()->get_clients();
50 uint8_t client_count = this->service_->get_server()->get_client_count();
51
52 for (uint8_t i = 0; i < client_count; i++) {
53 uint16_t client = clients[i];
54 size_t length = this->value_.size();
55 // Find the client in the list of clients to notify
56 auto *entry = this->find_client_in_notify_list_(client);
57 if (entry == nullptr)
58 continue;
59 bool require_ack = entry->indicate;
60 // TODO: Remove this block when INDICATE acknowledgment is supported
61 if (require_ack) {
62 ESP_LOGW(TAG, "INDICATE acknowledgment is not yet supported (i.e. it works as a NOTIFY)");
63 require_ack = false;
64 }
65 esp_err_t err = esp_ble_gatts_send_indicate(this->service_->get_server()->get_gatts_if(), client, this->handle_,
66 length, this->value_.data(), require_ack);
67 if (err != ESP_OK) {
68 ESP_LOGE(TAG, "esp_ble_gatts_send_indicate failed %d", err);
69 return;
70 }
71 }
72}
73
75 // If the descriptor is the CCCD descriptor, listen to its write event to know if the client wants to be notified
76 if (descriptor->get_uuid() == ESPBTUUID::from_uint16(ESP_GATT_UUID_CHAR_CLIENT_CONFIG)) {
77 descriptor->on_write([this](std::span<const uint8_t> value, uint16_t conn_id) {
78 if (value.size() != 2)
79 return;
80 uint16_t cccd = encode_uint16(value[1], value[0]);
81 bool notify = (cccd & 1) != 0;
82 bool indicate = (cccd & 2) != 0;
83 // Remove existing entry if present
85 // Add new entry if needed
86 if (notify || indicate) {
87 this->clients_to_notify_.push_back({conn_id, indicate});
88 }
89 });
90 }
91 this->descriptors_.push_back(descriptor);
92}
93
95 this->descriptors_.erase(std::remove(this->descriptors_.begin(), this->descriptors_.end(), descriptor),
96 this->descriptors_.end());
97}
98
100 this->service_ = service;
101 esp_attr_control_t control;
102 control.auto_rsp = ESP_GATT_RSP_BY_APP;
103
104#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
105 char uuid_buf[esp32_ble::UUID_STR_LEN];
106 this->uuid_.to_str(uuid_buf);
107 ESP_LOGV(TAG, "Creating characteristic - %s", uuid_buf);
108#endif
109
110 esp_bt_uuid_t uuid = this->uuid_.get_uuid();
111 esp_err_t err = esp_ble_gatts_add_char(service->get_handle(), &uuid, static_cast<esp_gatt_perm_t>(this->permissions_),
112 this->properties_, nullptr, &control);
113
114 if (err != ESP_OK) {
115 ESP_LOGE(TAG, "esp_ble_gatts_add_char failed: %d", err);
116 return;
117 }
118
119 this->state_ = CREATING;
120}
121
123 if (this->state_ == CREATED)
124 return true;
125
126 if (this->state_ != CREATING_DEPENDENTS)
127 return false;
128
129 for (auto *descriptor : this->descriptors_) {
130 if (!descriptor->is_created())
131 return false;
132 }
133 // All descriptors are created if we reach here
134 this->state_ = CREATED;
135 return true;
136}
137
139 if (this->state_ == FAILED)
140 return true;
141
142 for (auto *descriptor : this->descriptors_) {
143 if (descriptor->is_failed()) {
144 this->state_ = FAILED;
145 return true;
146 }
147 }
148 return false;
149}
150
151void BLECharacteristic::set_property_bit_(esp_gatt_char_prop_t bit, bool value) {
152 if (value) {
153 this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | bit);
154 } else {
155 this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~bit);
156 }
157}
158
160 this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_BROADCAST, value);
161}
163 this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_INDICATE, value);
164}
166 this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_NOTIFY, value);
167}
168void BLECharacteristic::set_read_property(bool value) { this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_READ, value); }
169void BLECharacteristic::set_write_property(bool value) { this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_WRITE, value); }
171 this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_WRITE_NR, value);
172}
173
174void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
175 esp_ble_gatts_cb_param_t *param) {
176 switch (event) {
177 case ESP_GATTS_ADD_CHAR_EVT: {
178 if (this->uuid_ == ESPBTUUID::from_uuid(param->add_char.char_uuid)) {
179 this->handle_ = param->add_char.attr_handle;
180
181 for (auto *descriptor : this->descriptors_) {
182 descriptor->do_create(this);
183 }
184
185 this->state_ = CREATING_DEPENDENTS;
186 }
187 break;
188 }
189 case ESP_GATTS_READ_EVT: {
190 if (param->read.handle != this->handle_)
191 break; // Not this characteristic
192
193 if (!param->read.need_rsp)
194 break; // For some reason you can request a read but not want a response
195
196 if (this->on_read_callback_) {
197 (*this->on_read_callback_)(param->read.conn_id);
198 }
199
200 uint16_t max_offset = 22;
201
202 esp_gatt_rsp_t response;
203 if (param->read.is_long) {
204 if (this->value_read_offset_ >= this->value_.size()) {
205 response.attr_value.len = 0;
206 response.attr_value.offset = this->value_read_offset_;
207 this->value_read_offset_ = 0;
208 } else if (this->value_.size() - this->value_read_offset_ < max_offset) {
209 // Last message in the chain
210 response.attr_value.len = this->value_.size() - this->value_read_offset_;
211 response.attr_value.offset = this->value_read_offset_;
212 memcpy(response.attr_value.value, this->value_.data() + response.attr_value.offset, response.attr_value.len);
213 this->value_read_offset_ = 0;
214 } else {
215 response.attr_value.len = max_offset;
216 response.attr_value.offset = this->value_read_offset_;
217 memcpy(response.attr_value.value, this->value_.data() + response.attr_value.offset, response.attr_value.len);
218 this->value_read_offset_ += max_offset;
219 }
220 } else {
221 response.attr_value.offset = 0;
222 if (this->value_.size() + 1 > max_offset) {
223 response.attr_value.len = max_offset;
224 this->value_read_offset_ = max_offset;
225 } else {
226 response.attr_value.len = this->value_.size();
227 }
228 memcpy(response.attr_value.value, this->value_.data(), response.attr_value.len);
229 }
230
231 response.attr_value.handle = this->handle_;
232 response.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
233
234 esp_err_t err =
235 esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, ESP_GATT_OK, &response);
236 if (err != ESP_OK) {
237 ESP_LOGE(TAG, "esp_ble_gatts_send_response failed: %d", err);
238 }
239 break;
240 }
241 case ESP_GATTS_WRITE_EVT: {
242 if (this->handle_ != param->write.handle)
243 break;
244
245 esp_gatt_status_t status = ESP_GATT_OK;
246
247 if (param->write.is_prep) {
248 const size_t offset = param->write.offset;
249 const size_t write_len = param->write.len;
250 const size_t new_size = offset + write_len;
251 // Clean the buffer on the first prepared write event
252 if (offset == 0) {
253 this->value_.clear();
254 }
255
256 if (offset != this->value_.size()) {
257 status = ESP_GATT_INVALID_OFFSET;
258 } else if (new_size > ESP_GATT_MAX_ATTR_LEN) {
259 status = ESP_GATT_INVALID_ATTR_LEN;
260 } else {
261 if (this->value_.size() < new_size) {
262 this->value_.resize(new_size);
263 }
264 memcpy(this->value_.data() + offset, param->write.value, write_len);
265 }
266 } else {
267 this->set_value(ByteBuffer::wrap(param->write.value, param->write.len));
268 }
269
270 if (param->write.need_rsp) {
271 esp_gatt_rsp_t response;
272
273 response.attr_value.len = param->write.len;
274 response.attr_value.handle = this->handle_;
275 response.attr_value.offset = param->write.offset;
276 response.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
277 memcpy(response.attr_value.value, param->write.value, param->write.len);
278
279 esp_err_t err =
280 esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, &response);
281
282 if (err != ESP_OK) {
283 ESP_LOGE(TAG, "esp_ble_gatts_send_response failed: %d", err);
284 }
285 }
286
287 if (!param->write.is_prep) {
288 if (this->on_write_callback_) {
289 (*this->on_write_callback_)(this->value_, param->write.conn_id);
290 }
291 }
292
293 break;
294 }
295
296 case ESP_GATTS_EXEC_WRITE_EVT: {
297 // BLE stack will guarantee that ESP_GATTS_EXEC_WRITE_EVT is only received after prepared writes
298 if (this->value_.empty())
299 break;
300 if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) {
301 if (this->on_write_callback_) {
302 (*this->on_write_callback_)(this->value_, param->exec_write.conn_id);
303 }
304 }
305 esp_err_t err = esp_ble_gatts_send_response(gatts_if, param->exec_write.conn_id, param->exec_write.trans_id,
306 ESP_GATT_OK, nullptr);
307 if (err != ESP_OK) {
308 ESP_LOGE(TAG, "esp_ble_gatts_send_response failed: %d", err);
309 }
310 break;
311 }
312 default:
313 break;
314 }
315
316 for (auto *descriptor : this->descriptors_) {
317 descriptor->gatts_event_handler(event, gatts_if, param);
318 }
319}
320
322 // Since we typically have very few clients (often just 1), we can optimize
323 // for the common case by swapping with the last element and popping
324 for (size_t i = 0; i < this->clients_to_notify_.size(); i++) {
325 if (this->clients_to_notify_[i].conn_id == conn_id) {
326 // Swap with last element and pop (safe even when i is the last element)
327 this->clients_to_notify_[i] = this->clients_to_notify_.back();
328 this->clients_to_notify_.pop_back();
329 return;
330 }
331 }
332}
333
335 for (auto &entry : this->clients_to_notify_) {
336 if (entry.conn_id == conn_id) {
337 return &entry;
338 }
339 }
340 return nullptr;
341}
342
343} // namespace esp32_ble_server
344} // namespace esphome
345
346#endif
uint8_t status
Definition bl0942.h:8
A class modelled on the Java ByteBuffer class.
Definition bytebuffer.h:38
std::vector< uint8_t > get_data()
Definition bytebuffer.h:300
static ByteBuffer wrap(T value, Endian endianness=LITTLE)
Definition bytebuffer.h:156
static ESPBTUUID from_uuid(esp_bt_uuid_t uuid)
Definition ble_uuid.cpp:84
static ESPBTUUID from_uint16(uint16_t uuid)
Definition ble_uuid.cpp:17
ESPDEPRECATED("Use to_str() instead. Removed in 2026.8.0", "2026.2.0") std const char * to_str(std::span< char, UUID_STR_LEN > output) const
Definition ble_uuid.cpp:146
esp_bt_uuid_t get_uuid() const
Definition ble_uuid.cpp:145
ClientNotificationEntry * find_client_in_notify_list_(uint16_t conn_id)
void remove_descriptor(BLEDescriptor *descriptor)
std::unique_ptr< std::function< void(std::span< const uint8_t >, uint16_t)> > on_write_callback_
void set_property_bit_(esp_gatt_char_prop_t bit, bool value)
BLECharacteristic(ESPBTUUID uuid, uint32_t properties)
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
std::vector< BLEDescriptor * > descriptors_
void add_descriptor(BLEDescriptor *descriptor)
std::vector< ClientNotificationEntry > clients_to_notify_
std::unique_ptr< std::function< void(uint16_t)> > on_read_callback_
void on_write(std::function< void(std::span< const uint8_t >, uint16_t)> &&callback)
const uint16_t * get_clients() const
Definition ble_server.h:53
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
Definition helpers.h:728
static void uint32_t
uint16_t length
Definition tt21100.cpp:0