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