mirror of
https://github.com/esphome/esphome.git
synced 2026-08-22 22:26:21 +00:00
[ln882h_ble] Scan primitives and main-task scan report queue (#17835)
This commit is contained in:
@@ -12,6 +12,7 @@ component does (LibreTiny v1.13.0+).
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ID
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.types import ConfigType
|
||||
|
||||
DEPENDENCIES = ["ln882x"]
|
||||
@@ -31,6 +32,23 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
KEY_SCAN_LISTENER_COUNT = "ln882h_ble_scan_listener_count"
|
||||
|
||||
|
||||
def request_scan_listener_slot() -> None:
|
||||
"""Called from a consumer's codegen once per registered scan listener; sizes
|
||||
the controller's StaticVector listener storage (heap-free, mirrors the
|
||||
tracker's ble_device_base listener storage)."""
|
||||
CORE.data[KEY_SCAN_LISTENER_COUNT] = CORE.data.get(KEY_SCAN_LISTENER_COUNT, 0) + 1
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.FINAL)
|
||||
async def _add_listener_count() -> None:
|
||||
# FINAL: every consumer's to_code has requested its slot by now.
|
||||
if count := CORE.data.get(KEY_SCAN_LISTENER_COUNT, 0):
|
||||
cg.add_define("LN882H_BLE_SCAN_LISTENER_COUNT", count)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
@@ -46,3 +64,5 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add_platformio_option("custom_options.proj_config#h", ["CFG_SUPPORT_BLE=1"])
|
||||
|
||||
cg.add_define("USE_LN882H_BLE")
|
||||
|
||||
CORE.add_job(_add_listener_count)
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
// ln882h_ble.cpp
|
||||
//
|
||||
// BLE controller support for the LN882H (LibreTiny lightning-ln882h family) —
|
||||
// the platform analog of esp32_ble / rp2040_ble. Owns the one-time stack
|
||||
// bring-up (rw_init + the ln_* app init sequence) and the controller BLE
|
||||
// address (persistent KV entry, WiFi-MAC-derived once). Consumers
|
||||
// (ln882h_ble_tracker) build on this component and contain no SDK calls of
|
||||
// their own.
|
||||
// the platform analog of esp32_ble / rp2040_ble. Owns everything that talks to
|
||||
// the LN882H BLE SDK:
|
||||
// - one-time stack bring-up (rw_init + the ln_* app init sequence),
|
||||
// - the controller BLE address (persistent KV entry, WiFi-MAC-derived once),
|
||||
// - the raw controller scan primitives (ln_ble_scan_start/stop),
|
||||
// - the scan-report ring: the SDK's rw-task event callback decodes each
|
||||
// report (including the controller's RSSI sign quirk) into a fixed pool
|
||||
// and pushes it on a lock-free SPSC queue; loop() drains, dispatches on
|
||||
// the main task and returns reports to the pool — the same EventPool +
|
||||
// LockFreeQueue handoff esp32_ble uses, zero allocation at steady state.
|
||||
// Consumers contain no SDK calls of their own.
|
||||
//
|
||||
// BLE stack init and scan lifecycle mirror the SDK's ble_app usage. The BLE
|
||||
// stack itself is compiled and linked by the LibreTiny lightning-ln882h builder
|
||||
@@ -51,6 +57,9 @@ void ln_ble_scan_actv_creat(void);
|
||||
void ln_ble_scan_start(void *scan_param);
|
||||
void ln_ble_scan_stop(void);
|
||||
|
||||
using ble_evt_cb_t = void (*)(void *param);
|
||||
void ln_ble_evt_mgr_reg_evt(int evt_id, ble_evt_cb_t cb);
|
||||
|
||||
} // extern "C"
|
||||
|
||||
// ln_bd_addr_v_t mirrors the SDK's ln_bd_addr_t (ln_ble_app_defines.h) and is
|
||||
@@ -64,9 +73,10 @@ static_assert(alignof(struct ln_bd_addr_v_t) == 1, "ln_bd_addr_v_t must stay byt
|
||||
// CLK_G_BLE — hal/hal_clock.h clock gate bit for the BLE block
|
||||
// BLE_EVT_ID_SCAN_REPORT — ble/ble_evt.h event id for scan reports
|
||||
// GAPM_* — ble/mac/ble/hl/api/gapm_task.h, enums gapm_scan_type /
|
||||
// gapm_dup_filter_pol / gapm_scan_prop
|
||||
// gapm_dup_filter_pol / gapm_scan_prop / gapm_adv_report_info
|
||||
// ---------------------------------------------------------------------------
|
||||
static constexpr uint32_t CLK_G_BLE = 1u << 0;
|
||||
static constexpr int BLE_EVT_ID_SCAN_REPORT = 3;
|
||||
|
||||
// WiFi/BLE packet-traffic-indication (PTI) arbitration register. The LN882H SDK
|
||||
// exposes no symbolic name for this register; the address and value replicate
|
||||
@@ -93,6 +103,20 @@ static constexpr uint8_t GAPM_SCAN_TYPE_OBSERVER = 2;
|
||||
static constexpr uint8_t GAPM_DUP_FILT_DIS = 0;
|
||||
// gapm_scan_prop bits: PHY_1M = 1<<0, PHY_CODED = 1<<1, ACTIVE_1M = 1<<2, ACTIVE_CODED = 1<<3.
|
||||
static constexpr uint8_t GAPM_SCAN_PROP_PHY_1M_BIT = 1 << 0;
|
||||
static constexpr uint8_t GAPM_SCAN_PROP_ACTIVE_1M_BIT = 1 << 2;
|
||||
|
||||
// GAPM extended-advertising report types (bits 2:0 of ble_scan_report_t::info).
|
||||
// 0 = ADV_EXT (extended advertisement), 1 = ADV_LEG (legacy advertisement),
|
||||
// 2 = SCAN_RSP_EXT (scan response to extended adv), 3 = SCAN_RSP_LEG (scan response to legacy adv).
|
||||
static constexpr uint8_t GAPM_REPORT_TYPE_SCAN_RSP_EXT = 2;
|
||||
static constexpr uint8_t GAPM_REPORT_TYPE_SCAN_RSP_LEG = 3;
|
||||
// Bit 5 of ble_scan_report_t::info: the advertisement is scannable, i.e. a scan
|
||||
// response may follow (enum gapm_adv_report_info, GAPM_REPORT_INFO_SCAN_ADV_BIT).
|
||||
static constexpr uint8_t GAPM_REPORT_INFO_SCAN_ADV_BIT = 1u << 5;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SDK struct layouts
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Scan parameter block passed to ln_ble_scan_start(); mirrors the SDK layout,
|
||||
// with the pad byte explicit so the whole block zero-initialises.
|
||||
@@ -110,6 +134,29 @@ struct le_scan_parameters_t { // NOLINT(readability-identifier-naming) - mirror
|
||||
static_assert(sizeof(le_scan_parameters_t) == 8, "le_scan_parameters_t must match the SDK layout");
|
||||
static_assert(offsetof(le_scan_parameters_t, scan_intv) == 4, "unexpected padding in le_scan_parameters_t");
|
||||
|
||||
// Scan report delivered by the BLE_EVT_ID_SCAN_REPORT event. Layout verified on
|
||||
// hardware against the prebuilt BLE stack LibreTiny links: its report carries no
|
||||
// PHY fields and stores the advertisement data inline (flexible array), unlike
|
||||
// the newer upstream SDK header (which adds phy_prim/phy_second and a data pointer).
|
||||
struct ble_scan_report_t { // NOLINT(readability-identifier-naming) - mirrors the SDK type name
|
||||
uint8_t actv_idx;
|
||||
uint8_t info;
|
||||
uint8_t trans_addr_type;
|
||||
uint8_t trans_addr[6];
|
||||
uint8_t target_addr_type;
|
||||
uint8_t target_addr[6];
|
||||
int8_t tx_pwr;
|
||||
int8_t rssi; // signed dBm, range -127..+20 (ble_evt_scan_report_t from ln_ble_event_manager.h)
|
||||
uint16_t length;
|
||||
uint8_t data[0];
|
||||
};
|
||||
// Pin the layout of the hand-mirrored report struct too: the comment above
|
||||
// notes a newer SDK header uses a different layout (PHY fields + data pointer),
|
||||
// so silent drift here would corrupt every decoded advertisement.
|
||||
static_assert(sizeof(ble_scan_report_t) == 20, "ble_scan_report_t must match the linked BLE stack's layout");
|
||||
static_assert(offsetof(ble_scan_report_t, length) == 18, "unexpected padding in ble_scan_report_t");
|
||||
static_assert(offsetof(ble_scan_report_t, data) == 20, "advertisement data must follow the header inline");
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// __sprintf weak stub
|
||||
//
|
||||
@@ -133,11 +180,86 @@ namespace esphome::ln882h_ble {
|
||||
|
||||
static const char *const TAG = "ln882h_ble";
|
||||
|
||||
// The SDK event callback is a plain C function pointer with no user argument,
|
||||
// so it reaches the (single) component instance through a file-static pointer.
|
||||
static LN882HBLE *s_ble = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
// Scan parameter blocks handed to ln_ble_scan_start(void *). static storage:
|
||||
// the SDK may retain the pointer past the call (the block travels into a GAPM
|
||||
// message consumed later by the rw task), so a stack-local would leave the
|
||||
// controller reading a dead frame. Double-buffered: consecutive starts (the
|
||||
// enable() probe followed by the first real scan, or a parameter restart)
|
||||
// alternate blocks, so a rewrite can never race a previous block that is still
|
||||
// in flight — correct under either reading of SDK retention. All writers run
|
||||
// on the main task.
|
||||
static le_scan_parameters_t s_scan_params[2]{}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static uint8_t s_scan_params_idx = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
static le_scan_parameters_t *next_scan_params() {
|
||||
s_scan_params_idx ^= 1;
|
||||
return &s_scan_params[s_scan_params_idx];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Scan-report event callback — runs in the SDK's rw task context.
|
||||
// Decode the report (hardware-verified struct layout + the RSSI sign fix),
|
||||
// copy it into the queue and return; all dispatch happens in loop() on the
|
||||
// main task.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void ble_scan_callback(void *param) {
|
||||
if (s_ble == nullptr || param == nullptr)
|
||||
return;
|
||||
const auto *info = reinterpret_cast<const ble_scan_report_t *>(param);
|
||||
|
||||
// Fill the pool slot in place (the bk72xx_ble shape): no report on the rw
|
||||
// task's stack — its size is fixed by the prebuilt stack — one copy of the
|
||||
// payload instead of two, and only data_len bytes ever leave this frame.
|
||||
BLEScanReport *slot = s_ble->allocate_scan_report();
|
||||
if (slot == nullptr)
|
||||
return; // pool exhausted — counted as dropped in allocate_scan_report()
|
||||
|
||||
const uint8_t report_type = info->info & 0x07;
|
||||
|
||||
// BLE RSSI sign fix. The LN882H controller intermittently reports the RSSI with
|
||||
// a flipped sign: a real -58 dBm arrives as +58, above the SDK's documented
|
||||
// -127..+20 dBm maximum. Recover it by negating any value above +20 (verified
|
||||
// on-device: the out-of-range positives cluster at the magnitude of each
|
||||
// device's real readings). This is the ONLY LN882H-specific RSSI handling —
|
||||
// downstream the value is used exactly like on ESP32.
|
||||
const int8_t raw = info->rssi;
|
||||
|
||||
memcpy(slot->mac, info->trans_addr, 6);
|
||||
slot->rssi = (raw > 20) ? static_cast<int8_t>(-raw) : raw;
|
||||
slot->addr_type = info->trans_addr_type;
|
||||
slot->is_scan_response = report_type == GAPM_REPORT_TYPE_SCAN_RSP_EXT || report_type == GAPM_REPORT_TYPE_SCAN_RSP_LEG;
|
||||
slot->scannable = (info->info & GAPM_REPORT_INFO_SCAN_ADV_BIT) != 0;
|
||||
slot->data_len = (info->length <= sizeof(slot->data)) ? static_cast<uint8_t>(info->length)
|
||||
: static_cast<uint8_t>(sizeof(slot->data));
|
||||
memcpy(slot->data, info->data, slot->data_len);
|
||||
|
||||
s_ble->push_scan_report(slot);
|
||||
}
|
||||
|
||||
BLEScanReport *LN882HBLE::allocate_scan_report() {
|
||||
BLEScanReport *slot = this->report_pool_.allocate();
|
||||
if (slot == nullptr) {
|
||||
// Pool exhausted — the queue is full; count and drop.
|
||||
this->report_queue_.increment_dropped_count();
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
|
||||
void LN882HBLE::push_scan_report(BLEScanReport *report) {
|
||||
// Cannot fail: the pool is sized to the queue capacity.
|
||||
this->report_queue_.push(report);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Component lifecycle
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void LN882HBLE::setup() {
|
||||
s_ble = this;
|
||||
// Resolve the MAC early so get_mac_lsb_first() is valid for consumers before
|
||||
// the stack is up. The KV load also happens here (no stack dependency).
|
||||
this->resolve_mac_();
|
||||
@@ -175,23 +297,48 @@ void LN882HBLE::enable() {
|
||||
delay(10);
|
||||
|
||||
// Prime the scan activity with a short probe start/stop — the SDK's scan
|
||||
// manager completes activity creation on the first start.
|
||||
// static: its address is handed to ln_ble_scan_start(void *), which may
|
||||
// retain it past this call.
|
||||
static le_scan_parameters_t probe_p{};
|
||||
probe_p.type = GAPM_SCAN_TYPE_OBSERVER;
|
||||
probe_p.prop = GAPM_SCAN_PROP_PHY_1M_BIT;
|
||||
probe_p.dup_filt_pol = GAPM_DUP_FILT_DIS;
|
||||
probe_p.scan_intv = 160;
|
||||
probe_p.scan_wd = 16;
|
||||
ln_ble_scan_start(&probe_p);
|
||||
// manager completes activity creation on the first start. Uses the shared
|
||||
// static parameter block (see s_scan_params for the lifetime rationale).
|
||||
le_scan_parameters_t *probe = next_scan_params();
|
||||
probe->type = GAPM_SCAN_TYPE_OBSERVER;
|
||||
probe->prop = GAPM_SCAN_PROP_PHY_1M_BIT;
|
||||
probe->dup_filt_pol = GAPM_DUP_FILT_DIS;
|
||||
probe->scan_intv = 160;
|
||||
probe->scan_wd = 16;
|
||||
ln_ble_scan_start(probe);
|
||||
delay(10);
|
||||
ln_ble_scan_stop();
|
||||
|
||||
// Register the scan-report event exactly once, after the event manager is up.
|
||||
// Repeated registration corrupts the SDK's event registry (verified on
|
||||
// hardware), which is why this lives here and not in scan_start().
|
||||
ln_ble_evt_mgr_reg_evt(BLE_EVT_ID_SCAN_REPORT, ble_scan_callback);
|
||||
|
||||
this->state_ = BLEComponentState::ACTIVE;
|
||||
ESP_LOGD(TAG, "BLE stack initialised");
|
||||
}
|
||||
|
||||
void LN882HBLE::loop() {
|
||||
// Drain the lock-free ring filled by the rw task; all per-report work runs
|
||||
// here on the main task, then the report returns to the pool.
|
||||
BLEScanReport *report = this->report_queue_.pop();
|
||||
if (report == nullptr)
|
||||
return;
|
||||
do {
|
||||
#ifdef LN882H_BLE_SCAN_LISTENER_COUNT
|
||||
for (auto *listener : this->scan_listeners_)
|
||||
listener->on_scan_report(*report);
|
||||
#endif
|
||||
this->report_pool_.release(report);
|
||||
} while ((report = this->report_queue_.pop()) != nullptr);
|
||||
|
||||
// Log dropped reports — only reachable when reports were processed; drops can
|
||||
// only occur while the queue is full, and only this loop drains it.
|
||||
uint16_t dropped = this->report_queue_.get_and_reset_dropped_count();
|
||||
if (dropped > 0)
|
||||
ESP_LOGW(TAG, "Dropped %u scan reports due to queue overflow", dropped);
|
||||
}
|
||||
|
||||
void LN882HBLE::get_mac_lsb_first(uint8_t out[6]) const { memcpy(out, this->ble_mac_, sizeof(this->ble_mac_)); }
|
||||
|
||||
void LN882HBLE::dump_config() {
|
||||
@@ -246,6 +393,50 @@ void LN882HBLE::resolve_mac_() {
|
||||
memcpy(this->ble_mac_, bt_addr.addr, 6);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Controller scan primitives
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void LN882HBLE::scan_start(uint16_t interval, uint16_t window, bool active) {
|
||||
if (!this->is_active())
|
||||
this->enable();
|
||||
|
||||
if (this->scanning_) {
|
||||
// Already scanning - stop first so this call cleanly restarts with the new
|
||||
// parameters (re-entry guard). Give the GAPM stop the same settle time
|
||||
// enable() grants between consecutive GAPM operations before restarting.
|
||||
this->scan_stop();
|
||||
delay(10); // NOLINT — restart-only, mirrors enable()'s inter-operation settle
|
||||
}
|
||||
|
||||
// Double-buffered static block — see s_scan_params for the lifetime rationale.
|
||||
le_scan_parameters_t *p = next_scan_params();
|
||||
p->dup_filt_pol = GAPM_DUP_FILT_DIS;
|
||||
p->type = GAPM_SCAN_TYPE_OBSERVER;
|
||||
p->scan_intv = interval;
|
||||
p->scan_wd = window;
|
||||
// Legacy 1M PHY only: consumers size their buffers for legacy advertisements
|
||||
// (62 B); coded/extended PHY (up to 255 B) would be silently truncated.
|
||||
p->prop = GAPM_SCAN_PROP_PHY_1M_BIT;
|
||||
if (active)
|
||||
p->prop |= GAPM_SCAN_PROP_ACTIVE_1M_BIT;
|
||||
|
||||
ln_ble_scan_start(p);
|
||||
// ln_ble_scan_start() returns void, so this tracks the requested state, not a
|
||||
// confirmed one — a controller-side failure surfaces as an idle scanner (no
|
||||
// reports), which the consumer's start retry/backoff owns.
|
||||
this->scanning_ = true;
|
||||
}
|
||||
|
||||
void LN882HBLE::scan_stop() {
|
||||
// No-op when idle, as documented: the guard keeps a redundant SDK stop off
|
||||
// the GAPM path (scan_start()'s re-entry guard calls this while scanning).
|
||||
if (!this->scanning_)
|
||||
return;
|
||||
ln_ble_scan_stop();
|
||||
this->scanning_ = false;
|
||||
}
|
||||
|
||||
} // namespace esphome::ln882h_ble
|
||||
|
||||
#endif // USE_LN882H_BLE
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
#ifdef USE_LN882H_BLE
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/event_pool.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/lock_free_queue.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
@@ -16,9 +19,52 @@ enum class BLEComponentState : uint8_t {
|
||||
ACTIVE,
|
||||
};
|
||||
|
||||
/// One scan report from the controller, decoded from the SDK's rw-task event
|
||||
/// (RSSI already sign-corrected).
|
||||
struct BLEScanReport {
|
||||
uint8_t mac[6]; // as the controller delivers it (LSB-first)
|
||||
int8_t rssi; // signed dBm (-127..+20)
|
||||
uint8_t addr_type;
|
||||
bool is_scan_response; // report is a scan response (active scan)
|
||||
bool scannable; // advertisement may be followed by a scan response
|
||||
uint8_t data_len; // bytes valid in data[] (<= 62)
|
||||
// Each report carries ONE frame — a legacy advertisement (<=31 B) or a scan
|
||||
// response (<=31 B) — delivered split, exactly as the SDK reports them. The
|
||||
// TRACKER merges the pair into a single frame before any consumer sees it
|
||||
// (Bluedroid semantics, HubCapabilities::merges_scan_response). 62 is twice
|
||||
// the legacy maximum: defensive headroom for the data_len clamp, and the
|
||||
// same width as the merged framing downstream.
|
||||
uint8_t data[62];
|
||||
|
||||
// EventPool contract: nothing is heap-allocated inside a report.
|
||||
void release() {}
|
||||
};
|
||||
|
||||
/// Consumer interface for controller scan reports. on_scan_report() always runs
|
||||
/// on the ESPHome main task: reports are queued from the SDK's rw task and
|
||||
/// drained by the controller's loop(), so consumers never deal with cross-task
|
||||
/// state (the esp32_ble event-queue pattern).
|
||||
class BLEScanListener {
|
||||
public:
|
||||
virtual void on_scan_report(const BLEScanReport &report) = 0;
|
||||
|
||||
protected:
|
||||
~BLEScanListener() = default; // deletion via this interface is not part of the contract
|
||||
};
|
||||
|
||||
// Maximum reports buffered between the rw task and loop(). Sized from the
|
||||
// measured worst case, not copied: WiFi/BLE coexistence delays rw-task report
|
||||
// delivery by up to ~136 ms on this device (see the tracker's pending-adv
|
||||
// timeout rationale), and a busy 2.4 GHz environment delivers ~200-400
|
||||
// reports/s — a stall plus one loop() interval buffers ~30-60 reports, so 63
|
||||
// usable slots absorb it with margin. ~4.7 KB at high water, reached only
|
||||
// during such stalls.
|
||||
static constexpr uint8_t MAX_SCAN_REPORT_QUEUE_SIZE = 64;
|
||||
|
||||
class LN882HBLE final : public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
@@ -37,12 +83,53 @@ class LN882HBLE final : public Component {
|
||||
/// (the bk72xx sibling exposes the same accessor).
|
||||
void get_mac_lsb_first(uint8_t out[6]) const;
|
||||
|
||||
#ifdef LN882H_BLE_SCAN_LISTENER_COUNT
|
||||
/// Register a consumer for scan reports (delivered on the main task via loop()).
|
||||
/// Storage is codegen-sized: the consumer's codegen requests a slot via
|
||||
/// request_scan_listener_slot(), which emits LN882H_BLE_SCAN_LISTENER_COUNT.
|
||||
void register_scan_listener(BLEScanListener *listener) { this->scan_listeners_.push_back(listener); }
|
||||
#endif
|
||||
|
||||
/// Start the controller scan. Interval/window are in BLE units (0.625 ms);
|
||||
/// active enables scan requests on the 1M PHY. Enables the stack first if
|
||||
/// needed. Scans the legacy 1M PHY only (extended/coded PHY advertisements
|
||||
/// exceed the legacy 62-byte framing consumers are sized for).
|
||||
void scan_start(uint16_t interval, uint16_t window, bool active);
|
||||
/// Stop the controller scan (no-op when not scanning).
|
||||
void scan_stop();
|
||||
|
||||
/// Internal, SDK rw-task event-callback context: allocate a pool slot for a
|
||||
/// scan report. Returns nullptr (and counts the drop) when the queue is full;
|
||||
/// the callback fills the slot in place — no intermediate copy.
|
||||
BLEScanReport *allocate_scan_report();
|
||||
/// Internal: hand a filled slot to the main-task queue (cannot fail — the
|
||||
/// pool is sized to the queue capacity).
|
||||
void push_scan_report(BLEScanReport *report);
|
||||
|
||||
protected:
|
||||
void resolve_mac_();
|
||||
|
||||
#ifdef LN882H_BLE_SCAN_LISTENER_COUNT
|
||||
// Codegen-sized: no heap allocation, no std::vector template instantiation —
|
||||
// the same StaticVector pattern as the tracker's ble_device_base listeners.
|
||||
StaticVector<BLEScanListener *, LN882H_BLE_SCAN_LISTENER_COUNT> scan_listeners_;
|
||||
#endif
|
||||
// Report ring: the SDK event callback (rw task) allocates a report from the
|
||||
// pool, fills it and pushes the pointer; loop() pops, dispatches and releases.
|
||||
// Lock-free SPSC, zero allocation at steady state — the esp32_ble pattern.
|
||||
// Overflow drops the NEWEST report (allocate fails, producer counts and
|
||||
// returns) — under a coexistence stall the freshest advertisements are lost
|
||||
// while queued ones drain. Deliberate: matches esp32_ble, and dropping from
|
||||
// the head would need consumer-side locking this design exists to avoid.
|
||||
esphome::LockFreeQueue<BLEScanReport, MAX_SCAN_REPORT_QUEUE_SIZE> report_queue_;
|
||||
// Pool sized to queue capacity (SIZE-1): the ring reserves one slot, so
|
||||
// allocate() returns nullptr before push() can fail. This prevents leaking a
|
||||
// pool slot on a failed push and keeps release() off the producer path.
|
||||
esphome::EventPool<BLEScanReport, MAX_SCAN_REPORT_QUEUE_SIZE - 1> report_pool_;
|
||||
uint8_t ble_mac_[6]{0}; // controller (LSB-first) order, as ln_bd_addr_t stores it
|
||||
BLEComponentState state_{BLEComponentState::STATE_OFF};
|
||||
bool enable_on_boot_{false};
|
||||
bool scanning_{false}; // controller scan running (re-entry guard for scan_start)
|
||||
};
|
||||
|
||||
} // namespace esphome::ln882h_ble
|
||||
|
||||
@@ -455,6 +455,7 @@
|
||||
#ifdef USE_LIBRETINY
|
||||
#define USE_BK72XX_BLE
|
||||
#define USE_LN882H_BLE
|
||||
#define LN882H_BLE_SCAN_LISTENER_COUNT 1
|
||||
#define USE_CAPTIVE_PORTAL
|
||||
#define USE_WIFI_SCAN_RESULTS_LOCK
|
||||
#define USE_SOCKET_IMPL_LWIP_SOCKETS
|
||||
|
||||
Reference in New Issue
Block a user