mirror of
https://github.com/esphome/esphome.git
synced 2024-11-22 23:18:10 +01: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:
parent
15f0e54cbf
commit
49465223a4
4 changed files with 112 additions and 3 deletions
|
@ -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,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_) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue