8000
Skip to content

[ble_client] Reject descriptor_uuid combined with notify at validation time - #18109

Merged
bdraco merged 4 commits into
devfrom
ble-client-reject-notify-descriptor
Aug 6, 2026
Merged

[ble_client] Reject descriptor_uuid combined with notify at validation time#18109
bdraco merged 4 commits into
devfrom
ble-client-reject-notify-descriptor

Conversation

@bdraco
@bdraco bdraco commented Aug 5, 2026
Copy link
Copy Markdown
Member

What does this implement/fix?

Using descriptor_uuid together with notify: true on a ble_client sensor or text_sensor has never worked and cannot work; BLE has no descriptor notification PDU and ESP-IDF has no descriptor variant of esp_ble_gattc_register_for_notify, descriptors can only be read or written. At runtime the descriptor handle overwrote the handle used to filter notify events, so the sensor never reached the established state and produced no data. This was the second half of the report in #18096.

Reject the combination at validation time with a message explaining that descriptors can only be polled; on_notify is rejected alongside notify since the automation also never fires without notifications. In addition, on_notify now implies notify: true; previously an on_notify automation without notify validated but never fired because nothing registered for notifications. The descriptor_uuid and on_notify constants and the validators move to the ble_client package so the sensor and text_sensor platforms share them; unit tests cover both validators and the schema wiring.

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • New developer-facing feature (adds functionality for component developers; no end-user configuration change)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected) — policy
  • Developer breaking change (an API change that could break external components) — policy
  • Undocumented C++ API change (removal or change of undocumented public methods that lambda users may depend on) — policy
  • Code quality improvements to existing code or addition of tests
  • Other

Related issue or feature (if applicable):

Pull request in esphome.io with documentation (if applicable):

  • esphome/esphome.io#<esphome.io PR number goes here>

Pull request in developers.esphome.io with developer documentation (if applicable):

  • esphome/developers.esphome.io#<developers.esphome.io PR number goes here>

Test Environment

  • ESP32
  • ESP32 IDF
  • ESP8266
  • RP2040/RP2350
  • BK72xx
  • RTL87xx
  • LN882x
  • nRF52840

Example entry for config.yaml:

# Example config.yaml
ble_client:
  - mac_address: "AA:BB:CC:DD:EE:FF"
    id: myclient

text_sensor:
  - platform: ble_client
    ble_client_id: myclient
    name: "Descriptor value"
    service_uuid: "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
    characteristic_uuid: "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
    descriptor_uuid: "2902"
    # notify: true would now fail validation, descriptors can only be polled

Checklist:

  • The code change is tested and works locally.
  • Tests have been added to verify that the new code works (under tests/ folder).

If user exposed functionality or configuration variables are added/changed:

…n time

The combination has never worked and cannot work; BLE has no descriptor
notification PDU and ESP-IDF has no descriptor variant of
esp_ble_gattc_register_for_notify. At runtime the descriptor handle
overwrote the handle used to filter notify events, so the sensor never
reached the established state. Fail configuration validation instead
with a message explaining descriptors can only be polled. The
descriptor_uuid constant and the validator move to the ble_client
package so both the sensor and text_sensor platforms share them.
@esphome
esphome Bot commented Aug 5, 2026
Copy link
Copy Markdown
Contributor

To use the changes from this PR as an external component, add the following to your ESPHome configuration YAML file:

external_components:
  - source: github://pr#18109
    components: [ble_client]
    refresh: 1h

(Added by the PR bot)

@codecov
codecov Bot commented Aug 5, 2026
Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.25%. Comparing base (26256d3) to head (5e5c317).
⚠️ Report is 1 commits behind head on dev.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##              dev   #18109   +/-   ##
=======================================
  Coverage   87.25%   87.25%           
=======================================
  Files          64       64           
  Lines       14692    14692           
  Branches     2216     2216           
=======================================
  Hits        12820    12820           
  Misses       1565     1565           
  Partials      307      307           
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@bluetoothbot
bluetoothbot commented Aug 5, 2026
Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@bluetoothbot bluetoothbot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

No blocking issues found — ready to merge.

