-
Notifications
You must be signed in to change notification settings - Fork 47
/
__init__.py
150 lines (122 loc) · 5.04 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
"""Library to handle connection with Switchbot"""
import time
import binascii
import logging
import bluepy
DEFAULT_RETRY_COUNT = 3
DEFAULT_RETRY_TIMEOUT = 0.2
UUID = "cba20d00-224d-11e6-9fb8-0002a5d5c51b"
HANDLE = "cba20002-224d-11e6-9fb8-0002a5d5c51b"
KEY_PASSWORD_PREFIX = "5711"
PRESS_KEY = "570100"
ON_KEY = "570101"
OFF_KEY = "570102"
OPEN_KEY = "570f450105ff00" # 570F4501010100
CLOSE_KEY = "570f450105ff64" # 570F4501010164
POSITION_KEY = "570F450105ff" # +actual_position ex: 570F450105ff32 for 50%
STOP_KEY = "570F450100ff"
ON_KEY_SUFFIX = "01"
OFF_KEY_SUFFIX = "02"
PRESS_KEY_SUFFIX = "00"
_LOGGER = logging.getLogger(__name__)
class SwitchbotDevice:
# pylint: disable=too-few-public-methods
"""Base Representation of a Switchbot Device."""
def __init__(self, mac, retry_count=DEFAULT_RETRY_COUNT, password=None, interface=None) -> None:
self._interface = interface
self._mac = mac
self._device = None
self._retry_count = retry_count
if password is None or password == "":
self._password_encoded = None
else:
self._password_encoded = '%x' % (binascii.crc32(password.encode('ascii')) & 0xffffffff)
def _connect(self) -> None:
if self._device is not None:
return
try:
_LOGGER.debug("Connecting to Switchbot...")
self._device = bluepy.btle.Peripheral(self._mac,
bluepy.btle.ADDR_TYPE_RANDOM,
self._interface)
_LOGGER.debug("Connected to Switchbot.")
except bluepy.btle.BTLEException:
_LOGGER.debug("Failed connecting to Switchbot.", exc_info=True)
self._device = None
raise
def _disconnect(self) -> None:
if self._device is None:
return
_LOGGER.debug("Disconnecting")
try:
self._device.disconnect()
except bluepy.btle.BTLEException:
_LOGGER.warning("Error disconnecting from Switchbot.", exc_info=True)
finally:
self._device = None
def _commandkey(self, key) -> str:
if self._password_encoded is None:
return key
key_suffix = PRESS_KEY_SUFFIX
if key == ON_KEY:
key_suffix = ON_KEY_SUFFIX
elif key == OFF_KEY:
key_suffix = OFF_KEY_SUFFIX
return KEY_PASSWORD_PREFIX + self._password_encoded + key_suffix
def _writekey(self, key) -> bool:
_LOGGER.debug("Prepare to send")
hand_service = self._device.getServiceByUUID(UUID)
hand = hand_service.getCharacteristics(HANDLE)[0]
_LOGGER.debug("Sending command, %s", key)
write_result = hand.write(binascii.a2b_hex(key), withResponse=True)
if not write_result:
_LOGGER.error("Sent command but didn't get a response from Switchbot confirming command was sent. "
"Please check the Switchbot.")
else:
_LOGGER.info("Successfully sent command to Switchbot (MAC: %s).", self._mac)
return write_result
def _sendcommand(self, key, retry) -> bool:
send_success = False
command = self._commandkey(key)
_LOGGER.debug("Sending command to switchbot %s", command)
try:
self._connect()
send_success = self._writekey(command)
except bluepy.btle.BTLEException:
_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)
return False
_LOGGER.warning("Cannot connect to Switchbot. Retrying (remaining: %d)...", retry)
time.sleep(DEFAULT_RETRY_TIMEOUT)
return self._sendcommand(key, retry - 1)
class Switchbot(SwitchbotDevice):
"""Representation of a Switchbot."""
def turn_on(self) -> bool:
"""Turn device on."""
return self._sendcommand(ON_KEY, self._retry_count)
def turn_off(self) -> bool:
"""Turn device off."""
return self._sendcommand(OFF_KEY, self._retry_count)
def press(self) -> bool:
"""Press command to device."""
return self._sendcommand(PRESS_KEY, self._retry_count)
class SwitchbotCurtain(SwitchbotDevice):
"""Representation of a Switchbot Curtain."""
def open(self) -> bool:
"""Send open command."""
return self._sendcommand(OPEN_KEY, self._retry_count)
def close(self) -> bool:
"""Send close command."""
return self._sendcommand(CLOSE_KEY, self._retry_count)
def stop(self) -> bool:
"""Send stop command to device."""
return self._sendcommand(STOP_KEY, self._retry_count)
def set_position(self, position: int) -> bool:
"""Send position command (0-100) to device."""
hex_position = "%0.2X" % (100 - position) # curtain position in reverse mode
return self._sendcommand(POSITION_KEY + hex_position, self._retry_count)