88
99DEFAULT_RETRY_COUNT = 3
1010DEFAULT_RETRY_TIMEOUT = 0.2
11+ DEFAULT_TIME_BETWEEN_UPDATE_COMMAND = 10
1112
1213UUID = "cba20d00-224d-11e6-9fb8-0002a5d5c51b"
1314HANDLE = "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
116129class Switchbot (SwitchbotDevice ):
117130 """Representation of a Switchbot."""
@@ -132,12 +145,28 @@ def press(self) -> bool:
132145class 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