mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	esp32_ble_tracker continuous and one shot scanning modes (#3649)
Co-authored-by: Jonathan Valdez <@jonofmac> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
		| @@ -23,6 +23,8 @@ CONF_ESP32_BLE_ID = "esp32_ble_id" | |||||||
| CONF_SCAN_PARAMETERS = "scan_parameters" | CONF_SCAN_PARAMETERS = "scan_parameters" | ||||||
| CONF_WINDOW = "window" | CONF_WINDOW = "window" | ||||||
| CONF_ACTIVE = "active" | CONF_ACTIVE = "active" | ||||||
|  | CONF_CONTINUOUS = "continuous" | ||||||
|  | CONF_ON_SCAN_END = "on_scan_end" | ||||||
| esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker") | esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker") | ||||||
| ESP32BLETracker = esp32_ble_tracker_ns.class_("ESP32BLETracker", cg.Component) | ESP32BLETracker = esp32_ble_tracker_ns.class_("ESP32BLETracker", cg.Component) | ||||||
| ESPBTClient = esp32_ble_tracker_ns.class_("ESPBTClient") | ESPBTClient = esp32_ble_tracker_ns.class_("ESPBTClient") | ||||||
| @@ -42,6 +44,13 @@ BLEManufacturerDataAdvertiseTrigger = esp32_ble_tracker_ns.class_( | |||||||
|     "BLEManufacturerDataAdvertiseTrigger", |     "BLEManufacturerDataAdvertiseTrigger", | ||||||
|     automation.Trigger.template(adv_data_t_const_ref), |     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): | def validate_scan_parameters(config): | ||||||
| @@ -138,6 +147,7 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|                         CONF_WINDOW, default="30ms" |                         CONF_WINDOW, default="30ms" | ||||||
|                     ): cv.positive_time_period_milliseconds, |                     ): cv.positive_time_period_milliseconds, | ||||||
|                     cv.Optional(CONF_ACTIVE, default=True): cv.boolean, |                     cv.Optional(CONF_ACTIVE, default=True): cv.boolean, | ||||||
|  |                     cv.Optional(CONF_CONTINUOUS, default=True): cv.boolean, | ||||||
|                 } |                 } | ||||||
|             ), |             ), | ||||||
|             validate_scan_parameters, |             validate_scan_parameters, | ||||||
| @@ -168,6 +178,9 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|                 cv.Required(CONF_MANUFACTURER_ID): bt_uuid, |                 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) | ).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_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_window(int(params[CONF_WINDOW].total_milliseconds / 0.625))) | ||||||
|     cg.add(var.set_scan_active(params[CONF_ACTIVE])) |     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, []): |     for conf in config.get(CONF_ON_BLE_ADVERTISE, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|         if CONF_MAC_ADDRESS in conf: |         if CONF_MAC_ADDRESS in conf: | ||||||
| @@ -215,11 +229,36 @@ async def to_code(config): | |||||||
|         if CONF_MAC_ADDRESS in conf: |         if CONF_MAC_ADDRESS in conf: | ||||||
|             cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex)) |             cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex)) | ||||||
|         await automation.build_automation(trigger, [(adv_data_t_const_ref, "x")], conf) |         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: |     if CORE.using_esp_idf: | ||||||
|         add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) |         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): | async def register_ble_device(var, config): | ||||||
|     paren = await cg.get_variable(config[CONF_ESP32_BLE_ID]) |     paren = await cg.get_variable(config[CONF_ESP32_BLE_ID]) | ||||||
|     cg.add(paren.register_listener(var)) |     cg.add(paren.register_listener(var)) | ||||||
|   | |||||||
| @@ -76,6 +76,14 @@ class BLEManufacturerDataAdvertiseTrigger : public Trigger<const adv_data_t &>, | |||||||
|   ESPBTUUID uuid_; |   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 esp32_ble_tracker | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
|   | |||||||
| @@ -46,13 +46,15 @@ void ESP32BLETracker::setup() { | |||||||
|   global_esp32_ble_tracker = this; |   global_esp32_ble_tracker = this; | ||||||
|   this->scan_result_lock_ = xSemaphoreCreateMutex(); |   this->scan_result_lock_ = xSemaphoreCreateMutex(); | ||||||
|   this->scan_end_lock_ = xSemaphoreCreateMutex(); |   this->scan_end_lock_ = xSemaphoreCreateMutex(); | ||||||
|  |   this->scanner_idle_ = true; | ||||||
|   if (!ESP32BLETracker::ble_setup()) { |   if (!ESP32BLETracker::ble_setup()) { | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   if (this->scan_continuous_) { | ||||||
|     global_esp32_ble_tracker->start_scan_(true); |     global_esp32_ble_tracker->start_scan_(true); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void ESP32BLETracker::loop() { | void ESP32BLETracker::loop() { | ||||||
| @@ -68,14 +70,25 @@ void ESP32BLETracker::loop() { | |||||||
|     ble_event = this->ble_events_.pop(); |     ble_event = this->ble_events_.pop(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   if (this->scanner_idle_) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   bool connecting = false; |   bool connecting = false; | ||||||
|   for (auto *client : this->clients_) { |   for (auto *client : this->clients_) { | ||||||
|     if (client->state() == ClientState::CONNECTING || client->state() == ClientState::DISCOVERED) |     if (client->state() == ClientState::CONNECTING || client->state() == ClientState::DISCOVERED) | ||||||
|       connecting = true; |       connecting = true; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (!connecting && xSemaphoreTake(this->scan_end_lock_, 0L)) { |   if (!connecting && xSemaphoreTake(this->scan_end_lock_, 0L)) { | ||||||
|     xSemaphoreGive(this->scan_end_lock_); |     xSemaphoreGive(this->scan_end_lock_); | ||||||
|  |     if (this->scan_continuous_) { | ||||||
|       global_esp32_ble_tracker->start_scan_(false); |       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)) { |   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() { | bool ESP32BLETracker::ble_setup() { | ||||||
|   // Initialize non-volatile storage for the bluetooth controller |   // Initialize non-volatile storage for the bluetooth controller | ||||||
|   esp_err_t err = nvs_flash_init(); |   esp_err_t err = nvs_flash_init(); | ||||||
| @@ -225,6 +247,7 @@ void ESP32BLETracker::start_scan_(bool first) { | |||||||
|       listener->on_scan_end(); |       listener->on_scan_end(); | ||||||
|   } |   } | ||||||
|   this->already_discovered_.clear(); |   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_.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_.own_addr_type = BLE_ADDR_TYPE_PUBLIC; | ||||||
|   this->scan_params_.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL; |   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) { | void ESP32BLETracker::register_client(ESPBTClient *client) { | ||||||
|   client->app_id = ++this->app_id_; |   client->app_id = ++this->app_id_; | ||||||
|   this->clients_.push_back(client); |   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 Interval: %.1f ms", this->scan_interval_ * 0.625f); | ||||||
|   ESP_LOGCONFIG(TAG, "  Scan Window: %.1f ms", this->scan_window_ * 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, "  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) { | void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) { | ||||||
|   const uint64_t address = device.address_uint64(); |   const uint64_t address = device.address_uint64(); | ||||||
|   for (auto &disc : this->already_discovered_) { |   for (auto &disc : this->already_discovered_) { | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/automation.h" | ||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
| #include "queue.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_interval(uint32_t scan_interval) { scan_interval_ = scan_interval; } | ||||||
|   void set_scan_window(uint32_t scan_window) { scan_window_ = scan_window; } |   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_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. |   /// Setup the FreeRTOS task and the Bluetooth stack. | ||||||
|   void setup() override; |   void setup() override; | ||||||
| @@ -188,11 +190,15 @@ class ESP32BLETracker : public Component { | |||||||
|  |  | ||||||
|   void print_bt_device_info(const ESPBTDevice &device); |   void print_bt_device_info(const ESPBTDevice &device); | ||||||
|  |  | ||||||
|  |   void start_scan(); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   /// The FreeRTOS task managing the bluetooth interface. |   /// The FreeRTOS task managing the bluetooth interface. | ||||||
|   static bool ble_setup(); |   static bool ble_setup(); | ||||||
|   /// Start a single scan by setting up the parameters and doing some esp-idf calls. |   /// Start a single scan by setting up the parameters and doing some esp-idf calls. | ||||||
|   void start_scan_(bool first); |   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. |   /// 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); |   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); |   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_duration_; | ||||||
|   uint32_t scan_interval_; |   uint32_t scan_interval_; | ||||||
|   uint32_t scan_window_; |   uint32_t scan_window_; | ||||||
|  |   bool scan_continuous_; | ||||||
|   bool scan_active_; |   bool scan_active_; | ||||||
|  |   bool scanner_idle_; | ||||||
|   SemaphoreHandle_t scan_result_lock_; |   SemaphoreHandle_t scan_result_lock_; | ||||||
|   SemaphoreHandle_t scan_end_lock_; |   SemaphoreHandle_t scan_end_lock_; | ||||||
|   size_t scan_result_index_{0}; |   size_t scan_result_index_{0}; | ||||||
| @@ -235,6 +243,19 @@ class ESP32BLETracker : public Component { | |||||||
| // NOLINTNEXTLINE | // NOLINTNEXTLINE | ||||||
| extern ESP32BLETracker *global_esp32_ble_tracker; | extern ESP32BLETracker *global_esp32_ble_tracker; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class ESP32BLEStartScanAction : public Action<Ts...> { | ||||||
|  |  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 esp32_ble_tracker | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user