@bdraco
bdraco commented Aug 5, 2026
Copy link
Copy Markdown
Member Author

Previously-accepted YAML now hard-fails — worth a release note and a docs update

That's kinda the point, its a nonsense combination that users shouldn't ever have.

Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a long-standing invalid configuration in the ble_client sensor/text_sensor platforms by rejecting descriptor_uuid used together with notify: true during config validation, preventing a runtime state where notifications never arrive.

Changes:

  • Introduces a shared validate_descriptor_not_notify validator in the ble_client package and applies it to both ble_client sensor and text_sensor schemas.
  • Moves the shared CONF_DESCRIPTOR_UUID constant to the ble_client package so both platforms reuse the same key definition.
  • Adds unit tests covering the new validator behavior.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
esphome/components/ble_client/init.py Adds shared CONF_DESCRIPTOR_UUID and a validator that rejects descriptor_uuid combined with notify.
esphome/components/ble_client/sensor/init.py Reuses the shared constant/validator and applies the validator to the sensor schema.
esphome/components/ble_client/text_sensor/init.py Reuses the shared constant/validator and applies the validator to the text_sensor schema.
tests/component_tests/ble_client/test_validation.py Adds unit tests validating the new rejection/acceptance cases.
tests/component_tests/ble_client/init.py Test package marker for the new ble_client component test directory.

Comment thread esphome/components/ble_client/__init__.py Outdated
bdraco added 2 commits August 5, 2026 17:44
on_notify never fires without notifications either, so steering users
from notify toward on_notify would trade one dead config for another.
Schema level tests now feed full configs through both platform schemas
so a refactor cannot silently drop the validator.
@bluetoothbot
Copy link
Copy Markdown
Contributor

@bdraco: > Previously-accepted YAML now hard-fails — worth a release note and a docs update

Agreed, dropping that finding. Combination never produced data at runtime. Hard failure beats silent no-op. Verified no in-tree YAML uses descriptor_uuid, so nothing breaks in CI.

@bluetoothbot
Copy link
Copy Markdown
Contributor

PR Review — [ble_client] Reject descriptor_uuid combined with notify at validation time

Merge-ready. Both findings from the previous round are resolved, and the one remaining note is a pre-existing footgun in adjacent code.

What is solid here:

  • The validator now covers on_notify as well as notify, and the error message names both remediations ("remove descriptor_uuid to receive characteristic notifications, or remove notify and on_notify to poll the descriptor") — this closes prior finding #2 and @Copilot's inline comment on the same line.
  • Four schema-level tests were added on top of the direct validator tests, so the wiring into ble_client.sensor.CONFIG_SCHEMA and ble_client.text_sensor.CONFIG_SCHEMA is now asserted, not assumed — this closes prior finding #1.
  • I re-confirmed the root cause in the C++: ble_sensor.cpp:78 and ble_text_sensor.cpp:78 overwrite this->handle with the descriptor handle before register_for_notify(chr->handle), so reg_for_notify.handle == this->handle never matches and the node never reaches ESTABLISHED. Rejecting at config time is the right layer.
  • Test hygiene checks out: the two text_sensor schema tests both use name: "test", which trips the duplicate-entity check in entity_helpers.py:637 if CORE.unique_ids leaks between tests. The autouse reset_core fixture in tests/component_tests/conftest.py clears it — I verified both orderings pass with a CORE.reset() in between. tests/component_tests/ble_client/__init__.py matches the layout of the other component test packages.
  • No in-tree YAML uses descriptor_uuid, so no existing component test starts failing.

Remaining note:

  • [Pre-Existing Issue] on_notify without notify: true still validates and never fires — same silent no-op class, one step away from where the new error message sends users. Non-blocking, and reasonable as a follow-up.
  • The earlier "release note / docs update" point is dropped per @bdraco: "That's kinda the point, its a nonsense combination that users shouldn't ever have." Not re-raised.

✅ Resolved since last review (2)

Previously-flagged issues verified fixed
  • tests/component_tests/ble_client/test_validation.py:14 Tests exercise the validator directly, never the two schemas it was added to
  • esphome/components/ble_client/__init__.py:35 descriptor_uuid + on_notify still validates but is silently dead

