ESPHome 2026.11.0-dev
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
11
12ESPHOME_LOG_TAG(TAG, "esp32_ble_server.characteristic");
13
15 for (auto *descriptor : this->descriptors_) {
16 delete descriptor; // NOLINT(cppcoreguidelines-owning-memory)
17 }
18}
19
20BLECharacteristic::BLECharacteristic(const ESPBTUUID uuid, uint32_t properties) : uuid_(uuid) {
21 this->properties_ = (esp_gatt_char_prop_t) 0;
22
23 this->set_broadcast_property((properties & PROPERTY_BROADCAST) != 0);
24 this->set_indicate_property((properties & PROPERTY_INDICATE) != 0);
25 this->set_notify_property((properties & PROPERTY_NOTIFY) != 0);
26 this->set_read_property((properties & PROPERTY_READ) != 0);
27 this->set_write_property((properties & PROPERTY_WRITE) != 0);
28 this->set_write_no_response_property((properties & PROPERTY_WRITE_NR) != 0);
29}
30
32
33void BLECharacteristic::set_value(const uint8_t *data, size_t length) {
34 if (length == 0) { // An empty constant is a nullptr table
35 this->value_.clear();
36 return;
37 }
38 this->value_.assign(data, data + length);
39}
40
41void BLECharacteristic::set_value(std::vector<uint8_t> &&buffer) { this->value_ = std::move(buffer); }
42
43void BLECharacteristic::set_value(std::initializer_list<uint8_t> data) {
44 this->set_value(std::vector<uint8_t>(data)); // Delegate to move overload
45}
46
47void BLECharacteristic::set_value(const std::string &buffer) {
48 this->set_value(std::vector<uint8_t>(buffer.begin(), buffer.end())); // Delegate to move overload
49}
50
52 if (this->service_ == nullptr || this->service_->get_server() == nullptr ||
53 this->service_->get_server()->get_connected_client_count() == 0)
54 return;
55
56 const uint16_t *clients = this->service_->get_server()->get_clients();
57 uint8_t client_count = this->service_->get_server()->get_client_count();
58
59 for (uint8_t i = 0; i < client_count; i++) {
60 uint16_t client = clients[i];
61 size_t length = this->value_.size();
62 // Find the client in the list of clients to notify
63 auto *entry = this->find_client_in_notify_list_(client);
64 if (entry == nullptr)
65 continue;
66 bool require_ack = entry->indicate;
67 // TODO: Remove this block when INDICATE acknowledgment is supported
68 if (require_ack) {
69 ESP_LOGW(TAG, "INDICATE acknowledgment is not yet supported (i.e. it works as a NOTIFY)");
70 require_ack = false;
71 }
72 esp_err_t err = esp_ble_gatts_send_indicate(this->service_->get_server()->get_gatts_if(), client, this->handle_,
73 length, this->value_.data(), require_ack);
74 if (err != ESP_OK) {
75 ESP_LOGE(TAG, "esp_ble_gatts_send_indicate failed %d", err);
76 return;
77 }
78 }
79}
80
82 // If the descriptor is the CCCD descriptor, listen to its write event to know if the client wants to be notified
83 if (descriptor->get_uuid() == ESPBTUUID::from_uint16(ESP_GATT_UUID_CHAR_CLIENT_CONFIG)) {
84 descriptor->on_write([this](std::span<const uint8_t> value, uint16_t conn_id) {
85 if (value.size() != 2)
86 return;
87 uint16_t cccd = encode_uint16(value[1], value[0]);
88 bool notify = (cccd & 1) != 0;
89 bool indicate = (cccd & 2) != 0;
90 // Remove existing entry if present
92 // Add new entry if needed
93 if (notify || indicate) {
94 this->clients_to_notify_.push_back({conn_id, indicate});
95 }
96 });
97 }
98 this->descriptors_.push_back(descriptor);
99}
100
102 this->descriptors_.erase(std::remove(this->descriptors_.begin(), this->descriptors_.end(), descriptor),
103 this->descriptors_.end());
104}
105
107 this->service_ = service;
108 esp_attr_control_t control;
109 control.auto_rsp = ESP_GATT_RSP_BY_APP;
110
111#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
112 char uuid_buf[esp32_ble::UUID_STR_LEN];
113 this->uuid_.to_str(uuid_buf);
114 ESP_LOGV(TAG, "Creating characteristic - %s", uuid_buf);
115#endif
116
117 esp_bt_uuid_t uuid = this->uuid_.get_uuid();
118 esp_err_t err = esp_ble_gatts_add_char(service->get_handle(), &uuid, static_cast<esp_gatt_perm_t>(this->permissions_),
119 this->properties_, nullptr, &control);
120
121 if (err != ESP_OK) {
122 ESP_LOGE(TAG, "esp_ble_gatts_add_char failed: %d", err);
123 return;
124 }
125
126 this->state_ = CREATING;
127}
128
130 if (this->state_ == CREATED)
131 return true;
132
133 if (this->state_ != CREATING_DEPENDENTS)
134 return false;
135
136 for (auto *descriptor : this->descriptors_) {
137 if (!descriptor->is_created())
138 return false;
139 }
140 // All descriptors are created if we reach here
141 this->state_ = CREATED;
142 return true;
143}
144
146 if (this->state_ == FAILED)
147 return true;
148
149 for (auto *descriptor : this->descriptors_) {
150 if (descriptor->is_failed()) {
151 this->state_ = FAILED;
152 return true;
153 }
154 }
155 return false;
156}
157
158void BLECharacteristic::set_property_bit_(esp_gatt_char_prop_t bit, bool value) {
159 if (value) {
160 this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | bit);
161 } else {
162 this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~bit);
163 }
164}
165
167 this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_BROADCAST, value);
168}
170 this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_INDICATE, value);
171}
173 this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_NOTIFY, value);
174}
175void BLECharacteristic::set_read_property(bool value) { this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_READ, value); }
176void BLECharacteristic::set_write_property(bool value) { this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_WRITE, value); }
178 this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_WRITE_NR, value);
179}
180
181void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
182 esp_ble_gatts_cb_param_t *param) {
183 switch (event) {
184 case ESP_GATTS_ADD_CHAR_EVT: {
185 if (this->uuid_ == ESPBTUUID::from_uuid(param->add_char.char_uuid)) {
186 this->handle_ = param->add_char.attr_handle;
187
188 for (auto *descriptor : this->descriptors_) {
189 descriptor->do_create(this);
190 }
191
192 this->state_ = CREATING_DEPENDENTS;
193 }
194 break;
195 }
196 case ESP_GATTS_READ_EVT: {
197 if (param->read.handle != this->handle_)
198 break; // Not this characteristic
199
200 if (!param->read.need_rsp)
201 break; // For some reason you can request a read but not want a response
202
203 if (this->on_read_callback_) {
204 (*this->on_read_callback_)(param->read.conn_id);
205 }
206
207 // Use the client-supplied offset for long reads; short reads always start at 0.
208 // The Bluedroid stack truncates ATT_READ_RSP / ATT_READ_BLOB_RSP to MTU-1, so we
209 // just provide as much data as we have from the requested offset and let the stack
210 // handle framing. The client issues subsequent blob reads with increasing offsets
211 // until it has received the whole value.
212 const uint16_t offset = param->read.is_long ? param->read.offset : 0;
213 esp_gatt_status_t status = ESP_GATT_OK;
214 esp_gatt_rsp_t response;
215 response.attr_value.offset = offset;
216
217 if (offset > this->value_.size()) {
218 status = ESP_GATT_INVALID_OFFSET;
219 response.attr_value.len = 0;
220 } else {
221 size_t remaining = this->value_.size() - offset;
222 if (remaining > ESP_GATT_MAX_ATTR_LEN) {
223 ESP_LOGW(TAG, "Characteristic length %u exceeds buffer size of %u, truncating",
224 static_cast<unsigned>(remaining), ESP_GATT_MAX_ATTR_LEN);
225 remaining = ESP_GATT_MAX_ATTR_LEN;
226 }
227 response.attr_value.len = remaining;
228 memcpy(response.attr_value.value, this->value_.data() + offset, remaining);
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, status, &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 esphome::esp32_ble_server
344
345#endif
uint8_t status
Definition bl0942.h:8
static ESPBTUUID from_uuid(esp_bt_uuid_t uuid)
Source compatibility with the historical esp32_ble API (esp32 builds only).
static ESPBTUUID from_uint16(uint16_t uuid)
A class modelled on the Java ByteBuffer class.
Definition bytebuffer.h:37
std::vector< uint8_t > get_data()
Definition bytebuffer.h:299
static ByteBuffer wrap(T value, Endian endianness=LITTLE)
Definition bytebuffer.h:155
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:59
uint32_t buffer
Definition gif.cpp:50
ESPHOME_LOG_TAG(TAG, "esp32_ble")
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:998
static void uint32_t
uint16_t length
Definition tt21100.cpp:0