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:
Jonathan V 2022-09-13 18:10:12 -05:00 committed by GitHub
parent 15f0e54cbf
commit 49465223a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 112 additions and 3 deletions

View file

@ -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))

View file

@ -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

View file

@ -46,14 +46,16 @@ 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() {
BLEEvent *ble_event = this->ble_events_.pop(); BLEEvent *ble_event = this->ble_events_.pop();
@ -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_) {

View file

@ -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