From cd3fa621aa7a4b63b25ef0197fd51256cb2cc36f Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Tue, 3 Nov 2020 20:29:03 +0300 Subject: [PATCH 01/10] Use bleak for reading data --- requirements.txt | 1 + tion_btle/s3.py | 65 ++++++++++++++++------------- tion_btle/tion.py | 102 ++++++++++++++++++---------------------------- 3 files changed, 76 insertions(+), 92 deletions(-) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..dc1a339 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +bleak==0.9.1 \ No newline at end of file diff --git a/tion_btle/s3.py b/tion_btle/s3.py index 573e248..ff45c7f 100644 --- a/tion_btle/s3.py +++ b/tion_btle/s3.py @@ -1,4 +1,6 @@ import logging +import asyncio +from bleak import exc from typing import Callable if __package__ == "": @@ -6,7 +8,6 @@ else: from .tion import tion, TionException -from bluepy import btle import time logging.basicConfig(level=logging.DEBUG) @@ -38,7 +39,8 @@ def __try_get_state(self) -> bytearray: _LOGGER.debug("Response is %s", bytes(response).hex()) return response - def pair(self): + async def pair(self): + raise NotImplementedError() def get_pair_command() -> bytearray: return self.create_command(self.command_PAIR) _LOGGER.setLevel("DEBUG") @@ -55,17 +57,17 @@ def get_pair_command() -> bytearray: self._disconnect() _LOGGER.debug("Done!") - def create_command(self, command: int) -> bytearray: + async def create_command(self, command: int) -> bytearray: command_special = 1 if command == self.command_PAIR else 0 return bytearray([self.command_prefix, command, command_special, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, self.command_suffix]) - def get(self, keep_connection=False) -> dict: - def get_status_command() -> bytearray: - return self.create_command(self.command_REQUEST_PARAMS) + async def get(self, keep_connection=False) -> dict: + async def get_status_command() -> bytearray: + return await self.create_command(self.command_REQUEST_PARAMS) - def decode_response(response: bytearray) -> dict: - def process_mode(mode_code: int) -> str: + async def decode_response(response: bytearray) -> dict: + async def process_mode(mode_code: int) -> str: try: mode = self.modes[mode_code] except IndexError: @@ -76,15 +78,15 @@ def process_mode(mode_code: int) -> str: try: self.fan_speed = int(list("{:02x}".format(response[2]))[1]) result = {"code": 200, - "mode": process_mode(int(list("{:02x}".format(response[2]))[0])), + "mode": await process_mode(int(list("{:02x}".format(response[2]))[0])), "fan_speed": self.fan_speed, "heater_temp": response[3], - "heater": self._process_status(response[4] & 1), - "status": self._process_status(response[4] >> 1 & 1), - "timer": self._process_status(response[4] >> 2 & 1), - "sound": self._process_status(response[4] >> 3 & 1), - "out_temp": self.decode_temperature(response[7]), - "in_temp": self.decode_temperature(response[8]), + "heater": await self._process_status(response[4] & 1), + "status": await self._process_status(response[4] >> 1 & 1), + "timer": await self._process_status(response[4] >> 2 & 1), + "sound": await self._process_status(response[4] >> 3 & 1), + "out_temp": await self.decode_temperature(response[7]), + "in_temp": await self.decode_temperature(response[8]), "filter_remain": response[10] * 256 + response[9], "time": "{}:{}".format(response[11], response[12]), "request_error_code": response[13], @@ -106,34 +108,39 @@ def process_mode(mode_code: int) -> str: return result try: - self._do_action(self._connect) - self._enable_notifications() - self._do_action(self._try_write, request=get_status_command()) + await self._do_action(self._connect) + await self._enable_notifications() + await self._do_action(self._try_write, request=await get_status_command()) i = 0 try: - while i < 10: - if self._btle.waitForNotifications(1.0): - byte_response = self._delegation.data - result = decode_response(byte_response) - break + while i < 10 and not self._delegation.have_new_data: + await asyncio.sleep(1) i += 1 + if i < 10: + byte_response = self._delegation.data else: - _LOGGER.debug("Waiting too long for data") - self.notify.read() - except btle.BTLEDisconnectError as e: + byte_response = await self._btle.read_gatt_char( + self.uuid_notify + ) + msg = "Waiting too long for data. Use data from %s as response" % self.uuid_notify + _LOGGER.warning(msg) + result = await decode_response(byte_response) + + except exc.BleakError as e: _LOGGER.debug("Got %s while waiting for notification", str(e)) - self._disconnect() - + await self._disconnect() except TionException as e: _LOGGER.error(str(e)) result = {"code": 400, "error": "Got exception " + str(e)} finally: - self._disconnect() + await self._disconnect() return result def set(self, request: dict, keep_connection=False): + raise NotImplementedError() + def encode_request(request: dict) -> bytearray: def encode_mode(mode: str) -> int: return self.modes.index(mode) if mode in self.modes else 2 diff --git a/tion_btle/tion.py b/tion_btle/tion.py index 612cca8..8e8e6e8 100644 --- a/tion_btle/tion.py +++ b/tion_btle/tion.py @@ -1,33 +1,33 @@ import abc import logging import time +import asyncio from typing import Callable - -from bluepy import btle -from bluepy.btle import DefaultDelegate +from bleak import BleakClient +from bleak import exc _LOGGER = logging.getLogger(__name__) -class TionDelegation(DefaultDelegate): +class TionDelegation: def __init__(self): - self._data = None - DefaultDelegate.__init__(self) + self._have_new_data: bool = False + self._data: bytearray = bytearray() - def handleNotification(self, handle: int, data: bytes): + def handleNotification(self, handle: int, data: bytearray): self._data = data + self._have_new_data = True _LOGGER.debug("Got data in %d response %s", handle, bytes(data).hex()) - def handleDiscovery(self, dev, isNewDev, isNewData): - if isNewDev: - _LOGGER.debug("Discovered device %s", dev.addr) - elif isNewData: - _LOGGER.debug("Received new data from %s", dev.addr) - @property - def data(self) -> bytes: + def data(self) -> bytearray: + self._have_new_data = False return self._data + @property + def have_new_data(self) -> bool: + return self._have_new_data + class TionException(Exception): def __init__(self, expression, message): @@ -41,8 +41,8 @@ class tion: uuid_write: str = "" def __init__(self, mac: str): - self._mac = mac - self._btle: btle.Peripheral = btle.Peripheral(None) + self._mac: str = mac + self._btle: BleakClient = BleakClient(self.mac) self._delegation = TionDelegation() self._fan_speed = 0 @@ -91,7 +91,7 @@ def get(self, keep_connection: bool = False) -> dict: def mac(self): return self._mac - def decode_temperature(self, raw: bytes) -> int: + async def decode_temperature(self, raw: bytes) -> int: """ Converts temperature from bytes with addition code to int Args: raw: raw temperature value from Tion @@ -106,7 +106,7 @@ def decode_temperature(self, raw: bytes) -> int: return result - def _process_status(self, code: int) -> str: + async def _process_status(self, code: int) -> str: try: status = self.statuses[code] except IndexError: @@ -114,62 +114,48 @@ def _process_status(self, code: int) -> str: return status @property - def connection_status(self): - connection_status = "disc" - try: - connection_status = self._btle.getState() - except btle.BTLEInternalError as e: - if str(e) == "Helper not started (did you call connect()?)": - pass - else: - raise e - except btle.BTLEDisconnectError as e: - pass - except BrokenPipeError as e: - self._btle = btle.Peripheral(None) - - return connection_status + async def connection_status(self): + return "connected" if await self._btle.is_connected() else "disc" - def _connect(self): + async def _connect(self): if self.mac == "dummy": _LOGGER.info("Dummy connect") return - if self.connection_status == "disc": + if await self.connection_status == "disc": try: - self._btle.connect(self.mac, btle.ADDR_TYPE_RANDOM) - for tc in self._btle.getCharacteristics(): - if tc.uuid == self.uuid_notify: - self.notify = tc - if tc.uuid == self.uuid_write: - self.write = tc - except btle.BTLEDisconnectError as e: + await self._btle.connect() + except exc.BleakError as e: _LOGGER.warning("Got %s exception", str(e)) time.sleep(2) raise e - def _disconnect(self): - if self.connection_status != "disc": + async def _disconnect(self): + if await self.connection_status != "disc": if self.mac != "dummy": - self._btle.disconnect() + await self._btle.disconnect() - def _try_write(self, request: bytearray): + async def _try_write(self, request: bytearray): if self.mac != "dummy": - _LOGGER.debug("Writing %s to %s", bytes(request).hex(), self.write.uuid) - return self.write.write(request) + _LOGGER.debug("Writing %s to %s", bytes(request).hex(), self.uuid_write) + return await self._btle.write_gatt_char( + self.uuid_write, + request, + False + ) else: _LOGGER.info("Dummy write") return "dummy write" - def _do_action(self, action: Callable, max_tries: int = 3, *args, **kwargs): + async def _do_action(self, action: Callable, max_tries: int = 3, *args, **kwargs): tries: int = 0 while tries < max_tries: _LOGGER.debug("Doing " + action.__name__ + ". Attempt " + str(tries + 1) + "/" + str(max_tries)) try: if action.__name__ != '_connect': - self._connect() + await self._connect() - response = action(*args, **kwargs) + response = await action(*args, **kwargs) break except Exception as e: tries += 1 @@ -189,19 +175,9 @@ def _do_action(self, action: Callable, max_tries: int = 3, *args, **kwargs): return response - def _enable_notifications(self): + async def _enable_notifications(self): _LOGGER.debug("Enabling notification") - setup_data = b"\x01\x00" - - _LOGGER.debug("Notify handler is %s", self.notify.getHandle()) - notify_handle = self.notify.getHandle() + 1 - - _LOGGER.debug("Will write %s to %s handle", setup_data, notify_handle) - result = self._btle.writeCharacteristic(notify_handle, setup_data, withResponse=True) - _LOGGER.debug("Result is %s", result) - self._btle.withDelegate(self._delegation) - self.notify.read() - return result + return await self._btle.start_notify(self.uuid_notify, self._delegation.handleNotification) @property def fan_speed(self): From 9e9ecb8dd8a495944cd5da2bbf96f10ec982146d Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Tue, 3 Nov 2020 22:32:23 +0300 Subject: [PATCH 02/10] Use async bleak for writing settings --- tion_btle/s3.py | 34 +++++++++++++++++++--------------- tion_btle/tion.py | 31 +++++++++++++++++++++++-------- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/tion_btle/s3.py b/tion_btle/s3.py index ff45c7f..3d9aa3e 100644 --- a/tion_btle/s3.py +++ b/tion_btle/s3.py @@ -107,6 +107,8 @@ async def process_mode(mode_code: int) -> str: finally: return result + result: dict = {} + try: await self._do_action(self._connect) await self._enable_notifications() @@ -133,19 +135,19 @@ async def process_mode(mode_code: int) -> str: except TionException as e: _LOGGER.error(str(e)) result = {"code": 400, "error": "Got exception " + str(e)} - finally: await self._disconnect() - return result + if not keep_connection: + await self._disconnect() - def set(self, request: dict, keep_connection=False): - raise NotImplementedError() + return result - def encode_request(request: dict) -> bytearray: - def encode_mode(mode: str) -> int: + async def set(self, request: dict, keep_connection=False): + async def encode_request(request: dict) -> bytearray: + async def encode_mode(mode: str) -> int: return self.modes.index(mode) if mode in self.modes else 2 - def encode_status(status: str) -> int: + async def encode_status(status: str) -> int: return self.statuses.index(status) if status in self.statuses else 0 try: @@ -155,18 +157,20 @@ def encode_status(status: str) -> int: except KeyError: pass - settings = {**self.get(True), **request} - new_settings = self.create_command(self.command_SET_PARAMS) + current_settings = await self.get(True) + _LOGGER.debug("set: got '%s' settings" % current_settings) + settings = {**current_settings, **request} + new_settings = await self.create_command(self.command_SET_PARAMS) new_settings[2] = int(settings["fan_speed"]) new_settings[3] = int(settings["heater_temp"]) - new_settings[4] = encode_mode(settings["mode"]) - new_settings[5] = encode_status(settings["heater"]) | (encode_status(settings["status"]) << 1) | ( - encode_status(settings["sound"]) << 3) + new_settings[4] = await encode_mode(settings["mode"]) + new_settings[5] = await encode_status(settings["heater"]) | (await encode_status(settings["status"]) << 1) | ( + await encode_status(settings["sound"]) << 3) return new_settings try: - self._do_action(self._connect) - self._do_action(self._try_write, request=encode_request(request)) + await self._do_action(self._connect) + await self._do_action(self._try_write, request=await encode_request(request)) except TionException as e: _LOGGER.error(str(e)) finally: - self._disconnect() + await self._disconnect() diff --git a/tion_btle/tion.py b/tion_btle/tion.py index 8e8e6e8..a8a396d 100644 --- a/tion_btle/tion.py +++ b/tion_btle/tion.py @@ -1,6 +1,7 @@ import abc import logging import time +import bleak import asyncio from typing import Callable from bleak import BleakClient @@ -47,7 +48,7 @@ def __init__(self, mac: str): self._fan_speed = 0 @abc.abstractmethod - def _send_request(self, request: bytearray) -> bytearray: + async def _send_request(self, request: bytearray) -> bytearray: """ Send request to device Args: @@ -69,7 +70,7 @@ def _decode_response(self, response: bytearray) -> dict: pass @abc.abstractmethod - def _encode_request(self, request: dict) -> bytearray: + async def _encode_request(self, request: dict) -> bytearray: """ Encode dictionary of request to byte array Args: @@ -80,7 +81,7 @@ def _encode_request(self, request: dict) -> bytearray: pass @abc.abstractmethod - def get(self, keep_connection: bool = False) -> dict: + async def get(self, keep_connection: bool = False) -> dict: """ Get device information Returns: dictionay with device paramters @@ -115,7 +116,9 @@ async def _process_status(self, code: int) -> str: @property async def connection_status(self): - return "connected" if await self._btle.is_connected() else "disc" + status = "connected" if await self._btle.is_connected() else "disc" + _LOGGER.debug("connection_status is %s" % status) + return status async def _connect(self): if self.mac == "dummy": @@ -125,15 +128,22 @@ async def _connect(self): if await self.connection_status == "disc": try: await self._btle.connect() + await self._btle.get_services() except exc.BleakError as e: _LOGGER.warning("Got %s exception", str(e)) time.sleep(2) raise e + else: + _LOGGER.debug("Already connected!") async def _disconnect(self): - if await self.connection_status != "disc": - if self.mac != "dummy": - await self._btle.disconnect() + try: + if await self.connection_status != "disc": + if self.mac != "dummy": + await self._btle.disconnect() + finally: + # re-init client + self._btle = BleakClient(self.mac) async def _try_write(self, request: bytearray): if self.mac != "dummy": @@ -177,7 +187,12 @@ async def _do_action(self, action: Callable, max_tries: int = 3, *args, **kwargs async def _enable_notifications(self): _LOGGER.debug("Enabling notification") - return await self._btle.start_notify(self.uuid_notify, self._delegation.handleNotification) + try: + result = await self._btle.start_notify(self.uuid_notify, self._delegation.handleNotification) + except bleak.exc.BleakError as e: + _LOGGER.critical("Got exception %s while enabling notifications!" % str(e)) + raise e + return result @property def fan_speed(self): From b9f55c77f0020dea120acf394d88010956653f85 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Tue, 3 Nov 2020 22:44:14 +0300 Subject: [PATCH 03/10] Use async bleak for pairing --- tion_btle/s3.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/tion_btle/s3.py b/tion_btle/s3.py index 3d9aa3e..bdf6a7c 100644 --- a/tion_btle/s3.py +++ b/tion_btle/s3.py @@ -40,22 +40,23 @@ def __try_get_state(self) -> bytearray: return response async def pair(self): - raise NotImplementedError() - def get_pair_command() -> bytearray: - return self.create_command(self.command_PAIR) + async def get_pair_command() -> bytearray: + return await self.create_command(self.command_PAIR) + _LOGGER.setLevel("DEBUG") - _LOGGER.debug("Pairing") - _LOGGER.debug("Connecting") - self._do_action(self._connect) - _LOGGER.debug("Collecting characteristic") - characteristic = self._btle.getServiceByUUID(self.uuid).getCharacteristics()[0] - _LOGGER.debug("Got characteristic %s for pairing", str(characteristic)) - pair_command = get_pair_command() - _LOGGER.debug("Sending pair command %s to %s", bytes(pair_command).hex(), str(characteristic)) - characteristic.write(bytes(get_pair_command())) - _LOGGER.debug("Disconnecting") - self._disconnect() - _LOGGER.debug("Done!") + _LOGGER.debug("Going to pair with %s" % self.mac) + try: + _LOGGER.debug("Connecting") + await self._do_action(self._connect) + _LOGGER.debug("BT Pairing") + await self._btle.pair() + pair_command = await get_pair_command() + _LOGGER.debug("Sending pair command %s to %s", bytes(pair_command).hex(), self.uuid_write) + await self._do_action(self._try_write, request=pair_command) + _LOGGER.debug("Done!") + finally: + _LOGGER.debug("Disconnecting") + await self._disconnect() async def create_command(self, command: int) -> bytearray: command_special = 1 if command == self.command_PAIR else 0 From 90ed922665e2c9bb4769de92626f38cf2847a8ae Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Tue, 3 Nov 2020 22:48:38 +0300 Subject: [PATCH 04/10] Bump version --- tion_btle/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tion_btle/const.py b/tion_btle/const.py index 92287c5..81343d7 100644 --- a/tion_btle/const.py +++ b/tion_btle/const.py @@ -1 +1 @@ -VERSION = '0.1.3' +VERSION = '0.2.0' From d0c1e1c145e2f0f829cdec430cc62d53f26d75e5 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Tue, 3 Nov 2020 22:51:27 +0300 Subject: [PATCH 05/10] Replace bluepy to bleak in install_requires --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c947256..6458d1c 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ version=tion_btle.const.VERSION, long_description="Module for working with Tion breezers", url='https://github.com/TionAPI/tion_python/tree/dev', - install_requires=['bluepy==1.3.0'], + install_requires=['bleak==0.9.1'], description='Python module for interacting with Tion breezers', packages=find_packages(), ) From b06ee294a9634676f04d9fefa788ecafc9942b66 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Wed, 4 Nov 2020 00:22:54 +0300 Subject: [PATCH 06/10] Raise more exceptions while get --- tion_btle/s3.py | 26 ++++++++++++++++++-------- tion_btle/tion.py | 5 +++++ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/tion_btle/s3.py b/tion_btle/s3.py index bdf6a7c..47b956d 100644 --- a/tion_btle/s3.py +++ b/tion_btle/s3.py @@ -4,9 +4,9 @@ from typing import Callable if __package__ == "": - from tion_btle.tion import tion, TionException + from tion_btle.tion import tion, TionException, TionExceptionGet else: - from .tion import tion, TionException + from .tion import tion, TionException, TionExceptionGet import time @@ -108,8 +108,6 @@ async def process_mode(mode_code: int) -> str: finally: return result - result: dict = {} - try: await self._do_action(self._connect) await self._enable_notifications() @@ -131,12 +129,13 @@ async def process_mode(mode_code: int) -> str: result = await decode_response(byte_response) except exc.BleakError as e: - _LOGGER.debug("Got %s while waiting for notification", str(e)) + _LOGGER.error("Got %s while waiting for notification", str(e)) await self._disconnect() + raise TionExceptionGet('get', str(e)) except TionException as e: _LOGGER.error(str(e)) - result = {"code": 400, "error": "Got exception " + str(e)} await self._disconnect() + raise e if not keep_connection: await self._disconnect() @@ -158,7 +157,11 @@ async def encode_status(status: str) -> int: except KeyError: pass - current_settings = await self.get(True) + try: + current_settings = await self.get(True) + except TionExceptionGet: + raise TionException('set', 'Could not get current settings!') + _LOGGER.debug("set: got '%s' settings" % current_settings) settings = {**current_settings, **request} new_settings = await self.create_command(self.command_SET_PARAMS) @@ -170,7 +173,14 @@ async def encode_status(status: str) -> int: return new_settings try: await self._do_action(self._connect) - await self._do_action(self._try_write, request=await encode_request(request)) + try: + encoded_request = await encode_request(request) + except (KeyError, TionException) as e: + _LOGGER.warning("Could not create encoded settings command: %s" % str(e)) + return + + await self._do_action(self._try_write, request=encoded_request) + except TionException as e: _LOGGER.error(str(e)) finally: diff --git a/tion_btle/tion.py b/tion_btle/tion.py index a8a396d..d1e38cd 100644 --- a/tion_btle/tion.py +++ b/tion_btle/tion.py @@ -36,6 +36,11 @@ def __init__(self, expression, message): self.message = message +class TionExceptionGet(TionException): + def __init__(self, expression, message): + super.__init__(expression, message) + + class tion: statuses = ['off', 'on'] uuid_notify: str = "" From ae5e8fff22ab74547f3ee260d79d2ecbccbaa759 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Wed, 4 Nov 2020 01:31:19 +0300 Subject: [PATCH 07/10] Fixes --- tion_btle/tion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tion_btle/tion.py b/tion_btle/tion.py index d1e38cd..6c94ce1 100644 --- a/tion_btle/tion.py +++ b/tion_btle/tion.py @@ -38,7 +38,7 @@ def __init__(self, expression, message): class TionExceptionGet(TionException): def __init__(self, expression, message): - super.__init__(expression, message) + super().__init__(expression, message) class tion: From 691d72e3a49fa10d82b5280ddeda6e920469b175 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Wed, 4 Nov 2020 02:29:07 +0300 Subject: [PATCH 08/10] Remove _do_action and check connect/disconnect counters. --- tion_btle/s3.py | 21 +++++++++------------ tion_btle/tion.py | 47 ++++++++++------------------------------------- 2 files changed, 19 insertions(+), 49 deletions(-) diff --git a/tion_btle/s3.py b/tion_btle/s3.py index 47b956d..9158b75 100644 --- a/tion_btle/s3.py +++ b/tion_btle/s3.py @@ -47,12 +47,12 @@ async def get_pair_command() -> bytearray: _LOGGER.debug("Going to pair with %s" % self.mac) try: _LOGGER.debug("Connecting") - await self._do_action(self._connect) + await self._connect() _LOGGER.debug("BT Pairing") await self._btle.pair() pair_command = await get_pair_command() _LOGGER.debug("Sending pair command %s to %s", bytes(pair_command).hex(), self.uuid_write) - await self._do_action(self._try_write, request=pair_command) + await self._try_write(request=pair_command) _LOGGER.debug("Done!") finally: _LOGGER.debug("Disconnecting") @@ -63,7 +63,7 @@ async def create_command(self, command: int) -> bytearray: return bytearray([self.command_prefix, command, command_special, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, self.command_suffix]) - async def get(self, keep_connection=False) -> dict: + async def get(self) -> dict: async def get_status_command() -> bytearray: return await self.create_command(self.command_REQUEST_PARAMS) @@ -109,9 +109,9 @@ async def process_mode(mode_code: int) -> str: return result try: - await self._do_action(self._connect) + await self._connect() await self._enable_notifications() - await self._do_action(self._try_write, request=await get_status_command()) + await self._try_write(request=await get_status_command()) i = 0 try: @@ -130,14 +130,11 @@ async def process_mode(mode_code: int) -> str: except exc.BleakError as e: _LOGGER.error("Got %s while waiting for notification", str(e)) - await self._disconnect() raise TionExceptionGet('get', str(e)) except TionException as e: _LOGGER.error(str(e)) - await self._disconnect() raise e - - if not keep_connection: + finally: await self._disconnect() return result @@ -158,7 +155,7 @@ async def encode_status(status: str) -> int: pass try: - current_settings = await self.get(True) + current_settings = await self.get() except TionExceptionGet: raise TionException('set', 'Could not get current settings!') @@ -172,14 +169,14 @@ async def encode_status(status: str) -> int: await encode_status(settings["sound"]) << 3) return new_settings try: - await self._do_action(self._connect) + await self._connect() try: encoded_request = await encode_request(request) except (KeyError, TionException) as e: _LOGGER.warning("Could not create encoded settings command: %s" % str(e)) return - await self._do_action(self._try_write, request=encoded_request) + await self._try_write(request=encoded_request) except TionException as e: _LOGGER.error(str(e)) diff --git a/tion_btle/tion.py b/tion_btle/tion.py index 6c94ce1..61ff12e 100644 --- a/tion_btle/tion.py +++ b/tion_btle/tion.py @@ -51,6 +51,7 @@ def __init__(self, mac: str): self._btle: BleakClient = BleakClient(self.mac) self._delegation = TionDelegation() self._fan_speed = 0 + self._connections: int = 0 @abc.abstractmethod async def _send_request(self, request: bytearray) -> bytearray: @@ -86,7 +87,7 @@ async def _encode_request(self, request: dict) -> bytearray: pass @abc.abstractmethod - async def get(self, keep_connection: bool = False) -> dict: + async def get(self) -> dict: """ Get device information Returns: dictionay with device paramters @@ -126,6 +127,7 @@ async def connection_status(self): return status async def _connect(self): + self._connections += 1 if self.mac == "dummy": _LOGGER.info("Dummy connect") return @@ -133,22 +135,21 @@ async def _connect(self): if await self.connection_status == "disc": try: await self._btle.connect() - await self._btle.get_services() except exc.BleakError as e: _LOGGER.warning("Got %s exception", str(e)) - time.sleep(2) + await asyncio.sleep(2) raise e else: _LOGGER.debug("Already connected!") async def _disconnect(self): - try: - if await self.connection_status != "disc": - if self.mac != "dummy": + self._connections -= 1 + if await self.connection_status != "disc": + if self.mac != "dummy": + if self._connections == 0: await self._btle.disconnect() - finally: - # re-init client - self._btle = BleakClient(self.mac) + # drop _services_resolved flag, that not dropped while disconnect + self._btle._services_resolved = False async def _try_write(self, request: bytearray): if self.mac != "dummy": @@ -162,34 +163,6 @@ async def _try_write(self, request: bytearray): _LOGGER.info("Dummy write") return "dummy write" - async def _do_action(self, action: Callable, max_tries: int = 3, *args, **kwargs): - tries: int = 0 - while tries < max_tries: - _LOGGER.debug("Doing " + action.__name__ + ". Attempt " + str(tries + 1) + "/" + str(max_tries)) - try: - if action.__name__ != '_connect': - await self._connect() - - response = await action(*args, **kwargs) - break - except Exception as e: - tries += 1 - _LOGGER.warning("Got exception while " + action.__name__ + ": " + str(e)) - pass - else: - if action.__name__ == '_connect': - message = "Could not connect to " + self.mac - elif action.__name__ == '__try_write': - message = "Could not write request + " + kwargs['request'].hex() - elif action.__name__ == '__try_get_state': - message = "Could not get updated state" - else: - message = "Could not do " + action.__name__ - - raise TionException(action.__name__, message) - - return response - async def _enable_notifications(self): _LOGGER.debug("Enabling notification") try: From a8655992b0edbb9de3f4f5a520b51bc624aebb33 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Wed, 4 Nov 2020 02:54:38 +0300 Subject: [PATCH 09/10] Cleaning, more exceptions. --- tion_btle/s3.py | 21 +++++---------------- tion_btle/tion.py | 2 +- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/tion_btle/s3.py b/tion_btle/s3.py index 9158b75..80974d7 100644 --- a/tion_btle/s3.py +++ b/tion_btle/s3.py @@ -18,8 +18,6 @@ class s3(tion): uuid = "6e400001-b5a3-f393-e0a9-e50e24dcca9e" uuid_write = "6e400002-b5a3-f393-e0a9-e50e24dcca9e" uuid_notify = "6e400003-b5a3-f393-e0a9-e50e24dcca9e" - write = None - notify = None modes = ['recirculation', 'mixed'] _btle = None @@ -34,19 +32,14 @@ class s3(tion): def __init__(self, mac: str): super().__init__(mac) - def __try_get_state(self) -> bytearray: - response = self._btle.getServiceByUUID(self.uuid).getCharacteristics()[0].read() - _LOGGER.debug("Response is %s", bytes(response).hex()) - return response - async def pair(self): async def get_pair_command() -> bytearray: return await self.create_command(self.command_PAIR) _LOGGER.setLevel("DEBUG") _LOGGER.debug("Going to pair with %s" % self.mac) + _LOGGER.debug("Connecting") try: - _LOGGER.debug("Connecting") await self._connect() _LOGGER.debug("BT Pairing") await self._btle.pair() @@ -121,19 +114,15 @@ async def process_mode(mode_code: int) -> str: if i < 10: byte_response = self._delegation.data else: - byte_response = await self._btle.read_gatt_char( - self.uuid_notify - ) - msg = "Waiting too long for data. Use data from %s as response" % self.uuid_notify - _LOGGER.warning(msg) + _LOGGER.warning("Waiting data too long. Will use data from uuid_notify (%s)." % self.uuid_notify) + byte_response = await self._btle.read_gatt_char(self.uuid_notify) result = await decode_response(byte_response) - except exc.BleakError as e: _LOGGER.error("Got %s while waiting for notification", str(e)) raise TionExceptionGet('get', str(e)) - except TionException as e: + except (exc.BleakError, TionException, TionExceptionGet) as e: _LOGGER.error(str(e)) - raise e + raise TionException('get', str(e)) finally: await self._disconnect() diff --git a/tion_btle/tion.py b/tion_btle/tion.py index 61ff12e..f9b1626 100644 --- a/tion_btle/tion.py +++ b/tion_btle/tion.py @@ -168,7 +168,7 @@ async def _enable_notifications(self): try: result = await self._btle.start_notify(self.uuid_notify, self._delegation.handleNotification) except bleak.exc.BleakError as e: - _LOGGER.critical("Got exception %s while enabling notifications!" % str(e)) + _LOGGER.warning("Got exception %s while enabling notifications!" % str(e)) raise e return result From 4efc5ed2128e1c01b3e0be8a3fcb0ef0e282e541 Mon Sep 17 00:00:00 2001 From: IATkachenko Date: Wed, 4 Nov 2020 03:23:06 +0300 Subject: [PATCH 10/10] Catch all exceptions and raise TionException in set --- tion_btle/s3.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tion_btle/s3.py b/tion_btle/s3.py index 80974d7..e1f6810 100644 --- a/tion_btle/s3.py +++ b/tion_btle/s3.py @@ -167,7 +167,8 @@ async def encode_status(status: str) -> int: await self._try_write(request=encoded_request) - except TionException as e: + except (exc.BleakError, TionException, TionExceptionGet) as e: _LOGGER.error(str(e)) + raise TionException('set', str(e)) finally: await self._disconnect()