diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index e647b74a8f..db7fcc68ae 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -23,6 +23,8 @@ CONF_ESP32_BLE_ID = "esp32_ble_id" CONF_SCAN_PARAMETERS = "scan_parameters" CONF_WINDOW = "window" CONF_ACTIVE = "active" +CONF_CONTINUOUS = "continuous" +CONF_ON_SCAN_END = "on_scan_end" esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker") ESP32BLETracker = esp32_ble_tracker_ns.class_("ESP32BLETracker", cg.Component) ESPBTClient = esp32_ble_tracker_ns.class_("ESPBTClient") @@ -42,6 +44,13 @@ BLEManufacturerDataAdvertiseTrigger = esp32_ble_tracker_ns.class_( "BLEManufacturerDataAdvertiseTrigger", automation.Trigger.template(adv_data_t_const_ref), ) +BLEEndOfScanTrigger = esp32_ble_tracker_ns.class_( + "BLEEndOfScanTrigger", automation.Trigger.template() +) +# Actions +ESP32BLEStartScanAction = esp32_ble_tracker_ns.class_( + "ESP32BLEStartScanAction", automation.Action +) def validate_scan_parameters(config): @@ -138,6 +147,7 @@ CONFIG_SCHEMA = cv.Schema( CONF_WINDOW, default="30ms" ): cv.positive_time_period_milliseconds, cv.Optional(CONF_ACTIVE, default=True): cv.boolean, + cv.Optional(CONF_CONTINUOUS, default=True): cv.boolean, } ), validate_scan_parameters, @@ -168,6 +178,9 @@ CONFIG_SCHEMA = cv.Schema( cv.Required(CONF_MANUFACTURER_ID): bt_uuid, } ), + cv.Optional(CONF_ON_SCAN_END): automation.validate_automation( + {cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BLEEndOfScanTrigger)} + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -186,6 +199,7 @@ async def to_code(config): cg.add(var.set_scan_interval(int(params[CONF_INTERVAL].total_milliseconds / 0.625))) cg.add(var.set_scan_window(int(params[CONF_WINDOW].total_milliseconds / 0.625))) cg.add(var.set_scan_active(params[CONF_ACTIVE])) + cg.add(var.set_scan_continuous(params[CONF_CONTINUOUS])) for conf in config.get(CONF_ON_BLE_ADVERTISE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) if CONF_MAC_ADDRESS in conf: @@ -215,11 +229,36 @@ async def to_code(config): if CONF_MAC_ADDRESS in conf: cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex)) await automation.build_automation(trigger, [(adv_data_t_const_ref, "x")], conf) + for conf in config.get(CONF_ON_SCAN_END, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) if CORE.using_esp_idf: add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) +ESP32_BLE_START_SCAN_ACTION_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(ESP32BLETracker), + cv.Optional(CONF_CONTINUOUS, default=False): cv.templatable(cv.boolean), + } +) + + +@automation.register_action( + "esp32_ble_tracker.start_scan", + ESP32BLEStartScanAction, + ESP32_BLE_START_SCAN_ACTION_SCHEMA, +) +async def esp32_ble_tracker_start_scan_action_to_code( + config, action_id, template_arg, args +): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + cg.add(var.set_continuous(config[CONF_CONTINUOUS])) + return var + + async def register_ble_device(var, config): paren = await cg.get_variable(config[CONF_ESP32_BLE_ID]) cg.add(paren.register_listener(var)) diff --git a/esphome/components/esp32_ble_tracker/automation.h b/esphome/components/esp32_ble_tracker/automation.h index 3505e9c26d..d9e3b820af 100644 --- a/esphome/components/esp32_ble_tracker/automation.h +++ b/esphome/components/esp32_ble_tracker/automation.h @@ -76,6 +76,14 @@ class BLEManufacturerDataAdvertiseTrigger : public Trigger, ESPBTUUID uuid_; }; +class BLEEndOfScanTrigger : public Trigger<>, public ESPBTDeviceListener { + public: + explicit BLEEndOfScanTrigger(ESP32BLETracker *parent) { parent->register_listener(this); } + + bool parse_device(const ESPBTDevice &device) override { return false; } + void on_scan_end() override { this->trigger(); } +}; + } // namespace esp32_ble_tracker } // namespace esphome diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 82945dc771..743f246d3a 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -46,13 +46,15 @@ void ESP32BLETracker::setup() { global_esp32_ble_tracker = this; this->scan_result_lock_ = xSemaphoreCreateMutex(); this->scan_end_lock_ = xSemaphoreCreateMutex(); - + this->scanner_idle_ = true; if (!ESP32BLETracker::ble_setup()) { this->mark_failed(); return; } - global_esp32_ble_tracker->start_scan_(true); + if (this->scan_continuous_) { + global_esp32_ble_tracker->start_scan_(true); + } } void ESP32BLETracker::loop() { @@ -68,14 +70,25 @@ void ESP32BLETracker::loop() { ble_event = this->ble_events_.pop(); } + if (this->scanner_idle_) { + return; + } + bool connecting = false; for (auto *client : this->clients_) { if (client->state() == ClientState::CONNECTING || client->state() == ClientState::DISCOVERED) connecting = true; } + if (!connecting && xSemaphoreTake(this->scan_end_lock_, 0L)) { xSemaphoreGive(this->scan_end_lock_); - global_esp32_ble_tracker->start_scan_(false); + if (this->scan_continuous_) { + global_esp32_ble_tracker->start_scan_(false); + } else if (xSemaphoreTake(this->scan_end_lock_, 0L) && !this->scanner_idle_) { + xSemaphoreGive(this->scan_end_lock_); + global_esp32_ble_tracker->end_of_scan_(); + return; + } } if (xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) { @@ -134,6 +147,15 @@ void ESP32BLETracker::loop() { } } +void ESP32BLETracker::start_scan() { + if (xSemaphoreTake(this->scan_end_lock_, 0L)) { + xSemaphoreGive(this->scan_end_lock_); + global_esp32_ble_tracker->start_scan_(true); + } else { + ESP_LOGW(TAG, "Scan requested when a scan is already in progress. Ignoring."); + } +} + bool ESP32BLETracker::ble_setup() { // Initialize non-volatile storage for the bluetooth controller esp_err_t err = nvs_flash_init(); @@ -225,6 +247,7 @@ 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; @@ -240,6 +263,22 @@ void ESP32BLETracker::start_scan_(bool first) { }); } +void ESP32BLETracker::end_of_scan_() { + if (!xSemaphoreTake(this->scan_end_lock_, 0L)) { + ESP_LOGW(TAG, "Cannot clean up end of scan!"); + return; + } + + ESP_LOGD(TAG, "End of scan."); + this->scanner_idle_ = true; + this->already_discovered_.clear(); + xSemaphoreGive(this->scan_end_lock_); + this->cancel_timeout("scan"); + + for (auto *listener : this->listeners_) + listener->on_scan_end(); +} + void ESP32BLETracker::register_client(ESPBTClient *client) { client->app_id = ++this->app_id_; this->clients_.push_back(client); @@ -719,7 +758,9 @@ void ESP32BLETracker::dump_config() { ESP_LOGCONFIG(TAG, " Scan Interval: %.1f ms", this->scan_interval_ * 0.625f); ESP_LOGCONFIG(TAG, " Scan Window: %.1f ms", this->scan_window_ * 0.625f); ESP_LOGCONFIG(TAG, " Scan Type: %s", this->scan_active_ ? "ACTIVE" : "PASSIVE"); + ESP_LOGCONFIG(TAG, " Continuous Scanning: %s", this->scan_continuous_ ? "True" : "False"); } + void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) { const uint64_t address = device.address_uint64(); for (auto &disc : this->already_discovered_) { diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 62fff30a20..1d2900af69 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/automation.h" #include "esphome/core/helpers.h" #include "queue.h" @@ -171,6 +172,7 @@ class ESP32BLETracker : public Component { void set_scan_interval(uint32_t scan_interval) { scan_interval_ = scan_interval; } void set_scan_window(uint32_t scan_window) { scan_window_ = scan_window; } void set_scan_active(bool scan_active) { scan_active_ = scan_active; } + void set_scan_continuous(bool scan_continuous) { scan_continuous_ = scan_continuous; } /// Setup the FreeRTOS task and the Bluetooth stack. void setup() override; @@ -188,11 +190,15 @@ class ESP32BLETracker : public Component { void print_bt_device_info(const ESPBTDevice &device); + void start_scan(); + protected: /// The FreeRTOS task managing the bluetooth interface. static bool ble_setup(); /// 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 + void end_of_scan_(); /// Callback that will handle all GAP events and redistribute them to other callbacks. static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); void real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); @@ -221,7 +227,9 @@ class ESP32BLETracker : public Component { uint32_t scan_duration_; uint32_t scan_interval_; uint32_t scan_window_; + bool scan_continuous_; bool scan_active_; + bool scanner_idle_; SemaphoreHandle_t scan_result_lock_; SemaphoreHandle_t scan_end_lock_; size_t scan_result_index_{0}; @@ -235,6 +243,19 @@ class ESP32BLETracker : public Component { // NOLINTNEXTLINE extern ESP32BLETracker *global_esp32_ble_tracker; +template class ESP32BLEStartScanAction : public Action { + public: + ESP32BLEStartScanAction(ESP32BLETracker *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(bool, continuous) + void play(Ts... x) override { + this->parent_->set_scan_continuous(this->continuous_.value(x...)); + this->parent_->start_scan(); + } + + protected: + ESP32BLETracker *parent_; +}; + } // namespace esp32_ble_tracker } // namespace esphome