From d19c35422b896501b9757875f711cda51ebfc833 Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Mon, 7 Jun 2021 22:06:48 +0200 Subject: [PATCH 01/33] New class to fetch all switchbot device data for all devices in range. --- switchbot/__init__.py | 48 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index 69dc05fe..b4aa6b36 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -30,8 +30,54 @@ _LOGGER = logging.getLogger(__name__) +class SwitchbotDevices: + """Get all switchbots and their data.""" + + def __init__(self, **kwargs) -> None: + self._time_between_update_command = kwargs.pop( + "time_between_update_command", DEFAULT_TIME_BETWEEN_UPDATE_COMMAND + ) + + def get_all_switchbots(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict: + """Find switchbot devices and their advertisement data, + returns after the given timeout period in seconds.""" + devices = None + services_data = {} + + waiting_time = self._time_between_update_command - time.time() + if waiting_time > 0: + time.sleep(waiting_time) + try: + devices = bluepy.btle.Scanner().scan(scan_timeout) + + except bluepy.btle.BTLEManagementError: + _LOGGER.warning("Error updating Switchbot.", exc_info=True) + + if devices is None: + if retry < 1: + _LOGGER.error( + "Switchbot update failed. Stopping trying.", exc_info=True + ) + return None + + _LOGGER.warning( + "Cannot update Switchbot. Retrying (remaining: %d)...", retry + ) + time.sleep(DEFAULT_RETRY_TIMEOUT) + return self.get_all_switchbots(retry - 1, scan_timeout) + + for dev in devices: + for (adtype, desc, value) in dev.getScanData(): + if value == UUID: + services_data[dev.addr] = {} + services_data[dev.addr]["rssi"] = dev.rssi + for (adtype, desc, value) in dev.getScanData(): + services_data[dev.addr][desc] = value + + return services_data + + class SwitchbotDevice: - # pylint: disable=too-few-public-methods # pylint: disable=too-many-instance-attributes """Base Representation of a Switchbot Device.""" From 793c9b76ec4bf6409c4253563b15fbdf65010ef1 Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Tue, 8 Jun 2021 18:33:46 +0200 Subject: [PATCH 02/33] Method to return all switchbot info --- switchbot/__init__.py | 67 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index b4aa6b36..2adec6f9 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -37,12 +37,13 @@ def __init__(self, **kwargs) -> None: self._time_between_update_command = kwargs.pop( "time_between_update_command", DEFAULT_TIME_BETWEEN_UPDATE_COMMAND ) + self._services_data = {} + self._reverse = kwargs.pop("reverse_mode", True) - def get_all_switchbots(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict: + def discover(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict: """Find switchbot devices and their advertisement data, returns after the given timeout period in seconds.""" devices = None - services_data = {} waiting_time = self._time_between_update_command - time.time() if waiting_time > 0: @@ -64,17 +65,65 @@ def get_all_switchbots(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict: "Cannot update Switchbot. Retrying (remaining: %d)...", retry ) time.sleep(DEFAULT_RETRY_TIMEOUT) - return self.get_all_switchbots(retry - 1, scan_timeout) + return self.discover(retry - 1, scan_timeout) for dev in devices: - for (adtype, desc, value) in dev.getScanData(): - if value == UUID: - services_data[dev.addr] = {} - services_data[dev.addr]["rssi"] = dev.rssi + for (_adt, _des, _val) in dev.getScanData(): + if _val == UUID: + self._services_data[dev.addr] = {} + self._services_data[dev.addr]["rssi"] = dev.rssi for (adtype, desc, value) in dev.getScanData(): - services_data[dev.addr][desc] = value + if adtype == 22: + self._services_data[dev.addr][desc] = value[4:] + self._services_data[dev.addr]["model"] = binascii.unhexlify( + value[4:6] + ).decode() + else: + self._services_data[dev.addr][desc] = value + + return self._services_data + + def all_bots(self) -> dict: + """Return all bots with their data.""" + self.discover() + bot_sensors = {} + + for item in self._services_data: + if self._services_data[item]["model"] == "H": + bot_sensors[item] = self._services_data[item] + _sensor_data = self._services_data[item]["16b Service Data"] + _sensor_data = binascii.unhexlify(_sensor_data.encode()) + _mode = _sensor_data[1] & 0b10000000 # 128 switch or 0 toggle + if _mode != 0: + bot_sensors[item]["mode"] = "switch" + else: + bot_sensors[item]["mode"] = "toggle" + + _is_on = _sensor_data[1] & 0b01000000 # 64 on or 0 for off + if _is_on == 0 and bot_sensors[item]["mode"] == "switch": + bot_sensors[item]["state"] = True + else: + bot_sensors[item]["state"] = False + + bot_sensors[item]["battery_percent"] = _sensor_data[2] & 0b01111111 + + def all_curtains(self) -> dict: + """Return all bots with their data.""" + self.discover() + curtain_sensors = {} + + for item in self._services_data: + if self._services_data[item]["model"] == "c": + curtain_sensors[item] = self._services_data[item] + _sensor_data = self._services_data[item]["16b Service Data"] + _sensor_data = binascii.unhexlify(_sensor_data.encode()) + + curtain_sensors[item]['calibrated'] = _sensor_data[1] & 0b01000000 + curtain_sensors[item]['battery_percent'] = _sensor_data[2] & 0b01111111 + _position = max(min(_sensor_data[3] & 0b01111111, 100), 0) + curtain_sensors[item]['pos'] = (100 - _position) if self._reverse else _position + curtain_sensors[item]['light_level'] = (_sensor_data[4] >> 4) & 0b00001111 # light sensor level (1-10) - return services_data class SwitchbotDevice: From 1b04a9b6b4f0c64a63d0b0e537d84535e3a97877 Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Tue, 8 Jun 2021 18:38:23 +0200 Subject: [PATCH 03/33] Update __init__.py --- switchbot/__init__.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index 2adec6f9..3cab304d 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -107,6 +107,8 @@ def all_bots(self) -> dict: bot_sensors[item]["battery_percent"] = _sensor_data[2] & 0b01111111 + return bot_sensors + def all_curtains(self) -> dict: """Return all bots with their data.""" self.discover() @@ -118,12 +120,17 @@ def all_curtains(self) -> dict: _sensor_data = self._services_data[item]["16b Service Data"] _sensor_data = binascii.unhexlify(_sensor_data.encode()) - curtain_sensors[item]['calibrated'] = _sensor_data[1] & 0b01000000 - curtain_sensors[item]['battery_percent'] = _sensor_data[2] & 0b01111111 + curtain_sensors[item]["calibrated"] = _sensor_data[1] & 0b01000000 + curtain_sensors[item]["battery_percent"] = _sensor_data[2] & 0b01111111 _position = max(min(_sensor_data[3] & 0b01111111, 100), 0) - curtain_sensors[item]['pos'] = (100 - _position) if self._reverse else _position - curtain_sensors[item]['light_level'] = (_sensor_data[4] >> 4) & 0b00001111 # light sensor level (1-10) + curtain_sensors[item]["pos"] = ( + (100 - _position) if self._reverse else _position + ) + curtain_sensors[item]["light_level"] = ( + _sensor_data[4] >> 4 + ) & 0b00001111 # light sensor level (1-10) + return curtain_sensors class SwitchbotDevice: From 0c70c4055ebcba28055bf5fa7e742c16a75a1450 Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Tue, 8 Jun 2021 19:04:50 +0200 Subject: [PATCH 04/33] Update __init__.py --- switchbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index 3cab304d..932c9c4b 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -120,7 +120,7 @@ def all_curtains(self) -> dict: _sensor_data = self._services_data[item]["16b Service Data"] _sensor_data = binascii.unhexlify(_sensor_data.encode()) - curtain_sensors[item]["calibrated"] = _sensor_data[1] & 0b01000000 + curtain_sensors[item]["calibrated"] = bool(_sensor_data[1] & 0b01000000) curtain_sensors[item]["battery_percent"] = _sensor_data[2] & 0b01111111 _position = max(min(_sensor_data[3] & 0b01111111, 100), 0) curtain_sensors[item]["pos"] = ( From 30bd74055bf27ea656d660c801e6c1ac38aafbee Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Thu, 10 Jun 2021 18:06:03 +0200 Subject: [PATCH 05/33] Refactor code into methods --- switchbot/__init__.py | 113 +++++++++++++++++++++++------------------- 1 file changed, 62 insertions(+), 51 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index 932c9c4b..b2fc24fa 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -68,70 +68,81 @@ def discover(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict: return self.discover(retry - 1, scan_timeout) for dev in devices: - for (_adt, _des, _val) in dev.getScanData(): - if _val == UUID: - self._services_data[dev.addr] = {} - self._services_data[dev.addr]["rssi"] = dev.rssi - for (adtype, desc, value) in dev.getScanData(): - if adtype == 22: - self._services_data[dev.addr][desc] = value[4:] - self._services_data[dev.addr]["model"] = binascii.unhexlify( - value[4:6] - ).decode() - else: - self._services_data[dev.addr][desc] = value + if dev.getValueText(7) == UUID: + dev_id = dev.addr.replace(":", "") + self._services_data[dev_id] = {} + self._services_data[dev_id]["mac_address"] = dev.addr + self._services_data[dev_id]["rssi"] = dev.rssi + for (adtype, desc, value) in dev.getScanData(): + if adtype == 22: + _model = binascii.unhexlify(value[4:6]).decode() + if _model == "H": + self._services_data[dev_id][ + "serviceData" + ] = self._process_wohand(value[4:]) + self._services_data[dev_id]['serviceData'][ + 'model' + ] = _model + self._services_data[dev_id]['serviceData'][ + 'modelName' + ] = "WoHand" + if _model == "c": + self._services_data[dev_id][ + 'serviceData' + ] = self._process_wocurtain(value[4:]) + self._services_data[dev_id]['serviceData'][ + 'model' + ] = _model + self._services_data[dev_id]['serviceData'][ + 'modelName' + ] = "WoCurtain" + else: + self._services_data[dev_id][desc] = value return self._services_data - def all_bots(self) -> dict: - """Return all bots with their data.""" - self.discover() + def _process_wohand(self, data) -> dict: + """Process woHand/Bot services data.""" bot_sensors = {} - for item in self._services_data: - if self._services_data[item]["model"] == "H": - bot_sensors[item] = self._services_data[item] - _sensor_data = self._services_data[item]["16b Service Data"] - _sensor_data = binascii.unhexlify(_sensor_data.encode()) - _mode = _sensor_data[1] & 0b10000000 # 128 switch or 0 toggle - if _mode != 0: - bot_sensors[item]["mode"] = "switch" - else: - bot_sensors[item]["mode"] = "toggle" - - _is_on = _sensor_data[1] & 0b01000000 # 64 on or 0 for off - if _is_on == 0 and bot_sensors[item]["mode"] == "switch": - bot_sensors[item]["state"] = True - else: - bot_sensors[item]["state"] = False - - bot_sensors[item]["battery_percent"] = _sensor_data[2] & 0b01111111 + _sensor_data = binascii.unhexlify(data.encode()) + bot_sensors["modeSwitch"] = bool( + _sensor_data[1] & 0b10000000 + ) # 128 switch or 0 press + + _is_on = bool(_sensor_data[1] & 0b01000000) # 64 on or 0 for off + if bool(_sensor_data[1] & 0b10000000): + bot_sensors["state"] = False + else: + bot_sensors["state"] = bool(_sensor_data[1] & 0b01000000) + + bot_sensors["battery"] = _sensor_data[2] & 0b01111111 return bot_sensors - def all_curtains(self) -> dict: - """Return all bots with their data.""" - self.discover() + def _process_wocurtain(self, data) -> dict: + """Process woCurtain/Curtain services data.""" curtain_sensors = {} - for item in self._services_data: - if self._services_data[item]["model"] == "c": - curtain_sensors[item] = self._services_data[item] - _sensor_data = self._services_data[item]["16b Service Data"] - _sensor_data = binascii.unhexlify(_sensor_data.encode()) - - curtain_sensors[item]["calibrated"] = bool(_sensor_data[1] & 0b01000000) - curtain_sensors[item]["battery_percent"] = _sensor_data[2] & 0b01111111 - _position = max(min(_sensor_data[3] & 0b01111111, 100), 0) - curtain_sensors[item]["pos"] = ( - (100 - _position) if self._reverse else _position - ) - curtain_sensors[item]["light_level"] = ( - _sensor_data[4] >> 4 - ) & 0b00001111 # light sensor level (1-10) + _sensor_data = binascii.unhexlify(data.encode()) + + curtain_sensors["calibration"] = bool(_sensor_data[1] & 0b01000000) + curtain_sensors["battery"] = _sensor_data[2] & 0b01111111 + _position = max(min(_sensor_data[3] & 0b01111111, 100), 0) + curtain_sensors["position"] = (100 - _position) if self._reverse else _position + curtain_sensors["lightLevel"] = ( + _sensor_data[4] >> 4 + ) & 0b00001111 # light sensor level (1-10) return curtain_sensors + def get_curtains(self) -> dict: + """Return all WoCurtain/Curtains devices with services data.""" + _curtains = [item for item in self._services_data if item['model'] == 'c'] + + def get_bots(self) -> dict: + """Return all WoHand/Bot devices with services data.""" + _bots = [item for item in self._services_data if item['model'] == 'H'] class SwitchbotDevice: # pylint: disable=too-many-instance-attributes From 27f7f712391c0ec9f56ccb7e33eb383464371fb3 Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Thu, 10 Jun 2021 18:19:58 +0200 Subject: [PATCH 06/33] Update __init__.py --- switchbot/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index b2fc24fa..ba5fa236 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -110,8 +110,8 @@ def _process_wohand(self, data) -> dict: _sensor_data[1] & 0b10000000 ) # 128 switch or 0 press - _is_on = bool(_sensor_data[1] & 0b01000000) # 64 on or 0 for off - if bool(_sensor_data[1] & 0b10000000): + # 64 on or 0 for off + if not bool(_sensor_data[1] & 0b10000000): bot_sensors["state"] = False else: bot_sensors["state"] = bool(_sensor_data[1] & 0b01000000) From b4d14b5c0624e8ca2b2cea23a25ce87a231f2a8d Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Thu, 10 Jun 2021 19:14:39 +0200 Subject: [PATCH 07/33] Update __init__.py --- switchbot/__init__.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index ba5fa236..d7437b0e 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -71,14 +71,14 @@ def discover(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict: if dev.getValueText(7) == UUID: dev_id = dev.addr.replace(":", "") self._services_data[dev_id] = {} - self._services_data[dev_id]["mac_address"] = dev.addr - self._services_data[dev_id]["rssi"] = dev.rssi + self._services_data[dev_id]['mac_address'] = dev.addr + self._services_data[dev_id]['rssi'] = dev.rssi for (adtype, desc, value) in dev.getScanData(): if adtype == 22: _model = binascii.unhexlify(value[4:6]).decode() if _model == "H": self._services_data[dev_id][ - "serviceData" + 'serviceData' ] = self._process_wohand(value[4:]) self._services_data[dev_id]['serviceData'][ 'model' @@ -111,7 +111,7 @@ def _process_wohand(self, data) -> dict: ) # 128 switch or 0 press # 64 on or 0 for off - if not bool(_sensor_data[1] & 0b10000000): + if bot_sensors["modeSwitch"]: bot_sensors["state"] = False else: bot_sensors["state"] = bool(_sensor_data[1] & 0b01000000) @@ -138,11 +138,23 @@ def _process_wocurtain(self, data) -> dict: def get_curtains(self) -> dict: """Return all WoCurtain/Curtains devices with services data.""" - _curtains = [item for item in self._services_data if item['model'] == 'c'] + _curtain_devices = {} + + for item in self._services_data: + if item['serviceData']['model'] == 'c': + _curtain_devices[item] = item + + return _curtain_devices def get_bots(self) -> dict: """Return all WoHand/Bot devices with services data.""" - _bots = [item for item in self._services_data if item['model'] == 'H'] + _bot_devices = {} + + for item in self._services_data: + if item['serviceData']['model'] == 'H': + _bot_devices[item] = item + + return _bot_devices class SwitchbotDevice: # pylint: disable=too-many-instance-attributes From 22fea6770b3c458837cd60042307d4002b6b844d Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Thu, 10 Jun 2021 19:55:40 +0200 Subject: [PATCH 08/33] Update __init__.py --- switchbot/__init__.py | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index d7437b0e..fce3c931 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -71,30 +71,26 @@ def discover(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict: if dev.getValueText(7) == UUID: dev_id = dev.addr.replace(":", "") self._services_data[dev_id] = {} - self._services_data[dev_id]['mac_address'] = dev.addr - self._services_data[dev_id]['rssi'] = dev.rssi + self._services_data[dev_id]["mac_address"] = dev.addr + self._services_data[dev_id]["rssi"] = dev.rssi for (adtype, desc, value) in dev.getScanData(): if adtype == 22: _model = binascii.unhexlify(value[4:6]).decode() if _model == "H": self._services_data[dev_id][ - 'serviceData' + "serviceData" ] = self._process_wohand(value[4:]) - self._services_data[dev_id]['serviceData'][ - 'model' - ] = _model - self._services_data[dev_id]['serviceData'][ - 'modelName' + self._services_data[dev_id]["serviceData"]["model"] = _model + self._services_data[dev_id]["serviceData"][ + "modelName" ] = "WoHand" if _model == "c": self._services_data[dev_id][ - 'serviceData' + "serviceData" ] = self._process_wocurtain(value[4:]) - self._services_data[dev_id]['serviceData'][ - 'model' - ] = _model - self._services_data[dev_id]['serviceData'][ - 'modelName' + self._services_data[dev_id]["serviceData"]["model"] = _model + self._services_data[dev_id]["serviceData"][ + "modelName" ] = "WoCurtain" else: self._services_data[dev_id][desc] = value @@ -106,15 +102,12 @@ def _process_wohand(self, data) -> dict: bot_sensors = {} _sensor_data = binascii.unhexlify(data.encode()) - bot_sensors["modeSwitch"] = bool( + bot_sensors["switchMode"] = bool( _sensor_data[1] & 0b10000000 ) # 128 switch or 0 press # 64 on or 0 for off - if bot_sensors["modeSwitch"]: - bot_sensors["state"] = False - else: - bot_sensors["state"] = bool(_sensor_data[1] & 0b01000000) + bot_sensors["state"] = bool(_sensor_data[1] & 0b01000000) bot_sensors["battery"] = _sensor_data[2] & 0b01111111 @@ -141,8 +134,8 @@ def get_curtains(self) -> dict: _curtain_devices = {} for item in self._services_data: - if item['serviceData']['model'] == 'c': - _curtain_devices[item] = item + if self._services_data[item]["serviceData"]["model"] == "c": + _curtain_devices[item] = self._services_data[item] return _curtain_devices @@ -151,11 +144,12 @@ def get_bots(self) -> dict: _bot_devices = {} for item in self._services_data: - if item['serviceData']['model'] == 'H': - _bot_devices[item] = item + if self._services_data[item]["serviceData"]["model"] == "H": + _bot_devices[item] = self._services_data[item] return _bot_devices + class SwitchbotDevice: # pylint: disable=too-many-instance-attributes """Base Representation of a Switchbot Device.""" From 7ee2b6a1bae385b085ff370ece267b7f8241c703 Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Thu, 10 Jun 2021 20:38:20 +0200 Subject: [PATCH 09/33] Add invert for switch, cleanup variable names --- switchbot/__init__.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index fce3c931..8df87cf8 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -39,6 +39,7 @@ def __init__(self, **kwargs) -> None: ) self._services_data = {} self._reverse = kwargs.pop("reverse_mode", True) + self._invert_switch = kwargs.pop("invert_switch", False) def discover(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict: """Find switchbot devices and their advertisement data, @@ -99,35 +100,39 @@ def discover(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict: def _process_wohand(self, data) -> dict: """Process woHand/Bot services data.""" - bot_sensors = {} + _bot_data = {} _sensor_data = binascii.unhexlify(data.encode()) - bot_sensors["switchMode"] = bool( + _bot_data["switchMode"] = bool( _sensor_data[1] & 0b10000000 - ) # 128 switch or 0 press + ) # 128 switch or 0 press. - # 64 on or 0 for off - bot_sensors["state"] = bool(_sensor_data[1] & 0b01000000) + # 64 off or 0 for on, if not inversed in app. + if self._invert_switch: + _bot_data["isOn"] = not bool(_sensor_data[1] & 0b01000000) - bot_sensors["battery"] = _sensor_data[2] & 0b01111111 + else: + _bot_data["isOn"] = bool(_sensor_data[1] & 0b01000000) + + _bot_data["battery"] = _sensor_data[2] & 0b01111111 - return bot_sensors + return _bot_data def _process_wocurtain(self, data) -> dict: """Process woCurtain/Curtain services data.""" - curtain_sensors = {} + _curtain_data = {} _sensor_data = binascii.unhexlify(data.encode()) - curtain_sensors["calibration"] = bool(_sensor_data[1] & 0b01000000) - curtain_sensors["battery"] = _sensor_data[2] & 0b01111111 + _curtain_data["calibration"] = bool(_sensor_data[1] & 0b01000000) + _curtain_data["battery"] = _sensor_data[2] & 0b01111111 _position = max(min(_sensor_data[3] & 0b01111111, 100), 0) - curtain_sensors["position"] = (100 - _position) if self._reverse else _position - curtain_sensors["lightLevel"] = ( + _curtain_data["position"] = (100 - _position) if self._reverse else _position + _curtain_data["lightLevel"] = ( _sensor_data[4] >> 4 ) & 0b00001111 # light sensor level (1-10) - return curtain_sensors + return _curtain_data def get_curtains(self) -> dict: """Return all WoCurtain/Curtains devices with services data.""" From 34d79c2cd14541017cee923ac72ceda5a61d7558 Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Fri, 11 Jun 2021 07:18:25 +0200 Subject: [PATCH 10/33] Add method for spesific mac to Switchbots class, inverse isOn check, add get is calibrated to main switchbot class. --- switchbot/__init__.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index 8df87cf8..89c46b9d 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -40,6 +40,7 @@ def __init__(self, **kwargs) -> None: self._services_data = {} self._reverse = kwargs.pop("reverse_mode", True) self._invert_switch = kwargs.pop("invert_switch", False) + self._mac = kwargs.pop("mac", False) def discover(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict: """Find switchbot devices and their advertisement data, @@ -109,10 +110,10 @@ def _process_wohand(self, data) -> dict: # 64 off or 0 for on, if not inversed in app. if self._invert_switch: - _bot_data["isOn"] = not bool(_sensor_data[1] & 0b01000000) + _bot_data["isOn"] = bool(_sensor_data[1] & 0b01000000) else: - _bot_data["isOn"] = bool(_sensor_data[1] & 0b01000000) + _bot_data["isOn"] = not bool(_sensor_data[1] & 0b01000000) _bot_data["battery"] = _sensor_data[2] & 0b01111111 @@ -154,6 +155,16 @@ def get_bots(self) -> dict: return _bot_devices + def get_device(self) -> dict: + """Return data for specific device.""" + _switchbot_device = {} + + for item in self._services_data: + if self._services_data[item]["mac_address"] == self._mac: + _switchbot_device[item] = self._services_data[item] + + return _switchbot_device + class SwitchbotDevice: # pylint: disable=too-many-instance-attributes @@ -369,7 +380,7 @@ def __init__(self, *args, **kwargs) -> None: self._reverse = kwargs.pop("reverse_mode", True) self._pos = 0 self._light_level = 0 - self._is_calibrated = 0 + self._is_calibrated = None super().__init__(*args, **kwargs) def open(self) -> bool: @@ -400,7 +411,7 @@ def update(self, scan_timeout=5) -> None: if barray is None: return - self._is_calibrated = barray[1] & 0b01000000 + self._is_calibrated = bool(barray[1] & 0b01000000) self._battery_percent = barray[2] & 0b01111111 position = max(min(barray[3] & 0b01111111, 100), 0) self._pos = (100 - position) if self._reverse else position @@ -419,3 +430,7 @@ def get_light_level(self) -> int: def is_reversed(self) -> bool: """Returns True if the curtain open from left to right.""" return self._reverse + + def is_calibrated(self) -> bool: + """Returns True curtain is calibrated.""" + return self._is_calibrated From 8c65d33c2eb2f96d0363764b302483edea4f861f Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Fri, 11 Jun 2021 07:41:51 +0200 Subject: [PATCH 11/33] Add temperature sensor to discovery. (I have no way to test this) --- switchbot/__init__.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index 89c46b9d..a0f96c83 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -94,6 +94,14 @@ def discover(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict: self._services_data[dev_id]["serviceData"][ "modelName" ] = "WoCurtain" + if _model == "T": + self._services_data[dev_id][ + "serviceData" + ] = self._process_wocurtain(value[4:]) + self._services_data[dev_id]["serviceData"]["model"] = _model + self._services_data[dev_id]["serviceData"][ + "modelName" + ] = "WoSensorTH" else: self._services_data[dev_id][desc] = value @@ -135,6 +143,27 @@ def _process_wocurtain(self, data) -> dict: return _curtain_data + # pylint: disable=R0201 + def _process_wosensorth(self, data) -> dict: + """Process woSensorTH/Temp sensor services data.""" + _wosensorth_data = {} + + _sensor_data = binascii.unhexlify(data.encode()) + + _temp_sign = _sensor_data[4] & 0b10000000 + _temp_c = _temp_sign * ((_sensor_data[4] & 0b01111111) + (_sensor_data[3] / 10)) + _temp_f = (_temp_c * 9 / 5) + 32 + _temp_f = (_temp_f * 10) / 10 + + _wosensorth_data["temp"]["c"] = _temp_c + _wosensorth_data["temp"]["f"] = _temp_f + + _wosensorth_data["fahrenheit"] = bool(_sensor_data[5] & 0b10000000) + _wosensorth_data["humidity"] = _sensor_data[5] & 0b01111111 + _wosensorth_data["battery"] = _sensor_data[2] & 0b01111111 + + return _wosensorth_data + def get_curtains(self) -> dict: """Return all WoCurtain/Curtains devices with services data.""" _curtain_devices = {} From 5fd971b4ca88c5866dcfd988d96a9dd8e801d7ed Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Fri, 11 Jun 2021 18:25:56 +0200 Subject: [PATCH 12/33] Update __init__.py --- switchbot/__init__.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index a0f96c83..340313ac 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -39,8 +39,6 @@ def __init__(self, **kwargs) -> None: ) self._services_data = {} self._reverse = kwargs.pop("reverse_mode", True) - self._invert_switch = kwargs.pop("invert_switch", False) - self._mac = kwargs.pop("mac", False) def discover(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict: """Find switchbot devices and their advertisement data, @@ -107,6 +105,7 @@ def discover(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict: return self._services_data + # pylint: disable=R0201 def _process_wohand(self, data) -> dict: """Process woHand/Bot services data.""" _bot_data = {} @@ -117,11 +116,11 @@ def _process_wohand(self, data) -> dict: ) # 128 switch or 0 press. # 64 off or 0 for on, if not inversed in app. - if self._invert_switch: - _bot_data["isOn"] = bool(_sensor_data[1] & 0b01000000) + if _bot_data["switchMode"]: + _bot_data["isOn"] = not bool(_sensor_data[1] & 0b01000000) else: - _bot_data["isOn"] = not bool(_sensor_data[1] & 0b01000000) + _bot_data["isOn"] = False _bot_data["battery"] = _sensor_data[2] & 0b01111111 @@ -184,12 +183,12 @@ def get_bots(self) -> dict: return _bot_devices - def get_device(self) -> dict: + def get_device(self, mac) -> dict: """Return data for specific device.""" _switchbot_device = {} for item in self._services_data: - if self._services_data[item]["mac_address"] == self._mac: + if self._services_data[item]["mac_address"] == mac: _switchbot_device[item] = self._services_data[item] return _switchbot_device From 2aefece45388c2bd763a51db35fffe6b165e8d5b Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Sat, 12 Jun 2021 19:11:42 +0200 Subject: [PATCH 13/33] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 99cd67ae..cda38fcb 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ name = 'PySwitchbot', packages = ['switchbot'], install_requires=['bluepy'], - version = '0.10.1', + version = '0.10.2', description = 'A library to communicate with Switchbot', author='Daniel Hjelseth Hoyer', url='https://github.com/Danielhiversen/pySwitchbot/', From a8bb1ac7f570bee49ddf23537706a091148467af Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Mon, 14 Jun 2021 06:34:57 +0200 Subject: [PATCH 14/33] Remove waiting time. This can be handled by hassio --- switchbot/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index 340313ac..53c36d9b 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -45,9 +45,6 @@ def discover(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict: returns after the given timeout period in seconds.""" devices = None - waiting_time = self._time_between_update_command - time.time() - if waiting_time > 0: - time.sleep(waiting_time) try: devices = bluepy.btle.Scanner().scan(scan_timeout) From fbde75a58ae8b96e1013f57f67e40ab218186650 Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Wed, 16 Jun 2021 10:31:10 +0200 Subject: [PATCH 15/33] Restructured switchbot base class for device discovery and single poll for all device updates. --- switchbot/__init__.py | 433 ++++++++++++++++++------------------------ 1 file changed, 185 insertions(+), 248 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index 53c36d9b..9ecbfa54 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -1,4 +1,4 @@ -"""Library to handle connection with Switchbot""" +"""Library to handle connection with Switchbot.""" import binascii import logging import time @@ -6,8 +6,7 @@ import bluepy DEFAULT_RETRY_COUNT = 3 -DEFAULT_RETRY_TIMEOUT = 0.2 -DEFAULT_TIME_BETWEEN_UPDATE_COMMAND = 10 +DEFAULT_RETRY_TIMEOUT = 1 UUID = "cba20d00-224d-11e6-9fb8-0002a5d5c51b" HANDLE = "cba20002-224d-11e6-9fb8-0002a5d5c51b" @@ -30,181 +29,76 @@ _LOGGER = logging.getLogger(__name__) -class SwitchbotDevices: - """Get all switchbots and their data.""" +def _process_wohand(data) -> dict: + """Process woHand/Bot services data.""" + _bot_data = {} - def __init__(self, **kwargs) -> None: - self._time_between_update_command = kwargs.pop( - "time_between_update_command", DEFAULT_TIME_BETWEEN_UPDATE_COMMAND - ) - self._services_data = {} - self._reverse = kwargs.pop("reverse_mode", True) + _sensor_data = binascii.unhexlify(data.encode()) - def discover(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict: - """Find switchbot devices and their advertisement data, - returns after the given timeout period in seconds.""" - devices = None + # 128 switch or 0 press. + _bot_data["switchMode"] = bool(_sensor_data[1] & 0b10000000) - try: - devices = bluepy.btle.Scanner().scan(scan_timeout) + # 64 off or 0 for on, if not inversed in app. + if _bot_data["switchMode"]: + _bot_data["isOn"] = not bool(_sensor_data[1] & 0b01000000) - except bluepy.btle.BTLEManagementError: - _LOGGER.warning("Error updating Switchbot.", exc_info=True) + else: + _bot_data["isOn"] = False - if devices is None: - if retry < 1: - _LOGGER.error( - "Switchbot update failed. Stopping trying.", exc_info=True - ) - return None + _bot_data["battery"] = _sensor_data[2] & 0b01111111 - _LOGGER.warning( - "Cannot update Switchbot. Retrying (remaining: %d)...", retry - ) - time.sleep(DEFAULT_RETRY_TIMEOUT) - return self.discover(retry - 1, scan_timeout) + return _bot_data - for dev in devices: - if dev.getValueText(7) == UUID: - dev_id = dev.addr.replace(":", "") - self._services_data[dev_id] = {} - self._services_data[dev_id]["mac_address"] = dev.addr - self._services_data[dev_id]["rssi"] = dev.rssi - for (adtype, desc, value) in dev.getScanData(): - if adtype == 22: - _model = binascii.unhexlify(value[4:6]).decode() - if _model == "H": - self._services_data[dev_id][ - "serviceData" - ] = self._process_wohand(value[4:]) - self._services_data[dev_id]["serviceData"]["model"] = _model - self._services_data[dev_id]["serviceData"][ - "modelName" - ] = "WoHand" - if _model == "c": - self._services_data[dev_id][ - "serviceData" - ] = self._process_wocurtain(value[4:]) - self._services_data[dev_id]["serviceData"]["model"] = _model - self._services_data[dev_id]["serviceData"][ - "modelName" - ] = "WoCurtain" - if _model == "T": - self._services_data[dev_id][ - "serviceData" - ] = self._process_wocurtain(value[4:]) - self._services_data[dev_id]["serviceData"]["model"] = _model - self._services_data[dev_id]["serviceData"][ - "modelName" - ] = "WoSensorTH" - else: - self._services_data[dev_id][desc] = value - return self._services_data +def _process_wocurtain(data, reverse=True) -> dict: + """Process woCurtain/Curtain services data.""" + _curtain_data = {} - # pylint: disable=R0201 - def _process_wohand(self, data) -> dict: - """Process woHand/Bot services data.""" - _bot_data = {} + _sensor_data = binascii.unhexlify(data.encode()) - _sensor_data = binascii.unhexlify(data.encode()) - _bot_data["switchMode"] = bool( - _sensor_data[1] & 0b10000000 - ) # 128 switch or 0 press. + _curtain_data["calibration"] = bool(_sensor_data[1] & 0b01000000) + _curtain_data["battery"] = _sensor_data[2] & 0b01111111 + _position = max(min(_sensor_data[3] & 0b01111111, 100), 0) + _curtain_data["position"] = (100 - _position) if reverse else _position - # 64 off or 0 for on, if not inversed in app. - if _bot_data["switchMode"]: - _bot_data["isOn"] = not bool(_sensor_data[1] & 0b01000000) + # light sensor level (1-10) + _curtain_data["lightLevel"] = (_sensor_data[4] >> 4) & 0b00001111 - else: - _bot_data["isOn"] = False + return _curtain_data - _bot_data["battery"] = _sensor_data[2] & 0b01111111 - return _bot_data +def _process_wosensorth(data) -> dict: + """Process woSensorTH/Temp sensor services data.""" + _wosensorth_data = {} - def _process_wocurtain(self, data) -> dict: - """Process woCurtain/Curtain services data.""" - _curtain_data = {} + _sensor_data = binascii.unhexlify(data.encode()) - _sensor_data = binascii.unhexlify(data.encode()) + _temp_sign = _sensor_data[4] & 0b10000000 + _temp_c = _temp_sign * ((_sensor_data[4] & 0b01111111) + (_sensor_data[3] / 10)) + _temp_f = (_temp_c * 9 / 5) + 32 + _temp_f = (_temp_f * 10) / 10 - _curtain_data["calibration"] = bool(_sensor_data[1] & 0b01000000) - _curtain_data["battery"] = _sensor_data[2] & 0b01111111 - _position = max(min(_sensor_data[3] & 0b01111111, 100), 0) - _curtain_data["position"] = (100 - _position) if self._reverse else _position - _curtain_data["lightLevel"] = ( - _sensor_data[4] >> 4 - ) & 0b00001111 # light sensor level (1-10) + _wosensorth_data["temp"]["c"] = _temp_c + _wosensorth_data["temp"]["f"] = _temp_f - return _curtain_data + _wosensorth_data["fahrenheit"] = bool(_sensor_data[5] & 0b10000000) + _wosensorth_data["humidity"] = _sensor_data[5] & 0b01111111 + _wosensorth_data["battery"] = _sensor_data[2] & 0b01111111 - # pylint: disable=R0201 - def _process_wosensorth(self, data) -> dict: - """Process woSensorTH/Temp sensor services data.""" - _wosensorth_data = {} - - _sensor_data = binascii.unhexlify(data.encode()) - - _temp_sign = _sensor_data[4] & 0b10000000 - _temp_c = _temp_sign * ((_sensor_data[4] & 0b01111111) + (_sensor_data[3] / 10)) - _temp_f = (_temp_c * 9 / 5) + 32 - _temp_f = (_temp_f * 10) / 10 - - _wosensorth_data["temp"]["c"] = _temp_c - _wosensorth_data["temp"]["f"] = _temp_f - - _wosensorth_data["fahrenheit"] = bool(_sensor_data[5] & 0b10000000) - _wosensorth_data["humidity"] = _sensor_data[5] & 0b01111111 - _wosensorth_data["battery"] = _sensor_data[2] & 0b01111111 - - return _wosensorth_data - - def get_curtains(self) -> dict: - """Return all WoCurtain/Curtains devices with services data.""" - _curtain_devices = {} - - for item in self._services_data: - if self._services_data[item]["serviceData"]["model"] == "c": - _curtain_devices[item] = self._services_data[item] - - return _curtain_devices - - def get_bots(self) -> dict: - """Return all WoHand/Bot devices with services data.""" - _bot_devices = {} - - for item in self._services_data: - if self._services_data[item]["serviceData"]["model"] == "H": - _bot_devices[item] = self._services_data[item] - - return _bot_devices - - def get_device(self, mac) -> dict: - """Return data for specific device.""" - _switchbot_device = {} - - for item in self._services_data: - if self._services_data[item]["mac_address"] == mac: - _switchbot_device[item] = self._services_data[item] - - return _switchbot_device + return _wosensorth_data class SwitchbotDevice: - # pylint: disable=too-many-instance-attributes """Base Representation of a Switchbot Device.""" - def __init__(self, mac, password=None, interface=None, **kwargs) -> None: + def __init__(self, mac=None, password=None, interface=None, **kwargs) -> None: + """Switchbot base class constructor.""" self._interface = interface self._mac = mac self._device = None - self._battery_percent = 0 + self._all_services_data = {} + self._switchbot_device_data = {} self._retry_count = kwargs.pop("retry_count", DEFAULT_RETRY_COUNT) - self._time_between_update_command = kwargs.pop( - "time_between_update_command", DEFAULT_TIME_BETWEEN_UPDATE_COMMAND - ) - self._last_time_command_send = time.time() if password is None or password == "": self._password_encoded = None else: @@ -216,13 +110,13 @@ def _connect(self) -> None: if self._device is not None: return try: - _LOGGER.debug("Connecting to Switchbot...") + _LOGGER.debug("Connecting to Switchbot") self._device = bluepy.btle.Peripheral( self._mac, bluepy.btle.ADDR_TYPE_RANDOM, self._interface ) - _LOGGER.debug("Connected to Switchbot.") + _LOGGER.debug("Connected to Switchbot") except bluepy.btle.BTLEException: - _LOGGER.debug("Failed connecting to Switchbot.", exc_info=True) + _LOGGER.debug("Failed connecting to Switchbot", exc_info=True) self._device = None raise @@ -233,7 +127,7 @@ def _disconnect(self) -> None: try: self._device.disconnect() except bluepy.btle.BTLEException: - _LOGGER.warning("Error disconnecting from Switchbot.", exc_info=True) + _LOGGER.warning("Error disconnecting from Switchbot", exc_info=True) finally: self._device = None @@ -253,14 +147,13 @@ def _writekey(self, key) -> bool: hand = hand_service.getCharacteristics(HANDLE)[0] _LOGGER.debug("Sending command, %s", key) write_result = hand.write(binascii.a2b_hex(key), withResponse=True) - self._last_time_command_send = time.time() if not write_result: _LOGGER.error( "Sent command but didn't get a response from Switchbot confirming command was sent." - " Please check the Switchbot." + " Please check the Switchbot" ) else: - _LOGGER.info("Successfully sent command to Switchbot (MAC: %s).", self._mac) + _LOGGER.info("Successfully sent command to Switchbot (MAC: %s)", self._mac) return write_result def _sendcommand(self, key, retry) -> bool: @@ -271,101 +164,133 @@ def _sendcommand(self, key, retry) -> bool: self._connect() send_success = self._writekey(command) except bluepy.btle.BTLEException: - _LOGGER.warning("Error talking to Switchbot.", exc_info=True) + _LOGGER.warning("Error talking to Switchbot", exc_info=True) finally: self._disconnect() if send_success: return True if retry < 1: _LOGGER.error( - "Switchbot communication failed. Stopping trying.", exc_info=True + "Switchbot communication failed. Stopping trying", exc_info=True ) return False - _LOGGER.warning( - "Cannot connect to Switchbot. Retrying (remaining: %d)...", retry - ) + _LOGGER.warning("Cannot connect to Switchbot. Retrying (remaining: %d)", retry) time.sleep(DEFAULT_RETRY_TIMEOUT) return self._sendcommand(key, retry - 1) - def get_servicedata(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> bytearray: - """Get BTLE 16b Service Data, - returns after the given timeout period in seconds.""" + def discover(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict: + """Find switchbot devices and their advertisement data.""" + devices = None - waiting_time = self._time_between_update_command - time.time() - if waiting_time > 0: - time.sleep(waiting_time) try: devices = bluepy.btle.Scanner().scan(scan_timeout) except bluepy.btle.BTLEManagementError: - _LOGGER.warning("Error updating Switchbot.", exc_info=True) + _LOGGER.warning("Error scanning for Switchbot devices", exc_info=True) if devices is None: if retry < 1: _LOGGER.error( - "Switchbot update failed. Stopping trying.", exc_info=True + "Scanning for Switchbot devices failed. Stop trying", exc_info=True ) return None _LOGGER.warning( - "Cannot update Switchbot. Retrying (remaining: %d)...", retry + "Error scanning for Switchbot devices. Retrying (remaining: %d)", + retry, ) time.sleep(DEFAULT_RETRY_TIMEOUT) - return self.get_servicedata(retry - 1, scan_timeout) + return self.discover(retry - 1, scan_timeout) - for device in devices: - if self._mac.lower() == device.addr.lower(): - for (adtype, _, value) in device.getScanData(): + for dev in devices: + if dev.getValueText(7) == UUID: + dev_id = dev.addr.replace(":", "") + self._all_services_data[dev_id] = {} + self._all_services_data[dev_id]["mac_address"] = dev.addr + for (adtype, desc, value) in dev.getScanData(): if adtype == 22: - service_data = value[4:].encode() - service_data = binascii.unhexlify(service_data) - - return service_data + _model = binascii.unhexlify(value[4:6]).decode() + if _model == "H": + self._all_services_data[dev_id]["data"] = _process_wohand( + value[4:] + ) + self._all_services_data[dev_id]["data"]["rssi"] = dev.rssi + self._all_services_data[dev_id]["model"] = _model + self._all_services_data[dev_id]["modelName"] = "WoHand" + if _model == "c": + self._all_services_data[dev_id][ + "data" + ] = _process_wocurtain(value[4:]) + self._all_services_data[dev_id]["data"]["rssi"] = dev.rssi + self._all_services_data[dev_id]["model"] = _model + self._all_services_data[dev_id]["modelName"] = "WoCurtain" + if _model == "T": + self._all_services_data[dev_id][ + "data" + ] = _process_wosensorth(value[4:]) + self._all_services_data[dev_id]["data"]["rssi"] = dev.rssi + self._all_services_data[dev_id]["model"] = _model + self._all_services_data[dev_id]["modelName"] = "WoSensorTH" + else: + self._all_services_data[dev_id][desc] = value - return None + return self._all_services_data def get_mac(self) -> str: - """Returns the mac address of the device.""" - return self._mac + """Return mac address of device.""" + if self._mac: + return self._mac - def get_min_time_update(self): - """Returns the first time an update can be executed.""" - return self._last_time_command_send + self._time_between_update_command + return None def get_battery_percent(self) -> int: - """Returns device battery level in percent.""" - return self._battery_percent + """Return device battery level in percent.""" + if not self._switchbot_device_data: + self.get_device(self._mac) + return self._switchbot_device_data["data"]["battery"] + + def get_curtains(self) -> dict: + """Return all WoCurtain/Curtains devices with services data.""" + _curtain_devices = {} + + for item in self._all_services_data: + if self._all_services_data[item]["data"]["model"] == "c": + _curtain_devices[item] = self._all_services_data[item] + + return _curtain_devices + + def get_bots(self) -> dict: + """Return all WoHand/Bot devices with services data.""" + _bot_devices = {} + + for item in self._all_services_data: + if self._all_services_data[item]["data"]["model"] == "H": + _bot_devices[item] = self._all_services_data[item] + + return _bot_devices + + def get_device(self, mac) -> dict: + """Return data for specific device.""" + for item in self._all_services_data: + if self._all_services_data[item]["mac_address"] == mac: + self._switchbot_device_data[item] = self._all_services_data[item] + + return self._switchbot_device_data class Switchbot(SwitchbotDevice): """Representation of a Switchbot.""" def __init__(self, *args, **kwargs) -> None: - self._is_on = None - self._mode = None + """Switchbot Bot/WoHand constructor.""" + self._inverse = kwargs.pop("inverse_mode", False) super().__init__(*args, **kwargs) def update(self, scan_timeout=5) -> None: - """Updates the mode, battery percent and state of the device.""" - barray = self.get_servicedata(scan_timeout=scan_timeout) - - if barray is None: - return - - _mode = barray[1] & 0b10000000 # 128 switch or 0 toggle - if _mode != 0: - self._mode = "switch" - else: - self._mode = "toggle" - - _is_on = barray[1] & 0b01000000 # 64 on or 0 for off - if _is_on == 0 and self._mode == "switch": - self._is_on = True - else: - self._is_on = False - - self._battery_percent = barray[2] & 0b01111111 + """Update mode, battery percent and state of device.""" + self.discover(scan_timeout=scan_timeout) + self.get_device(self._mac) def turn_on(self) -> bool: """Turn device on.""" @@ -380,42 +305,47 @@ def press(self) -> bool: return self._sendcommand(PRESS_KEY, self._retry_count) def switch_mode(self) -> str: - """Return Toggle or Switch from cache. - Run update first.""" - return self._mode + """Return true or false from cache.""" + # To get actual position call update() first. + if not self._switchbot_device_data: + self.update() + return self._switchbot_device_data["data"]["switchMode"] def is_on(self) -> bool: - """Return switch state from cache. - Run update first.""" - return self._is_on + """Return switch state from cache.""" + # To get actual position call update() first. + if not self._switchbot_device_data: + self.update() + + if self._inverse: + return not self._switchbot_device_data["data"]["isOn"] + + return self._switchbot_device_data["data"]["isOn"] class SwitchbotCurtain(SwitchbotDevice): """Representation of a Switchbot Curtain.""" def __init__(self, *args, **kwargs) -> None: - """Constructor for a Switchbot Curtain. - The position of the curtain is saved in self._pos with 0 = open and 100 = closed. - This is independent of the calibration of the curtain bot (Open left to right/ - Open right to left/Open from the middle). - The parameter 'reverse_mode' reverse these values, - if 'reverse_mode' = True, position = 0 equals close - and position = 100 equals open. The parameter is default set to True so that - the definition of position is the same as in Home Assistant.""" + """Switchbot Curtain/WoCurtain constructor.""" + + # The position of the curtain is saved returned with 0 = open and 100 = closed. + # This is independent of the calibration of the curtain bot (Open left to right/ + # Open right to left/Open from the middle). + # The parameter 'reverse_mode' reverse these values, + # if 'reverse_mode' = True, position = 0 equals close + # and position = 100 equals open. The parameter is default set to True so that + # the definition of position is the same as in Home Assistant. + self._reverse = kwargs.pop("reverse_mode", True) - self._pos = 0 - self._light_level = 0 - self._is_calibrated = None super().__init__(*args, **kwargs) def open(self) -> bool: """Send open command.""" - self._pos = 100 if self._reverse else 0 return self._sendcommand(OPEN_KEY, self._retry_count) def close(self) -> bool: """Send close command.""" - self._pos = 0 if self._reverse else 100 return self._sendcommand(CLOSE_KEY, self._retry_count) def stop(self) -> bool: @@ -425,37 +355,44 @@ def stop(self) -> bool: def set_position(self, position: int) -> bool: """Send position command (0-100) to device.""" position = (100 - position) if self._reverse else position - self._pos = position hex_position = "%0.2X" % position return self._sendcommand(POSITION_KEY + hex_position, self._retry_count) def update(self, scan_timeout=5) -> None: - """Updates the current position, battery percent and light level of the device.""" - barray = self.get_servicedata(scan_timeout=scan_timeout) - - if barray is None: - return - - self._is_calibrated = bool(barray[1] & 0b01000000) - self._battery_percent = barray[2] & 0b01111111 - position = max(min(barray[3] & 0b01111111, 100), 0) - self._pos = (100 - position) if self._reverse else position - self._light_level = (barray[4] >> 4) & 0b00001111 # light sensor level (1-10) + """Update position, battery percent and light level of device.""" + self.discover(scan_timeout=scan_timeout) + self.get_device(self._mac) def get_position(self) -> int: - """Returns the current cached position (0-100), the actual position could vary. - To get the actual position call update() first.""" - return self._pos + """Return cached position (0-100) of Curtain.""" + # To get actual position call update() first. + if not self._switchbot_device_data: + self.update() + return self._switchbot_device_data["position"] def get_light_level(self) -> int: - """Returns the current cached light level, the actual light level could vary. - To get the actual light level call update() first.""" - return self._light_level + """Return cached light level.""" + # To get actual light level call update() first. + if not self._switchbot_device_data: + self.update() + return self._switchbot_device_data["lightLevel"] def is_reversed(self) -> bool: - """Returns True if the curtain open from left to right.""" + """Return True if the curtain open from left to right.""" return self._reverse def is_calibrated(self) -> bool: - """Returns True curtain is calibrated.""" - return self._is_calibrated + """Return True curtain is calibrated.""" + # To get actual light level call update() first. + if not self._switchbot_device_data: + self.update() + return self._switchbot_device_data["calibration"] + + +class SwitchbotDevices(Switchbot, SwitchbotCurtain): + """Superclass for all switchbot device types.""" + + def __init__(self, mac, *args, **kwargs) -> None: + """HA coordinator switchbot class constructor.""" + super().__init__(*args, **kwargs) + self._mac = mac From eeb071660c4d6c70f6ebc885eb220d3fc136beb6 Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Wed, 16 Jun 2021 12:46:29 +0200 Subject: [PATCH 16/33] Update __init__.py --- switchbot/__init__.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index 9ecbfa54..0599df1e 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -247,7 +247,7 @@ def get_mac(self) -> str: def get_battery_percent(self) -> int: """Return device battery level in percent.""" if not self._switchbot_device_data: - self.get_device(self._mac) + self.get_device_data(self._mac) return self._switchbot_device_data["data"]["battery"] def get_curtains(self) -> dict: @@ -270,8 +270,11 @@ def get_bots(self) -> dict: return _bot_devices - def get_device(self, mac) -> dict: + def get_device_data(self, mac) -> dict: """Return data for specific device.""" + if not self._switchbot_device_data: + self.discover() + for item in self._all_services_data: if self._all_services_data[item]["mac_address"] == mac: self._switchbot_device_data[item] = self._all_services_data[item] @@ -290,7 +293,7 @@ def __init__(self, *args, **kwargs) -> None: def update(self, scan_timeout=5) -> None: """Update mode, battery percent and state of device.""" self.discover(scan_timeout=scan_timeout) - self.get_device(self._mac) + self.get_device_data(self._mac) def turn_on(self) -> bool: """Turn device on.""" @@ -361,21 +364,21 @@ def set_position(self, position: int) -> bool: def update(self, scan_timeout=5) -> None: """Update position, battery percent and light level of device.""" self.discover(scan_timeout=scan_timeout) - self.get_device(self._mac) + self.get_device_data(self._mac) def get_position(self) -> int: """Return cached position (0-100) of Curtain.""" # To get actual position call update() first. if not self._switchbot_device_data: self.update() - return self._switchbot_device_data["position"] + return self._switchbot_device_data["data"]["position"] def get_light_level(self) -> int: """Return cached light level.""" # To get actual light level call update() first. if not self._switchbot_device_data: self.update() - return self._switchbot_device_data["lightLevel"] + return self._switchbot_device_data["data"]["lightLevel"] def is_reversed(self) -> bool: """Return True if the curtain open from left to right.""" @@ -386,7 +389,7 @@ def is_calibrated(self) -> bool: # To get actual light level call update() first. if not self._switchbot_device_data: self.update() - return self._switchbot_device_data["calibration"] + return self._switchbot_device_data["data"]["calibration"] class SwitchbotDevices(Switchbot, SwitchbotCurtain): From b8959e39c3e03052ec7fc04cba3ee334072746d1 Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Wed, 16 Jun 2021 12:56:59 +0200 Subject: [PATCH 17/33] Update __init__.py --- switchbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index 0599df1e..142f0dcc 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -277,7 +277,7 @@ def get_device_data(self, mac) -> dict: for item in self._all_services_data: if self._all_services_data[item]["mac_address"] == mac: - self._switchbot_device_data[item] = self._all_services_data[item] + self._switchbot_device_data = self._all_services_data[item] return self._switchbot_device_data From 8fc00aa98cbb3c4d80203fcdf202d4264c81689c Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Wed, 16 Jun 2021 13:09:12 +0200 Subject: [PATCH 18/33] Update __init__.py --- switchbot/__init__.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index 142f0dcc..310c09fb 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -186,8 +186,9 @@ def discover(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict: try: devices = bluepy.btle.Scanner().scan(scan_timeout) - except bluepy.btle.BTLEManagementError: - _LOGGER.warning("Error scanning for Switchbot devices", exc_info=True) + except bluepy.btle.BTLEManagementError as err: + _LOGGER.error("Error scanning for switchbot devices", exc_info=True) + raise bluepy.btle.BTLEManagementError(err) from err if devices is None: if retry < 1: @@ -292,7 +293,12 @@ def __init__(self, *args, **kwargs) -> None: def update(self, scan_timeout=5) -> None: """Update mode, battery percent and state of device.""" - self.discover(scan_timeout=scan_timeout) + try: + self.discover(scan_timeout=scan_timeout) + except bluepy.btle.BTLEManagementError as err: + _LOGGER.error("Error fetching initial data", exc_info=True) + raise bluepy.btle.BTLEManagementError(err) from err + self.get_device_data(self._mac) def turn_on(self) -> bool: @@ -363,7 +369,12 @@ def set_position(self, position: int) -> bool: def update(self, scan_timeout=5) -> None: """Update position, battery percent and light level of device.""" - self.discover(scan_timeout=scan_timeout) + try: + self.discover(scan_timeout=scan_timeout) + except bluepy.btle.BTLEManagementError as err: + _LOGGER.error("Error fetching initial data", exc_info=True) + raise bluepy.btle.BTLEManagementError(err) from err + self.get_device_data(self._mac) def get_position(self) -> int: From 28a23238da09cca337c81bb855553d899fd065da Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Wed, 16 Jun 2021 13:16:12 +0200 Subject: [PATCH 19/33] Update __init__.py --- switchbot/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index 310c09fb..f81eafce 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -188,7 +188,7 @@ def discover(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict: except bluepy.btle.BTLEManagementError as err: _LOGGER.error("Error scanning for switchbot devices", exc_info=True) - raise bluepy.btle.BTLEManagementError(err) from err + raise bluepy.btle.BTLEManagementError from err if devices is None: if retry < 1: @@ -297,7 +297,7 @@ def update(self, scan_timeout=5) -> None: self.discover(scan_timeout=scan_timeout) except bluepy.btle.BTLEManagementError as err: _LOGGER.error("Error fetching initial data", exc_info=True) - raise bluepy.btle.BTLEManagementError(err) from err + raise bluepy.btle.BTLEManagementError from err self.get_device_data(self._mac) @@ -373,7 +373,7 @@ def update(self, scan_timeout=5) -> None: self.discover(scan_timeout=scan_timeout) except bluepy.btle.BTLEManagementError as err: _LOGGER.error("Error fetching initial data", exc_info=True) - raise bluepy.btle.BTLEManagementError(err) from err + raise bluepy.btle.BTLEManagementError from err self.get_device_data(self._mac) From 54bfa055cb39bb9f41b467e5f0fd322d756ed27f Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Wed, 16 Jun 2021 13:30:59 +0200 Subject: [PATCH 20/33] Prevent additional runs if update fails. --- switchbot/__init__.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index f81eafce..8eb2b4b9 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -186,9 +186,8 @@ def discover(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict: try: devices = bluepy.btle.Scanner().scan(scan_timeout) - except bluepy.btle.BTLEManagementError as err: + except bluepy.btle.BTLEManagementError: _LOGGER.error("Error scanning for switchbot devices", exc_info=True) - raise bluepy.btle.BTLEManagementError from err if devices is None: if retry < 1: @@ -293,13 +292,10 @@ def __init__(self, *args, **kwargs) -> None: def update(self, scan_timeout=5) -> None: """Update mode, battery percent and state of device.""" - try: - self.discover(scan_timeout=scan_timeout) - except bluepy.btle.BTLEManagementError as err: - _LOGGER.error("Error fetching initial data", exc_info=True) - raise bluepy.btle.BTLEManagementError from err + _scan_btle_adv = self.discover(scan_timeout=scan_timeout) - self.get_device_data(self._mac) + if _scan_btle_adv: + self.get_device_data(self._mac) def turn_on(self) -> bool: """Turn device on.""" @@ -369,13 +365,10 @@ def set_position(self, position: int) -> bool: def update(self, scan_timeout=5) -> None: """Update position, battery percent and light level of device.""" - try: - self.discover(scan_timeout=scan_timeout) - except bluepy.btle.BTLEManagementError as err: - _LOGGER.error("Error fetching initial data", exc_info=True) - raise bluepy.btle.BTLEManagementError from err + _scan_btle_adv = self.discover(scan_timeout=scan_timeout) - self.get_device_data(self._mac) + if _scan_btle_adv: + self.get_device_data(self._mac) def get_position(self) -> int: """Return cached position (0-100) of Curtain.""" From 377afcbf6ffaeba734828dfb4cd0b69326dd2fbb Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Wed, 16 Jun 2021 13:51:08 +0200 Subject: [PATCH 21/33] Update __init__.py --- switchbot/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index 8eb2b4b9..4c09bbc7 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -272,7 +272,7 @@ def get_bots(self) -> dict: def get_device_data(self, mac) -> dict: """Return data for specific device.""" - if not self._switchbot_device_data: + if not self._all_services_data: self.discover() for item in self._all_services_data: @@ -292,9 +292,9 @@ def __init__(self, *args, **kwargs) -> None: def update(self, scan_timeout=5) -> None: """Update mode, battery percent and state of device.""" - _scan_btle_adv = self.discover(scan_timeout=scan_timeout) + _success = self.discover(scan_timeout=scan_timeout) - if _scan_btle_adv: + if _success: self.get_device_data(self._mac) def turn_on(self) -> bool: @@ -365,9 +365,9 @@ def set_position(self, position: int) -> bool: def update(self, scan_timeout=5) -> None: """Update position, battery percent and light level of device.""" - _scan_btle_adv = self.discover(scan_timeout=scan_timeout) + _success = self.discover(scan_timeout=scan_timeout) - if _scan_btle_adv: + if _success: self.get_device_data(self._mac) def get_position(self) -> int: From c7b75c76737e4f26b5e65c9787536cea512ee727 Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Wed, 16 Jun 2021 14:20:37 +0200 Subject: [PATCH 22/33] Update __init__.py --- switchbot/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index 4c09bbc7..f6576542 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -287,8 +287,9 @@ class Switchbot(SwitchbotDevice): def __init__(self, *args, **kwargs) -> None: """Switchbot Bot/WoHand constructor.""" - self._inverse = kwargs.pop("inverse_mode", False) super().__init__(*args, **kwargs) + self._inverse = kwargs.pop("inverse_mode", False) + self._switchbot_device_data = {} def update(self, scan_timeout=5) -> None: """Update mode, battery percent and state of device.""" @@ -342,8 +343,9 @@ def __init__(self, *args, **kwargs) -> None: # and position = 100 equals open. The parameter is default set to True so that # the definition of position is the same as in Home Assistant. - self._reverse = kwargs.pop("reverse_mode", True) super().__init__(*args, **kwargs) + self._reverse = kwargs.pop("reverse_mode", True) + self._switchbot_device_data = {} def open(self) -> bool: """Send open command.""" From c2d9a6ecd7caf79cae65b981c59c04ac668b36aa Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Wed, 16 Jun 2021 14:34:36 +0200 Subject: [PATCH 23/33] Update __init__.py --- switchbot/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index f6576542..619da168 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -293,9 +293,9 @@ def __init__(self, *args, **kwargs) -> None: def update(self, scan_timeout=5) -> None: """Update mode, battery percent and state of device.""" - _success = self.discover(scan_timeout=scan_timeout) + self._switchbot_device_data = self.discover(scan_timeout=scan_timeout) - if _success: + if self._switchbot_device_data: self.get_device_data(self._mac) def turn_on(self) -> bool: @@ -367,9 +367,9 @@ def set_position(self, position: int) -> bool: def update(self, scan_timeout=5) -> None: """Update position, battery percent and light level of device.""" - _success = self.discover(scan_timeout=scan_timeout) + self._switchbot_device_data = self.discover(scan_timeout=scan_timeout) - if _success: + if self._switchbot_device_data: self.get_device_data(self._mac) def get_position(self) -> int: From 51ace7b69324d2a8fac4213c8587ec3a316ba6dd Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Wed, 16 Jun 2021 14:57:23 +0200 Subject: [PATCH 24/33] Update __init__.py --- switchbot/__init__.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index 619da168..f7dd99e0 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -289,13 +289,12 @@ def __init__(self, *args, **kwargs) -> None: """Switchbot Bot/WoHand constructor.""" super().__init__(*args, **kwargs) self._inverse = kwargs.pop("inverse_mode", False) - self._switchbot_device_data = {} def update(self, scan_timeout=5) -> None: """Update mode, battery percent and state of device.""" - self._switchbot_device_data = self.discover(scan_timeout=scan_timeout) + _success = self.discover(scan_timeout=scan_timeout) - if self._switchbot_device_data: + if _success: self.get_device_data(self._mac) def turn_on(self) -> bool: @@ -345,7 +344,6 @@ def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._reverse = kwargs.pop("reverse_mode", True) - self._switchbot_device_data = {} def open(self) -> bool: """Send open command.""" @@ -367,9 +365,9 @@ def set_position(self, position: int) -> bool: def update(self, scan_timeout=5) -> None: """Update position, battery percent and light level of device.""" - self._switchbot_device_data = self.discover(scan_timeout=scan_timeout) + _success = self.discover(scan_timeout=scan_timeout) - if self._switchbot_device_data: + if _success: self.get_device_data(self._mac) def get_position(self) -> int: @@ -401,7 +399,8 @@ def is_calibrated(self) -> bool: class SwitchbotDevices(Switchbot, SwitchbotCurtain): """Superclass for all switchbot device types.""" - def __init__(self, mac, *args, **kwargs) -> None: + def __init__(self, *args, **kwargs) -> None: """HA coordinator switchbot class constructor.""" super().__init__(*args, **kwargs) - self._mac = mac + Switchbot().__init__(self) + SwitchbotCurtain().__init__(self) From 9044c5c86d27e56801ba2a085ac9c10315824eca Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Wed, 16 Jun 2021 15:15:21 +0200 Subject: [PATCH 25/33] Update __init__.py --- switchbot/__init__.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index f7dd99e0..ac8b79ad 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -1,6 +1,7 @@ """Library to handle connection with Switchbot.""" import binascii import logging +import threading import time import bluepy @@ -27,6 +28,7 @@ PRESS_KEY_SUFFIX = "00" _LOGGER = logging.getLogger(__name__) +CONNECT_LOCK = threading.Lock() def _process_wohand(data) -> dict: @@ -161,8 +163,9 @@ def _sendcommand(self, key, retry) -> bool: command = self._commandkey(key) _LOGGER.debug("Sending command to switchbot %s", command) try: - self._connect() - send_success = self._writekey(command) + with CONNECT_LOCK: + self._connect() + send_success = self._writekey(command) except bluepy.btle.BTLEException: _LOGGER.warning("Error talking to Switchbot", exc_info=True) finally: @@ -394,13 +397,3 @@ def is_calibrated(self) -> bool: if not self._switchbot_device_data: self.update() return self._switchbot_device_data["data"]["calibration"] - - -class SwitchbotDevices(Switchbot, SwitchbotCurtain): - """Superclass for all switchbot device types.""" - - def __init__(self, *args, **kwargs) -> None: - """HA coordinator switchbot class constructor.""" - super().__init__(*args, **kwargs) - Switchbot().__init__(self) - SwitchbotCurtain().__init__(self) From 17f053fcdfce4d9ff88a173e13c92edef31c633c Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Wed, 16 Jun 2021 18:02:22 +0200 Subject: [PATCH 26/33] Update setup.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Hjelseth Høyer --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index cda38fcb..9e0807a5 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ name = 'PySwitchbot', packages = ['switchbot'], install_requires=['bluepy'], - version = '0.10.2', + version = '0.11.0', description = 'A library to communicate with Switchbot', author='Daniel Hjelseth Hoyer', url='https://github.com/Danielhiversen/pySwitchbot/', From 8aadde5b300b46ddd5c1986f8e1344bcfe7a8e5b Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Wed, 16 Jun 2021 18:20:04 +0200 Subject: [PATCH 27/33] Update switchbot/__init__.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Hjelseth Høyer --- switchbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index ac8b79ad..1661e2d2 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -181,7 +181,7 @@ def _sendcommand(self, key, retry) -> bool: time.sleep(DEFAULT_RETRY_TIMEOUT) return self._sendcommand(key, retry - 1) - def discover(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict: + def discover(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict | None: """Find switchbot devices and their advertisement data.""" devices = None From e12f20f18b5de956ca525de65cfbe4cf70922338 Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Wed, 16 Jun 2021 20:05:41 +0200 Subject: [PATCH 28/33] New class for device scanning. --- switchbot/__init__.py | 227 +++++++++++++++++++++++++++--------------- 1 file changed, 147 insertions(+), 80 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index 1661e2d2..8c99054f 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -1,4 +1,6 @@ """Library to handle connection with Switchbot.""" +from __future__ import annotations + import binascii import logging import threading @@ -90,15 +92,116 @@ def _process_wosensorth(data) -> dict: return _wosensorth_data +class GetSwitchbotDevices: + """Scan for all Switchbot devices and return by type.""" + + def __init__(self) -> None: + """Get switchbot devices class constructor.""" + self._all_services_data = {} + self._switchbot_device_data = {} + + def discover(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict | None: + """Find switchbot devices and their advertisement data.""" + + devices = None + + try: + devices = bluepy.btle.Scanner().scan(scan_timeout) + + except bluepy.btle.BTLEManagementError: + _LOGGER.error("Error scanning for switchbot devices", exc_info=True) + + if devices is None: + if retry < 1: + _LOGGER.error( + "Scanning for Switchbot devices failed. Stop trying", exc_info=True + ) + return None + + _LOGGER.warning( + "Error scanning for Switchbot devices. Retrying (remaining: %d)", + retry, + ) + time.sleep(DEFAULT_RETRY_TIMEOUT) + return self.discover(retry - 1, scan_timeout) + + for dev in devices: + if dev.getValueText(7) == UUID: + dev_id = dev.addr.replace(":", "") + self._all_services_data[dev_id] = {} + self._all_services_data[dev_id]["mac_address"] = dev.addr + for (adtype, desc, value) in dev.getScanData(): + if adtype == 22: + _model = binascii.unhexlify(value[4:6]).decode() + if _model == "H": + self._all_services_data[dev_id]["data"] = _process_wohand( + value[4:] + ) + self._all_services_data[dev_id]["data"]["rssi"] = dev.rssi + self._all_services_data[dev_id]["model"] = _model + self._all_services_data[dev_id]["modelName"] = "WoHand" + elif _model == "c": + self._all_services_data[dev_id][ + "data" + ] = _process_wocurtain(value[4:]) + self._all_services_data[dev_id]["data"]["rssi"] = dev.rssi + self._all_services_data[dev_id]["model"] = _model + self._all_services_data[dev_id]["modelName"] = "WoCurtain" + elif _model == "T": + self._all_services_data[dev_id][ + "data" + ] = _process_wosensorth(value[4:]) + self._all_services_data[dev_id]["data"]["rssi"] = dev.rssi + self._all_services_data[dev_id]["model"] = _model + self._all_services_data[dev_id]["modelName"] = "WoSensorTH" + + else: + continue + else: + self._all_services_data[dev_id][desc] = value + + return self._all_services_data + + def get_curtains(self) -> dict: + """Return all WoCurtain/Curtains devices with services data.""" + _curtain_devices = {} + + for item in self._all_services_data: + if self._all_services_data[item]["data"]["model"] == "c": + _curtain_devices[item] = self._all_services_data[item] + + return _curtain_devices + + def get_bots(self) -> dict: + """Return all WoHand/Bot devices with services data.""" + _bot_devices = {} + + for item in self._all_services_data: + if self._all_services_data[item]["data"]["model"] == "H": + _bot_devices[item] = self._all_services_data[item] + + return _bot_devices + + def get_device_data(self, mac) -> dict: + """Return data for specific device.""" + if not self._all_services_data: + self.discover() + + for item in self._all_services_data: + if self._all_services_data[item]["mac_address"] == mac: + self._switchbot_device_data = self._all_services_data[item] + + return self._switchbot_device_data + + class SwitchbotDevice: """Base Representation of a Switchbot Device.""" - def __init__(self, mac=None, password=None, interface=None, **kwargs) -> None: + def __init__(self, mac, password=None, interface=None, **kwargs) -> None: """Switchbot base class constructor.""" self._interface = interface self._mac = mac self._device = None - self._all_services_data = {} self._switchbot_device_data = {} self._retry_count = kwargs.pop("retry_count", DEFAULT_RETRY_COUNT) if password is None or password == "": @@ -181,7 +284,20 @@ def _sendcommand(self, key, retry) -> bool: time.sleep(DEFAULT_RETRY_TIMEOUT) return self._sendcommand(key, retry - 1) - def discover(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict | None: + def get_mac(self) -> str: + """Return mac address of device.""" + if self._mac: + return self._mac + + return None + + def get_battery_percent(self) -> int: + """Return device battery level in percent.""" + if not self._switchbot_device_data: + self.get_device_data(self._mac) + return self._switchbot_device_data["data"]["battery"] + + def get_device_data(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict | None: """Find switchbot devices and their advertisement data.""" devices = None @@ -204,83 +320,40 @@ def discover(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict | None: retry, ) time.sleep(DEFAULT_RETRY_TIMEOUT) - return self.discover(retry - 1, scan_timeout) + return self.get_device_data(retry - 1, scan_timeout) for dev in devices: - if dev.getValueText(7) == UUID: - dev_id = dev.addr.replace(":", "") - self._all_services_data[dev_id] = {} - self._all_services_data[dev_id]["mac_address"] = dev.addr + if self._mac.lower() == dev.addr.lower(): + self._switchbot_device_data["mac_address"] = dev.addr for (adtype, desc, value) in dev.getScanData(): if adtype == 22: _model = binascii.unhexlify(value[4:6]).decode() if _model == "H": - self._all_services_data[dev_id]["data"] = _process_wohand( + self._switchbot_device_data["data"] = _process_wohand( value[4:] ) - self._all_services_data[dev_id]["data"]["rssi"] = dev.rssi - self._all_services_data[dev_id]["model"] = _model - self._all_services_data[dev_id]["modelName"] = "WoHand" - if _model == "c": - self._all_services_data[dev_id][ - "data" - ] = _process_wocurtain(value[4:]) - self._all_services_data[dev_id]["data"]["rssi"] = dev.rssi - self._all_services_data[dev_id]["model"] = _model - self._all_services_data[dev_id]["modelName"] = "WoCurtain" - if _model == "T": - self._all_services_data[dev_id][ - "data" - ] = _process_wosensorth(value[4:]) - self._all_services_data[dev_id]["data"]["rssi"] = dev.rssi - self._all_services_data[dev_id]["model"] = _model - self._all_services_data[dev_id]["modelName"] = "WoSensorTH" - else: - self._all_services_data[dev_id][desc] = value - - return self._all_services_data - - def get_mac(self) -> str: - """Return mac address of device.""" - if self._mac: - return self._mac - - return None - - def get_battery_percent(self) -> int: - """Return device battery level in percent.""" - if not self._switchbot_device_data: - self.get_device_data(self._mac) - return self._switchbot_device_data["data"]["battery"] - - def get_curtains(self) -> dict: - """Return all WoCurtain/Curtains devices with services data.""" - _curtain_devices = {} - - for item in self._all_services_data: - if self._all_services_data[item]["data"]["model"] == "c": - _curtain_devices[item] = self._all_services_data[item] - - return _curtain_devices - - def get_bots(self) -> dict: - """Return all WoHand/Bot devices with services data.""" - _bot_devices = {} - - for item in self._all_services_data: - if self._all_services_data[item]["data"]["model"] == "H": - _bot_devices[item] = self._all_services_data[item] - - return _bot_devices - - def get_device_data(self, mac) -> dict: - """Return data for specific device.""" - if not self._all_services_data: - self.discover() + self._switchbot_device_data["data"]["rssi"] = dev.rssi + self._switchbot_device_data["model"] = _model + self._switchbot_device_data["modelName"] = "WoHand" + elif _model == "c": + self._switchbot_device_data["data"] = _process_wocurtain( + value[4:] + ) + self._switchbot_device_data["data"]["rssi"] = dev.rssi + self._switchbot_device_data["model"] = _model + self._switchbot_device_data["modelName"] = "WoCurtain" + elif _model == "T": + self._switchbot_device_data["data"] = _process_wosensorth( + value[4:] + ) + self._switchbot_device_data["data"]["rssi"] = dev.rssi + self._switchbot_device_data["model"] = _model + self._switchbot_device_data["modelName"] = "WoSensorTH" - for item in self._all_services_data: - if self._all_services_data[item]["mac_address"] == mac: - self._switchbot_device_data = self._all_services_data[item] + else: + continue + else: + self._switchbot_device_data[desc] = value return self._switchbot_device_data @@ -295,10 +368,7 @@ def __init__(self, *args, **kwargs) -> None: def update(self, scan_timeout=5) -> None: """Update mode, battery percent and state of device.""" - _success = self.discover(scan_timeout=scan_timeout) - - if _success: - self.get_device_data(self._mac) + self.get_device_data(scan_timeout=scan_timeout) def turn_on(self) -> bool: """Turn device on.""" @@ -323,7 +393,7 @@ def is_on(self) -> bool: """Return switch state from cache.""" # To get actual position call update() first. if not self._switchbot_device_data: - self.update() + return None if self._inverse: return not self._switchbot_device_data["data"]["isOn"] @@ -368,10 +438,7 @@ def set_position(self, position: int) -> bool: def update(self, scan_timeout=5) -> None: """Update position, battery percent and light level of device.""" - _success = self.discover(scan_timeout=scan_timeout) - - if _success: - self.get_device_data(self._mac) + self.get_device_data(scan_timeout=scan_timeout) def get_position(self) -> int: """Return cached position (0-100) of Curtain.""" From 9f28e151b8b94027338c61bb36cc54703fe5d2ee Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Thu, 17 Jun 2021 07:08:23 +0200 Subject: [PATCH 29/33] Changed scan timeout and retry count to use same variable format as rest of class. --- switchbot/__init__.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index 8c99054f..1eee87b5 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -10,6 +10,7 @@ DEFAULT_RETRY_COUNT = 3 DEFAULT_RETRY_TIMEOUT = 1 +DEFAULT_SCAN_TIMEOUT = 5 UUID = "cba20d00-224d-11e6-9fb8-0002a5d5c51b" HANDLE = "cba20002-224d-11e6-9fb8-0002a5d5c51b" @@ -100,7 +101,9 @@ def __init__(self) -> None: self._all_services_data = {} self._switchbot_device_data = {} - def discover(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict | None: + def discover( + self, retry=DEFAULT_RETRY_COUNT, scan_timeout=DEFAULT_SCAN_TIMEOUT + ) -> dict | None: """Find switchbot devices and their advertisement data.""" devices = None @@ -203,6 +206,7 @@ def __init__(self, mac, password=None, interface=None, **kwargs) -> None: self._mac = mac self._device = None self._switchbot_device_data = {} + self._scan_timeout = kwargs.pop("scan_timeout", DEFAULT_SCAN_TIMEOUT) self._retry_count = kwargs.pop("retry_count", DEFAULT_RETRY_COUNT) if password is None or password == "": self._password_encoded = None @@ -294,16 +298,16 @@ def get_mac(self) -> str: def get_battery_percent(self) -> int: """Return device battery level in percent.""" if not self._switchbot_device_data: - self.get_device_data(self._mac) + self.get_device_data(retry=self._retry_count) return self._switchbot_device_data["data"]["battery"] - def get_device_data(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict | None: + def get_device_data(self, retry=DEFAULT_RETRY_COUNT) -> dict | None: """Find switchbot devices and their advertisement data.""" devices = None try: - devices = bluepy.btle.Scanner().scan(scan_timeout) + devices = bluepy.btle.Scanner().scan(self._scan_timeout) except bluepy.btle.BTLEManagementError: _LOGGER.error("Error scanning for switchbot devices", exc_info=True) @@ -320,7 +324,7 @@ def get_device_data(self, retry=DEFAULT_RETRY_COUNT, scan_timeout=5) -> dict | N retry, ) time.sleep(DEFAULT_RETRY_TIMEOUT) - return self.get_device_data(retry - 1, scan_timeout) + return self.get_device_data(retry=retry - 1) for dev in devices: if self._mac.lower() == dev.addr.lower(): @@ -366,9 +370,9 @@ def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._inverse = kwargs.pop("inverse_mode", False) - def update(self, scan_timeout=5) -> None: + def update(self) -> None: """Update mode, battery percent and state of device.""" - self.get_device_data(scan_timeout=scan_timeout) + self.get_device_data(retry=self._retry_count) def turn_on(self) -> bool: """Turn device on.""" @@ -436,9 +440,9 @@ def set_position(self, position: int) -> bool: hex_position = "%0.2X" % position return self._sendcommand(POSITION_KEY + hex_position, self._retry_count) - def update(self, scan_timeout=5) -> None: + def update(self) -> None: """Update position, battery percent and light level of device.""" - self.get_device_data(scan_timeout=scan_timeout) + self.get_device_data(retry=self._retry_count) def get_position(self) -> int: """Return cached position (0-100) of Curtain.""" From da0695a3683685eab3d640356c115dcb89f01318 Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Fri, 18 Jun 2021 07:12:27 +0200 Subject: [PATCH 30/33] Handle _connect, _writekey and _disconnect via CONNECT_LOCK. --- switchbot/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index 1eee87b5..c1d4f834 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -269,14 +269,14 @@ def _sendcommand(self, key, retry) -> bool: send_success = False command = self._commandkey(key) _LOGGER.debug("Sending command to switchbot %s", command) - try: - with CONNECT_LOCK: + with CONNECT_LOCK: + try: self._connect() send_success = self._writekey(command) - except bluepy.btle.BTLEException: - _LOGGER.warning("Error talking to Switchbot", exc_info=True) - finally: - self._disconnect() + except bluepy.btle.BTLEException: + _LOGGER.warning("Error talking to Switchbot", exc_info=True) + finally: + self._disconnect() if send_success: return True if retry < 1: From 4a3f2bfa484753b494a611fb9f504238e4d293be Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Fri, 18 Jun 2021 19:03:25 +0200 Subject: [PATCH 31/33] SwitchbotDevice only returns data if update is called, btle interface can be specified on scanning methods. -Add basic error handling to prevent key errors on discovery exceptions. --- switchbot/__init__.py | 79 ++++++++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index c1d4f834..d7ae5131 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -96,10 +96,10 @@ def _process_wosensorth(data) -> dict: class GetSwitchbotDevices: """Scan for all Switchbot devices and return by type.""" - def __init__(self) -> None: + def __init__(self, interface=None) -> None: """Get switchbot devices class constructor.""" + self._interface = interface self._all_services_data = {} - self._switchbot_device_data = {} def discover( self, retry=DEFAULT_RETRY_COUNT, scan_timeout=DEFAULT_SCAN_TIMEOUT @@ -109,7 +109,7 @@ def discover( devices = None try: - devices = bluepy.btle.Scanner().scan(scan_timeout) + devices = bluepy.btle.Scanner(self._interface).scan(scan_timeout) except bluepy.btle.BTLEManagementError: _LOGGER.error("Error scanning for switchbot devices", exc_info=True) @@ -165,8 +165,11 @@ def discover( return self._all_services_data - def get_curtains(self) -> dict: + def get_curtains(self) -> dict | None: """Return all WoCurtain/Curtains devices with services data.""" + if not self._all_services_data: + self.discover() + _curtain_devices = {} for item in self._all_services_data: @@ -175,26 +178,39 @@ def get_curtains(self) -> dict: return _curtain_devices - def get_bots(self) -> dict: + def get_bots(self) -> dict | None: """Return all WoHand/Bot devices with services data.""" - _bot_devices = {} + if not self._all_services_data: + self.discover() - for item in self._all_services_data: - if self._all_services_data[item]["data"]["model"] == "H": - _bot_devices[item] = self._all_services_data[item] + if self._all_services_data: + + _bot_devices = {} + + for item in self._all_services_data: + if self._all_services_data[item]["data"]["model"] == "H": + _bot_devices[item] = self._all_services_data[item] - return _bot_devices + return _bot_devices - def get_device_data(self, mac) -> dict: + return None + + def get_device_data(self, mac) -> dict | None: """Return data for specific device.""" if not self._all_services_data: self.discover() - for item in self._all_services_data: - if self._all_services_data[item]["mac_address"] == mac: - self._switchbot_device_data = self._all_services_data[item] + if self._all_services_data: - return self._switchbot_device_data + _switchbot_data = {} + + for item in self._all_services_data: + if self._all_services_data[item]["mac_address"] == mac: + _switchbot_data = self._all_services_data[item] + + return _switchbot_data + + return None class SwitchbotDevice: @@ -290,24 +306,25 @@ def _sendcommand(self, key, retry) -> bool: def get_mac(self) -> str: """Return mac address of device.""" - if self._mac: - return self._mac - - return None + return self._mac def get_battery_percent(self) -> int: """Return device battery level in percent.""" if not self._switchbot_device_data: - self.get_device_data(retry=self._retry_count) + return None return self._switchbot_device_data["data"]["battery"] - def get_device_data(self, retry=DEFAULT_RETRY_COUNT) -> dict | None: + def get_device_data(self, retry=DEFAULT_RETRY_COUNT, interface=None) -> dict | None: """Find switchbot devices and their advertisement data.""" + if interface: + _interface = interface + else: + _interface = self._interface devices = None try: - devices = bluepy.btle.Scanner().scan(self._scan_timeout) + devices = bluepy.btle.Scanner(_interface).scan(self._scan_timeout) except bluepy.btle.BTLEManagementError: _LOGGER.error("Error scanning for switchbot devices", exc_info=True) @@ -324,7 +341,7 @@ def get_device_data(self, retry=DEFAULT_RETRY_COUNT) -> dict | None: retry, ) time.sleep(DEFAULT_RETRY_TIMEOUT) - return self.get_device_data(retry=retry - 1) + return self.get_device_data(retry=retry - 1, interface=_interface) for dev in devices: if self._mac.lower() == dev.addr.lower(): @@ -370,9 +387,9 @@ def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._inverse = kwargs.pop("inverse_mode", False) - def update(self) -> None: + def update(self, interface=None) -> None: """Update mode, battery percent and state of device.""" - self.get_device_data(retry=self._retry_count) + self.get_device_data(retry=self._retry_count, interface=interface) def turn_on(self) -> bool: """Turn device on.""" @@ -390,7 +407,7 @@ def switch_mode(self) -> str: """Return true or false from cache.""" # To get actual position call update() first. if not self._switchbot_device_data: - self.update() + return None return self._switchbot_device_data["data"]["switchMode"] def is_on(self) -> bool: @@ -440,22 +457,22 @@ def set_position(self, position: int) -> bool: hex_position = "%0.2X" % position return self._sendcommand(POSITION_KEY + hex_position, self._retry_count) - def update(self) -> None: + def update(self, interface=None) -> None: """Update position, battery percent and light level of device.""" - self.get_device_data(retry=self._retry_count) + self.get_device_data(retry=self._retry_count, interface=interface) def get_position(self) -> int: """Return cached position (0-100) of Curtain.""" # To get actual position call update() first. if not self._switchbot_device_data: - self.update() + return None return self._switchbot_device_data["data"]["position"] def get_light_level(self) -> int: """Return cached light level.""" # To get actual light level call update() first. if not self._switchbot_device_data: - self.update() + return None return self._switchbot_device_data["data"]["lightLevel"] def is_reversed(self) -> bool: @@ -466,5 +483,5 @@ def is_calibrated(self) -> bool: """Return True curtain is calibrated.""" # To get actual light level call update() first. if not self._switchbot_device_data: - self.update() + return None return self._switchbot_device_data["data"]["calibration"] From cfb246684214e5956cfd7e9eec1e04361a655e3e Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Fri, 18 Jun 2021 19:09:26 +0200 Subject: [PATCH 32/33] Small key reference fix in GetSwitchbotDevices class. --- switchbot/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index d7ae5131..ddb91790 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -173,7 +173,7 @@ def get_curtains(self) -> dict | None: _curtain_devices = {} for item in self._all_services_data: - if self._all_services_data[item]["data"]["model"] == "c": + if self._all_services_data[item]["model"] == "c": _curtain_devices[item] = self._all_services_data[item] return _curtain_devices @@ -188,7 +188,7 @@ def get_bots(self) -> dict | None: _bot_devices = {} for item in self._all_services_data: - if self._all_services_data[item]["data"]["model"] == "H": + if self._all_services_data[item]["model"] == "H": _bot_devices[item] = self._all_services_data[item] return _bot_devices From 70b39a40c1f3014e6c5005495395cb65a7eec397 Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Fri, 18 Jun 2021 19:16:02 +0200 Subject: [PATCH 33/33] Cleanup pointless error handling code. --- switchbot/__init__.py | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/switchbot/__init__.py b/switchbot/__init__.py index ddb91790..4aab1014 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -183,34 +183,26 @@ def get_bots(self) -> dict | None: if not self._all_services_data: self.discover() - if self._all_services_data: + _bot_devices = {} - _bot_devices = {} - - for item in self._all_services_data: - if self._all_services_data[item]["model"] == "H": - _bot_devices[item] = self._all_services_data[item] - - return _bot_devices + for item in self._all_services_data: + if self._all_services_data[item]["model"] == "H": + _bot_devices[item] = self._all_services_data[item] - return None + return _bot_devices def get_device_data(self, mac) -> dict | None: """Return data for specific device.""" if not self._all_services_data: self.discover() - if self._all_services_data: + _switchbot_data = {} - _switchbot_data = {} - - for item in self._all_services_data: - if self._all_services_data[item]["mac_address"] == mac: - _switchbot_data = self._all_services_data[item] - - return _switchbot_data + for item in self._all_services_data: + if self._all_services_data[item]["mac_address"] == mac: + _switchbot_data = self._all_services_data[item] - return None + return _switchbot_data class SwitchbotDevice: