ESPHome 2025.10.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(const std::vector<uint8_t> &buffer) {
39 xSemaphoreTake(this->set_value_lock_, 0L);
40 this->value_ = buffer;
41 xSemaphoreGive(this->set_value_lock_);
42}
43void BLECharacteristic::set_value(const std::string &buffer) {
44 this->set_value(std::vector<uint8_t>(buffer.begin(), buffer.end()));
45}
46
48 if (this->service_ == nullptr || this->service_->get_server() == nullptr ||
49 this->service_->get_server()->get_connected_client_count() == 0)
50 return;
51
52 for (auto &client : this->service_->get_server()->get_clients()) {
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(BLEDescriptorEvt::VectorEvt::ON_WRITE, [this](const std::vector<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 ESP_LOGV(TAG, "Creating characteristic - %s", this->uuid_.to_string().c_str());
104
105 esp_bt_uuid_t uuid = this->uuid_.get_uuid();
106 esp_err_t err = esp_ble_gatts_add_char(service->get_handle(), &uuid, static_cast<esp_gatt_perm_t>(this->permissions_),
107 this->properties_, nullptr, &control);
108
109 if (err != ESP_OK) {
110 ESP_LOGE(TAG, "esp_ble_gatts_add_char failed: %d", err);
111 return;
112 }
113
114 this->state_ = CREATING;
115}
116
118 if (this->state_ == CREATED)
119 return true;
120
121 if (this->state_ != CREATING_DEPENDENTS)
122 return false;
123
124 bool created = true;
125 for (auto *descriptor : this->descriptors_) {
126 created &= descriptor->is_created();
127 }
128 if (created)
129 this->state_ = CREATED;
130 return this->state_ == CREATED;
131}
132
134 if (this->state_ == FAILED)
135 return true;
136
137 bool failed = false;
138 for (auto *descriptor : this->descriptors_) {
139 failed |= descriptor->is_failed();
140 }
141 if (failed)
142 this->state_ = FAILED;
143 return this->state_ == FAILED;
144}
145
147 if (value) {
148 this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST);
149 } else {
150 this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST);
151 }
152}
154 if (value) {
155 this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE);
156 } else {
157 this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE);
158 }
159}
161 if (value) {
162 this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY);
163 } else {
164 this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY);
165 }
166}
168 if (value) {
169 this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ);
170 } else {
171 this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ);
172 }
173}
175 if (value) {
176 this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE);
177 } else {
178 this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE);
179 }
180}
182 if (value) {
183 this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR);
184 } else {
185 this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR);
186 }
187}
188
189void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
190 esp_ble_gatts_cb_param_t *param) {
191 switch (event) {
192 case ESP_GATTS_ADD_CHAR_EVT: {
193 if (this->uuid_ == ESPBTUUID::from_uuid(param->add_char.char_uuid)) {
194 this->handle_ = param->add_char.attr_handle;
195
196 for (auto *descriptor : this->descriptors_) {
197 descriptor->do_create(this);
198 }
199
200 this->state_ = CREATING_DEPENDENTS;
201 }
202 break;
203 }
204 case ESP_GATTS_READ_EVT: {
205 if (param->read.handle != this->handle_)
206 break; // Not this characteristic
207
208 if (!param->read.need_rsp)
209 break; // For some reason you can request a read but not want a response
210
212 param->read.conn_id);
213
214 uint16_t max_offset = 22;
215
216 esp_gatt_rsp_t response;
217 if (param->read.is_long) {
218 if (this->value_.size() - this->value_read_offset_ < max_offset) {
219 // Last message in the chain
220 response.attr_value.len = this->value_.size() - this->value_read_offset_;
221 response.attr_value.offset = this->value_read_offset_;
222 memcpy(response.attr_value.value, this->value_.data() + response.attr_value.offset, response.attr_value.len);
223 this->value_read_offset_ = 0;
224 } else {
225 response.attr_value.len = max_offset;
226 response.attr_value.offset = this->value_read_offset_;
227 memcpy(response.attr_value.value, this->value_.data() + response.attr_value.offset, response.attr_value.len);
228 this->value_read_offset_ += max_offset;
229 }
230 } else {
231 response.attr_value.offset = 0;
232 if (this->value_.size() + 1 > max_offset) {
233 response.attr_value.len = max_offset;
234 this->value_read_offset_ = max_offset;
235 } else {
236 response.attr_value.len = this->value_.size();
237 }
238 memcpy(response.attr_value.value, this->value_.data(), response.attr_value.len);
239 }
240
241 response.attr_value.handle = this->handle_;
242 response.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
243
244 esp_err_t err =
245 esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, ESP_GATT_OK, &response);
246 if (err != ESP_OK) {
247 ESP_LOGE(TAG, "esp_ble_gatts_send_response failed: %d", err);
248 }
249 break;
250 }
251 case ESP_GATTS_WRITE_EVT: {
252 if (this->handle_ != param->write.handle)
253 break;
254
255 if (param->write.is_prep) {
256 this->value_.insert(this->value_.end(), param->write.value, param->write.value + param->write.len);
257 this->write_event_ = true;
258 } else {
259 this->set_value(ByteBuffer::wrap(param->write.value, param->write.len));
260 }
261
262 if (param->write.need_rsp) {
263 esp_gatt_rsp_t response;
264
265 response.attr_value.len = param->write.len;
266 response.attr_value.handle = this->handle_;
267 response.attr_value.offset = param->write.offset;
268 response.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
269 memcpy(response.attr_value.value, param->write.value, param->write.len);
270
271 esp_err_t err =
272 esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, &response);
273
274 if (err != ESP_OK) {
275 ESP_LOGE(TAG, "esp_ble_gatts_send_response failed: %d", err);
276 }
277 }
278
279 if (!param->write.is_prep) {
281 BLECharacteristicEvt::VectorEvt::ON_WRITE, this->value_, param->write.conn_id);
282 }
283
284 break;
285 }
286
287 case ESP_GATTS_EXEC_WRITE_EVT: {
288 if (!this->write_event_)
289 break;
290 this->write_event_ = false;
291 if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) {
293 BLECharacteristicEvt::VectorEvt::ON_WRITE, this->value_, param->exec_write.conn_id);
294 }
295 esp_err_t err =
296 esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, nullptr);
297 if (err != ESP_OK) {
298 ESP_LOGE(TAG, "esp_ble_gatts_send_response failed: %d", err);
299 }
300 break;
301 }
302 default:
303 break;
304 }
305
306 for (auto *descriptor : this->descriptors_) {
307 descriptor->gatts_event_handler(event, gatts_if, param);
308 }
309}
310
312 // Since we typically have very few clients (often just 1), we can optimize
313 // for the common case by swapping with the last element and popping
314 for (size_t i = 0; i < this->clients_to_notify_.size(); i++) {
315 if (this->clients_to_notify_[i].conn_id == conn_id) {
316 // Swap with last element and pop (safe even when i is the last element)
317 this->clients_to_notify_[i] = this->clients_to_notify_.back();
318 this->clients_to_notify_.pop_back();
319 return;
320 }
321 }
322}
323
325 for (auto &entry : this->clients_to_notify_) {
326 if (entry.conn_id == conn_id) {
327 return &entry;
328 }
329 }
330 return nullptr;
331}
332
333} // namespace esp32_ble_server
334} // namespace esphome
335
336#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
std::string to_string() const
Definition ble_uuid.cpp:172
static ESPBTUUID from_uuid(esp_bt_uuid_t uuid)
Definition ble_uuid.cpp:98
static ESPBTUUID from_uint16(uint16_t uuid)
Definition ble_uuid.cpp:17
esp_bt_uuid_t get_uuid() const
Definition ble_uuid.cpp:171
ClientNotificationEntry * find_client_in_notify_list_(uint16_t conn_id)
void remove_descriptor(BLEDescriptor *descriptor)
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_
const std::unordered_set< uint16_t > & get_clients()
Definition ble_server.h:61
EventEmitterListenerID on(EvtType event, std::function< void(Args...)> listener)
void emit_(EvtType event, Args... args)
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:185
uint16_t length
Definition tt21100.cpp:0