Skip to content
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ from warema_wms import Shade

shades = Shade.get_all_shades()
shades[0].set_shade_position(25) #0=open; 100=closed
shades[1].set_shade_tilt_position(100) #30=tilt open; 100=tilt closed
shades[1].get_shade_state(True) #Force update and get shade state
```

Expand Down
55 changes: 53 additions & 2 deletions warema_wms/shade.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

class Shade:
def __init__(self, wms_ctrl: WmsController, room, channel,
time_between_cmds=0.1, num_retries=3, position=0, is_moving=False):
time_between_cmds=0.1, num_retries=3, position=0, tilt_position=30, is_moving=False):
"""
Initializes a Shade entity
:param wms_ctrl: Allows to pass in your own version of a WmsController
Expand All @@ -28,6 +28,7 @@ def __init__(self, wms_ctrl: WmsController, room, channel,
self.time_between_cmds = time_between_cmds
self.num_retries = num_retries
self.position = position # 0 for open 100 for closed
self.tilt_position = tilt_position # 30=tilt open 100=tilt closed, 6 Raffstores show different values here, from 20 - 28 & 101 - 104
self.is_moving = is_moving
self.state_last_updated = None

Expand All @@ -48,6 +49,7 @@ def update_shade_state(self):
try:
self.is_moving = False if shutter_xml.find('fahrt').text == '0' else True
self.position = int(shutter_xml.find('position').text) / 2
self.tilt_position = int(shutter_xml.find('winkel').text) / 2
self.state_last_updated = datetime.now()
return True
except AttributeError:
Expand All @@ -67,7 +69,7 @@ def get_shade_state(self, force_update=False):
"""
if force_update or self.state_last_updated is None:
self.update_shade_state()
return self.position, self.is_moving, self.state_last_updated
return self.position, self.is_moving, self.tilt_position, self.state_last_updated

def set_shade_position(self, new_position):
"""
Expand All @@ -88,6 +90,37 @@ def set_shade_position(self, new_position):
.format(self.room.name, self.channel.name, new_position))
return False

def set_shade_tilt_position(self, new_tilt_position):
"""
Sets shade to new_tilt.
:param new_tilt: New tilt of shade (30=open, 100=closed)
"""
for _ in range(self.num_retries):
self._try_cmd_n_times(lambda: self.wms_ctrl.send_rx_check_ready(self.room.id, self.channel.id),
self.num_retries)
time.sleep(self.time_between_cmds)
self.wms_ctrl.send_tx_tilt_shade(self.room.id, self.channel.id, new_tilt_position * 2)
if self._verify_tilt_cmd_sent(new_tilt_position):
return True
logger.warning("Shade {}:{} could not be set to target tilt {}"
.format(self.room.name, self.channel.name, new_tilt_position))
return False

def stop_moving_shade(self):
"""
Stops a moving shade.
"""
for _ in range(self.num_retries):
self._try_cmd_n_times(lambda: self.wms_ctrl.send_rx_check_ready(self.room.id, self.channel.id),
self.num_retries)
time.sleep(self.time_between_cmds)
self.wms_ctrl.send_tx_stop_shade(self.room.id, self.channel.id)
if self._verify_stop_cmd_sent():
return True
logger.warning("Shade {}:{} could not be stopped."
.format(self.room.name, self.channel.name))
return False

def _try_cmd_n_times(self, cmd, n=3):
for i in range(n):
ret = cmd()
Expand All @@ -105,6 +138,24 @@ def _verify_set_cmd_sent(self, target_position):
time.sleep(self.time_between_cmds)
return False

def _verify_tilt_cmd_sent(self, target_tilt_position):
time.sleep(self.time_between_cmds)
for _ in range(self.num_retries):
self.update_shade_state()
if self.is_moving or self.tilt_position == target_tilt_position:
return True
time.sleep(self.time_between_cmds)
return False

def _verify_stop_cmd_sent(self):
time.sleep(self.time_between_cmds)
for _ in range(self.num_retries):
self.update_shade_state()
if not self.is_moving:
return True
time.sleep(self.time_between_cmds)
return False

@staticmethod
def get_all_shades(wms_ctrl=None, time_between_cmds=0.1, num_retries=3):
"""
Expand Down
11 changes: 9 additions & 2 deletions warema_wms/wms_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@

TX_MOVE_SHADE = '0821'
SHADE_POSITION = '03{}ffffff'
TILT_POSITION = '03ff{}ffff'
STOP_SHADE = '01ffffffff' # at least at my setup it works (instead of STOP_SHADE = '0001ffffff')

logger = logging.getLogger('warema_wms')


class WmsController:
"""
This class is in charge of sending single commands to the WebControl server.
Commands should contain no (or as little as possible) logic.

Set log level to DEBUG to get a trace of sent commands and responses.
"""
def _retrieve_setup(self):
Expand Down Expand Up @@ -93,6 +93,13 @@ def send_tx_move_shade(self, room_id, channel_id, new_shade_pos):
return self._send_command(TX_MOVE_SHADE, format(room_id, '02x') + format(channel_id, '02x')
+ SHADE_POSITION.format(format(new_shade_pos, '02x')))

def send_tx_tilt_shade(self, room_id, channel_id, new_tilt_pos):
return self._send_command(TX_MOVE_SHADE, format(room_id, '02x') + format(channel_id, '02x')
+ TILT_POSITION.format(format(new_tilt_pos, '02x')))

def send_tx_stop_shade(self, room_id, channel_id):
return self._send_command(TX_MOVE_SHADE, format(room_id, '02x') + format(channel_id, '02x') + STOP_SHADE)

def send_rx_move_shade(self, room_id, channel_id):
"""
This cmd is send out by the JS app of the web control server after the cmd to set a new shade position
Expand Down