Pending BT Classic changes

(Working changes prior to rebase / merge upstream)
This commit is contained in:
RoboMagus 2024-11-16 16:05:09 +01:00
parent f1ea245928
commit ee89c32188
9 changed files with 166 additions and 26 deletions

View file

@ -15,8 +15,9 @@ void BTClassicPresenceDevice::update() {
parent()->addScan(esp32_bt_classic::bt_scan_item(u64_addr, num_scans));
}
void BTClassicPresenceDevice::on_scan_result(const esp32_bt_classic::rmt_name_result &result) {
void BTClassicPresenceDevice::on_scan_result(const esp32_bt_classic::rmt_name_result &result, const optional<esp32_bt_classic::bt_scan_item>& scan_item) {
const uint64_t result_addr = esp32_bt_classic::bd_addr_to_uint64(result.bda);
// ToDo: Use provided scan_item scans_remaining!!
if (result_addr == u64_addr) {
if (ESP_BT_STATUS_SUCCESS == result.stat) {
this->publish_state(true);

View file

@ -24,7 +24,7 @@ class BTClassicPresenceDevice : public PollingComponent,
void dump_config() override;
void update() override;
void on_scan_result(const esp32_bt_classic::rmt_name_result &result) override;
void on_scan_result(const esp32_bt_classic::rmt_name_result &result, const optional<esp32_bt_classic::bt_scan_item>& scan_item) override;
protected:
uint8_t scans_remaining{0};

View file

@ -8,9 +8,11 @@ from esphome.const import (
CONF_MAC_ADDRESS,
CONF_NUM_SCANS,
CONF_TRIGGER_ID,
ENTITY_CATEGORY_DIAGNOSTIC,
)
from esphome.core import CORE
from esphome import automation
from esphome.components import text_sensor
from esphome.components.esp32 import (
add_idf_sdkconfig_option,
get_esp32_variant,
@ -20,19 +22,25 @@ from esphome.components.esp32 import (
from .const import (
CONF_ON_SCAN_START,
CONF_ON_SCAN_RESULT,
CONF_LAST_ERROR,
)
_LOGGER = logging.getLogger(__name__)
AUTO_LOAD = ["esp32_bt_common"]
AUTO_LOAD = ["esp32_bt_common", "text_sensor"]
DEPENDENCIES = ["esp32"]
CODEOWNERS = ["@RoboMagus"]
NO_BLUETOOTH_VARIANTS = [esp32_const.VARIANT_ESP32S2]
MIN_IDF_VERSION = cv.Version(4, 4, 4)
# Required for support where BT scans report MAC for scan-result
#MIN_IDF_VERSION = cv.Version(4, 4, 4)
MIN_ARDUINO_VERSION = cv.Version(2, 0, 5)
# IDF V5+ seems to fix 'scans not working after a while...'
MIN_IDF_VERSION = cv.Version(5, 1, 0)
#MIN_ARDUINO_VERSION = cv.Version(3, 0, 0)
esp32_bt_classic_ns = cg.esphome_ns.namespace("esp32_bt_classic")
ESP32BtClassic = esp32_bt_classic_ns.class_("ESP32BtClassic", cg.Component)
@ -42,6 +50,9 @@ BtAddressConstRef = BtAddress.operator("ref").operator("const")
BtStatus = esp32_bt_classic_ns.class_("BtStatus")
BtStatusConstRef = BtStatus.operator("ref").operator("const")
ScanStatus = esp32_bt_classic_ns.class_("ScanStatus")
ScanStatusConstRef = ScanStatus.operator("ref").operator("const")
GAPEventHandler = esp32_bt_classic_ns.class_("GAPEventHandler")
# Actions
@ -51,7 +62,9 @@ BtClassicScanAction = esp32_bt_classic_ns.class_(
# Triggers
BtClassicScanResultTrigger = esp32_bt_classic_ns.class_(
"BtClassicScanResultTrigger",
automation.Trigger.template(BtAddress, BtStatusConstRef, cg.const_char_ptr),
automation.Trigger.template(
BtAddress, BtStatusConstRef, cg.const_char_ptr, ScanStatusConstRef
),
)
BtClassicScanStartTrigger = esp32_bt_classic_ns.class_(
"BtClassicScanStartTrigger", automation.Trigger.template()
@ -76,6 +89,9 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_MAC_ADDRESS): cv.ensure_list(cv.mac_address),
}
),
cv.Optional(CONF_LAST_ERROR): text_sensor.text_sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC
),
}
).extend(cv.COMPONENT_SCHEMA),
cv.require_framework_version(
@ -153,13 +169,25 @@ async def to_code(config):
trigger,
[
(BtAddressConstRef, "address"),
(BtStatusConstRef, "status"),
(BtStatusConstRef, "scan_result"),
(cg.const_char_ptr, "name"),
(ScanStatusConstRef, "status"),
],
conf,
)
if CONF_LAST_ERROR in config:
sens = await text_sensor.new_text_sensor(config[CONF_LAST_ERROR])
cg.add(var.set_last_error_sensor(sens))
if CORE.using_esp_idf:
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
add_idf_sdkconfig_option("CONFIG_BTDM_CTRL_MODE_BTDM", True)
add_idf_sdkconfig_option("CONFIG_BT_CLASSIC_ENABLED", True)
add_idf_sdkconfig_option("CONFIG_BTU_TASK_STACK_SIZE", 8192)
add_idf_sdkconfig_option("CONFIG_BT_LOG_GAP_TRACE_LEVEL_DEBUG", True)
add_idf_sdkconfig_option("CONFIG_BT_LOG_GAP_TRACE_LEVEL", 5)
add_idf_sdkconfig_option("CONFIG_BT_LOG_BTC_TRACE_LEVEL_DEBUG", True)
add_idf_sdkconfig_option("CONFIG_BT_LOG_BTC_TRACE_LEVEL", 5)

View file

@ -74,7 +74,7 @@ class BtClassicScanStartTrigger : public Trigger<>, public BtClassicScanStartLis
void on_scan_start() override { this->trigger(); }
};
class BtClassicScanResultTrigger : public Trigger<const BtAddress &, const BtStatus &, const char *>,
class BtClassicScanResultTrigger : public Trigger<const BtAddress &, const BtStatus &, const char *, const ScanStatus &>,
public BtClassicScanResultListner {
public:
explicit BtClassicScanResultTrigger(ESP32BtClassic *parent, std::initializer_list<uint64_t> addresses = {})
@ -82,7 +82,7 @@ class BtClassicScanResultTrigger : public Trigger<const BtAddress &, const BtSta
parent->register_scan_result_listener(this);
}
void on_scan_result(const rmt_name_result &result) override {
void on_scan_result(const rmt_name_result &result, const optional<bt_scan_item>& scan_item) override {
uint64_t result_addr = bd_addr_to_uint64(result.bda);
if (!addresses_.empty()) {
@ -91,7 +91,14 @@ class BtClassicScanResultTrigger : public Trigger<const BtAddress &, const BtSta
}
}
this->trigger(result_addr, result.stat, (const char *) result.rmt_name);
ScanStatus scan_status = SCAN_STATUS_SCANNING;
if (result.stat == ESP_BT_STATUS_SUCCESS) {
scan_status = SCAN_STATUS_FOUND;
}
else if(scan_item.has_value() && result.stat == ESP_BT_STATUS_FAIL && scan_item.value().scans_remaining == 0) {
scan_status = SCAN_STATUS_NOT_FOUND;
}
this->trigger(result_addr, result.stat, (const char *) result.rmt_name, scan_status);
}
protected:

View file

@ -13,13 +13,16 @@
#include <nvs_flash.h>
#include "esphome/components/esp32_bt_common/bt_defs.h"
// For time getting:
#include "esphome/components/homeassistant/time/homeassistant_time.h"
namespace esphome {
namespace esp32_bt_classic {
float ESP32BtClassic::get_setup_priority() const {
// Setup just after BLE, (but before AFTER_BLUETOOTH) to ensure both can co-exist!
return setup_priority::BLUETOOTH - 5.0f;
// return setup_priority::BLUETOOTH - 5.0f;
return setup_priority::AFTER_BLUETOOTH + 5.0f;
}
void ESP32BtClassic::setup() {
@ -29,6 +32,11 @@ void ESP32BtClassic::setup() {
if (!bt_setup_()) {
ESP_LOGE(TAG, "BT Classic could not be set up");
this->mark_failed();
#ifdef USE_TEXT_SENSOR
if (last_error_sensor_) {
last_error_sensor_->publish_state("boot");
}
#endif
return;
}
@ -74,6 +82,7 @@ bool ESP32BtClassic::bt_setup_() {
}
#endif
ESP_LOGD(TAG, "Initializing BlueDroid");
if (esp_bluedroid_get_status() == ESP_BLUEDROID_STATUS_UNINITIALIZED) {
if ((err = esp_bluedroid_init()) != ESP_OK) {
ESP_LOGE(TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(err));
@ -81,6 +90,7 @@ bool ESP32BtClassic::bt_setup_() {
}
}
ESP_LOGD(TAG, "Enabling BlueDroid");
if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) {
if ((err = esp_bluedroid_enable()) != ESP_OK) {
ESP_LOGE(TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(err));
@ -91,12 +101,16 @@ bool ESP32BtClassic::bt_setup_() {
bool success = gap_startup();
// BT takes some time to be fully set up, 200ms should be more than enough
delay(200); // NOLINT
for (int i = 10; i < 20; i++) {
App.feed_wdt();
delay(10); // NOLINT
}
return success;
}
bool ESP32BtClassic::gap_startup() {
ESP_LOGD(TAG, "Startup GAP");
// set discoverable and connectable mode, wait to be connected
esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
@ -134,14 +148,19 @@ void ESP32BtClassic::startScan(const uint64_t u64_addr) {
esp_bd_addr_t bd_addr;
uint64_to_bd_addr(u64_addr, bd_addr);
ESP_LOGD(TAG, "Start scanning for %02X:%02X:%02X:%02X:%02X:%02X", EXPAND_MAC_F(bd_addr));
esp_err_t result = esp_bt_gap_read_remote_name(bd_addr);
if (result == ESP_OK) {
scanPending_ = true;
last_scan_ms = millis();
for (auto *listener : scan_start_listners_) {
listener->on_scan_start();
}
}
else {
ESP_LOGE(TAG, "Could not start scan! Error: %s\n BlueDroid status: %d\n Controller status: %d", esp_err_to_name(result), esp_bluedroid_get_status(), esp_bt_controller_get_status());
}
esp_bt_gap_read_remote_name(bd_addr);
}
void ESP32BtClassic::addScan(const bt_scan_item &scan) {
@ -176,10 +195,10 @@ void ESP32BtClassic::real_gap_event_handler_(esp_bt_gap_cb_event_t event, esp_bt
switch (event) {
case ESP_BT_GAP_READ_REMOTE_NAME_EVT: {
handle_scan_result(param->read_rmt_name);
const auto& scan_item = handle_scan_result(param->read_rmt_name);
for (auto *listener : scan_result_listners_) {
listener->on_scan_result(param->read_rmt_name);
listener->on_scan_result(param->read_rmt_name, scan_item);
}
break;
@ -191,23 +210,30 @@ void ESP32BtClassic::real_gap_event_handler_(esp_bt_gap_cb_event_t event, esp_bt
}
}
void ESP32BtClassic::handle_scan_result(const rmt_name_result &result) {
optional<bt_scan_item> ESP32BtClassic::handle_scan_result(const rmt_name_result &result) {
scanPending_ = false;
uint32_t scanDuration = millis() - last_scan_ms;
const uint64_t u64_addr = bd_addr_to_uint64(result.bda);
optional<bt_scan_item> active_scan_item{};
auto it = active_scan_list_.begin();
while (it != active_scan_list_.end()) {
if (it->address == u64_addr) {
// copy scan_item to return value before modifications!
active_scan_item = *it;
active_scan_item->scan_duration = scanDuration;
// If device was found, remove it from the scan list
if (ESP_BT_STATUS_SUCCESS == result.stat) {
ESP_LOGI(TAG, "Found device '%02X:%02X:%02X:%02X:%02X:%02X' (%s) with %d scans remaining",
EXPAND_MAC_F(result.bda), result.rmt_name, it->scans_remaining);
ESP_LOGI(TAG, "Found device '%02X:%02X:%02X:%02X:%02X:%02X' (%s) in %lu ms with %d scans remaining",
EXPAND_MAC_F(result.bda), result.rmt_name, scanDuration, it->scans_remaining);
active_scan_list_.erase(it);
} else {
it->next_scan_time = millis() + scan_delay_;
ESP_LOGD(TAG, "Device '%02X:%02X:%02X:%02X:%02X:%02X' scan result: %s (%d)", EXPAND_MAC_F(result.bda),
esp_bt_status_to_str(result.stat), result.stat);
ESP_LOGD(TAG, "Device '%02X:%02X:%02X:%02X:%02X:%02X' scan result: %s (%d) in %lu ms", EXPAND_MAC_F(result.bda),
esp_bt_status_to_str(result.stat), result.stat, scanDuration);
ESP_LOGD(TAG, "BlueDroid status: %d\n Controller status: %d", esp_bluedroid_get_status(), esp_bt_controller_get_status());
if (it->scans_remaining == 0) {
ESP_LOGW(TAG, "Device '%02X:%02X:%02X:%02X:%02X:%02X' not found on final scan. Removing from scan list.",
@ -227,9 +253,17 @@ void ESP32BtClassic::handle_scan_result(const rmt_name_result &result) {
it++;
}
#ifdef USE_TEXT_SENSOR
if (last_error_sensor_ && result.stat == ESP_BT_STATUS_FAIL && scanDuration < 100) {
auto current_time = esphome::homeassistant::global_homeassistant_time->now();
last_error_sensor_->publish_state(current_time.strftime("%Y-%m-%d %H:%M:%S"));
}
#endif
if (active_scan_list_.empty()) {
ESP_LOGD(TAG, "Scan complete. No more devices left to scan.");
}
return active_scan_item;
}
void ESP32BtClassic::dump_config() {

View file

@ -9,6 +9,10 @@
#include "esphome/components/esp32_bt_common/queue.h"
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
#endif
// IDF headers
#include <esp_bt_defs.h>
#include <esp_gap_bt_api.h>
@ -29,10 +33,12 @@ struct BtGapEvent {
};
struct bt_scan_item {
bt_scan_item(uint64_t u64_addr, uint8_t num_scans) : address(u64_addr), scans_remaining(num_scans) {}
bt_scan_item() : address{}, scans_remaining{}, next_scan_time{}, scan_duration{} {}
bt_scan_item(uint64_t u64_addr, uint8_t num_scans) : address(u64_addr), scans_remaining(num_scans), next_scan_time{}, scan_duration{} {}
uint64_t address;
uint8_t scans_remaining;
uint32_t next_scan_time;
uint32_t scan_duration;
};
struct BtAddress {
@ -70,6 +76,24 @@ struct BtStatus {
esp_bt_status_t status_;
};
struct ScanStatus {
ScanStatus(scan_status_t status) : status_(status) {}
// Implicit conversion operators
operator scan_status_t() const { return status_; }
operator const char *() const { return c_str(); }
operator std::string() const { return c_str(); }
// Explicit type accessors
scan_status_t scan_status() const { return status_; }
const char *c_str() const { return scan_status_to_str(status_); }
std::string str() const { return c_str(); }
protected:
scan_status_t status_;
};
class BtClassicItf {
public:
virtual void addScan(const bt_scan_item &scan) = 0;
@ -92,7 +116,7 @@ class BtClassicScanStartListner : public BtClassicChildBase {
class BtClassicScanResultListner : public BtClassicChildBase {
public:
virtual void on_scan_result(const rmt_name_result &result) = 0;
virtual void on_scan_result(const rmt_name_result &result, const optional<bt_scan_item> &scan_item) = 0;
};
// -----------------------------------------------
@ -106,6 +130,10 @@ class ESP32BtClassic : public Component, public BtClassicItf {
void dump_config() override;
float get_setup_priority() const override;
#ifdef USE_TEXT_SENSOR
void set_last_error_sensor(text_sensor::TextSensor *last_error) { last_error_sensor_ = last_error; }
#endif
void register_scan_start_listener(BtClassicScanStartListner *listner) {
listner->set_parent(this);
scan_start_listners_.push_back(listner);
@ -125,7 +153,7 @@ class ESP32BtClassic : public Component, public BtClassicItf {
void startScan(const uint64_t u64_addr);
void handle_scan_result(const rmt_name_result &result);
optional<bt_scan_item> handle_scan_result(const rmt_name_result &result);
bool bt_setup_();
bool gap_startup();
@ -141,6 +169,9 @@ class ESP32BtClassic : public Component, public BtClassicItf {
// Ble-Queue which thread safety precautions:
esp32_bt_common::Queue<BtGapEvent> bt_events_;
#ifdef USE_TEXT_SENSOR
text_sensor::TextSensor *last_error_sensor_{nullptr};
#endif
const uint32_t scan_delay_{100}; // (ms) minimal time between consecutive scans
};

View file

@ -2,3 +2,5 @@ CONF_ESP32_BTCLASSIC_ID = "esp32_btclassic_id"
CONF_ON_SCAN_START = "on_scan_start"
CONF_ON_SCAN_RESULT = "on_scan_result"
CONF_LAST_ERROR = "last_error"

View file

@ -12,6 +12,17 @@
namespace esphome {
namespace esp32_bt_classic {
typedef struct {
scan_status_t code;
const char *msg;
} scan_status_msg_t;
static const scan_status_msg_t scan_status_msg_table[] = {
{SCAN_STATUS_SCANNING, "Scanning"},
{SCAN_STATUS_FOUND, "Found"},
{SCAN_STATUS_NOT_FOUND, "Not Found"},
};
typedef struct {
esp_bt_status_t code;
const char *msg;
@ -51,6 +62,16 @@ const char *esp_bt_status_to_str(esp_bt_status_t code) {
return "Unknown Status";
}
const char *scan_status_to_str(scan_status_t status) {
for (int i = 0; i < sizeof(scan_status_msg_table) / sizeof(scan_status_msg_table[0]); ++i) {
if (scan_status_msg_table[i].code == status) {
return scan_status_msg_table[i].msg;
}
}
return "Unknown";
}
void uint64_to_bd_addr(uint64_t address, esp_bd_addr_t &bd_addr) {
bd_addr[0] = (address >> 40) & 0xff;
bd_addr[1] = (address >> 32) & 0xff;
@ -79,6 +100,14 @@ std::string u64_addr_to_str(uint64_t address) {
return mac;
}
uint64_t str_to_u64_addr(const char* addr_str) {
esp_bd_addr_t addr;
if(str_to_bd_addr(addr_str, addr)) {
return bd_addr_to_uint64(addr);
}
return 0;
}
std::string bd_addr_to_str(const esp_bd_addr_t &addr) {
char mac[24];
snprintf(mac, sizeof(mac), "%02X:%02X:%02X:%02X:%02X:%02X", EXPAND_MAC_F(addr));

View file

@ -15,6 +15,13 @@ const char *const TAG = "esp32_bt_classic";
// Helper for printing Bt MAC addresses for format "%02X:%02X:%02X:%02X:%02X:%02X"
#define EXPAND_MAC_F(addr) addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]
typedef enum {
SCAN_STATUS_SCANNING= 0,
SCAN_STATUS_FOUND,
SCAN_STATUS_NOT_FOUND,
} scan_status_t;
const char *scan_status_to_str(scan_status_t status);
const char *esp_bt_status_to_str(esp_bt_status_t code);
// bd_addr_t <--> uint64_t conversion functions:
@ -26,6 +33,7 @@ std::string bd_addr_to_str(const esp_bd_addr_t &addr);
bool str_to_bd_addr(const char *addr_str, esp_bd_addr_t &addr);
std::string u64_addr_to_str(uint64_t address);
uint64_t str_to_u64_addr(const char* addr_str);
template<typename T> void moveItemToBack(std::vector<T> &v, size_t item_index) {
T tmp(v[item_index]);