Skip to content

fix: handle BleakError gracefully when reading device name characteristic - #248

Open
TheCheif wants to merge 4 commits into
Arbuzov:masterfrom
TheCheif:fix-bleak-device-name-error
Open

fix: handle BleakError gracefully when reading device name characteristic#248
TheCheif wants to merge 4 commits into
Arbuzov:masterfrom
TheCheif:fix-bleak-device-name-error

Conversation

@TheCheif

@TheCheif TheCheif commented Aug 10, 2026

Copy link
Copy Markdown

On some Bluetooth adapters, operating systems, or specific models of the DeLonghi PrimaDonna, the standard GATT Device Name characteristic
(00002A00-0000-1000-8000-00805F9B34FB) is either not readable, not exposed, or blocked by the host controller.

Currently, if read_gatt_char throws a BleakError while attempting to read this characteristic in get_device_name(), the exception is caught
by the outer block which sets self.connected = False. This results in the integration constantly flagging itself as disconnected and looping setup
attempts, despite successfully connecting and being able to write to the control characteristic.

Solution

This PR wraps the standard GATT name retrieval in a localized try/except block. If reading the name fails:

  1. It logs an info message.
  2. It falls back to the configured device name (self.name) or a default string so self.hostname is populated.
  3. It then continues and successfully writes the initialization commands and marks self.connected = True.

Summary by Sourcery

Handle failures when reading the Bluetooth GATT device name without marking the device as disconnected.

Bug Fixes:

  • Prevent BleakError raised during NAME_CHARACTERISTIC reads from causing the integration to report the device as disconnected and loop reconnection attempts.

Enhancements:

  • Fall back to a configured or default device name when the GATT device name characteristic is unreadable.
  • Add a constant for a default DeLonghi PrimaDonna device name used when the Bluetooth name cannot be read.

@sourcery-ai

sourcery-ai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Handles failures when reading the Bluetooth GATT Device Name characteristic so the device remains marked as connected, adding a default device name fallback and localized error handling around the name read logic.

Sequence diagram for updated get_device_name Bluetooth interaction

sequenceDiagram
    actor HomeAssistant
    participant DelonghiDevice
    participant BleakClient as _client

    HomeAssistant->>DelonghiDevice: get_device_name()
    activate DelonghiDevice
    DelonghiDevice->>DelonghiDevice: _lock acquisition
    DelonghiDevice->>DelonghiDevice: _connect()

    alt NAME_CHARACTERISTIC readable
        DelonghiDevice->>BleakClient: read_gatt_char(NAME_CHARACTERISTIC)
        BleakClient-->>DelonghiDevice: name_bytes
        DelonghiDevice->>DelonghiDevice: hostname = name_bytes.decode('utf-8')
    else BleakError while reading name
        DelonghiDevice->>DelonghiDevice: _LOGGER.debug(...)
        DelonghiDevice->>DelonghiDevice: hostname = self.name or DEFAULT_DEVICE_NAME
    end

    DelonghiDevice->>BleakClient: write_gatt_char(CONTROLL_CHARACTERISTIC, DEBUG)
    BleakClient-->>DelonghiDevice: ack
    DelonghiDevice-->>HomeAssistant: hostname
    deactivate DelonghiDevice
Loading

File-Level Changes

Change Details Files
Localize BleakError handling when reading the Bluetooth GATT name characteristic and fall back to a configured or default device name.
  • Wrap the GATT device name read in an inner try/except block inside get_device_name instead of letting BleakError propagate to the outer connection error handler.
  • On BleakError when reading the name characteristic, log a debug message and set hostname to self.name or a default device name constant.
  • Keep the connection sequence (_connect and initialization write to the control characteristic) intact so self.connected can remain True when the name read fails.
custom_components/delonghi_primadonna/device.py
Introduce a default device name constant for use when the Bluetooth GATT name characteristic is unavailable.
  • Add DEFAULT_DEVICE_NAME constant with a string value for the DeLonghi PrimaDonna.
  • Export DEFAULT_DEVICE_NAME alongside existing constants for use by device logic.
custom_components/delonghi_primadonna/const.py

Possibly linked issues

  • #PrimaDonna ELITE ECAM 656.75: PR adds a fallback when Device Name GATT read fails, matching the issue’s GATT 133 name-read problem on ECAM 656.75.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot 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.

Hey - I've found 1 issue, and left some high level feedback:

  • Catching both BleakError and generic Exception in get_device_name is quite broad; consider limiting this to BleakError or a narrower set of expected errors to avoid masking unrelated issues.
  • The if not self.hostname fallback implies self.hostname may be uninitialized or contain stale data; consider explicitly initializing or resetting self.hostname before attempting the read so the logic is more predictable.
  • Logging the full exception at info level for BleakError may be noisy in normal operation; consider using debug level or a more concise message to reduce log verbosity for expected failures.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Catching both `BleakError` and generic `Exception` in `get_device_name` is quite broad; consider limiting this to `BleakError` or a narrower set of expected errors to avoid masking unrelated issues.
- The `if not self.hostname` fallback implies `self.hostname` may be uninitialized or contain stale data; consider explicitly initializing or resetting `self.hostname` before attempting the read so the logic is more predictable.
- Logging the full exception at info level for `BleakError` may be noisy in normal operation; consider using debug level or a more concise message to reduce log verbosity for expected failures.

## Individual Comments

