Skip to content

Commit 8965ddb

Browse files
feature: reading states from curtain (#21)
* feature: reading states from curtain *feature: reading state of battery, position and light level from curtain *fix/feature: reverse mode for curtain * style fix * version compatibility * code cleanup * fix: read wrong position state * fix: read wrong position state, now minimum time between update and last command * style fix * fix: reverse reading position * Update switchbot/__init__.py Co-authored-by: Daniel Hjelseth Høyer <[email protected]> * Update switchbot/__init__.py Co-authored-by: Daniel Hjelseth Høyer <[email protected]> * fix: resolve conversation on Line 150 - 153 in #21 Co-authored-by: Daniel Hjelseth Høyer <[email protected]>
1 parent ba2ba71 commit 8965ddb

File tree

1 file changed

+71
-3
lines changed

1 file changed

+71
-3
lines changed

switchbot/__init__.py

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
DEFAULT_RETRY_COUNT = 3
1010
DEFAULT_RETRY_TIMEOUT = 0.2
11+
DEFAULT_TIME_BETWEEN_UPDATE_COMMAND = 10
1112

1213
UUID = "cba20d00-224d-11e6-9fb8-0002a5d5c51b"
1314
HANDLE = "cba20002-224d-11e6-9fb8-0002a5d5c51b"
@@ -34,11 +35,14 @@ class SwitchbotDevice:
3435
# pylint: disable=too-few-public-methods
3536
"""Base Representation of a Switchbot Device."""
3637

37-
def __init__(self, mac, retry_count=DEFAULT_RETRY_COUNT, password=None, interface=None) -> None:
38+
def __init__(self, mac, password=None, interface=None, **kwargs) -> None:
3839
self._interface = interface
3940
self._mac = mac
4041
self._device = None
41-
self._retry_count = retry_count
42+
self._retry_count = kwargs.pop("retry_count", DEFAULT_RETRY_COUNT)
43+
self._time_between_update_command = kwargs.pop("time_between_update_command",
44+
DEFAULT_TIME_BETWEEN_UPDATE_COMMAND)
45+
self._last_time_command_send = time.time()
4246
if password is None or password == "":
4347
self._password_encoded = None
4448
else:
@@ -85,6 +89,7 @@ def _writekey(self, key) -> bool:
8589
hand = hand_service.getCharacteristics(HANDLE)[0]
8690
_LOGGER.debug("Sending command, %s", key)
8791
write_result = hand.write(binascii.a2b_hex(key), withResponse=True)
92+
self._last_time_command_send = time.time()
8893
if not write_result:
8994
_LOGGER.error("Sent command but didn't get a response from Switchbot confirming command was sent. "
9095
"Please check the Switchbot.")
@@ -112,6 +117,14 @@ def _sendcommand(self, key, retry) -> bool:
112117
time.sleep(DEFAULT_RETRY_TIMEOUT)
113118
return self._sendcommand(key, retry - 1)
114119

120+
def get_mac(self) -> str:
121+
"""Returns the mac address of the device."""
122+
return self._mac
123+
124+
def get_min_time_update(self):
125+
"""Returns the first time an update can be executed."""
126+
return self._last_time_command_send + self._time_between_update_command
127+
115128

116129
class Switchbot(SwitchbotDevice):
117130
"""Representation of a Switchbot."""
@@ -132,12 +145,28 @@ def press(self) -> bool:
132145
class SwitchbotCurtain(SwitchbotDevice):
133146
"""Representation of a Switchbot Curtain."""
134147

148+
def __init__(self, *args, **kwargs) -> None:
149+
"""Constructor for a Switchbot Curtain.
150+
The position of the curtain is saved in self._pos with 0 = open and 100 = closed.
151+
This is independent of the calibration of the curtain bot (Open left to right/
152+
Open right to left/Open from the middle).
153+
The parameter 'reverse_mode' reverse these values, if 'reverse_mode' = True, position = 0 equals close
154+
and position = 100 equals open. The parameter is default set to True so that
155+
the definition of position is the same as in Home Assistant."""
156+
self._reverse = kwargs.pop('reverse_mode', True)
157+
self._pos = 0
158+
self._light_level = 0
159+
self._battery_percent = 0
160+
super().__init__(*args, **kwargs)
161+
135162
def open(self) -> bool:
136163
"""Send open command."""
164+
self._pos = (100 if self._reverse else 0)
137165
return self._sendcommand(OPEN_KEY, self._retry_count)
138166

139167
def close(self) -> bool:
140168
"""Send close command."""
169+
self._pos = (0 if self._reverse else 100)
141170
return self._sendcommand(CLOSE_KEY, self._retry_count)
142171

143172
def stop(self) -> bool:
@@ -146,5 +175,44 @@ def stop(self) -> bool:
146175

147176
def set_position(self, position: int) -> bool:
148177
"""Send position command (0-100) to device."""
149-
hex_position = "%0.2X" % (100 - position) # curtain position in reverse mode
178+
position = ((100 - position) if self._reverse else position)
179+
self._pos = position
180+
hex_position = "%0.2X" % position
150181
return self._sendcommand(POSITION_KEY + hex_position, self._retry_count)
182+
183+
def update(self, scan_timeout=5) -> None:
184+
"""Updates the current position, battery percent and light level of the device.
185+
Returns after the given timeout period in seconds."""
186+
waiting_time = self.get_min_time_update() - time.time()
187+
if waiting_time > 0:
188+
time.sleep(waiting_time)
189+
devices = bluepy.btle.Scanner().scan(scan_timeout)
190+
191+
for device in devices:
192+
if self.get_mac().lower() == device.addr.lower():
193+
for (adtype, _, value) in device.getScanData():
194+
if adtype == 22:
195+
barray = bytearray(value, 'ascii')
196+
self._battery_percent = int(barray[-6:-4], 16)
197+
position = max(min(int(barray[-4:-2], 16), 100), 0)
198+
self._pos = ((100 - position) if self._reverse else position)
199+
self._light_level = int(barray[-2:], 16)
200+
201+
def get_position(self) -> int:
202+
"""Returns the current cached position (0-100), the actual position could vary.
203+
To get the actual position call update() first."""
204+
return self._pos
205+
206+
def get_battery_percent(self) -> int:
207+
"""Returns the current cached battery percent (0-100), the actual battery percent could vary.
208+
To get the actual battery percent call update() first."""
209+
return self._battery_percent
210+
211+
def get_light_level(self) -> int:
212+
"""Returns the current cached light level, the actual light level could vary.
213+
To get the actual light level call update() first."""
214+
return self._light_level
215+
216+
def is_reversed(self) -> bool:
217+
"""Returns True if the curtain open from left to right."""
218+
return self._reverse

0 commit comments

Comments
 (0)