Skip to content

Commit

Permalink
Merge pull request #35 from monty68/dev
Browse files Browse the repository at this point in the history
Fixed issue with write_gatt_char() response flag
  • Loading branch information
monty68 authored Oct 31, 2023
2 parents 57fcf02 + 931b522 commit e60b871
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 43 deletions.
6 changes: 6 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter"
},
"python.formatting.provider": "none"
}
2 changes: 1 addition & 1 deletion custom_components/uniled/lib/ble_banlanx1.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ class _BANLANX1(UNILEDBLEModel):

##
## Device Control
##
##
def construct_status_query(self, device: UNILEDDevice) -> bytearray:
"""The bytes to send for a state query."""
return self.construct_message(bytearray([0xAA, 0x2F, 0x00]))
Expand Down
99 changes: 58 additions & 41 deletions custom_components/uniled/lib/ble_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,42 @@
from bleak.backends.scanner import AdvertisementData
from bleak.backends.service import BleakGATTCharacteristic, BleakGATTServiceCollection
from bleak.exc import BleakDBusError
from bleak_retry_connector import BLEAK_RETRY_EXCEPTIONS as BLEAK_EXCEPTIONS
from bleak_retry_connector import (
BLEAK_RETRY_EXCEPTIONS as BLEAK_EXCEPTIONS,
BleakClientWithServiceCache,
BleakError,
BleakNotFoundError,
BleakConnectionError,
establish_connection,
retry_bluetooth_connection_error,
)

#from .ble_retry import (
# retry_bluetooth_connection_error,
# BLEAK_DISCONNECT_DELAY,
# BLEAK_BACKOFF_TIME,
#)

from .ble_model import UNILEDBLEModel
from .ble_retry import (
retry_bluetooth_connection_error,
BLEAK_DISCONNECT_DELAY,
BLEAK_BACKOFF_TIME,
)
from .classes import UNILEDDevice
from .models_db import UNILED_TRANSPORT_BLE, UNILED_BLE_MODELS

import logging

_LOGGER = logging.getLogger(__name__)

BLE_MULTI_COMMAND_SETTLE_DELAY = 0.3
BLE_NOTFICATION_TIMEOUT = 2.0
RETRY_BACKOFF_EXCEPTIONS = (BleakDBusError,)

BLE_MULTI_COMMAND_SETTLE_DELAY = 0.5
BLE_NOTFICATION_TIMEOUT = 5.0

BLE_DEFAULT_ATTEMPTS = 3
BLE_BLEAK_BACKOFF_TIME = 0.25
BLE_BLEAK_DISCONNECT_DELAY = 120

class CharacteristicMissingError(Exception):
"""Raised when a characteristic is missing."""


class ChannelMissingError(Exception):
"""Raised when a channel is missing."""

Expand Down Expand Up @@ -250,29 +256,31 @@ async def _ensure_connected(self) -> None:
ble_device_callback=lambda: self._ble_device,
)

_LOGGER.debug("%s: Connected", self.name)
resolved = self._resolve_characteristics(client.services)
if not resolved:
# Try to handle services failing to load
await asyncio.sleep(BLEAK_BACKOFF_TIME)
# services = await client.get_services()
services = client.services
resolved = self._resolve_characteristics(services)
if client and client.is_connected:
_LOGGER.debug("%s: Connected", self.name)
resolved = self._resolve_characteristics(client.services)
if not resolved:
# Try to handle services failing to load
await asyncio.sleep(BLE_BLEAK_BACKOFF_TIME)
# services = await client.get_services()
services = client.services
resolved = self._resolve_characteristics(services)

self._client = client
self._reset_disconnect_timer()

self._client = client
self._reset_disconnect_timer()
if client and self._read_char:
_LOGGER.debug("%s: Subscribe to notifications: %s", self.name, self._read_char)
self._last_notification_data = ()
await client.start_notify(self._read_char, self._notification_handler)

if client and self._read_char:
_LOGGER.debug("%s: Subscribe to notifications", self.name)
self._last_notification_data = ()
await client.start_notify(self._read_char, self._notification_handler)

