Compare commits

...
Author SHA1 Message Date
J. Nick Koston 47f8393f68 Merge remote-tracking branch 'origin/ble-server-caller-owned' into HEAD 2026-11-14 08:39:59 -06:00
J. Nick Koston f245927b49 Merge remote-tracking branch 'origin/dev' into HEAD 2026-11-14 08:36:58 -06:00
J. Nick Koston bd0aa95dcb Merge remote-tracking branch 'origin/ble-server-caller-owned' into ble-server-service-entry
# Conflicts:
#	esphome/components/esp32_ble_server/ble_server.cpp
2026-11-12 00:31:14 -06:00
J. Nick Koston deb8ff7747 Delete only unclaimed services created through this server's GATT interface 2026-11-12 00:30:38 -06:00
J. Nick Koston a4f10e34d2 Merge remote-tracking branch 'origin/ble-server-caller-owned' into ble-server-service-entry 2026-11-12 00:15:17 -06:00
J. Nick Koston be7ebd4502 Claim ADD_CHAR_EVT only while creating, in the characteristic's own service 2026-11-12 00:14:57 -06:00
J. Nick Koston db8aabb0a5 Walk the pointer list when deleting an unclaimed service 2026-11-11 23:52:22 -06:00
J. Nick Koston 278c58acaa Merge remote-tracking branch 'origin/ble-server-caller-owned' into ble-server-service-entry 2026-11-11 23:51:42 -06:00
J. Nick Koston 8441f30da4 Delete a service whose create arrives after it was removed 2026-11-11 23:51:36 -06:00
J. Nick Koston c468be5fe6 Merge remote-tracking branch 'origin/ble-server-caller-owned' into ble-server-service-entry 2026-11-11 23:43:11 -06:00
J. Nick Koston a1ede4e5c6 Skip the stack calls when a service is removed before it was created 2026-11-11 23:42:59 -06:00
J. Nick Koston c277712e47 Merge remote-tracking branch 'origin/ble-server-caller-owned' into ble-server-service-entry 2026-11-11 23:42:25 -06:00
J. Nick Koston 93efaca879 Forget a deleted characteristic's handles until it is created again 2026-11-11 23:38:31 -06:00
J. Nick Koston b7d59ccf7d Merge branch 'dev' into ble-server-caller-owned 2026-11-11 23:24:22 -06:00
J. Nick Koston 1bf9d1a7b2 Narrow the created characteristic count so owned_ fits in BLEService's padding 2026-11-11 23:10:10 -06:00
J. Nick Koston 70ec155ea2 Merge remote-tracking branch 'origin/ble-server-caller-owned' into ble-server-service-entry
# Conflicts:
#	esphome/components/esp32_ble_server/ble_server.cpp
2026-11-11 23:10:10 -06:00
J. Nick Koston f55557f581 Bind each parent only when adding, share one error message and trim comments 2026-11-11 23:09:30 -06:00
J. Nick Koston cb17035ee4 Find an already added service in the pointer list 2026-11-11 23:07:30 -06:00
J. Nick Koston 9d30de6f8a Merge remote-tracking branch 'origin/ble-server-caller-owned' into ble-server-service-entry 2026-11-11 23:06:50 -06:00
J. Nick Koston b78f3df6c1 Refuse a service, characteristic or descriptor that is already registered
Each object is bound to its parent when added and unbound when removed or when the
parent is destroyed, so none can be in two lists, be freed twice or keep a stale parent.
2026-11-11 23:05:09 -06:00
J. Nick Koston ce9763bea5 Merge remote-tracking branch 'origin/ble-server-caller-owned' into ble-server-service-entry
# Conflicts:
#	esphome/components/esp32_ble_server/ble_server.cpp
2026-11-11 22:26:34 -06:00
J. Nick Koston 507f754c78 Use std::erase, guard the descriptor's characteristic and dereference with an expression 2026-11-11 22:25:46 -06:00
J. Nick Koston 95f297a126 Merge remote-tracking branch 'origin/ble-server-caller-owned' into ble-server-service-entry 2026-11-11 22:23:52 -06:00
J. Nick Koston e465f754c6 Remove every copy of a descriptor and clear only a characteristic's own service 2026-11-11 22:22:06 -06:00
J. Nick Koston a971115a68 Merge remote-tracking branch 'origin/ble-server-caller-owned' into ble-server-service-entry
# Conflicts:
#	esphome/components/esp32_ble_server/ble_server.cpp
2026-11-11 21:50:00 -06:00
J. Nick Koston 4e09edb59a Unqueue removed services, free only member descriptors and bind added characteristics 2026-11-11 21:48:28 -06:00
J. Nick Koston 4e6f994b7c Merge remote-tracking branch 'upstream/dev' into ble-server-caller-owned 2026-11-09 20:59:46 -08:00
J. Nick Koston ad9027207b Merge branch 'ble-server-caller-owned' into ble-server-service-entry 2026-11-09 02:23:28 -08:00
J. Nick Koston 195d16eb27 Document that a caller-owned service is added once 2026-11-09 02:23:20 -08:00
J. Nick Koston d05bea3046 Merge branch 'ble-server-caller-owned' into ble-server-service-entry 2026-11-09 02:18:56 -08:00
J. Nick Koston fab9d004cc Point the caller-owned API comments at component members 2026-11-09 02:18:41 -08:00
J. Nick Koston f976b0c89a Keep the service owned flag on the service and list plain pointers 2026-11-09 02:18:21 -08:00
J. Nick Koston 7479304f75 Merge branch 'ble-server-caller-owned' into ble-server-service-entry
# Conflicts:
#	esphome/components/esp32_ble_server/ble_server.cpp
2026-11-09 02:09:53 -08:00
J. Nick Koston d4ffa31c4a Pick the instance id before allocating so create_service never deletes 2026-11-09 02:09:13 -08:00
J. Nick Koston 93416cb81e Merge branch 'ble-server-caller-owned' into ble-server-service-entry 2026-11-09 01:52:34 -08:00
J. Nick Koston 1296f3bc63 Test caller-owned registration and YAML descriptors added by reference 2026-11-09 01:52:06 -08:00
J. Nick Koston 2b37efad87 Merge branch 'ble-server-caller-owned' into ble-server-service-entry 2026-11-09 01:43:29 -08:00
J. Nick Koston e40165dd84 Clear ownership on remove_descriptor and parent pointers of caller-owned children 2026-11-09 01:43:12 -08:00
J. Nick Koston 91996b4b7c Make the service lookup getters const and return the UUID by reference 2026-11-09 01:10:09 -08:00
J. Nick Koston 21c0d5c464 [esp32_ble_server] Keep only the service pointer in the server's service list 2026-11-09 00:51:43 -08:00
J. Nick Koston 9579244ef3 Shorten the BLEService constructor comment 2026-11-09 00:35:39 -08:00
J. Nick Koston 60812d8055 [esp32_ble_server] Accept services, characteristics and descriptors the caller keeps alive 2026-11-09 00:22:41 -08:00
11 changed files with 211 additions and 68 deletions

