Skip to content

fix(bthome_receiver): register listener via esp32_ble_tracker helper - #16

Open
magliaral wants to merge 1 commit into
dz0ny:mainfrom
magliaral:main
Open

fix(bthome_receiver): register listener via esp32_ble_tracker helper#16
magliaral wants to merge 1 commit into
dz0ny:mainfrom
magliaral:main

Conversation

@magliaral

Copy link
Copy Markdown

PR: Use register_ble_device() helper so the Bluedroid listener is counted for StaticVector sizing

Suggested title: fix(bthome_receiver): register via esp32_ble_tracker.register_ble_device() so listener is counted for StaticVector sizing


Problem

When bthome_receiver (Bluedroid mode) is used together with other BLE listener
components (e.g. victron_ble, Xiaomi/BTHome sensor platforms, ble_presence, …),
one of the other components silently stops receiving advertisements. No warning,
no error — the affected device is simply deaf. Which component breaks depends on
registration order in the generated main.cpp, so the symptom looks random and is
very hard to diagnose.

Observed in the field: ESPHome 2026.6.5, ESP32-S3, Arduino framework, config with
victron_ble + 1× bthome_receiver. The last-registered victron_ble instance
received no data while its advertisements were demonstrably arriving at the tracker
(verified with esp32_ble_tracker: VERY_VERBOSE). Removing bthome_receiver from
the config made the victron instance work again.

Root cause

Since the 2026.x releases, esp32_ble_tracker stores its listeners in a
fixed-capacity StaticVector sized at compile time:

StaticVector<ESPBTDeviceListener *, ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT> listeners_;

The ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT define is computed during codegen by
counting calls to the official Python helper
esp32_ble_tracker.register_ble_device() (it increments
_get_registration_counts().listeners).

bthome_receiver registers its hub manually, bypassing the counter:

parent = await cg.get_variable(tracker_id)
cg.add(parent.register_listener(var))

The hub therefore performs a runtime register_listener() call that was never
accounted for at compile time. With N other listeners in the config, the vector has
capacity N, but N+1 registrations happen at startup — and StaticVector::push_back
on a full vector silently drops the excess element. Whichever listener registers
last (typically the last platform instance in the YAML) never receives
parse_device() callbacks.

Reproduction / evidence

Config: 3× victron_ble + bthome_receiver (bluedroid, esp32_ble_id set),
esphome compile --only-generate:

src/esphome/core/defines.h:
  #define ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT 3   <-- capacity 3

src/main.cpp:
  ble_tracker->register_listener(bthome_receiver_bthomereceiverhub_id);
  ble_tracker->register_listener(v1);
  ble_tracker->register_listener(v2);
  ble_tracker->register_listener(v3);                  <-- 4th call, silently dropped

Fix

Register through the official helper, which both performs the registration and
increments the compile-time listener count:

tracker_id = config.get(esp32_ble_tracker.CONF_ESP32_BLE_ID)
if tracker_id is not None:
    await esp32_ble_tracker.register_ble_device(var, config)

After the fix, the same test config generates
ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT 4 with 4 registrations — all listeners
get their slot. NimBLE mode is unaffected (the branch is only taken in Bluedroid
mode when a tracker ID is configured).

Testing

  • esphome config passes on ESPHome 2026.6.5 (ESP32-S3, Arduino).
  • esphome compile --only-generate: define now matches the actual number of
    runtime registrations (verified with and without the patch, see above).
  • On-device: with the patched component, 3× victron_ble (SmartShunt, SmartSolar,
    Orion DC-DC) and bthome_receiver (3 Shelly BLU H&T) all receive data
    concurrently on an ESP32-S3.

Related note (separate issue?)

The registration currently only happens when esp32_ble_id is set explicitly in
YAML, because the schema declares the tracker reference as cv.Optional instead of
cv.GenerateID. Without an explicit esp32_ble_id, the Bluedroid receiver
initializes but is never attached to the tracker (deaf, no warning). Happy to file
that separately / include it here if preferred — switching the schema entry to
cv.GenerateID(esp32_ble_tracker.CONF_ESP32_BLE_ID) and dropping the if would fix
both ergonomics and this footgun in one go.


Suggested commit message:

fix(bthome_receiver): register via esp32_ble_tracker.register_ble_device()

The manual parent.register_listener() codegen bypasses the listener
count used to size the tracker's StaticVector
(ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT). With other BLE listener
components present, the vector is one slot too small and the
last-registered listener is silently dropped at runtime, breaking an
unrelated component (e.g. victron_ble) without any log output.

Using the official register_ble_device() helper performs the same
registration and increments the compile-time count.

Manual parent.register_listener() bypassed the listener count used to
size the ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT StaticVector, which
silently dropped the last-registered listener at runtime. Use the
official register_ble_device() helper so the listener is counted.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant