Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion custom_components/ef_ble/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""The unofficial EcoFlow BLE devices integration"""

import asyncio
import logging
from collections.abc import Callable
from functools import partial
Expand Down Expand Up @@ -130,8 +131,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: DeviceConfigEntry) -> bo
max_attempts=0 if eflib.is_solar_only(device) else None,
)
)
state = await device.wait_until_authenticated_or_error(raise_on_error=True)
async with asyncio.timeout(timeout):
state = await device.wait_until_authenticated_or_error(raise_on_error=True)
except (ConnectionTimeout, BleakError, TimeoutError) as e:
await device.disconnect()
raise ConfigEntryNotReady(
translation_key="could_not_connect",
translation_placeholders={"time": str(timeout), "error_msg": str(e)},
Expand Down
11 changes: 11 additions & 0 deletions custom_components/ef_ble/eflib/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,17 @@ async def sendRequest(self, send_data: bytes, response_handler=None):
try:
await self._sendRequest(send_data, response_handler)
except Exception as e: # noqa: BLE001
if self._client is None or not self._client.is_connected:
# The BLE link dropped mid-request - e.g. BlueZ raising "Remote peer
# disconnected" synchronously from start_notify. bleak does not
# always fire its disconnected callback for a synchronous GATT
# failure, so nothing else would drive a reconnect and
# `wait_until_authenticated_or_error` hangs forever.
self._logger.warning(
"BLE link lost while sending request (%s); reconnecting", e
)
self.disconnected()
return
self._logger.log_filtered(
LogOptions.CONNECTION_DEBUG,
(
Expand Down
21 changes: 10 additions & 11 deletions custom_components/ef_ble/eflib/frame_assembler.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from .crc import crc8, crc16
from .encpacket import EncPacket
from .encryption import EncryptionStrategy
from .exceptions import PacketParseError
from .packet import Packet


Expand Down Expand Up @@ -244,10 +243,12 @@ def parse(self, data: bytes) -> bytes | None:
"""
Extract the payload from one EncPacket frame, scanning for the prefix

Returns the payload when a complete valid frame is found, or None if the data is
incomplete and another BLE notification is expected to arrive. Raises
PacketParseError only when the data is clearly unrecoverable (no prefix found,
or only CRC-invalid candidates with nothing left to scan).
Returns the payload when a complete valid frame is found, or None when no frame
is available yet and another BLE notification is expected to arrive: incomplete
data, stale/foreign notification bytes with no frame prefix, or only CRC-invalid
candidates. This assembler runs only during the auth handshake, where the right
response to unparseable data is to wait for the next notification (and let the
connection timeout bound a truly stuck device) rather than abort the handshake.
"""
if self._buffer:
data = self._buffer + data
Expand All @@ -256,9 +257,7 @@ def parse(self, data: bytes) -> bytes | None:
while data:
start = data.find(EncPacket.PREFIX)
if start < 0:
raise PacketParseError(
f"SimplePacketAssembler: no prefix found in: {data.hex()}"
)
return None
if start > 0:
data = data[start:]

Expand Down Expand Up @@ -287,6 +286,6 @@ def parse(self, data: bytes) -> bytes | None:

return payload_data

raise PacketParseError(
f"SimplePacketAssembler: no valid frame found in: {data.hex()}"
)
# Loop only exits here when the data was fully consumed without yielding a frame
# (all candidates were false prefixes / CRC failures). Wait for more data.
return None
Loading