mirror of
https://github.com/esphome/esphome.git
synced 2026-08-22 22:26:21 +00:00
[ln882h_ble_tracker] Automation triggers and actions (#17778)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
co-authored by
pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
J. Nick Koston
parent
64f0e38a51
commit
d548454dbe
@@ -2,6 +2,7 @@
|
||||
top of the ln882h_ble controller. With continuous: false nothing scans until
|
||||
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
|
||||
@@ -12,10 +13,19 @@ 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"]
|
||||
@@ -26,6 +36,31 @@ LN882HBLETracker = ln882h_ble_tracker_ns.class_(
|
||||
"LN882HBLETracker", ble_device_base.BLEHub, cg.Component
|
||||
)
|
||||
|
||||
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()
|
||||
)
|
||||
|
||||
|
||||
# LN882H SDK reference scan rate: 100 ms interval / 50 ms window (50 % duty).
|
||||
SCAN_PARAMETERS_SCHEMA = ble_device_base.scan_parameters_schema(
|
||||
@@ -33,15 +68,108 @@ 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_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_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,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_SCAN_END): automation.validate_automation(
|
||||
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(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,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(LN882HBLETracker),
|
||||
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(
|
||||
"ln882h_ble_tracker.stop_scan",
|
||||
StopScanAction,
|
||||
automation.maybe_simple_id(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(LN882HBLETracker),
|
||||
}
|
||||
)
|
||||
),
|
||||
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)
|
||||
@@ -60,4 +188,41 @@ async def to_code(config: ConfigType) -> None:
|
||||
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_active(scan[CONF_ACTIVE]))
|
||||
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, []):
|
||||
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()
|
||||
|
||||
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, []):
|
||||
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)
|
||||
)
|
||||
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()
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
// Automation triggers and actions for ln882h_ble_tracker: triggers follow the
|
||||
// esp32_ble_tracker design; only the scan-control actions are
|
||||
// platform-specific.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
#include "ln882h_ble_tracker.h"
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <initializer_list>
|
||||
|
||||
namespace esphome::ln882h_ble_tracker {
|
||||
|
||||
template<typename... Ts> class StartScanAction final : public Action<Ts...>, public Parented<LN882HBLETracker> {
|
||||
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<LN882HBLETracker> {
|
||||
public:
|
||||
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<const ble_device_base::ESPBTDevice &>,
|
||||
public ble_device_base::ESPBTDeviceListener {
|
||||
public:
|
||||
explicit ESPBTAdvertiseTrigger(LN882HBLETracker *parent) { parent->register_listener(this); }
|
||||
|
||||
void set_addresses(std::initializer_list<uint64_t> 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<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 ble_device_base::adv_data_t &>,
|
||||
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<uint16_t>(uuid));
|
||||
}
|
||||
void set_service_uuid32(uint64_t uuid) {
|
||||
this->uuid_ = ble_device_base::ESPBTUUID::from_uint32(static_cast<uint32_t>(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<const ble_device_base::adv_data_t &>,
|
||||
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<uint16_t>(uuid));
|
||||
}
|
||||
void set_manufacturer_uuid32(uint64_t uuid) {
|
||||
this->uuid_ = ble_device_base::ESPBTUUID::from_uint32(static_cast<uint32_t>(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
|
||||
@@ -22,7 +22,10 @@ void LN882HBLETracker::setup() {
|
||||
// Receive the controller's scan reports; the controller queues them from the
|
||||
// rw task and delivers here on the main task.
|
||||
this->parent_->register_scan_listener(this);
|
||||
if (!this->scan_continuous_) {
|
||||
// scan_running_ check: an on_boot start_scan action (priority 600) runs
|
||||
// before this setup() (200) and enable_loop() is a no-op pre-setup — parking
|
||||
// the loop here would strand that already-running scan.
|
||||
if (!this->scan_continuous_ && !this->scan_running_ && !this->pending_start_) {
|
||||
// Say so once: with continuous: false nothing scans until an explicit
|
||||
// start_scan() — silence here reads as a broken scanner.
|
||||
ESP_LOGD(TAG, "Scanning not started (continuous: false) - waiting for an explicit start_scan()");
|
||||
@@ -61,6 +64,14 @@ void LN882HBLETracker::on_ota_global_state(ota::OTAState state, float progress,
|
||||
#endif // USE_OTA_STATE_LISTENER
|
||||
|
||||
void LN882HBLETracker::loop() {
|
||||
if (this->pending_start_) {
|
||||
// A start_scan latched before the controller's setup(); safe now — loop()
|
||||
// only runs after every component set up.
|
||||
this->pending_start_ = false;
|
||||
if (!this->scan_running_) {
|
||||
this->start_scan_();
|
||||
}
|
||||
}
|
||||
// Flush pending scannable advertisements whose scan response never arrived
|
||||
// (device didn't answer / frame lost) — delivered unmerged after the timeout.
|
||||
// Main-task only, like every consumer of pending_adv_.
|
||||
@@ -263,12 +274,34 @@ void LN882HBLETracker::process_adv_(const uint8_t *mac, int8_t rssi, uint8_t add
|
||||
void LN882HBLETracker::start_scan() {
|
||||
// Mirrors esp32_ble_tracker::start_scan(): caller sets scan_continuous_ via
|
||||
// set_scan_continuous() first, then calls start_scan() to begin scanning.
|
||||
if (!this->parent_->is_ready()) {
|
||||
// An on_boot automation (priority 600) runs before the controller's
|
||||
// setup() has resolved the BLE MAC; scan_start() now would rw_init() the
|
||||
// all-zero address and bring BLE up before WiFi. Latch; loop() applies
|
||||
// the start once every setup() has run.
|
||||
this->pending_start_ = true;
|
||||
return;
|
||||
}
|
||||
if (!this->scan_running_) {
|
||||
this->start_scan_();
|
||||
}
|
||||
}
|
||||
|
||||
void LN882HBLETracker::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 LN882HBLETracker::stop_scan() {
|
||||
// Cancel a start latched before the controller's setup(); without this an
|
||||
// on_boot start_scan/stop_scan pair would still start at the first loop().
|
||||
this->pending_start_ = false;
|
||||
this->scan_continuous_ = false;
|
||||
this->stop_scan_();
|
||||
}
|
||||
@@ -308,7 +341,10 @@ void LN882HBLETracker::stop_scan_() {
|
||||
// scanner failing to come back up.
|
||||
ESP_LOGD(TAG, "BLE scan stopped");
|
||||
this->end_scan_period_(millis()); // also resets the period clock so on_scan_end does not double-fire
|
||||
if (!this->scan_continuous_) {
|
||||
// scan_running_ re-check: an on_scan_end automation runs synchronously inside
|
||||
// end_scan_period_() and may have called start_scan() — parking the loop then
|
||||
// would leave the radio scanning with no period timing or pending-adv sweep.
|
||||
if (!this->scan_continuous_ && !this->scan_running_) {
|
||||
// Nothing left to time; start_scan_() re-enables the loop.
|
||||
this->disable_loop();
|
||||
}
|
||||
|
||||
@@ -52,7 +52,22 @@ class LN882HBLETracker : public Component,
|
||||
void set_scan_interval(uint16_t scan_interval) { this->scan_interval_ = scan_interval; }
|
||||
void set_scan_window(uint16_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().
|
||||
@@ -125,6 +140,8 @@ class LN882HBLETracker : public Component,
|
||||
uint16_t scan_window_{80}; // 80 × 0.625 ms = 50 ms (SDK SCAN_WINDOW_DEF; 50/100 = 50 %)
|
||||
uint32_t scan_duration_{300000};
|
||||
bool scan_continuous_{true};
|
||||
bool pending_start_{false}; // start_scan() latched before the controller's setup()
|
||||
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_running_before_ota_{false}; // one-shot scan running at OTA start, restarted on OTA failure
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
esphome:
|
||||
name: ln-trigger-codegen
|
||||
on_boot:
|
||||
then:
|
||||
- ln882h_ble_tracker.start_scan:
|
||||
continuous: true
|
||||
# Bare form: restores the configured scan_parameters mode — no
|
||||
# set_continuous emitted (asserted in the codegen test).
|
||||
- ln882h_ble_tracker.start_scan:
|
||||
- ln882h_ble_tracker.stop_scan:
|
||||
|
||||
ln882x:
|
||||
board: generic-ln882h
|
||||
|
||||
ln882h_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,52 @@
|
||||
"""Codegen tests for the tracker automations: the generated main is the
|
||||
automated check on the setter calls and the listener accounting (the
|
||||
test.ln882x-ard.yaml compile fixture proves linkage, not codegen shape)."""
|
||||
|
||||
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.
|
||||
# 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
|
||||
# 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
|
||||
)
|
||||
|
||||
# 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,33 @@
|
||||
packages:
|
||||
ln882h_ble_tracker: !include common.yaml
|
||||
|
||||
esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- ln882h_ble_tracker.start_scan
|
||||
- ln882h_ble_tracker.start_scan:
|
||||
continuous: true
|
||||
- ln882h_ble_tracker.start_scan:
|
||||
continuous: !lambda return false;
|
||||
- ln882h_ble_tracker.stop_scan
|
||||
|
||||
ln882h_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());
|
||||
on_ble_service_data_advertise:
|
||||
- service_uuid: ABCD
|
||||
then:
|
||||
- lambda: |-
|
||||
ESP_LOGD("main", "Length of 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());
|
||||
on_scan_end:
|
||||
- then:
|
||||
- lambda: |-
|
||||
ESP_LOGD("main", "Scan ended");
|
||||
Reference in New Issue
Block a user