🟢 Suggestions

1. [Pre-Existing Issue] `on_notify` without `notify: true` is still silently dead
esphome/components/ble_client/__init__.py:36-38

The new validator correctly catches descriptor_uuid + on_notify. The adjacent case it does not catch is on_notify with notify: false (the default) and no descriptor_uuid — that config validates, compiles, and never fires.

Verified in the C++ on dev:

  • esp_ble_gattc_register_for_notify is only ever reached via BLEClientBase::register_for_notify(), and the only two call sites are sensor/ble_sensor.cpp:80 and text_sensor/ble_text_sensor.cpp:80, both inside if (this->notify_).
  • BLESensorNotifyTrigger (sensor/automation.h) and BLETextSensorNotifyTrigger (text_sensor/automation.h) have no registration path of their own — they only compare param->notify.handle against sensor_->handle. With no registration, ESP_GATTC_NOTIFY_EVT never arrives.

Why it matters: this is the same class of silent no-op this PR is fixing, and it is the most likely landing spot for someone following the new error message. A user with descriptor_uuid + on_notify reads "remove descriptor_uuid to receive characteristic notifications", drops the descriptor, and still gets nothing because they never set notify: true.

This predates the changeset, so it is not a blocker for this PR. A natural follow-up (or a cheap addition here, since the validator is already in place) would be to reject or warn on on_notify without notify: true in the same helper.

    if CONF_DESCRIPTOR_UUID in config and (
        config.get(CONF_NOTIFY) or CONF_ON_NOTIFY in config
    ):

Checklist

  • Fix addresses the stated root cause (descriptor handle overwrites notify filter handle)
  • New validation path covered by tests, including schema wiring for both platforms
  • Edge cases covered (type: rssi, on_notify, absent keys)
  • Test isolation — no shared CORE state between the same-name entity tests
  • No existing in-tree config broken by the new rejection
  • Constant placement and re-export keep ble_client.sensor.CONF_DESCRIPTOR_UUID importable
  • No scope creep relative to PR description
  • Adjacent invalid combinations rejected — suggestion #1

Automated review by Kōan (Claude) HEAD=7ff61cb 4 min 40s

@bluetoothbot bluetoothbot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

No blocking issues found — ready to merge.

@bdraco
bdraco marked this pull request as ready for review August 5, 2026 23:10
@bdraco
bdraco requested a review from clydebarrow as a code owner August 5, 2026 23:10
@github-actions
github-actions Bot commented Aug 5, 2026
Copy link
Copy Markdown
Contributor

Memory Impact Analysis

Components: ble_client
Platform: esp32-idf

Metric Target Branch This PR Change
RAM 48,056 bytes 48,056 bytes ➡️ +0 bytes (0.00%)
Flash 659,771 bytes 659,771 bytes ➡️ +0 bytes (0.00%)

Note: This analysis measures static RAM and Flash usage only (compile-time allocation).
Dynamic memory (heap) cannot be measured automatically.
⚠️ You must test this PR on a real device to measure free heap and ensure no runtime memory issues.

This analysis runs automatically when components change. Memory usage is measured from a representative test configuration.

An on_notify automation without notify true validated but never fired
because nothing registered for notifications. Presence of on_notify now
enables notify during validation, so the automation works and the new
error message cannot steer users into a dead configuration.
@esphome
esphome Bot requested a review from buxtronix August 5, 2026 23:17
@esphome
esphome Bot commented Aug 5, 2026
Copy link
Copy Markdown
Contributor

👋 Hi there! I've automatically requested reviews from codeowners based on the files changed in this PR.

@buxtronix, @clydebarrow - You've been requested to review this PR as codeowner(s) of 1 file(s) that were modified. Thanks for your time! 🙏

@bdraco
bdraco commented Aug 6, 2026
Copy link
Copy Markdown
Member Author

thanks

@bdraco
bdraco merged commit 2dfcde4 into dev Aug 6, 2026
38 checks passed
@bdraco
bdraco deleted the ble-client-reject-notify-descriptor branch August 6, 2026 00:09
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 7, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

0