if (client and self._model) and (
on_connect := self._model.construct_connect_message(self)
) is not None:
# Send any "on connection" message(s)
await self.send_command(on_connect)
await asyncio.sleep(BLE_MULTI_COMMAND_SETTLE_DELAY)
if (client and self._model) and (
on_connect := self._model.construct_connect_message(self)
) is not None:
# Send any "on connection" message(s)
_LOGGER.debug("%s: Sending 'on connection' command", self.name)
await self.send_command(on_connect)
await asyncio.sleep(BLE_MULTI_COMMAND_SETTLE_DELAY)

async def _send_command_while_connected(
self, commands: list[bytes], retry: int | None = None
Expand Down Expand Up @@ -311,21 +319,21 @@ async def _send_command_while_connected(

raise RuntimeError("Unreachable")

@retry_bluetooth_connection_error
@retry_bluetooth_connection_error(BLE_DEFAULT_ATTEMPTS)
async def _send_command_locked(self, commands: list[bytes]) -> None:
"""Send command to device and read response."""
try:
await self._execute_command_locked(commands)
await self._execute_command_locked(commands)
except BleakDBusError as ex:
# Disconnect so we can reset state and try again
await asyncio.sleep(BLE_BLEAK_BACKOFF_TIME)
_LOGGER.debug(
"%s: RSSI: %s; Backing off %ss; Disconnecting due to error: %s",
self.name,
self.rssi,
BLEAK_BACKOFF_TIME,
BLE_BLEAK_BACKOFF_TIME,
ex,
)
await asyncio.sleep(BLEAK_BACKOFF_TIME)
await self._execute_disconnect()
raise
except BleakError as ex:
Expand All @@ -347,8 +355,8 @@ async def _execute_command_locked(self, commands: list[bytes]) -> None:
raise CharacteristicMissingError("Read characteristic missing")
to_send = len(commands)
for command in commands:
_LOGGER.debug("%s: Sending command: %s ", self.name, command.hex())
await self._client.write_gatt_char(self._write_char, command, False)
_LOGGER.debug("%s: Sending command: [%s] %s", self.name, command.hex(), self._write_char)
await self._client.write_gatt_char(self._write_char, command, None)
if to_send > 1:
await asyncio.sleep(BLE_MULTI_COMMAND_SETTLE_DELAY)

Expand All @@ -358,7 +366,7 @@ def _reset_disconnect_timer(self) -> None:
self._disconnect_timer.cancel()
self._expected_disconnect = False
self._disconnect_timer = self.loop.call_later(
BLEAK_DISCONNECT_DELAY, self._disconnect
BLE_BLEAK_DISCONNECT_DELAY, self._disconnect
)

def _disconnected(self, client: BleakClientWithServiceCache) -> None:
Expand All @@ -371,6 +379,9 @@ def _disconnected(self, client: BleakClientWithServiceCache) -> None:
self.rssi,
)

if not self._expected_disconnect:
raise RuntimeError("Unexpected disconnection!")

def _disconnect(self) -> None:
"""Disconnect from device."""
self._disconnect_timer = None
Expand All @@ -379,7 +390,7 @@ def _disconnect(self) -> None:
async def _execute_timed_disconnect(self) -> None:
"""Execute timed disconnection."""
_LOGGER.debug(
"%s: Disconnecting after timeout of %d", self.name, BLEAK_DISCONNECT_DELAY
"%s: Disconnecting after timeout of %d", self.name, BLE_BLEAK_DISCONNECT_DELAY
)
await self._execute_disconnect()

Expand All @@ -398,10 +409,16 @@ async def _execute_disconnect(self) -> None:
self._read_char = None
self._write_char = None

if client:
if client and client.is_connected:
if read_char:
await client.stop_notify(read_char)
_LOGGER.debug("%s: Stopped notifications from device", self.name)
try:
await client.stop_notify(read_char)
_LOGGER.debug("%s: Stopped notifications from device", self.name)
except BleakError:
_LOGGER.debug(
"%s: Failed to stop notifications", self.name, exc_info=True
)

if client.is_connected:
await client.disconnect()
_LOGGER.debug("%s: Disconnected from device", self.name)
Expand Down
2 changes: 1 addition & 1 deletion custom_components/uniled/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@
"iot_class": "local_polling",
"issue_tracker": "https://github.com/monty68/uniled/issues",
"requirements": [],
"version": "1.0.3"
"version": "1.0.4"
}

0 comments on commit e60b871

Please sign in to comment.