diff --git a/esphome/components/bk72xx_ble_tracker/automation.h b/esphome/components/bk72xx_ble_tracker/automation.h index 5a49fed04a..9017d19d71 100644 --- a/esphome/components/bk72xx_ble_tracker/automation.h +++ b/esphome/components/bk72xx_ble_tracker/automation.h @@ -8,8 +8,8 @@ #include "bk72xx_ble_tracker.h" -#include "esphome/components/ble_device_base/automation.h" #include "esphome/core/automation.h" +#include "esphome/core/helpers.h" namespace esphome::bk72xx_ble_tracker { diff --git a/esphome/components/ln882h_ble_tracker/__init__.py b/esphome/components/ln882h_ble_tracker/__init__.py index 08d14d0de4..8e958ff9f0 100644 --- a/esphome/components/ln882h_ble_tracker/__init__.py +++ b/esphome/components/ln882h_ble_tracker/__init__.py @@ -5,7 +5,8 @@ an explicit start_scan() call.""" from esphome import automation import esphome.codegen as cg from esphome.components import ble_device_base, ln882h_ble, ota -from esphome.components.const import CONF_SCAN_PARAMETERS, CONF_WINDOW +from esphome.components.ble_device_base import automation as ble_automation +from esphome.components.const import CONF_ON_SCAN_END, CONF_SCAN_PARAMETERS, CONF_WINDOW import esphome.config_validation as cv from esphome.const import ( CONF_ACTIVE, @@ -13,19 +14,16 @@ from esphome.const import ( CONF_DURATION, CONF_ID, CONF_INTERVAL, - CONF_MAC_ADDRESS, CONF_MANUFACTURER_ID, CONF_ON_BLE_ADVERTISE, CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE, CONF_ON_BLE_SERVICE_DATA_ADVERTISE, CONF_SERVICE_UUID, - CONF_TRIGGER_ID, ) from esphome.core import ID from esphome.types import ConfigType CONF_LN882H_BLE_ID = "ln882h_ble_id" -CONF_ON_SCAN_END = "on_scan_end" DEPENDENCIES = ["ln882x"] AUTO_LOAD = ["ble_device_base", "ln882h_ble"] @@ -39,27 +37,10 @@ LN882HBLETracker = ln882h_ble_tracker_ns.class_( StartScanAction = ln882h_ble_tracker_ns.class_("StartScanAction", automation.Action) StopScanAction = ln882h_ble_tracker_ns.class_("StopScanAction", automation.Action) -ESPBTDeviceConstRef = ( - cg.esphome_ns.namespace("ble_device_base") - .class_("ESPBTDevice") - .operator("ref") - .operator("const") -) -ESPBTAdvertiseTrigger = ln882h_ble_tracker_ns.class_( - "ESPBTAdvertiseTrigger", automation.Trigger.template(ESPBTDeviceConstRef) -) -adv_data_t = cg.std_vector.template(cg.uint8) -adv_data_t_const_ref = adv_data_t.operator("ref").operator("const") -BLEServiceDataAdvertiseTrigger = ln882h_ble_tracker_ns.class_( - "BLEServiceDataAdvertiseTrigger", automation.Trigger.template(adv_data_t_const_ref) -) -BLEManufacturerDataAdvertiseTrigger = ln882h_ble_tracker_ns.class_( - "BLEManufacturerDataAdvertiseTrigger", - automation.Trigger.template(adv_data_t_const_ref), -) -BLEEndOfScanTrigger = ln882h_ble_tracker_ns.class_( - "BLEEndOfScanTrigger", automation.Trigger.template() -) +ESPBTAdvertiseTrigger = ble_automation.ESPBTAdvertiseTrigger +BLEServiceDataAdvertiseTrigger = ble_automation.BLEServiceDataAdvertiseTrigger +BLEManufacturerDataAdvertiseTrigger = ble_automation.BLEManufacturerDataAdvertiseTrigger +BLEEndOfScanTrigger = ble_automation.BLEEndOfScanTrigger # LN882H SDK reference scan rate: 100 ms interval / 50 ms window (50 % duty). @@ -68,60 +49,33 @@ SCAN_PARAMETERS_SCHEMA = ble_device_base.scan_parameters_schema( ) -# UUID string length -> setter width. 16/32-bit go out as plain hex literals, -# 128-bit as a reversed byte array (BLE wire order). Keyed exhaustively so an -# impossible length fails as a KeyError instead of silently picking a width -# (bt_uuid validation upstream only ever produces these three). -_UUID_WIDTHS = { - len(ble_device_base.BT_UUID16_FORMAT): "16", - len(ble_device_base.BT_UUID32_FORMAT): "32", - len(ble_device_base.BT_UUID128_FORMAT): "128", -} - CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(LN882HBLETracker), cv.GenerateID(CONF_LN882H_BLE_ID): cv.use_id(ln882h_ble.LN882HBLE), cv.Optional(CONF_SCAN_PARAMETERS, default={}): SCAN_PARAMETERS_SCHEMA, - cv.Optional(CONF_ON_BLE_ADVERTISE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESPBTAdvertiseTrigger), - cv.Optional(CONF_MAC_ADDRESS): cv.ensure_list(cv.mac_address), - } + cv.Optional(CONF_ON_BLE_ADVERTISE): ble_automation.advertise_trigger_schema( + ESPBTAdvertiseTrigger ), - cv.Optional(CONF_ON_BLE_SERVICE_DATA_ADVERTISE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - BLEServiceDataAdvertiseTrigger - ), - cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, - cv.Required(CONF_SERVICE_UUID): ble_device_base.bt_uuid, - } + cv.Optional( + CONF_ON_BLE_SERVICE_DATA_ADVERTISE + ): ble_automation.uuid_trigger_schema( + BLEServiceDataAdvertiseTrigger, + {cv.Required(CONF_SERVICE_UUID): ble_device_base.bt_uuid}, ), cv.Optional( CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE - ): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - BLEManufacturerDataAdvertiseTrigger - ), - cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, - cv.Required(CONF_MANUFACTURER_ID): ble_device_base.bt_uuid, - } + ): ble_automation.uuid_trigger_schema( + BLEManufacturerDataAdvertiseTrigger, + {cv.Required(CONF_MANUFACTURER_ID): ble_device_base.bt_uuid}, ), - cv.Optional(CONF_ON_SCAN_END): automation.validate_automation( - {cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BLEEndOfScanTrigger)} + cv.Optional(CONF_ON_SCAN_END): ble_automation.scan_end_trigger_schema( + BLEEndOfScanTrigger ), } ).extend(cv.COMPONENT_SCHEMA) -# Triggers register as ble_device_base listeners in their constructors; count -# them where they are created so the StaticVector cannot be undersized. Shares -# the define with register_ble_device() via the core slot-counter factory. -_count_listener = cg.slot_counter(ble_device_base.LISTENER_COUNT_DEFINE) - - @automation.register_action( "ln882h_ble_tracker.start_scan", StartScanAction, @@ -191,11 +145,7 @@ async def to_code(config: ConfigType) -> None: cg.add(var.set_configured_continuous(scan[CONF_CONTINUOUS])) for conf in config.get(CONF_ON_BLE_ADVERTISE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - if (macs := conf.get(CONF_MAC_ADDRESS)) is not None: - cg.add(trigger.set_addresses([it.as_hex for it in macs])) - await automation.build_automation(trigger, [(ESPBTDeviceConstRef, "x")], conf) - _count_listener() + await ble_automation.advertise_trigger_to_code(conf, var) for trigger_key, uuid_key, setter_prefix in ( (CONF_ON_BLE_SERVICE_DATA_ADVERTISE, CONF_SERVICE_UUID, "set_service_uuid"), @@ -206,23 +156,9 @@ async def to_code(config: ConfigType) -> None: ), ): for conf in config.get(trigger_key, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - uuid = conf[uuid_key] - width = _UUID_WIDTHS[len(uuid)] - value = ( - ble_device_base.as_hex(uuid) - if width != "128" - else ble_device_base.as_reversed_hex_array(uuid) + await ble_automation.uuid_trigger_to_code( + conf, var, uuid_key, setter_prefix ) - cg.add(getattr(trigger, f"{setter_prefix}{width}")(value)) - if (mac := conf.get(CONF_MAC_ADDRESS)) is not None: - cg.add(trigger.set_address(mac.as_hex)) - await automation.build_automation( - trigger, [(adv_data_t_const_ref, "x")], conf - ) - _count_listener() for conf in config.get(CONF_ON_SCAN_END, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - _count_listener() + await ble_automation.scan_end_trigger_to_code(conf, var) diff --git a/esphome/components/ln882h_ble_tracker/automation.h b/esphome/components/ln882h_ble_tracker/automation.h index 43f526e064..8b211384a0 100644 --- a/esphome/components/ln882h_ble_tracker/automation.h +++ b/esphome/components/ln882h_ble_tracker/automation.h @@ -1,6 +1,5 @@ -// Automation triggers and actions for ln882h_ble_tracker: triggers follow the -// esp32_ble_tracker design; only the scan-control actions are -// platform-specific. +// Scan-control actions for ln882h_ble_tracker. The automation triggers are the +// neutral ble_device_base classes (ble_device_base/automation.h). #pragma once @@ -11,9 +10,6 @@ #include "esphome/core/automation.h" #include "esphome/core/helpers.h" -#include -#include - namespace esphome::ln882h_ble_tracker { template class StartScanAction final : public Action, public Parented { @@ -46,124 +42,6 @@ template class StopScanAction final : public Action, publ void play(const Ts &...x) override { this->parent_->stop_scan(); } }; -// --------------------------------------------------------------------------- -// Automation triggers. -// -// Each trigger is a ble_device_base::ESPBTDeviceListener registered on the hub — -// the same design as esp32_ble_tracker, where the triggers sit in the listener -// list and their parse_device() return feeds the "Found device" suppression. -// --------------------------------------------------------------------------- - -// on_ble_advertise: fires on every BLE advertisement, optionally filtered to one or more MACs. -class ESPBTAdvertiseTrigger final : public Trigger, - public ble_device_base::ESPBTDeviceListener { - public: - explicit ESPBTAdvertiseTrigger(LN882HBLETracker *parent) { parent->register_listener(this); } - - void set_addresses(std::initializer_list addresses) { this->addresses_ = addresses; } - - bool parse_device(const ble_device_base::ESPBTDevice &device) override { - if (!this->addresses_.empty() && std::find(this->addresses_.begin(), this->addresses_.end(), - device.address_uint64()) == this->addresses_.end()) { - return false; - } - this->trigger(device); - return true; - } - - protected: - FixedVector addresses_; -}; - -// on_ble_service_data_advertise: fires when an advertisement contains service -// data for the given UUID. Optional single-MAC filter. -class BLEServiceDataAdvertiseTrigger final : public Trigger, - public ble_device_base::ESPBTDeviceListener { - public: - explicit BLEServiceDataAdvertiseTrigger(LN882HBLETracker *parent) { parent->register_listener(this); } - - void set_service_uuid16(uint64_t uuid) { - this->uuid_ = ble_device_base::ESPBTUUID::from_uint16(static_cast(uuid)); - } - void set_service_uuid32(uint64_t uuid) { - this->uuid_ = ble_device_base::ESPBTUUID::from_uint32(static_cast(uuid)); - } - void set_service_uuid128(const uint8_t *uuid) { this->uuid_ = ble_device_base::ESPBTUUID::from_raw(uuid); } - - void set_address(uint64_t address) { - this->address_ = address; - this->has_address_ = true; - } - - bool parse_device(const ble_device_base::ESPBTDevice &device) override { - if (this->has_address_ && device.address_uint64() != this->address_) { - return false; - } - for (const auto &sd : device.get_service_datas()) { - if (sd.uuid == this->uuid_) { - this->trigger(sd.data); - return true; - } - } - return false; - } - - protected: - ble_device_base::ESPBTUUID uuid_{}; - uint64_t address_{0}; - bool has_address_{false}; -}; - -// on_ble_manufacturer_data_advertise: fires when an advertisement contains -// manufacturer data for the given ID. Optional single-MAC filter. -class BLEManufacturerDataAdvertiseTrigger final : public Trigger, - public ble_device_base::ESPBTDeviceListener { - public: - explicit BLEManufacturerDataAdvertiseTrigger(LN882HBLETracker *parent) { parent->register_listener(this); } - - void set_manufacturer_uuid16(uint64_t uuid) { - this->uuid_ = ble_device_base::ESPBTUUID::from_uint16(static_cast(uuid)); - } - void set_manufacturer_uuid32(uint64_t uuid) { - this->uuid_ = ble_device_base::ESPBTUUID::from_uint32(static_cast(uuid)); - } - void set_manufacturer_uuid128(const uint8_t *uuid) { this->uuid_ = ble_device_base::ESPBTUUID::from_raw(uuid); } - - void set_address(uint64_t address) { - this->address_ = address; - this->has_address_ = true; - } - - bool parse_device(const ble_device_base::ESPBTDevice &device) override { - if (this->has_address_ && device.address_uint64() != this->address_) { - return false; - } - for (const auto &md : device.get_manufacturer_datas()) { - if (md.uuid == this->uuid_) { - this->trigger(md.data); - return true; - } - } - return false; - } - - protected: - ble_device_base::ESPBTUUID uuid_{}; - uint64_t address_{0}; - bool has_address_{false}; -}; - -// on_scan_end: fires whenever a scan period ends (duration elapsed or stop_scan -// called). A listener whose on_scan_end() hook fires the trigger — never claims -// devices (parse_device always returns false). -class BLEEndOfScanTrigger final : public Trigger<>, public ble_device_base::ESPBTDeviceListener { - public: - explicit BLEEndOfScanTrigger(LN882HBLETracker *parent) { parent->register_listener(this); } - - bool parse_device(const ble_device_base::ESPBTDevice &device) override { return false; } - void on_scan_end() override { this->trigger(); } -}; - } // namespace esphome::ln882h_ble_tracker #endif // USE_LIBRETINY diff --git a/tests/component_tests/bk72xx_ble_tracker/config/test_automations.yaml b/tests/component_tests/bk72xx_ble_tracker/config/test_automations.yaml index 1701807984..a60e7cca05 100644 --- a/tests/component_tests/bk72xx_ble_tracker/config/test_automations.yaml +++ b/tests/component_tests/bk72xx_ble_tracker/config/test_automations.yaml @@ -4,12 +4,17 @@ esphome: then: - bk72xx_ble_tracker.start_scan: continuous: true + # Bare form: restores the configured scan_parameters mode — no + # set_continuous emitted (asserted in the codegen test). + - bk72xx_ble_tracker.start_scan: - bk72xx_ble_tracker.stop_scan bk72xx: board: cb2s bk72xx_ble_tracker: + scan_parameters: + continuous: false on_ble_advertise: - mac_address: - AC:37:43:77:5F:4C diff --git a/tests/component_tests/bk72xx_ble_tracker/test_automations_codegen.py b/tests/component_tests/bk72xx_ble_tracker/test_automations_codegen.py index c98bb8a111..3a03f98adf 100644 --- a/tests/component_tests/bk72xx_ble_tracker/test_automations_codegen.py +++ b/tests/component_tests/bk72xx_ble_tracker/test_automations_codegen.py @@ -39,9 +39,15 @@ def test_trigger_codegen( "set_manufacturer_uuid128((uint8_t*)(const uint8_t[16]){0xCD,0xAB,0xCD,0xAB," "0xCD,0xAB,0xCD,0xAB,0xCD,0xAB,0xCD,0xAB,0xCD,0xAB,0xCD,0xAB})" in main_cpp ) - # scan-control actions: templatable continuous lambda + parented actions + # scan-control actions: templatable continuous lambda + parented actions. + # Exactly one set_continuous: the bare start_scan emits none, pinning the + # restore-configured-mode divergence from esp32 against a future default=. + assert main_cpp.count("->set_continuous(") == 1 assert "startscanaction_id->set_continuous(" in main_cpp assert "stopscanaction_id->set_parent(" in main_cpp + # scan_parameters continuous: false reaches the YAML-mode setter, not the + # runtime override. + assert "->set_configured_continuous(false)" in main_cpp # Constructor call, not just the declaration: the parent argument is what # registers the trigger as a listener. assert re.search( diff --git a/tests/component_tests/ln882h_ble_tracker/__init__.py b/tests/component_tests/ln882h_ble_tracker/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/component_tests/ln882h_ble_tracker/config/test_automations.yaml b/tests/component_tests/ln882h_ble_tracker/config/test_automations.yaml index d83e6c0883..16a215f026 100644 --- a/tests/component_tests/ln882h_ble_tracker/config/test_automations.yaml +++ b/tests/component_tests/ln882h_ble_tracker/config/test_automations.yaml @@ -13,6 +13,8 @@ ln882x: board: generic-ln882h ln882h_ble_tracker: + scan_parameters: + continuous: false on_ble_advertise: - mac_address: - AC:37:43:77:5F:4C diff --git a/tests/component_tests/ln882h_ble_tracker/test_automations_codegen.py b/tests/component_tests/ln882h_ble_tracker/test_automations_codegen.py index 43c14c8054..608a4c6694 100644 --- a/tests/component_tests/ln882h_ble_tracker/test_automations_codegen.py +++ b/tests/component_tests/ln882h_ble_tracker/test_automations_codegen.py @@ -40,10 +40,13 @@ def test_trigger_codegen( assert main_cpp.count("->set_continuous(") == 1 assert "startscanaction_id->set_continuous(" in main_cpp assert "stopscanaction_id->set_parent(" in main_cpp + # scan_parameters continuous: false reaches the YAML-mode setter, not the + # runtime override. + assert "->set_configured_continuous(false)" in main_cpp # Constructor call, not just the declaration: the parent argument is what # registers the trigger as a listener. assert re.search( - r"new\(\w+\) ln882h_ble_tracker::BLEEndOfScanTrigger\(\w+\)", main_cpp + r"new\(\w+\) ble_device_base::BLEEndOfScanTrigger\(\w+\)", main_cpp ) # Seven triggers register as listeners; an undercount silently drops the