### Comment 1
<location path="custom_components/delonghi_primadonna/device.py" line_range="721-723" />
<code_context>
+                            uuid.UUID(NAME_CHARACTERISTIC)
+                        )
+                    ).decode('utf-8')
+                except (BleakError, Exception) as error:
+                    _LOGGER.info('Could not read NAME_CHARACTERISTIC: %s', error)
+                    if not self.hostname:
+                        self.hostname = self.name or "DeLonghi PrimaDonna"
                 await self._client.write_gatt_char(
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Avoid catching bare Exception and narrow the error handling scope.

Catching `Exception` here will hide unexpected failures and turn them into a hostname fallback, making real bugs harder to spot. Limit this block to known Bluetooth-related exceptions (e.g., `BleakError` and any specific timeout/cancellation types you expect) and allow other exceptions to propagate.

Suggested implementation:

```python
                try:
                    self.hostname = bytes(
                        await self._client.read_gatt_char(
                            uuid.UUID(NAME_CHARACTERISTIC)
                        )
                    ).decode('utf-8')
                except BleakError as error:
                    _LOGGER.info('Could not read NAME_CHARACTERISTIC: %s', error)
                    if not self.hostname:
                        self.hostname = self.name or "DeLonghi PrimaDonna"
                await self._client.write_gatt_char(
                    uuid.UUID(CONTROLL_CHARACTERISTIC), bytearray(DEBUG)
                )

```

If you expect other specific non-fatal Bluetooth-related exceptions (e.g., timeout or cancellation exceptions from the async framework or Bleak), you can extend the `except` clause to include only those explicit types instead of a bare `Exception`. That will keep the error handling narrow while still covering the known failure modes.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread custom_components/delonghi_primadonna/device.py Outdated
@TheCheif

Copy link
Copy Markdown
Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot 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.

Hey - I've left some high level feedback:

  • The description mentions logging an info message, but the implementation uses _LOGGER.debug; consider aligning the log level with the intended behavior so users can actually see the fallback in normal operation.
  • Instead of hardcoding "DeLonghi PrimaDonna" in get_device_name, consider centralizing this default name in a constant or configuration to avoid duplication and ease future changes.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The description mentions logging an info message, but the implementation uses `_LOGGER.debug`; consider aligning the log level with the intended behavior so users can actually see the fallback in normal operation.
- Instead of hardcoding "DeLonghi PrimaDonna" in `get_device_name`, consider centralizing this default name in a constant or configuration to avoid duplication and ease future changes.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@TheCheif

Copy link
Copy Markdown
Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot 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.

Hey - I've left some high level feedback:

  • Consider downgrading the log for a non-critical BleakError when reading the name characteristic from info to debug to avoid noisy logs on systems where this is expected to fail.
  • The explicit initialization self.hostname = '' right before the nested try may be redundant given the immediate reassignment in both the success and exception paths; you could simplify by assigning only inside those branches.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider downgrading the log for a non-critical `BleakError` when reading the name characteristic from `info` to `debug` to avoid noisy logs on systems where this is expected to fail.
- The explicit initialization `self.hostname = ''` right before the nested `try` may be redundant given the immediate reassignment in both the success and exception paths; you could simplify by assigning only inside those branches.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@TheCheif

Copy link
Copy Markdown
Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot 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.

Hey - I've left some high level feedback:

  • The docstring/PR description says an info message is logged when the name read fails, but the implementation uses _LOGGER.debug; consider aligning the log level (or description) so operators can actually notice when the fallback path is used.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The docstring/PR description says an info message is logged when the name read fails, but the implementation uses `_LOGGER.debug`; consider aligning the log level (or description) so operators can actually notice when the fallback path is used.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@TheCheif

Copy link
Copy Markdown
Author

@sourcery-ai dismiss

@TheCheif

Copy link
Copy Markdown
Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot 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.

Hey - I've found 1 issue, and left some high level feedback:

  • The PR description mentions logging an info message on failure, but the implementation uses _LOGGER.debug; consider aligning the log level and wording so the behavior matches the documented intent.
  • When falling back to self.name or DEFAULT_DEVICE_NAME, ensure self.name is always a meaningful, non-empty string or add additional validation so hostname cannot become an empty or unexpected value.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The PR description mentions logging an info message on failure, but the implementation uses `_LOGGER.debug`; consider aligning the log level and wording so the behavior matches the documented intent.
- When falling back to `self.name or DEFAULT_DEVICE_NAME`, ensure `self.name` is always a meaningful, non-empty string or add additional validation so `hostname` cannot become an empty or unexpected value.

## Individual Comments

### Comment 1
<location path="custom_components/delonghi_primadonna/device.py" line_range="721-723" />
<code_context>
+                            uuid.UUID(NAME_CHARACTERISTIC)
+                        )
+                    ).decode('utf-8')
+                except BleakError as error:
+                    _LOGGER.debug('Could not read NAME_CHARACTERISTIC: %s', error)
+                    self.hostname = self.name or DEFAULT_DEVICE_NAME
                 await self._client.write_gatt_char(
</code_context>
<issue_to_address>
**suggestion:** Consider using a higher log level when the device name cannot be read.

Because this fallback changes user-visible behavior and may hide real connectivity or device issues, consider raising the log level (e.g. to `warning` or `info`) so these failures are visible in normal deployments, while still including the detailed error message.

```suggestion
                except BleakError as error:
                    _LOGGER.warning('Could not read NAME_CHARACTERISTIC, using fallback name: %s', error)
                    self.hostname = self.name or DEFAULT_DEVICE_NAME
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +721 to +723
except BleakError as error:
_LOGGER.debug('Could not read NAME_CHARACTERISTIC: %s', error)
self.hostname = self.name or DEFAULT_DEVICE_NAME

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.

suggestion: Consider using a higher log level when the device name cannot be read.

Because this fallback changes user-visible behavior and may hide real connectivity or device issues, consider raising the log level (e.g. to warning or info) so these failures are visible in normal deployments, while still including the detailed error message.

Suggested change
except BleakError as error:
_LOGGER.debug('Could not read NAME_CHARACTERISTIC: %s', error)
self.hostname = self.name or DEFAULT_DEVICE_NAME
except BleakError as error:
_LOGGER.warning('Could not read NAME_CHARACTERISTIC, using fallback name: %s', error)
self.hostname = self.name or DEFAULT_DEVICE_NAME

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