No files matched your search

@@ -41,7 +41,7 @@ from esphome.const import (
__version__ as ESPHOME_VERSION,
)
from esphome.core import CORE, ID, MACAddress
from esphome.cpp_generator import MockObj, StaticCastExpression
from esphome.cpp_generator import MockObj, StaticCastExpression, UnaryOpExpression
import esphome.final_validate as fv
from esphome.schema_extractors import SCHEMA_EXTRACT
from esphome.types import ConfigType, TemplateArgsType
@@ -704,7 +704,8 @@ async def to_code_descriptor(descriptor_conf, char_conf, char_var):
descriptor_conf[CONF_MAX_LENGTH],
descriptor_permissions(descriptor_conf, char_conf),
)
cg.add(char_var.add_descriptor(desc_var))
# Static storage, so the characteristic must not free it
cg.add(char_var.add_descriptor(UnaryOpExpression("*", desc_var)))
cg.add(desc_var.set_value(value))
if (on_write_conf := descriptor_conf.get(CONF_ON_WRITE)) is not None:
await _build_on_write_automation(desc_var, on_write_conf)
@@ -2,6 +2,8 @@
#include "ble_server.h"
#include "ble_service.h"
#include <vector>
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
@@ -13,7 +15,12 @@ ESPHOME_LOG_TAG(TAG, "esp32_ble_server.characteristic");
BLECharacteristic::~BLECharacteristic() {
for (auto *descriptor : this->descriptors_) {
delete descriptor; // NOLINT(cppcoreguidelines-owning-memory)
if (descriptor->owned_) {
delete descriptor; // NOLINT(cppcoreguidelines-owning-memory)
} else if (descriptor->characteristic_ == this) {
// A caller-owned descriptor outlives this characteristic, and may already belong to another one
descriptor->characteristic_ = nullptr;
}
}
}
@@ -79,10 +86,27 @@ void BLECharacteristic::notify() {
}
}
void BLECharacteristic::add_descriptor(BLEDescriptor *descriptor) {
void BLECharacteristic::do_delete() {
this->clients_to_notify_.clear();
// The stack may reuse the handles, so a deleted characteristic must not match events until it is created again
this->handle_ = 0xFFFF;
this->state_ = INIT;
for (auto *descriptor : this->descriptors_) {
descriptor->handle_ = 0xFFFF;
descriptor->state_ = BLEDescriptor::INIT;
}
}
bool BLECharacteristic::add_descriptor(BLEDescriptor &descriptor) {
// Bound until removed or the parent is destroyed
if (descriptor.characteristic_ != nullptr) {
ESP_LOGE(TAG, "Already added");
return false;
}
descriptor.characteristic_ = this;
// If the descriptor is the CCCD descriptor, listen to its write event to know if the client wants to be notified
if (descriptor->get_uuid() == ESPBTUUID::from_uint16(ESP_GATT_UUID_CHAR_CLIENT_CONFIG)) {
descriptor->on_write([this](std::span<const uint8_t> value, uint16_t conn_id) {
if (descriptor.get_uuid() == ESPBTUUID::from_uint16(ESP_GATT_UUID_CHAR_CLIENT_CONFIG)) {
descriptor.on_write([this](std::span<const uint8_t> value, uint16_t conn_id) {
if (value.size() != 2)
return;
uint16_t cccd = encode_uint16(value[1], value[0]);
@@ -96,16 +120,18 @@ void BLECharacteristic::add_descriptor(BLEDescriptor *descriptor) {
}
});
}
this->descriptors_.push_back(descriptor);
this->descriptors_.push_back(&descriptor);
return true;
}
void BLECharacteristic::remove_descriptor(BLEDescriptor *descriptor) {
this->descriptors_.erase(std::remove(this->descriptors_.begin(), this->descriptors_.end(), descriptor),
this->descriptors_.end());
if (std::erase(this->descriptors_, descriptor) == 0)
return;
descriptor->owned_ = false; // the caller owns it again
descriptor->characteristic_ = nullptr;
}
void BLECharacteristic::do_create(BLEService *service) {
this->service_ = service;
void BLECharacteristic::do_create() {
esp_attr_control_t control;
control.auto_rsp = ESP_GATT_RSP_BY_APP;
@@ -116,8 +142,9 @@ void BLECharacteristic::do_create(BLEService *service) {
#endif
esp_bt_uuid_t uuid = this->uuid_.get_uuid();
esp_err_t err = esp_ble_gatts_add_char(service->get_handle(), &uuid, static_cast<esp_gatt_perm_t>(this->permissions_),
this->properties_, nullptr, &control);
esp_err_t err =
esp_ble_gatts_add_char(this->service_->get_handle(), &uuid, static_cast<esp_gatt_perm_t>(this->permissions_),
this->properties_, nullptr, &control);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gatts_add_char failed: %d", err);
@@ -188,11 +215,15 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt
break;
}
case ESP_GATTS_ADD_CHAR_EVT: {
if (this->uuid_ == ESPBTUUID::from_uuid(param->add_char.char_uuid)) {
// Only while creating, in its own service: a late event for a removed service or a same-UUID
// characteristic elsewhere must not set the handle or start the descriptors
if (this->state_ == CREATING && this->service_ != nullptr &&
this->service_->get_handle() == param->add_char.service_handle &&
this->uuid_ == ESPBTUUID::from_uuid(param->add_char.char_uuid)) {
this->handle_ = param->add_char.attr_handle;
for (auto *descriptor : this->descriptors_) {
descriptor->do_create(this);
descriptor->do_create();
}
this->state_ = CREATING_DEPENDENTS;
@@ -25,6 +25,8 @@ using namespace bytebuffer;
class BLEService;
class BLECharacteristic final {
friend class BLEService;
public:
BLECharacteristic(ESPBTUUID uuid, uint32_t properties, esp_gatt_perm_t permissions);
~BLECharacteristic();
@@ -44,11 +46,18 @@ class BLECharacteristic final {
void notify();
void do_create(BLEService *service);
void do_delete() { this->clients_to_notify_.clear(); }
void do_create();
void do_delete();
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
void add_descriptor(BLEDescriptor *descriptor);
/// Takes ownership of a heap-allocated descriptor and frees it with this characteristic.
void add_descriptor(BLEDescriptor *descriptor) {
if (this->add_descriptor(*descriptor))
descriptor->owned_ = true;
}
/// Adds a descriptor the caller keeps alive (such as a component member); it is never freed here.
/// False when it already belongs to a characteristic.
bool add_descriptor(BLEDescriptor &descriptor);
void remove_descriptor(BLEDescriptor *descriptor);
BLEService *get_service() { return this->service_; }
@@ -106,6 +115,8 @@ class BLECharacteristic final {
CREATING_DEPENDENTS,
CREATED,
} state_{INIT};
// Set by BLEService::create_characteristic(), which allocates it; the service frees it
bool owned_{false};
};
} // namespace esphome::esp32_ble_server
@@ -27,8 +27,7 @@ BLEDescriptor::BLEDescriptor(ESPBTUUID uuid, uint16_t max_len, esp_gatt_perm_t p
BLEDescriptor::~BLEDescriptor() { free(this->value_.attr_value); } // NOLINT
void BLEDescriptor::do_create(BLECharacteristic *characteristic) {
this->characteristic_ = characteristic;
void BLEDescriptor::do_create() {
esp_attr_control_t control;
control.auto_rsp = ESP_GATT_AUTO_RSP;
@@ -66,7 +65,8 @@ void BLEDescriptor::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
esp_ble_gatts_cb_param_t *param) {
switch (event) {
case ESP_GATTS_ADD_CHAR_DESCR_EVT: {
if (this->characteristic_ != nullptr && this->uuid_ == ESPBTUUID::from_uuid(param->add_char_descr.descr_uuid) &&
// Bound by add_descriptor(), so only a descriptor do_create() is creating may claim the event
if (this->state_ == CREATING && this->uuid_ == ESPBTUUID::from_uuid(param->add_char_descr.descr_uuid) &&
this->characteristic_->get_service()->get_handle() == param->add_char_descr.service_handle &&
this->characteristic_ == this->characteristic_->get_service()->get_last_created_characteristic()) {
this->handle_ = param->add_char_descr.attr_handle;
@@ -20,11 +20,13 @@ class BLECharacteristic;
// Base class for BLE descriptors
class BLEDescriptor {
friend class BLECharacteristic;
public:
BLEDescriptor(ESPBTUUID uuid, uint16_t max_len = 100, bool read = true, bool write = true);
BLEDescriptor(ESPBTUUID uuid, uint16_t max_len, esp_gatt_perm_t permissions);
virtual ~BLEDescriptor();
void do_create(BLECharacteristic *characteristic);
void do_create();
ESPBTUUID get_uuid() const { return this->uuid_; }
void set_value(const uint8_t *data, size_t length);
@@ -60,6 +62,8 @@ class BLEDescriptor {
CREATING,
CREATED,
} state_{INIT};
// Set when the characteristic took ownership through add_descriptor(BLEDescriptor *) and frees it
bool owned_{false};
};
} // namespace esphome::esp32_ble_server
@@ -1,5 +1,8 @@
#include "ble_server.h"
#include <algorithm>
#include <vector>
#include "esphome/components/ble_device_base/ble_device.h"
#include "esphome/components/esp32_ble/ble.h"
#include "esphome/core/log.h"
@@ -76,11 +79,11 @@ void BLEServer::loop() {
// it is at the top of the GATT table
this->device_information_service_->do_create(this);
// Create all services previously created
for (auto &entry : this->services_) {
if (entry.service == this->device_information_service_) {
for (auto *service : this->services_) {
if (service == this->device_information_service_) {
continue;
}
entry.service->do_create(this);
service->do_create(this);
}
this->state_ = STARTING_SERVICE;
}
@@ -123,31 +126,52 @@ void BLEServer::release_advertising_() {
}
BLEService *BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t num_handles) {
// Checked before allocating, so a failure never deletes a service and pulls in its destructor
const uint8_t inst_id = this->free_inst_id_(uuid);
if (inst_id == INVALID_INST_ID)
return nullptr;
auto *service = new BLEService(uuid, num_handles, advertise); // NOLINT(cppcoreguidelines-owning-memory)
service->owned_ = true;
this->register_service_(*service, inst_id);
return service;
}
bool BLEServer::add_service(BLEService &service) {
// Adding it twice would free or start it twice
if (std::ranges::find(this->services_, &service) != this->services_.end()) {
ESP_LOGE(TAG, "Already added");
return false;
}
const uint8_t inst_id = this->free_inst_id_(service.get_uuid());
if (inst_id == INVALID_INST_ID)
return false;
this->register_service_(service, inst_id);
return true;
}
uint8_t BLEServer::free_inst_id_(const ESPBTUUID &uuid) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char uuid_buf[esp32_ble::UUID_STR_LEN];
uuid.to_str(uuid_buf);
ESP_LOGV(TAG, "Creating BLE service - %s", uuid_buf);
ESP_LOGV(TAG, "Adding BLE service - %s", uuid_buf);
#endif
// Calculate the inst_id for the service
uint8_t inst_id = 0;
for (; inst_id < 0xFF; inst_id++) {
if (this->get_service(uuid, inst_id) == nullptr) {
break;
}
for (uint8_t inst_id = 0; inst_id < INVALID_INST_ID; inst_id++) {
if (this->get_service(uuid, inst_id) == nullptr)
return inst_id;
}
if (inst_id == 0xFF) {
char warn_uuid_buf[esp32_ble::UUID_STR_LEN];
uuid.to_str(warn_uuid_buf);
ESP_LOGW(TAG, "Could not create BLE service %s, too many instances", warn_uuid_buf);
return nullptr;
}
BLEService *service = // NOLINT(cppcoreguidelines-owning-memory)
new BLEService(uuid, num_handles, inst_id, advertise);
this->services_.push_back({uuid, inst_id, service});
char warn_uuid_buf[esp32_ble::UUID_STR_LEN];
uuid.to_str(warn_uuid_buf);
ESP_LOGW(TAG, "Could not create BLE service %s, too many instances", warn_uuid_buf);
return INVALID_INST_ID;
}
void BLEServer::register_service_(BLEService &service, uint8_t inst_id) {
service.inst_id_ = inst_id;
BLEService *ptr = &service; // an lvalue reuses the push_back already linked for services_to_start_
this->services_.push_back(ptr);
if (this->parent_->is_active() && this->registered_) {
service->do_create(this);
service.do_create(this);
}
return service;
}
void BLEServer::remove_service(ESPBTUUID uuid, uint8_t inst_id) {
@@ -157,9 +181,13 @@ void BLEServer::remove_service(ESPBTUUID uuid, uint8_t inst_id) {
ESP_LOGV(TAG, "Removing BLE service - %s %d", uuid_buf, inst_id);
#endif
for (auto it = this->services_.begin(); it != this->services_.end(); ++it) {
if (it->uuid == uuid && it->inst_id == inst_id) {
it->service->do_delete();
delete it->service; // NOLINT(cppcoreguidelines-owning-memory)
BLEService *service = *it;
if (service->get_inst_id() == inst_id && service->get_uuid() == uuid) {
service->do_delete();
// A service still waiting to start must not stay queued once it is unregistered or freed
std::erase(this->services_to_start_, service);
if (service->owned_)
delete service; // NOLINT(cppcoreguidelines-owning-memory)
this->services_.erase(it);
return;
}
@@ -170,9 +198,9 @@ void BLEServer::remove_service(ESPBTUUID uuid, uint8_t inst_id) {
}
BLEService *BLEServer::get_service(ESPBTUUID uuid, uint8_t inst_id) {
for (auto &entry : this->services_) {
if (entry.uuid == uuid && entry.inst_id == inst_id) {
return entry.service;
for (auto *service : this->services_) {
if (service->get_inst_id() == inst_id && service->get_uuid() == uuid) {
return service;
}
}
return nullptr;
@@ -215,8 +243,17 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga
break;
}
for (auto &entry : this->services_) {
entry.service->gatts_event_handler(event, gatts_if, param);
for (auto *service : this->services_) {
service->gatts_event_handler(event, gatts_if, param);
}
// A service this server asked for but removed while the stack was still creating it is deleted once its handle
// arrives; services of other GATT applications are left alone
if (event == ESP_GATTS_CREATE_EVT && gatts_if == this->gatts_if_ && param->create.status == ESP_GATT_OK) {
for (auto *service : this->services_) {
if (service->get_handle() == param->create.service_handle)
return;
}
esp_ble_gatts_delete_service(param->create.service_handle);
}
}
@@ -278,8 +315,8 @@ void BLEServer::ble_before_disabled_event_handler() {
// Delete all clients
this->client_count_ = 0;
// Delete all services
for (auto &entry : this->services_) {
entry.service->do_delete();
for (auto *service : this->services_) {
service->do_delete();
}
this->registered_ = false;
this->state_ = INIT;
@@ -49,6 +49,10 @@ class BLEServer final : public Component, public Parented<ESP32BLE> {
uint8_t get_max_clients() const { return this->max_clients_; }
BLEService *create_service(ESPBTUUID uuid, bool advertise = false, uint16_t num_handles = 15);
/// Adds a service the caller keeps alive (such as a component member); remove_service() never frees it.
/// Do not add it again after remove_service(). Returns false when it is already added or the UUID already has
/// 255 instances.
bool add_service(BLEService &service);
void remove_service(ESPBTUUID uuid, uint8_t inst_id = 0);
BLEService *get_service(ESPBTUUID uuid, uint8_t inst_id = 0);
void enqueue_start_service(BLEService *service) { this->services_to_start_.push_back(service); }
@@ -111,12 +115,9 @@ class BLEServer final : public Component, public Parented<ESP32BLE> {
std::function<void(uint16_t)> callback;
};
struct ServiceEntry {
ESPBTUUID uuid;
uint8_t inst_id;
BLEService *service;
};
static constexpr uint8_t INVALID_INST_ID = 0xFF;
uint8_t free_inst_id_(const ESPBTUUID &uuid);
void register_service_(BLEService &service, uint8_t inst_id);
void restart_advertising_();
void request_advertising_();
void release_advertising_();
@@ -159,7 +160,7 @@ class BLEServer final : public Component, public Parented<ESP32BLE> {
#endif
uint8_t client_count_{0};
uint8_t max_clients_{1};
std::vector<ServiceEntry> services_{};
std::vector<BLEService *> services_{};
std::vector<BLEService *> services_to_start_{};
BLEService *device_information_service_{};
@@ -12,8 +12,25 @@ BLEService::BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id, bo
: uuid_(uuid), num_handles_(num_handles), inst_id_(inst_id), advertise_(advertise) {}
BLEService::~BLEService() {
for (auto &chr : this->characteristics_)
delete chr; // NOLINT(cppcoreguidelines-owning-memory)
for (auto *chr : this->characteristics_) {
if (chr->owned_) {
delete chr; // NOLINT(cppcoreguidelines-owning-memory)
} else if (chr->service_ == this) {
// A caller-owned characteristic outlives this service, and may already belong to another one
chr->service_ = nullptr;
}
}
}
bool BLEService::add_characteristic(BLECharacteristic &characteristic) {
// Bound until the parent is destroyed
if (characteristic.service_ != nullptr) {
ESP_LOGE(TAG, "Already added");
return false;
}
characteristic.service_ = this;
this->characteristics_.push_back(&characteristic);
return true;
}
BLECharacteristic *BLEService::get_characteristic(ESPBTUUID uuid) {
@@ -39,7 +56,8 @@ BLECharacteristic *BLEService::create_characteristic(ESPBTUUID uuid, esp_gatt_ch
esp_gatt_perm_t permissions) {
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
BLECharacteristic *characteristic = new BLECharacteristic(uuid, properties, permissions);
this->characteristics_.push_back(characteristic);
characteristic->owned_ = true;
this->add_characteristic(*characteristic);
return characteristic;
}
@@ -69,8 +87,14 @@ void BLEService::do_delete() {
// Call all characteristics to delete
for (auto *characteristic : this->characteristics_)
characteristic->do_delete();
// Not created on the stack yet; if a create is in flight, the server deletes it when its handle arrives
if (this->handle_ == 0xFFFF) {
this->state_ = DELETED;
return;
}
this->stop_();
esp_err_t err = esp_ble_gatts_delete_service(this->handle_);
this->handle_ = 0xFFFF; // a later add creates it again with a new handle
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gatts_delete_service failed: %d", err);
return;
@@ -87,7 +111,7 @@ bool BLEService::do_create_characteristics_() {
auto *characteristic = this->characteristics_[this->created_characteristic_count_++];
this->last_created_characteristic_ = characteristic;
characteristic->do_create(this);
characteristic->do_create();
return true;
}
@@ -140,7 +164,7 @@ void BLEService::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t g
esp_ble_gatts_cb_param_t *param) {
switch (event) {
case ESP_GATTS_CREATE_EVT: {
if (this->uuid_ == ESPBTUUID::from_uuid(param->create.service_id.id.uuid) &&
if (this->state_ == CREATING && this->uuid_ == ESPBTUUID::from_uuid(param->create.service_id.id.uuid) &&
this->inst_id_ == param->create.service_id.id.inst_id) {
this->handle_ = param->create.service_handle;
this->state_ = CREATED;
@@ -20,8 +20,12 @@ class BLEServer;
using namespace esp32_ble;
class BLEService final {
friend class BLEServer;
public:
BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id, bool advertise);
/// The server picks the instance id when the service is added.
BLEService(ESPBTUUID uuid, uint16_t num_handles, bool advertise) : BLEService(uuid, num_handles, 0, advertise) {}
~BLEService();
BLECharacteristic *get_characteristic(ESPBTUUID uuid);
BLECharacteristic *get_characteristic(uint16_t uuid);
@@ -32,9 +36,11 @@ class BLEService final {
esp_gatt_perm_t permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE);
BLECharacteristic *create_characteristic(ESPBTUUID uuid, esp_gatt_char_prop_t properties,
esp_gatt_perm_t permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE);
/// Adds a characteristic the caller keeps alive; it is never freed here. False when it already belongs to a service.
bool add_characteristic(BLECharacteristic &characteristic);
ESPBTUUID get_uuid() { return this->uuid_; }
uint8_t get_inst_id() { return this->inst_id_; }
const ESPBTUUID &get_uuid() const { return this->uuid_; }
uint8_t get_inst_id() const { return this->inst_id_; }
BLECharacteristic *get_last_created_characteristic() { return this->last_created_characteristic_; }
uint16_t get_handle() { return this->handle_; }
@@ -57,7 +63,9 @@ class BLEService final {
protected:
std::vector<BLECharacteristic *> characteristics_;
BLECharacteristic *last_created_characteristic_{nullptr};
uint32_t created_characteristic_count_{0};
uint16_t created_characteristic_count_{0}; // never above num_handles_, so owned_ fits beside it
// Set by BLEServer::create_service(), which allocates it; remove_service() frees it
bool owned_{false};
BLEServer *server_ = nullptr;
ESPBTUUID uuid_;
uint16_t num_handles_;
@@ -147,6 +147,17 @@ def test_set_value_constants_share_a_flash_table(
assert "ByteBuffer::wrap" not in main_cpp
def test_yaml_descriptors_are_added_by_reference(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
main_cpp = generate_main(component_config_path("set_value.yaml"))
# Descriptors live in static storage, so the characteristic must not take ownership
assert "->add_descriptor((*test_descriptor));" in main_cpp
assert "->add_descriptor(test_descriptor);" not in main_cpp
def test_float32_rejects_out_of_range() -> None:
for value in (1e40, -1e40):
with pytest.raises(cv.Invalid, match="out of range for a float"):
@@ -1,3 +1,18 @@
esphome:
on_boot:
- lambda: |-
// Objects the caller keeps alive, registered without heap allocation
static esp32_ble_server::BLEService battery(esp32_ble::ESPBTUUID::from_uint16(0x180F), 6, false);
static esp32_ble_server::BLECharacteristic level(
esp32_ble::ESPBTUUID::from_uint16(0x2A19),
esp32_ble_server::BLECharacteristic::PROPERTY_READ | esp32_ble_server::BLECharacteristic::PROPERTY_NOTIFY,
ESP_GATT_PERM_READ);
static esp32_ble_server::BLE2902 level_cccd;
level.add_descriptor(level_cccd);
battery.add_characteristic(level);
if (id(ble_server).add_service(battery))
id(ble_server).enqueue_start_service(&battery);
esp32_ble_server:
id: ble_server
manufacturer_data: [0x72, 0x4, 0x00, 0x23]