From ee89c32188f41feb6dc72d9650fe7e3ab1e970d6 Mon Sep 17 00:00:00 2001 From: RoboMagus <68224306+RoboMagus@users.noreply.github.com> Date: Sat, 16 Nov 2024 16:05:09 +0100 Subject: [PATCH] Pending BT Classic changes (Working changes prior to rebase / merge upstream) --- .../bt_classic_presence_device.cpp | 3 +- .../bt_classic_presence_device.h | 2 +- .../components/esp32_bt_classic/__init__.py | 36 +++++++++-- .../components/esp32_bt_classic/automation.h | 13 +++- .../esp32_bt_classic/bt_classic.cpp | 62 ++++++++++++++----- .../components/esp32_bt_classic/bt_classic.h | 37 ++++++++++- esphome/components/esp32_bt_classic/const.py | 2 + esphome/components/esp32_bt_classic/utils.cpp | 29 +++++++++ esphome/components/esp32_bt_classic/utils.h | 8 +++ 9 files changed, 166 insertions(+), 26 deletions(-) diff --git a/esphome/components/bt_classic_presence/bt_classic_presence_device.cpp b/esphome/components/bt_classic_presence/bt_classic_presence_device.cpp index ef5e6d856d..96c4799b60 100644 --- a/esphome/components/bt_classic_presence/bt_classic_presence_device.cpp +++ b/esphome/components/bt_classic_presence/bt_classic_presence_device.cpp @@ -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& 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); diff --git a/esphome/components/bt_classic_presence/bt_classic_presence_device.h b/esphome/components/bt_classic_presence/bt_classic_presence_device.h index 9c5e34a85c..2cefd29b70 100644 --- a/esphome/components/bt_classic_presence/bt_classic_presence_device.h +++ b/esphome/components/bt_classic_presence/bt_classic_presence_device.h @@ -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& scan_item) override; protected: uint8_t scans_remaining{0}; diff --git a/esphome/components/esp32_bt_classic/__init__.py b/esphome/components/esp32_bt_classic/__init__.py index 9ddbfd1488..ae6bbc42a7 100644 --- a/esphome/components/esp32_bt_classic/__init__.py +++ b/esphome/components/esp32_bt_classic/__init__.py @@ -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) diff --git a/esphome/components/esp32_bt_classic/automation.h b/esphome/components/esp32_bt_classic/automation.h index d9c17546d3..3b7df96d74 100644 --- a/esphome/components/esp32_bt_classic/automation.h +++ b/esphome/components/esp32_bt_classic/automation.h @@ -74,7 +74,7 @@ class BtClassicScanStartTrigger : public Trigger<>, public BtClassicScanStartLis void on_scan_start() override { this->trigger(); } }; -class BtClassicScanResultTrigger : public Trigger, +class BtClassicScanResultTrigger : public Trigger, public BtClassicScanResultListner { public: explicit BtClassicScanResultTrigger(ESP32BtClassic *parent, std::initializer_list addresses = {}) @@ -82,7 +82,7 @@ class BtClassicScanResultTrigger : public Triggerregister_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& scan_item) override { uint64_t result_addr = bd_addr_to_uint64(result.bda); if (!addresses_.empty()) { @@ -91,7 +91,14 @@ class BtClassicScanResultTrigger : public Triggertrigger(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: diff --git a/esphome/components/esp32_bt_classic/bt_classic.cpp b/esphome/components/esp32_bt_classic/bt_classic.cpp index c7701bc5b1..711dc0d19d 100644 --- a/esphome/components/esp32_bt_classic/bt_classic.cpp +++ b/esphome/components/esp32_bt_classic/bt_classic.cpp @@ -13,13 +13,16 @@ #include #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)); - scanPending_ = true; - last_scan_ms = millis(); + esp_err_t result = esp_bt_gap_read_remote_name(bd_addr); - for (auto *listener : scan_start_listners_) { - listener->on_scan_start(); + 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 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 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() { diff --git a/esphome/components/esp32_bt_classic/bt_classic.h b/esphome/components/esp32_bt_classic/bt_classic.h index b0b1f788c8..a20a11aef4 100644 --- a/esphome/components/esp32_bt_classic/bt_classic.h +++ b/esphome/components/esp32_bt_classic/bt_classic.h @@ -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 #include @@ -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 &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 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 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 }; diff --git a/esphome/components/esp32_bt_classic/const.py b/esphome/components/esp32_bt_classic/const.py index 185d23ecfb..e4fdf88081 100644 --- a/esphome/components/esp32_bt_classic/const.py +++ b/esphome/components/esp32_bt_classic/const.py @@ -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" diff --git a/esphome/components/esp32_bt_classic/utils.cpp b/esphome/components/esp32_bt_classic/utils.cpp index c789261485..627c0f13d2 100644 --- a/esphome/components/esp32_bt_classic/utils.cpp +++ b/esphome/components/esp32_bt_classic/utils.cpp @@ -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)); diff --git a/esphome/components/esp32_bt_classic/utils.h b/esphome/components/esp32_bt_classic/utils.h index 89f68acffc..a76bf85c43 100644 --- a/esphome/components/esp32_bt_classic/utils.h +++ b/esphome/components/esp32_bt_classic/utils.h @@ -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 void moveItemToBack(std::vector &v, size_t item_index) { T tmp(v[item_index]);