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