[bk72xx_ble_tracker] Automation triggers and actions (#17776)

This commit is contained in:
Edvard Filistovič
2026-08-05 09:31:51 -05:00
committed by GitHub
parent d548454dbe
commit 0496627d2b
12 changed files with 583 additions and 10 deletions
@@ -12,18 +12,30 @@ Scan modes:
Use this when the radio is dedicated to BLE.
continuous: false — a started scan runs for `duration` ms, then stops. The
FIRST start is external too: nothing in this component
starts a non-continuous scan on boot, so until the
automation actions land (follow-up PR) the radio stays
idle. start_scan() is called from code (e.g. an api
client-connected automation) so the single-core radio
can service WiFi in between scans.
starts a non-continuous scan on boot — the radio stays
idle until bk72xx_ble_tracker.start_scan fires (e.g.
from an api client-connected automation), so the
single-core radio can service WiFi in between scans.
"""
from esphome import automation
import esphome.codegen as cg
from esphome.components import bk72xx_ble, ble_device_base, 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_CONTINUOUS, CONF_DURATION, CONF_ID, CONF_INTERVAL
from esphome.const import (
CONF_CONTINUOUS,
CONF_DURATION,
CONF_ID,
CONF_INTERVAL,
CONF_MANUFACTURER_ID,
CONF_ON_BLE_ADVERTISE,
CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE,
CONF_ON_BLE_SERVICE_DATA_ADVERTISE,
CONF_SERVICE_UUID,
)
from esphome.core import ID
from esphome.types import ConfigType
CONF_BK72XX_BLE_ID = "bk72xx_ble_id"
@@ -37,6 +49,14 @@ BK72xxBLETracker = bk72xx_ble_tracker_ns.class_(
"BK72xxBLETracker", ble_device_base.BLEHub, cg.Component
)
StartScanAction = bk72xx_ble_tracker_ns.class_("StartScanAction", automation.Action)
StopScanAction = bk72xx_ble_tracker_ns.class_("StopScanAction", automation.Action)
ESPBTAdvertiseTrigger = ble_automation.ESPBTAdvertiseTrigger
BLEServiceDataAdvertiseTrigger = ble_automation.BLEServiceDataAdvertiseTrigger
BLEManufacturerDataAdvertiseTrigger = ble_automation.BLEManufacturerDataAdvertiseTrigger
BLEEndOfScanTrigger = ble_automation.BLEEndOfScanTrigger
# interval defaults to the BK reference scan rate — 100 ms with the shared 30 ms
# window, a 30 % duty cycle. Converted to the controller's 0.625 ms BLE units in
@@ -48,10 +68,79 @@ CONFIG_SCHEMA = cv.Schema(
cv.GenerateID(): cv.declare_id(BK72xxBLETracker),
cv.GenerateID(CONF_BK72XX_BLE_ID): cv.use_id(bk72xx_ble.BK72xxBLE),
cv.Optional(CONF_SCAN_PARAMETERS, default={}): SCAN_PARAMETERS_SCHEMA,
cv.Optional(CONF_ON_BLE_ADVERTISE): ble_automation.advertise_trigger_schema(
ESPBTAdvertiseTrigger
),
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
): ble_automation.uuid_trigger_schema(
BLEManufacturerDataAdvertiseTrigger,
{cv.Required(CONF_MANUFACTURER_ID): ble_device_base.bt_uuid},
),
cv.Optional(CONF_ON_SCAN_END): ble_automation.scan_end_trigger_schema(
BLEEndOfScanTrigger
),
}
).extend(cv.COMPONENT_SCHEMA)
@automation.register_action(
"bk72xx_ble_tracker.start_scan",
StartScanAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(BK72xxBLETracker),
# Optional with no default, unlike esp32_ble_tracker: omitting it
# keeps whatever scan_parameters.continuous configured, instead of
# silently forcing one-shot.
cv.Optional(CONF_CONTINUOUS): cv.templatable(cv.boolean),
}
),
synchronous=True,
)
async def start_scan_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: list,
) -> cg.MockObj:
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
if (continuous := config.get(CONF_CONTINUOUS)) is not None:
template_ = await cg.templatable(continuous, args, cg.bool_)
cg.add(var.set_continuous(template_))
return var
@automation.register_action(
"bk72xx_ble_tracker.stop_scan",
StopScanAction,
automation.maybe_simple_id(
cv.Schema(
{
cv.GenerateID(): cv.use_id(BK72xxBLETracker),
}
)
),
synchronous=True,
)
async def stop_scan_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: list,
) -> cg.MockObj:
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
@@ -69,4 +158,23 @@ async def to_code(config: ConfigType) -> None:
cg.add(var.set_scan_interval(ble_device_base.to_ble_units(scan[CONF_INTERVAL])))
cg.add(var.set_scan_window(ble_device_base.to_ble_units(scan[CONF_WINDOW])))
cg.add(var.set_scan_duration(scan[CONF_DURATION].total_milliseconds))
cg.add(var.set_scan_continuous(scan[CONF_CONTINUOUS]))
cg.add(var.set_configured_continuous(scan[CONF_CONTINUOUS]))
for conf in config.get(CONF_ON_BLE_ADVERTISE, []):
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"),
(
CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE,
CONF_MANUFACTURER_ID,
"set_manufacturer_uuid",
),
):
for conf in config.get(trigger_key, []):
await ble_automation.uuid_trigger_to_code(
conf, var, uuid_key, setter_prefix
)
for conf in config.get(CONF_ON_SCAN_END, []):
await ble_automation.scan_end_trigger_to_code(conf, var)
@@ -0,0 +1,48 @@
// Automation triggers and actions for bk72xx_ble_tracker: triggers are the
// neutral ble_device_base classes; only the scan-control actions are
// platform-specific.
#pragma once
#ifdef USE_LIBRETINY
#include "bk72xx_ble_tracker.h"
#include "esphome/components/ble_device_base/automation.h"
#include "esphome/core/automation.h"
namespace esphome::bk72xx_ble_tracker {
template<typename... Ts> class StartScanAction final : public Action<Ts...>, public Parented<BK72xxBLETracker> {
public:
TEMPLATABLE_VALUE(bool, continuous)
void play(const Ts &...x) override {
// With continuous: set, the action wins. Without it, the configured value
// is used - stop_scan() clears the runtime flag permanently, so a bare
// stop_scan/start_scan pair would otherwise never resume continuous mode.
const bool want =
this->continuous_.has_value() ? this->continuous_.value(x...) : this->parent_->configured_continuous();
if (this->parent_->scan_running()) {
// Same mode on a running scan is a no-op (esp32 parity): re-anchoring
// the duration window here would let a repeated action keep a one-shot
// scan alive forever. A real mode switch re-anchors so a change to
// one-shot runs a full duration from now.
if (want != this->parent_->scan_continuous()) {
this->parent_->set_scan_continuous(want);
this->parent_->restart_scan_duration();
}
return;
}
this->parent_->set_scan_continuous(want);
this->parent_->start_scan();
}
};
template<typename... Ts> class StopScanAction final : public Action<Ts...>, public Parented<BK72xxBLETracker> {
public:
void play(const Ts &...x) override { this->parent_->stop_scan(); }
};
} // namespace esphome::bk72xx_ble_tracker
#endif // USE_LIBRETINY
@@ -210,6 +210,17 @@ void BK72xxBLETracker::start_scan() {
this->try_start_with_backoff_(millis(), /* force= */ true);
}
void BK72xxBLETracker::restart_scan_duration() {
if (!this->scan_running_)
return;
// Re-anchor only the one-shot duration clock. scan_period_start_ (the
// continuous-mode on_scan_end period) is deliberately left alone: a
// start_scan action fired more often than scan_duration_ would otherwise
// suppress on_scan_end indefinitely — and absence detection (ble_rssi's NAN
// publish) rides on that period.
this->scan_start_time_ = millis();
}
void BK72xxBLETracker::stop_scan() {
this->scan_continuous_ = false;
this->scan_requested_ = false; // also cancels a pending (not yet successful) start
@@ -70,7 +70,22 @@ class BK72xxBLETracker : public Component,
void set_scan_interval(uint32_t scan_interval) { this->scan_interval_ = scan_interval; }
void set_scan_window(uint32_t scan_window) { this->scan_window_ = scan_window; }
void set_scan_duration(uint32_t scan_duration) { this->scan_duration_ = scan_duration; }
/// Set from YAML (scan_parameters.continuous); also the value
/// configured_continuous() reports and a bare start_scan action restores.
void set_configured_continuous(bool scan_continuous) {
this->scan_continuous_ = scan_continuous;
this->scan_continuous_configured_ = scan_continuous;
}
/// Runtime control (esp32_ble_tracker lambda parity): does not change the
/// configured value, so configured_continuous() still reports what YAML
/// asked for.
void set_scan_continuous(bool scan_continuous) { this->scan_continuous_ = scan_continuous; }
bool scan_continuous() const { return this->scan_continuous_; }
bool configured_continuous() const { return this->scan_continuous_configured_; }
/// Re-anchor the one-shot duration clock of a running scan to now — used
/// when an action changes the scan mode without stopping the radio. The
/// continuous-mode on_scan_end period is deliberately not touched.
void restart_scan_duration();
// ---- Public scan control ----
// Mirrors esp32_ble_tracker: set_scan_continuous() + start_scan() / stop_scan().
@@ -134,6 +149,7 @@ class BK72xxBLETracker : public Component,
uint32_t scan_window_{48}; // 48 × 0.625 ms = 30 ms (30/100 = 30 %)
uint32_t scan_duration_{300000};
bool scan_continuous_{true};
bool scan_continuous_configured_{true}; // YAML value; stop_scan() must not lose it
#ifdef USE_OTA_STATE_LISTENER
bool scan_continuous_before_ota_{false}; // continuous mode saved at OTA start, restored on OTA failure
bool scan_requested_before_ota_{false}; // pending one-shot latch saved at OTA start, re-latched on OTA failure
@@ -0,0 +1,117 @@
// Platform-neutral BLE advertisement triggers: ESPBTDeviceListener subclasses
// registered on a BLEHub, exposed by each tracker under its own automation
// names. parse_device()'s return feeds the "Found device" suppression.
#pragma once
#include "ble_device.h"
#include "ble_hub.h"
#include "esphome/core/automation.h"
#include "esphome/core/helpers.h"
#include <algorithm>
#include <initializer_list>
namespace esphome::ble_device_base {
// on_ble_advertise: fires on every BLE advertisement, optionally filtered to one or more MACs.
class ESPBTAdvertiseTrigger final : public Trigger<const ESPBTDevice &>, public ESPBTDeviceListener {
public:
explicit ESPBTAdvertiseTrigger(BLEHub *parent) { parent->register_listener(this); }
void set_addresses(std::initializer_list<uint64_t> addresses) { this->addresses_ = addresses; }
bool parse_device(const 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<uint64_t> 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<const adv_data_t &>, public ESPBTDeviceListener {
public:
explicit BLEServiceDataAdvertiseTrigger(BLEHub *parent) { parent->register_listener(this); }
void set_service_uuid16(uint64_t uuid) { this->uuid_ = ESPBTUUID::from_uint16(static_cast<uint16_t>(uuid)); }
void set_service_uuid32(uint64_t uuid) { this->uuid_ = ESPBTUUID::from_uint32(static_cast<uint32_t>(uuid)); }
void set_service_uuid128(const uint8_t *uuid) { this->uuid_ = ESPBTUUID::from_raw(uuid); }
void set_address(uint64_t address) {
this->address_ = address;
this->has_address_ = true;
}
bool parse_device(const 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:
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<const adv_data_t &>, public ESPBTDeviceListener {
public:
explicit BLEManufacturerDataAdvertiseTrigger(BLEHub *parent) { parent->register_listener(this); }
void set_manufacturer_uuid16(uint64_t uuid) { this->uuid_ = ESPBTUUID::from_uint16(static_cast<uint16_t>(uuid)); }
void set_manufacturer_uuid32(uint64_t uuid) { this->uuid_ = ESPBTUUID::from_uint32(static_cast<uint32_t>(uuid)); }
void set_manufacturer_uuid128(const uint8_t *uuid) { this->uuid_ = ESPBTUUID::from_raw(uuid); }
void set_address(uint64_t address) {
this->address_ = address;
this->has_address_ = true;
}
bool parse_device(const 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:
ESPBTUUID uuid_{};
uint64_t address_{0};
bool has_address_{false};
};
// on_scan_end: fires whenever a scan period ends (duration elapsed or stop
// requested). A listener whose on_scan_end() hook fires the trigger — never
// claims devices (parse_device always returns false).
class BLEEndOfScanTrigger final : public Trigger<>, public ESPBTDeviceListener {
public:
explicit BLEEndOfScanTrigger(BLEHub *parent) { parent->register_listener(this); }
bool parse_device(const ESPBTDevice &device) override { return false; }
void on_scan_end() override { this->trigger(); }
};
} // namespace esphome::ble_device_base
@@ -0,0 +1,128 @@
"""Shared codegen for the neutral BLE advertisement triggers (automation.h)."""
from typing import Any
from esphome import automation
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_MAC_ADDRESS, CONF_TRIGGER_ID
from esphome.cpp_generator import MockObjClass
from esphome.types import ConfigType
from . import (
BT_UUID16_FORMAT,
BT_UUID32_FORMAT,
BT_UUID128_FORMAT,
LISTENER_COUNT_DEFINE,
as_hex,
as_reversed_hex_array,
ble_device_base_ns,
)
adv_data_t = cg.std_vector.template(cg.uint8)
adv_data_t_const_ref = adv_data_t.operator("ref").operator("const")
ESPBTDeviceConstRef = (
ble_device_base_ns.class_("ESPBTDevice").operator("ref").operator("const")
)
ESPBTAdvertiseTrigger = ble_device_base_ns.class_(
"ESPBTAdvertiseTrigger", automation.Trigger.template(ESPBTDeviceConstRef)
)
BLEServiceDataAdvertiseTrigger = ble_device_base_ns.class_(
"BLEServiceDataAdvertiseTrigger", automation.Trigger.template(adv_data_t_const_ref)
)
BLEManufacturerDataAdvertiseTrigger = ble_device_base_ns.class_(
"BLEManufacturerDataAdvertiseTrigger",
automation.Trigger.template(adv_data_t_const_ref),
)
BLEEndOfScanTrigger = ble_device_base_ns.class_(
"BLEEndOfScanTrigger", automation.Trigger.template()
)
# 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(BT_UUID16_FORMAT): "16",
len(BT_UUID32_FORMAT): "32",
len(BT_UUID128_FORMAT): "128",
}
def uuid_trigger_schema(
trigger_class: MockObjClass, extra: dict[Any, Any] | None = None
):
"""Schema for a UUID-filtered trigger — pairs with uuid_trigger_to_code().
`extra` carries the required UUID key (a cv marker, so a dict rather than
**kwargs); the optional single-mac filter is what uuid_trigger_to_code()
reads back.
"""
return automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(trigger_class),
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
**(extra or {}),
}
)
def advertise_trigger_schema(trigger_class: MockObjClass):
"""on_ble_advertise schema: multi-mac list filter, unlike the single-mac
uuid_trigger_schema() — pairs with advertise_trigger_to_code()."""
return automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(trigger_class),
cv.Optional(CONF_MAC_ADDRESS): cv.ensure_list(cv.mac_address),
}
)
def scan_end_trigger_schema(trigger_class: MockObjClass):
"""on_scan_end schema: id only — pairs with scan_end_trigger_to_code()."""
return automation.validate_automation(
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(trigger_class)}
)
# Triggers register as ble_device_base listeners in their constructors; count
# them where they are created so no backend can undercount the StaticVector
# (push_back past capacity drops silently). Shares the define with
# register_ble_device() via the core slot-counter factory.
_count_listener = cg.slot_counter(LISTENER_COUNT_DEFINE)
async def advertise_trigger_to_code(conf: ConfigType, var: cg.MockObj) -> None:
"""Build an on_ble_advertise trigger (optional multi-mac filter)."""
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()
async def scan_end_trigger_to_code(conf: ConfigType, var: cg.MockObj) -> None:
"""Build an on_scan_end trigger."""
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
_count_listener()
async def uuid_trigger_to_code(
conf: ConfigType, var: cg.MockObj, key: str, setter_prefix: str
) -> None:
"""Build a UUID-filtered advertise trigger.
The UUID width picks the setter: 16-/32-bit go out as a plain hex literal,
128-bit as a reversed byte array (BLE wire order).
"""
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
uuid = conf[key]
width = _UUID_WIDTHS[len(uuid)]
value = as_hex(uuid) if width != "128" else as_reversed_hex_array(uuid)
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()
+1
View File
@@ -27,6 +27,7 @@ CONF_LOOP = "loop"
CONF_NOX_INDEX = "nox_index"
CONF_ON_PACKET = "on_packet"
CONF_ON_RECEIVE = "on_receive"
CONF_ON_SCAN_END = "on_scan_end"
CONF_ON_STATE_CHANGE = "on_state_change"
CONF_PARITY = "parity"
CONF_RECEIVER_FREQUENCY = "receiver_frequency"
@@ -5,7 +5,7 @@ import logging
from esphome import automation
import esphome.codegen as cg
from esphome.components import ble_device_base, esp32_ble, ota
from esphome.components.const import CONF_SCAN_PARAMETERS, CONF_WINDOW
from esphome.components.const import CONF_ON_SCAN_END, CONF_SCAN_PARAMETERS, CONF_WINDOW
from esphome.components.esp32 import (
add_idf_sdkconfig_option,
request_bluetooth,
@@ -44,7 +44,6 @@ DEPENDENCIES = ["esp32"]
CODEOWNERS = ["@bdraco"]
CONF_ESP32_BLE_ID = "esp32_ble_id"
CONF_ON_SCAN_END = "on_scan_end"
CONF_SOFTWARE_COEXISTENCE = "software_coexistence"
_LOGGER = logging.getLogger(__name__)
@@ -0,0 +1,39 @@
esphome:
name: bk-trigger-codegen
on_boot:
then:
- bk72xx_ble_tracker.start_scan:
continuous: true
- bk72xx_ble_tracker.stop_scan
bk72xx:
board: cb2s
bk72xx_ble_tracker:
on_ble_advertise:
- mac_address:
- AC:37:43:77:5F:4C
- 11:22:33:44:55:66
then:
- lambda: 'ESP_LOGD("t", "%s", x.address_str().c_str());'
on_ble_service_data_advertise:
- service_uuid: ABCDABCD-ABCD-ABCD-ABCD-ABCDABCDABCD
mac_address: AC:37:43:77:5F:4C
then:
- lambda: 'ESP_LOGD("t", "%zu", x.size());'
- service_uuid: ABCDABCD
then:
- lambda: 'ESP_LOGD("t", "%zu", x.size());'
on_ble_manufacturer_data_advertise:
- manufacturer_id: ABCD
then:
- lambda: 'ESP_LOGD("t", "%zu", x.size());'
- manufacturer_id: ABCDABCD
then:
- lambda: 'ESP_LOGD("t", "%zu", x.size());'
- manufacturer_id: ABCDABCD-ABCD-ABCD-ABCD-ABCDABCDABCD
then:
- lambda: 'ESP_LOGD("t", "%zu", x.size());'
on_scan_end:
- then:
- lambda: 'ESP_LOGD("t", "end");'
@@ -0,0 +1,54 @@
"""Codegen tests for the tracker automations.
The shared trigger classes (ble_device_base/automation.h) are compiled by every
esp32 BLE compile test via AUTO_LOAD, but the BK-specific side — automation.h's
action templates and restart_scan_duration() — compiles on no CI board (the
bk72xx base board generic-bk7252 is BLE 4.2 and cannot build the tracker), and
validate fixtures never run to_code. The generated main is therefore the only
automated check on the setter spellings and the listener accounting."""
from collections.abc import Callable
from pathlib import Path
import re
from esphome.components import ble_device_base
from tests.component_tests.helpers import get_define_value
def test_trigger_codegen(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
main_cpp = generate_main(component_config_path("test_automations.yaml"))
# on_ble_advertise: multi-mac filter (two addresses in one initializer list)
assert "set_addresses({0xAC3743775F4CULL, 0x112233445566ULL})" in main_cpp
# 128-bit service uuid goes out reversed (BLE wire order); single-mac filter
assert (
"set_service_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
)
assert "set_address(0xAC3743775F4CULL)" in main_cpp
# 32-bit middle branch of the width dispatch
assert "set_service_uuid32(0xABCDABCDULL)" in main_cpp
# All three manufacturer widths: getattr() builds these names as strings,
# so a misspelling only ever fails here.
assert "set_manufacturer_uuid16(0xABCDULL)" in main_cpp
assert "set_manufacturer_uuid32(0xABCDABCDULL)" in main_cpp
assert (
"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
assert "startscanaction_id->set_continuous(" in main_cpp
assert "stopscanaction_id->set_parent(" 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+\) ble_device_base::BLEEndOfScanTrigger\(\w+\)", main_cpp
)
# Seven triggers register as listeners; an undercount silently drops the
# last trigger at runtime (StaticVector::push_back past capacity), so the
# define is the assertion that matters most.
assert get_define_value(ble_device_base.LISTENER_COUNT_DEFINE) == "7"
@@ -0,0 +1,52 @@
packages:
bk72xx_ble_tracker: !include common.yaml
esphome:
on_boot:
then:
- bk72xx_ble_tracker.start_scan
- bk72xx_ble_tracker.start_scan:
continuous: true
- bk72xx_ble_tracker.stop_scan
- bk72xx_ble_tracker.stop_scan: ble_tracker
bk72xx_ble_tracker:
on_ble_advertise:
- mac_address: AC:37:43:77:5F:4C
then:
- lambda: |-
ESP_LOGD("main", "The device address is %s", x.address_str().c_str());
- mac_address:
- AC:37:43:77:5F:4C
- AC:37:43:77:5F:4D
then:
- lambda: |-
ESP_LOGD("main", "The device address is %s", x.address_str().c_str());
on_ble_service_data_advertise:
- service_uuid: ABCD
# mac_address exercises the UUID triggers' set_address() codegen branch.
mac_address: AC:37:43:77:5F:4C
then:
- lambda: |-
ESP_LOGD("main", "Length of service data is %zu", x.size());
- service_uuid: ABCDABCD
then:
- lambda: |-
ESP_LOGD("main", "32-bit service data is %zu", x.size());
- service_uuid: ABCDABCD-ABCD-ABCD-ABCD-ABCDABCDABCD
then:
- lambda: |-
ESP_LOGD("main", "128-bit service data is %zu", x.size());
on_ble_manufacturer_data_advertise:
- manufacturer_id: ABCD
then:
- lambda: |-
ESP_LOGD("main", "Length of manufacturer data is %zu", x.size());
- manufacturer_id: ABCDABCD-ABCD-ABCD-ABCD-ABCDABCDABCD
then:
- lambda: |-
ESP_LOGD("main", "128-bit manufacturer data is %zu", x.size());
on_scan_end:
- then:
- lambda: |-
ESP_LOGD("main", "Scan ended");