diff --git a/CODEOWNERS b/CODEOWNERS index b639088900..a48d0ab2e0 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -89,9 +89,9 @@ esphome/components/ektf2232/* @jesserockz esphome/components/emc2101/* @ellull esphome/components/ens210/* @itn3rd77 esphome/components/esp32/* @esphome/core -esphome/components/esp32_ble/* @jesserockz +esphome/components/esp32_ble/* @Rapsssito @jesserockz esphome/components/esp32_ble_client/* @jesserockz -esphome/components/esp32_ble_server/* @clydebarrow @jesserockz +esphome/components/esp32_ble_server/* @Rapsssito @clydebarrow @jesserockz esphome/components/esp32_camera_web_server/* @ayufan esphome/components/esp32_can/* @Sympatron esphome/components/esp32_improv/* @jesserockz diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index b4cb595da0..57a7341505 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -1,15 +1,17 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome import automation from esphome.const import CONF_ID from esphome.core import CORE from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant, const DEPENDENCIES = ["esp32"] -CODEOWNERS = ["@jesserockz"] +CODEOWNERS = ["@jesserockz", "@Rapsssito"] CONFLICTS_WITH = ["esp32_ble_beacon"] CONF_BLE_ID = "ble_id" CONF_IO_CAPABILITY = "io_capability" +CONF_ENABLE_ON_BOOT = "enable_on_boot" NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2] @@ -20,6 +22,10 @@ GAPEventHandler = esp32_ble_ns.class_("GAPEventHandler") GATTcEventHandler = esp32_ble_ns.class_("GATTcEventHandler") GATTsEventHandler = esp32_ble_ns.class_("GATTsEventHandler") +BLEEnabledCondition = esp32_ble_ns.class_("BLEEnabledCondition", automation.Condition) +BLEEnableAction = esp32_ble_ns.class_("BLEEnableAction", automation.Action) +BLEDisableAction = esp32_ble_ns.class_("BLEDisableAction", automation.Action) + IoCapability = esp32_ble_ns.enum("IoCapability") IO_CAPABILITY = { "none": IoCapability.IO_CAP_NONE, @@ -35,6 +41,7 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum( IO_CAPABILITY, lower=True ), + cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA) @@ -50,9 +57,25 @@ FINAL_VALIDATE_SCHEMA = validate_variant async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) + cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT])) cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY])) + await cg.register_component(var, config) if CORE.using_esp_idf: add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True) + + +@automation.register_condition("ble.enabled", BLEEnabledCondition, cv.Schema({})) +async def ble_enabled_to_code(config, condition_id, template_arg, args): + return cg.new_Pvariable(condition_id, template_arg) + + +@automation.register_action("ble.enable", BLEEnableAction, cv.Schema({})) +async def ble_enable_to_code(config, action_id, template_arg, args): + return cg.new_Pvariable(action_id, template_arg) + + +@automation.register_action("ble.disable", BLEDisableAction, cv.Schema({})) +async def ble_disable_to_code(config, action_id, template_arg, args): + return cg.new_Pvariable(action_id, template_arg) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 6c9124447a..3797f3221e 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -5,8 +5,8 @@ #include "esphome/core/log.h" #include -#include #include +#include #include #include #include @@ -26,30 +26,85 @@ void ESP32BLE::setup() { global_ble = this; ESP_LOGCONFIG(TAG, "Setting up BLE..."); - if (!ble_setup_()) { - ESP_LOGE(TAG, "BLE could not be set up"); + if (!ble_pre_setup_()) { + ESP_LOGE(TAG, "BLE could not be prepared for configuration"); this->mark_failed(); return; } -#ifdef USE_ESP32_BLE_SERVER - this->advertising_ = new BLEAdvertising(); // NOLINT(cppcoreguidelines-owning-memory) - - this->advertising_->set_scan_response(true); - this->advertising_->set_min_preferred_interval(0x06); - this->advertising_->start(); -#endif // USE_ESP32_BLE_SERVER - - ESP_LOGD(TAG, "BLE setup complete"); + this->state_ = BLE_COMPONENT_STATE_DISABLED; + if (this->enable_on_boot_) { + this->enable(); + } } -bool ESP32BLE::ble_setup_() { +void ESP32BLE::enable() { + if (this->state_ != BLE_COMPONENT_STATE_DISABLED) + return; + + this->state_ = BLE_COMPONENT_STATE_ENABLE; +} + +void ESP32BLE::disable() { + if (this->state_ == BLE_COMPONENT_STATE_DISABLED) + return; + + this->state_ = BLE_COMPONENT_STATE_DISABLE; +} + +bool ESP32BLE::is_active() { return this->state_ == BLE_COMPONENT_STATE_ACTIVE; } + +void ESP32BLE::advertising_start() { + this->advertising_init_(); + if (!this->is_active()) + return; + this->advertising_->start(); +} + +void ESP32BLE::advertising_set_service_data(const std::vector &data) { + this->advertising_init_(); + this->advertising_->set_service_data(data); + this->advertising_start(); +} + +void ESP32BLE::advertising_set_manufacturer_data(const std::vector &data) { + this->advertising_init_(); + this->advertising_->set_manufacturer_data(data); + this->advertising_start(); +} + +void ESP32BLE::advertising_add_service_uuid(ESPBTUUID uuid) { + this->advertising_init_(); + this->advertising_->add_service_uuid(uuid); + this->advertising_start(); +} + +void ESP32BLE::advertising_remove_service_uuid(ESPBTUUID uuid) { + this->advertising_init_(); + this->advertising_->remove_service_uuid(uuid); + this->advertising_start(); +} + +bool ESP32BLE::ble_pre_setup_() { esp_err_t err = nvs_flash_init(); if (err != ESP_OK) { ESP_LOGE(TAG, "nvs_flash_init failed: %d", err); return false; } + return true; +} +void ESP32BLE::advertising_init_() { + if (this->advertising_ != nullptr) + return; + this->advertising_ = new BLEAdvertising(); // NOLINT(cppcoreguidelines-owning-memory) + + this->advertising_->set_scan_response(true); + this->advertising_->set_min_preferred_interval(0x06); +} + +bool ESP32BLE::ble_setup_() { + esp_err_t err; #ifdef USE_ARDUINO if (!btStart()) { ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status()); @@ -146,7 +201,88 @@ bool ESP32BLE::ble_setup_() { return true; } +bool ESP32BLE::ble_dismantle_() { + esp_err_t err = esp_bluedroid_disable(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_bluedroid_disable failed: %d", err); + return false; + } + err = esp_bluedroid_deinit(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_bluedroid_deinit failed: %d", err); + return false; + } + +#ifdef USE_ARDUINO + if (!btStop()) { + ESP_LOGE(TAG, "btStop failed: %d", esp_bt_controller_get_status()); + return false; + } +#else + if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_IDLE) { + // stop bt controller + if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED) { + err = esp_bt_controller_disable(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_bt_controller_disable failed: %s", esp_err_to_name(err)); + return false; + } + while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED) + ; + } + if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) { + err = esp_bt_controller_deinit(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_bt_controller_deinit failed: %s", esp_err_to_name(err)); + return false; + } + } + if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_IDLE) { + ESP_LOGE(TAG, "esp bt controller disable failed"); + return false; + } + } +#endif + return true; +} + void ESP32BLE::loop() { + switch (this->state_) { + case BLE_COMPONENT_STATE_OFF: + case BLE_COMPONENT_STATE_DISABLED: + return; + case BLE_COMPONENT_STATE_DISABLE: { + ESP_LOGD(TAG, "Disabling BLE..."); + + for (auto *ble_event_handler : this->ble_status_event_handlers_) { + ble_event_handler->ble_before_disabled_event_handler(); + } + + if (!ble_dismantle_()) { + ESP_LOGE(TAG, "BLE could not be dismantled"); + this->mark_failed(); + return; + } + this->state_ = BLE_COMPONENT_STATE_DISABLED; + return; + } + case BLE_COMPONENT_STATE_ENABLE: { + ESP_LOGD(TAG, "Enabling BLE..."); + this->state_ = BLE_COMPONENT_STATE_OFF; + + if (!ble_setup_()) { + ESP_LOGE(TAG, "BLE could not be set up"); + this->mark_failed(); + return; + } + + this->state_ = BLE_COMPONENT_STATE_ACTIVE; + return; + } + case BLE_COMPONENT_STATE_ACTIVE: + break; + } + BLEEvent *ble_event = this->ble_events_.pop(); while (ble_event != nullptr) { switch (ble_event->type_) { diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index cde17da425..023960d6e4 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -1,19 +1,21 @@ #pragma once #include "ble_advertising.h" +#include "ble_uuid.h" +#include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/helpers.h" -#include "queue.h" #include "ble_event.h" +#include "queue.h" #ifdef USE_ESP32 #include -#include #include +#include namespace esphome { namespace esp32_ble { @@ -35,6 +37,19 @@ enum IoCapability { IO_CAP_KBDISP = ESP_IO_CAP_KBDISP, }; +enum BLEComponentState { + /** Nothing has been initialized yet. */ + BLE_COMPONENT_STATE_OFF = 0, + /** BLE should be disabled on next loop. */ + BLE_COMPONENT_STATE_DISABLE, + /** BLE is disabled. */ + BLE_COMPONENT_STATE_DISABLED, + /** BLE should be enabled on next loop. */ + BLE_COMPONENT_STATE_ENABLE, + /** BLE is active. */ + BLE_COMPONENT_STATE_ACTIVE, +}; + class GAPEventHandler { public: virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0; @@ -52,20 +67,36 @@ class GATTsEventHandler { esp_ble_gatts_cb_param_t *param) = 0; }; +class BLEStatusEventHandler { + public: + virtual void ble_before_disabled_event_handler() = 0; +}; + class ESP32BLE : public Component { public: void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; } + void enable(); + void disable(); + bool is_active(); void setup() override; void loop() override; void dump_config() override; float get_setup_priority() const override; - BLEAdvertising *get_advertising() { return this->advertising_; } + void advertising_start(); + void advertising_set_service_data(const std::vector &data); + void advertising_set_manufacturer_data(const std::vector &data); + void advertising_add_service_uuid(ESPBTUUID uuid); + void advertising_remove_service_uuid(ESPBTUUID uuid); void register_gap_event_handler(GAPEventHandler *handler) { this->gap_event_handlers_.push_back(handler); } void register_gattc_event_handler(GATTcEventHandler *handler) { this->gattc_event_handlers_.push_back(handler); } void register_gatts_event_handler(GATTsEventHandler *handler) { this->gatts_event_handlers_.push_back(handler); } + void register_ble_status_event_handler(BLEStatusEventHandler *handler) { + this->ble_status_event_handlers_.push_back(handler); + } + void set_enable_on_boot(bool enable_on_boot) { this->enable_on_boot_ = enable_on_boot; } protected: static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); @@ -77,19 +108,40 @@ class ESP32BLE : public Component { void real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); bool ble_setup_(); + bool ble_dismantle_(); + bool ble_pre_setup_(); + void advertising_init_(); std::vector gap_event_handlers_; std::vector gattc_event_handlers_; std::vector gatts_event_handlers_; + std::vector ble_status_event_handlers_; + BLEComponentState state_{BLE_COMPONENT_STATE_OFF}; Queue ble_events_; BLEAdvertising *advertising_; esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; + bool enable_on_boot_; }; // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) extern ESP32BLE *global_ble; +template class BLEEnabledCondition : public Condition { + public: + bool check(Ts... x) override { return global_ble->is_active(); } +}; + +template class BLEEnableAction : public Action { + public: + void play(Ts... x) override { global_ble->enable(); } +}; + +template class BLEDisableAction : public Action { + public: + void play(Ts... x) override { global_ble->disable(); } +}; + } // namespace esp32_ble } // namespace esphome diff --git a/esphome/components/esp32_ble_server/__init__.py b/esphome/components/esp32_ble_server/__init__.py index 5e1ad71501..f53c9450f4 100644 --- a/esphome/components/esp32_ble_server/__init__.py +++ b/esphome/components/esp32_ble_server/__init__.py @@ -6,7 +6,7 @@ from esphome.core import CORE from esphome.components.esp32 import add_idf_sdkconfig_option AUTO_LOAD = ["esp32_ble"] -CODEOWNERS = ["@jesserockz", "@clydebarrow"] +CODEOWNERS = ["@jesserockz", "@clydebarrow", "@Rapsssito"] CONFLICTS_WITH = ["esp32_ble_beacon"] DEPENDENCIES = ["esp32"] @@ -41,6 +41,7 @@ async def to_code(config): parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID]) cg.add(parent.register_gatts_event_handler(var)) + cg.add(parent.register_ble_status_event_handler(var)) cg.add(var.set_parent(parent)) cg.add(var.set_manufacturer(config[CONF_MANUFACTURER])) diff --git a/esphome/components/esp32_ble_server/ble_characteristic.cpp b/esphome/components/esp32_ble_server/ble_characteristic.cpp index 15a51f6ede..6ff7d615f9 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_server/ble_characteristic.cpp @@ -11,6 +11,13 @@ namespace esp32_ble_server { static const char *const TAG = "esp32_ble_server.characteristic"; +BLECharacteristic::~BLECharacteristic() { + for (auto *descriptor : this->descriptors_) { + delete descriptor; // NOLINT(cppcoreguidelines-owning-memory) + } + vSemaphoreDelete(this->set_value_lock_); +} + BLECharacteristic::BLECharacteristic(const ESPBTUUID uuid, uint32_t properties) : uuid_(uuid) { this->set_value_lock_ = xSemaphoreCreateBinary(); xSemaphoreGive(this->set_value_lock_); @@ -98,6 +105,11 @@ void BLECharacteristic::notify(bool notification) { void BLECharacteristic::add_descriptor(BLEDescriptor *descriptor) { this->descriptors_.push_back(descriptor); } +void BLECharacteristic::remove_descriptor(BLEDescriptor *descriptor) { + this->descriptors_.erase(std::remove(this->descriptors_.begin(), this->descriptors_.end(), descriptor), + this->descriptors_.end()); +} + void BLECharacteristic::do_create(BLEService *service) { this->service_ = service; esp_attr_control_t control; diff --git a/esphome/components/esp32_ble_server/ble_characteristic.h b/esphome/components/esp32_ble_server/ble_characteristic.h index d7af3a934a..8837c796a5 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.h +++ b/esphome/components/esp32_ble_server/ble_characteristic.h @@ -25,6 +25,7 @@ class BLEService; class BLECharacteristic { public: BLECharacteristic(ESPBTUUID uuid, uint32_t properties); + ~BLECharacteristic(); void set_value(const uint8_t *data, size_t length); void set_value(std::vector value); @@ -52,6 +53,7 @@ class BLECharacteristic { void on_write(const std::function &)> &&func) { this->on_write_ = func; } void add_descriptor(BLEDescriptor *descriptor); + void remove_descriptor(BLEDescriptor *descriptor); BLEService *get_service() { return this->service_; } ESPBTUUID get_uuid() { return this->uuid_; } diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index ca244aba95..338413f64e 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -30,13 +30,13 @@ void BLEServer::setup() { ESP_LOGE(TAG, "BLE Server was marked failed by ESP32BLE"); return; } - - ESP_LOGD(TAG, "Setting up BLE Server..."); - global_ble_server = this; } void BLEServer::loop() { + if (!this->parent_->is_active()) { + return; + } switch (this->state_) { case RUNNING: return; @@ -53,10 +53,16 @@ void BLEServer::loop() { } case REGISTERING: { if (this->registered_) { - this->device_information_service_ = this->create_service(DEVICE_INFORMATION_SERVICE_UUID); - - this->create_device_characteristics_(); - + // Create all services previously created + for (auto &pair : this->services_) { + pair.second->do_create(this); + } + if (this->device_information_service_ == nullptr) { + this->create_service(ESPBTUUID::from_uint16(DEVICE_INFORMATION_SERVICE_UUID)); + this->device_information_service_ = + this->get_service(ESPBTUUID::from_uint16(DEVICE_INFORMATION_SERVICE_UUID)); + this->create_device_characteristics_(); + } this->state_ = STARTING_SERVICE; } break; @@ -67,7 +73,6 @@ void BLEServer::loop() { } if (this->device_information_service_->is_running()) { this->state_ = RUNNING; - this->can_proceed_ = true; this->restart_advertising_(); ESP_LOGD(TAG, "BLE server setup successfully"); } else if (!this->device_information_service_->is_starting()) { @@ -78,10 +83,13 @@ void BLEServer::loop() { } } +bool BLEServer::is_running() { return this->parent_->is_active() && this->state_ == RUNNING; } + +bool BLEServer::can_proceed() { return this->is_running() || !this->parent_->is_active(); } + void BLEServer::restart_advertising_() { - if (this->state_ == RUNNING) { - esp32_ble::global_ble->get_advertising()->set_manufacturer_data(this->manufacturer_data_); - esp32_ble::global_ble->get_advertising()->start(); + if (this->is_running()) { + this->parent_->advertising_set_manufacturer_data(this->manufacturer_data_); } } @@ -107,24 +115,36 @@ bool BLEServer::create_device_characteristics_() { return true; } -std::shared_ptr BLEServer::create_service(const uint8_t *uuid, bool advertise) { - return this->create_service(ESPBTUUID::from_raw(uuid), advertise); -} -std::shared_ptr BLEServer::create_service(uint16_t uuid, bool advertise) { - return this->create_service(ESPBTUUID::from_uint16(uuid), advertise); -} -std::shared_ptr BLEServer::create_service(const std::string &uuid, bool advertise) { - return this->create_service(ESPBTUUID::from_raw(uuid), advertise); -} -std::shared_ptr BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t num_handles, - uint8_t inst_id) { - ESP_LOGV(TAG, "Creating service - %s", uuid.to_string().c_str()); - std::shared_ptr service = std::make_shared(uuid, num_handles, inst_id); - this->services_.emplace_back(service); - if (advertise) { - esp32_ble::global_ble->get_advertising()->add_service_uuid(uuid); +void BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t num_handles, uint8_t inst_id) { + ESP_LOGV(TAG, "Creating BLE service - %s", uuid.to_string().c_str()); + // If the service already exists, do nothing + BLEService *service = this->get_service(uuid); + if (service != nullptr) { + ESP_LOGW(TAG, "BLE service %s already exists", uuid.to_string().c_str()); + return; } + service = new BLEService(uuid, num_handles, inst_id, advertise); // NOLINT(cppcoreguidelines-owning-memory) + this->services_.emplace(uuid.to_string(), service); service->do_create(this); +} + +void BLEServer::remove_service(ESPBTUUID uuid) { + ESP_LOGV(TAG, "Removing BLE service - %s", uuid.to_string().c_str()); + BLEService *service = this->get_service(uuid); + if (service == nullptr) { + ESP_LOGW(TAG, "BLE service %s not found", uuid.to_string().c_str()); + return; + } + service->do_delete(); + delete service; // NOLINT(cppcoreguidelines-owning-memory) + this->services_.erase(uuid.to_string()); +} + +BLEService *BLEServer::get_service(ESPBTUUID uuid) { + BLEService *service = nullptr; + if (this->services_.count(uuid.to_string()) > 0) { + service = this->services_.at(uuid.to_string()); + } return service; } @@ -144,7 +164,7 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga ESP_LOGD(TAG, "BLE Client disconnected"); if (this->remove_client_(param->disconnect.conn_id)) this->connected_clients_--; - esp32_ble::global_ble->get_advertising()->start(); + this->parent_->advertising_start(); for (auto *component : this->service_components_) { component->on_client_disconnect(); } @@ -159,11 +179,22 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga break; } - for (const auto &service : this->services_) { - service->gatts_event_handler(event, gatts_if, param); + for (const auto &pair : this->services_) { + pair.second->gatts_event_handler(event, gatts_if, param); } } +void BLEServer::ble_before_disabled_event_handler() { + // Delete all clients + this->clients_.clear(); + // Delete all services + for (auto &pair : this->services_) { + pair.second->do_delete(); + } + this->registered_ = false; + this->state_ = INIT; +} + float BLEServer::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH + 10; } void BLEServer::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE Server:"); } diff --git a/esphome/components/esp32_ble_server/ble_server.h b/esphome/components/esp32_ble_server/ble_server.h index 14c88be1a2..e379e67296 100644 --- a/esphome/components/esp32_ble_server/ble_server.h +++ b/esphome/components/esp32_ble_server/ble_server.h @@ -11,9 +11,9 @@ #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" -#include #include #include +#include #ifdef USE_ESP32 @@ -33,15 +33,16 @@ class BLEServiceComponent { virtual void stop(); }; -class BLEServer : public Component, public GATTsEventHandler, public Parented { +class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEventHandler, public Parented { public: void setup() override; void loop() override; void dump_config() override; float get_setup_priority() const override; - bool can_proceed() override { return this->can_proceed_; } + bool can_proceed() override; void teardown(); + bool is_running(); void set_manufacturer(const std::string &manufacturer) { this->manufacturer_ = manufacturer; } void set_model(const std::string &model) { this->model_ = model; } @@ -50,32 +51,28 @@ class BLEServer : public Component, public GATTsEventHandler, public Parentedrestart_advertising_(); } - std::shared_ptr create_service(const uint8_t *uuid, bool advertise = false); - std::shared_ptr create_service(uint16_t uuid, bool advertise = false); - std::shared_ptr create_service(const std::string &uuid, bool advertise = false); - std::shared_ptr create_service(ESPBTUUID uuid, bool advertise = false, uint16_t num_handles = 15, - uint8_t inst_id = 0); + void create_service(ESPBTUUID uuid, bool advertise = false, uint16_t num_handles = 15, uint8_t inst_id = 0); + void remove_service(ESPBTUUID uuid); + BLEService *get_service(ESPBTUUID uuid); esp_gatt_if_t get_gatts_if() { return this->gatts_if_; } uint32_t get_connected_client_count() { return this->connected_clients_; } - const std::map &get_clients() { return this->clients_; } + const std::unordered_map &get_clients() { return this->clients_; } void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) override; + void ble_before_disabled_event_handler() override; + void register_service_component(BLEServiceComponent *component) { this->service_components_.push_back(component); } protected: bool create_device_characteristics_(); void restart_advertising_(); - void add_client_(uint16_t conn_id, void *client) { - this->clients_.insert(std::pair(conn_id, client)); - } + void add_client_(uint16_t conn_id, void *client) { this->clients_.emplace(conn_id, client); } bool remove_client_(uint16_t conn_id) { return this->clients_.erase(conn_id) > 0; } - bool can_proceed_{false}; - std::string manufacturer_; optional model_; std::vector manufacturer_data_; @@ -83,10 +80,9 @@ class BLEServer : public Component, public GATTsEventHandler, public Parented clients_; - - std::vector> services_; - std::shared_ptr device_information_service_; + std::unordered_map clients_; + std::unordered_map services_; + BLEService *device_information_service_; std::vector service_components_; diff --git a/esphome/components/esp32_ble_server/ble_service.cpp b/esphome/components/esp32_ble_server/ble_service.cpp index e5aaebc137..368f03fb52 100644 --- a/esphome/components/esp32_ble_server/ble_service.cpp +++ b/esphome/components/esp32_ble_server/ble_service.cpp @@ -9,8 +9,8 @@ namespace esp32_ble_server { static const char *const TAG = "esp32_ble_server.service"; -BLEService::BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id) - : uuid_(uuid), num_handles_(num_handles), inst_id_(inst_id) {} +BLEService::BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id, bool advertise) + : uuid_(uuid), num_handles_(num_handles), inst_id_(inst_id), advertise_(advertise) {} BLEService::~BLEService() { for (auto &chr : this->characteristics_) @@ -58,6 +58,20 @@ void BLEService::do_create(BLEServer *server) { this->init_state_ = CREATING; } +void BLEService::do_delete() { + if (this->init_state_ == DELETING || this->init_state_ == DELETED) + return; + this->init_state_ = DELETING; + this->created_characteristic_count_ = 0; + this->last_created_characteristic_ = nullptr; + this->stop_(); + esp_err_t err = esp_ble_gatts_delete_service(this->handle_); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gatts_delete_service failed: %d", err); + return; + } +} + bool BLEService::do_create_characteristics_() { if (this->created_characteristic_count_ >= this->characteristics_.size() && (this->last_created_characteristic_ == nullptr || this->last_created_characteristic_->is_created())) @@ -75,24 +89,34 @@ bool BLEService::do_create_characteristics_() { void BLEService::start() { if (this->do_create_characteristics_()) return; + should_start_ = true; esp_err_t err = esp_ble_gatts_start_service(this->handle_); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ble_gatts_start_service failed: %d", err); return; } + if (this->advertise_) + esp32_ble::global_ble->advertising_add_service_uuid(this->uuid_); this->running_state_ = STARTING; } void BLEService::stop() { + should_start_ = false; + this->stop_(); +} + +void BLEService::stop_() { + if (this->running_state_ == STOPPING || this->running_state_ == STOPPED) + return; + this->running_state_ = STOPPING; esp_err_t err = esp_ble_gatts_stop_service(this->handle_); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ble_gatts_stop_service failed: %d", err); return; } - esp32_ble::global_ble->get_advertising()->remove_service_uuid(this->uuid_); - esp32_ble::global_ble->get_advertising()->start(); - this->running_state_ = STOPPING; + if (this->advertise_) + esp32_ble::global_ble->advertising_remove_service_uuid(this->uuid_); } bool BLEService::is_created() { return this->init_state_ == CREATED; } @@ -116,9 +140,16 @@ void BLEService::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t g this->inst_id_ == param->create.service_id.id.inst_id) { this->handle_ = param->create.service_handle; this->init_state_ = CREATED; + if (this->should_start_) + this->start(); } break; } + case ESP_GATTS_DELETE_EVT: + if (param->del.service_handle == this->handle_) { + this->init_state_ = DELETED; + } + break; case ESP_GATTS_START_EVT: { if (param->start.service_handle == this->handle_) { this->running_state_ = RUNNING; diff --git a/esphome/components/esp32_ble_server/ble_service.h b/esphome/components/esp32_ble_server/ble_service.h index 93b4217517..5e5883b6bf 100644 --- a/esphome/components/esp32_ble_server/ble_service.h +++ b/esphome/components/esp32_ble_server/ble_service.h @@ -22,7 +22,7 @@ using namespace esp32_ble; class BLEService { public: - BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id); + BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id, bool advertise); ~BLEService(); BLECharacteristic *get_characteristic(ESPBTUUID uuid); BLECharacteristic *get_characteristic(uint16_t uuid); @@ -38,6 +38,7 @@ class BLEService { BLEServer *get_server() { return this->server_; } void do_create(BLEServer *server); + 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 start(); @@ -48,6 +49,7 @@ class BLEService { bool is_running() { return this->running_state_ == RUNNING; } bool is_starting() { return this->running_state_ == STARTING; } + bool is_deleted() { return this->init_state_ == DELETED; } protected: std::vector characteristics_; @@ -58,8 +60,11 @@ class BLEService { uint16_t num_handles_; uint16_t handle_{0xFFFF}; uint8_t inst_id_; + bool advertise_{false}; + bool should_start_{false}; bool do_create_characteristics_(); + void stop_(); enum InitState : uint8_t { FAILED = 0x00, @@ -67,6 +72,8 @@ class BLEService { CREATING, CREATING_DEPENDENTS, CREATED, + DELETING, + DELETED, } init_state_{INIT}; enum RunningState : uint8_t { diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 8ba77c7db7..2ead59c025 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -212,6 +212,7 @@ async def to_code(config): parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID]) cg.add(parent.register_gap_event_handler(var)) cg.add(parent.register_gattc_event_handler(var)) + cg.add(parent.register_ble_status_event_handler(var)) cg.add(var.set_parent(parent)) params = config[CONF_SCAN_PARAMETERS] diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index f67f29477d..a5bbd85b47 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -64,17 +64,19 @@ void ESP32BLETracker::setup() { } }); #endif - - if (this->scan_continuous_) { - if (xSemaphoreTake(this->scan_end_lock_, 0L)) { - this->start_scan_(true); - } else { - ESP_LOGW(TAG, "Cannot start scan!"); - } - } } void ESP32BLETracker::loop() { + if (!this->parent_->is_active()) { + this->ble_was_disabled_ = true; + return; + } else if (this->ble_was_disabled_) { + this->ble_was_disabled_ = false; + // If the BLE stack was disabled, we need to start the scan again. + if (this->scan_continuous_) { + this->start_scan(); + } + } int connecting = 0; int discovered = 0; int searching = 0; @@ -182,8 +184,7 @@ void ESP32BLETracker::loop() { xSemaphoreGive(this->scan_end_lock_); } else { ESP_LOGD(TAG, "Stopping scan after failure..."); - esp_ble_gap_stop_scanning(); - this->cancel_timeout("scan"); + this->stop_scan_(); } if (this->scan_start_failed_) { ESP_LOGE(TAG, "Scan start failed: %d", this->scan_start_failed_); @@ -212,8 +213,7 @@ void ESP32BLETracker::loop() { client->set_state(ClientState::READY_TO_CONNECT); } else { ESP_LOGD(TAG, "Pausing scan to make connection..."); - esp_ble_gap_stop_scanning(); - this->cancel_timeout("scan"); + this->stop_scan_(); } break; } @@ -232,11 +232,31 @@ void ESP32BLETracker::start_scan() { void ESP32BLETracker::stop_scan() { ESP_LOGD(TAG, "Stopping scan."); this->scan_continuous_ = false; - esp_ble_gap_stop_scanning(); + this->stop_scan_(); +} + +void ESP32BLETracker::ble_before_disabled_event_handler() { + this->stop_scan_(); + xSemaphoreGive(this->scan_end_lock_); +} + +void ESP32BLETracker::stop_scan_() { this->cancel_timeout("scan"); + if (this->scanner_idle_) { + return; + } + esp_err_t err = esp_ble_gap_stop_scanning(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_stop_scanning failed: %d", err); + return; + } } void ESP32BLETracker::start_scan_(bool first) { + if (!this->parent_->is_active()) { + ESP_LOGW(TAG, "Cannot start scan while ESP32BLE is disabled."); + return; + } // The lock must be held when calling this function. if (xSemaphoreTake(this->scan_end_lock_, 0L)) { ESP_LOGE(TAG, "start_scan called without holding scan_end_lock_"); @@ -249,15 +269,23 @@ void ESP32BLETracker::start_scan_(bool first) { listener->on_scan_end(); } this->already_discovered_.clear(); - this->scanner_idle_ = false; this->scan_params_.scan_type = this->scan_active_ ? BLE_SCAN_TYPE_ACTIVE : BLE_SCAN_TYPE_PASSIVE; this->scan_params_.own_addr_type = BLE_ADDR_TYPE_PUBLIC; this->scan_params_.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL; this->scan_params_.scan_interval = this->scan_interval_; this->scan_params_.scan_window = this->scan_window_; - esp_ble_gap_set_scan_params(&this->scan_params_); - esp_ble_gap_start_scanning(this->scan_duration_); + esp_err_t err = esp_ble_gap_set_scan_params(&this->scan_params_); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_set_scan_params failed: %d", err); + return; + } + err = esp_ble_gap_start_scanning(this->scan_duration_); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_start_scanning failed: %d", err); + return; + } + this->scanner_idle_ = false; this->set_timeout("scan", this->scan_duration_ * 2000, []() { ESP_LOGE(TAG, "ESP-IDF BLE scan never terminated, rebooting to restore BLE stack..."); diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 6efdded3ff..0d986804ce 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -177,7 +177,11 @@ class ESPBTClient : public ESPBTDeviceListener { ClientState state_; }; -class ESP32BLETracker : public Component, public GAPEventHandler, public GATTcEventHandler, public Parented { +class ESP32BLETracker : public Component, + public GAPEventHandler, + public GATTcEventHandler, + public BLEStatusEventHandler, + public Parented { public: void set_scan_duration(uint32_t scan_duration) { scan_duration_ = scan_duration; } void set_scan_interval(uint32_t scan_interval) { scan_interval_ = scan_interval; } @@ -204,8 +208,10 @@ class ESP32BLETracker : public Component, public GAPEventHandler, public GATTcEv void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override; void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; + void ble_before_disabled_event_handler() override; protected: + void stop_scan_(); /// Start a single scan by setting up the parameters and doing some esp-idf calls. void start_scan_(bool first); /// Called when a scan ends @@ -236,6 +242,7 @@ class ESP32BLETracker : public Component, public GAPEventHandler, public GATTcEv bool scan_continuous_; bool scan_active_; bool scanner_idle_; + bool ble_was_disabled_{true}; bool raw_advertisements_{false}; bool parse_advertisements_{false}; SemaphoreHandle_t scan_result_lock_; diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 19340c3dd8..90e69e1cfa 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -16,9 +16,6 @@ static const char *const ESPHOME_MY_LINK = "https://my.home-assistant.io/redirec ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; } void ESP32ImprovComponent::setup() { - this->service_ = global_ble_server->create_service(improv::SERVICE_UUID, true); - this->setup_characteristics(); - #ifdef USE_BINARY_SENSOR if (this->authorizer_ != nullptr) { this->authorizer_->add_on_state_callback([this](bool state) { @@ -70,6 +67,19 @@ void ESP32ImprovComponent::setup_characteristics() { } void ESP32ImprovComponent::loop() { + if (!global_ble_server->is_running()) { + this->state_ = improv::STATE_STOPPED; + this->incoming_data_.clear(); + return; + } + if (this->service_ == nullptr) { + // Setup the service + ESP_LOGD(TAG, "Creating Improv service"); + global_ble_server->create_service(ESPBTUUID::from_raw(improv::SERVICE_UUID), true); + this->service_ = global_ble_server->get_service(ESPBTUUID::from_raw(improv::SERVICE_UUID)); + this->setup_characteristics(); + } + if (!this->incoming_data_.empty()) this->process_incoming_data_(); uint32_t now = millis(); @@ -80,11 +90,10 @@ void ESP32ImprovComponent::loop() { if (this->service_->is_created() && this->should_start_ && this->setup_complete_) { if (this->service_->is_running()) { - esp32_ble::global_ble->get_advertising()->start(); + esp32_ble::global_ble->advertising_start(); this->set_state_(improv::STATE_AWAITING_AUTHORIZATION); this->set_error_(improv::ERROR_NONE); - this->should_start_ = false; ESP_LOGD(TAG, "Service started!"); } else { this->service_->start(); @@ -138,10 +147,7 @@ void ESP32ImprovComponent::loop() { #endif std::vector data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls); this->send_response_(data); - this->set_timeout("end-service", 1000, [this] { - this->service_->stop(); - this->set_state_(improv::STATE_STOPPED); - }); + this->stop(); } break; } @@ -206,8 +212,7 @@ void ESP32ImprovComponent::set_state_(improv::State state) { service_data[6] = 0x00; // Reserved service_data[7] = 0x00; // Reserved - esp32_ble::global_ble->get_advertising()->set_service_data(service_data); - esp32_ble::global_ble->get_advertising()->start(); + esp32_ble::global_ble->advertising_set_service_data(service_data); } void ESP32ImprovComponent::set_error_(improv::Error error) { @@ -237,7 +242,10 @@ void ESP32ImprovComponent::start() { } void ESP32ImprovComponent::stop() { + this->should_start_ = false; this->set_timeout("end-service", 1000, [this] { + if (this->state_ == improv::STATE_STOPPED || this->service_ == nullptr) + return; this->service_->stop(); this->set_state_(improv::STATE_STOPPED); }); diff --git a/esphome/components/esp32_improv/esp32_improv_component.h b/esphome/components/esp32_improv/esp32_improv_component.h index 00c6cf885a..3ed377a6ad 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.h +++ b/esphome/components/esp32_improv/esp32_improv_component.h @@ -68,7 +68,7 @@ class ESP32ImprovComponent : public Component, public BLEServiceComponent { std::vector incoming_data_; wifi::WiFiAP connecting_sta_; - std::shared_ptr service_; + BLEService *service_ = nullptr; BLECharacteristic *status_; BLECharacteristic *error_; BLECharacteristic *rpc_; diff --git a/tests/test11.5.yaml b/tests/test11.5.yaml index 06985611e7..9b25b9f273 100644 --- a/tests/test11.5.yaml +++ b/tests/test11.5.yaml @@ -104,6 +104,12 @@ binary_sensor: on_release: then: - switch.turn_off: Led0 + - if: + condition: ble.enabled + then: + - ble.disable: + else: + - ble.enable: - platform: tm1638 id: Button1 @@ -273,6 +279,7 @@ output: demo: esp32_ble: + enable_on_boot: false esp32_ble_server: manufacturer: ESPHome