def test_is_traveling(self): """Test if cover is traveling.""" travelcalculator = TravelCalculator(25, 50) self.assertFalse(travelcalculator.is_traveling()) travelcalculator.set_position(20) self.assertFalse(travelcalculator.is_traveling()) travelcalculator.time_set_from_outside = 1000 travelcalculator.start_travel_down() travelcalculator.time_set_from_outside = 1004 self.assertTrue(travelcalculator.is_traveling()) travelcalculator.time_set_from_outside = 1005 self.assertFalse(travelcalculator.is_traveling())
def test_init(self): """Test initial status.""" travelcalculator = TravelCalculator(25, 50) assert not travelcalculator.is_closed() assert not travelcalculator.is_closing() assert not travelcalculator.is_opening() assert not travelcalculator.is_traveling() assert travelcalculator.position_reached() assert travelcalculator.current_position() is None
def test_init(self): """Test initial status.""" travelcalculator = TravelCalculator(25, 50) self.assertFalse(travelcalculator.is_closed()) self.assertFalse(travelcalculator.is_closing()) self.assertFalse(travelcalculator.is_opening()) self.assertFalse(travelcalculator.is_traveling()) self.assertTrue(travelcalculator.position_reached()) self.assertIsNone(travelcalculator.current_position())
def test_is_traveling(self): """Test if cover is traveling and position reached.""" travelcalculator = TravelCalculator(25, 50) with patch("time.time") as mock_time: mock_time.return_value = 1580000000.0 assert not travelcalculator.is_traveling() assert travelcalculator.position_reached() travelcalculator.set_position(80) assert not travelcalculator.is_traveling() assert travelcalculator.position_reached() mock_time.return_value = 1580000000.0 travelcalculator.start_travel_down() mock_time.return_value = 1580000004.0 assert travelcalculator.is_traveling() assert not travelcalculator.position_reached() mock_time.return_value = 1580000005.0 assert not travelcalculator.is_traveling() assert travelcalculator.position_reached()
def test_is_traveling(self): """Test if cover is traveling and position reached.""" travelcalculator = TravelCalculator(25, 50) with patch('time.time') as mock_time: mock_time.return_value = 1580000000.0 self.assertFalse(travelcalculator.is_traveling()) self.assertTrue(travelcalculator.position_reached()) travelcalculator.set_position(80) self.assertFalse(travelcalculator.is_traveling()) self.assertTrue(travelcalculator.position_reached()) mock_time.return_value = 1580000000.0 travelcalculator.start_travel_down() mock_time.return_value = 1580000004.0 self.assertTrue(travelcalculator.is_traveling()) self.assertFalse(travelcalculator.position_reached()) mock_time.return_value = 1580000005.0 self.assertFalse(travelcalculator.is_traveling()) self.assertTrue(travelcalculator.position_reached())
class CoverTimeBased(CoverDevice, RestoreEntity): def __init__(self, device_id, name, travel_time_down, travel_time_up, open_switch_entity_id, close_switch_entity_id): """Initialize the cover.""" from xknx.devices import TravelCalculator self._travel_time_down = travel_time_down self._travel_time_up = travel_time_up self._open_switch_entity_id = open_switch_entity_id self._close_switch_entity_id = close_switch_entity_id if name: self._name = name else: self._name = device_id self._unsubscribe_auto_updater = None self.tc = TravelCalculator(self._travel_time_down, self._travel_time_up) async def async_added_to_hass(self): """ Only cover's position matters. """ """ The rest is calculated from this attribute.""" old_state = await self.async_get_last_state() _LOGGER.debug('async_added_to_hass :: oldState %s', old_state) if (old_state is not None and self.tc is not None and old_state.attributes.get(ATTR_CURRENT_POSITION) is not None): self.tc.set_position( int(old_state.attributes.get(ATTR_CURRENT_POSITION))) def _handle_my_button(self): """Handle the MY button press""" if self.tc.is_traveling(): _LOGGER.debug('_handle_my_button :: button stops cover') self.tc.stop() self.stop_auto_updater() @property def name(self): """Return the name of the cover.""" return self._name @property def device_state_attributes(self): """Return the device state attributes.""" attr = {} if self._travel_time_down is not None: attr[CONF_TRAVELLING_TIME_DOWN] = self._travel_time_down if self._travel_time_up is not None: attr[CONF_TRAVELLING_TIME_UP] = self._travel_time_up return attr @property def current_cover_position(self): """Return the current position of the cover.""" return self.tc.current_position() @property def is_opening(self): """Return if the cover is opening or not.""" from xknx.devices import TravelStatus return self.tc.is_traveling() and \ self.tc.travel_direction == TravelStatus.DIRECTION_UP @property def is_closing(self): """Return if the cover is closing or not.""" from xknx.devices import TravelStatus return self.tc.is_traveling() and \ self.tc.travel_direction == TravelStatus.DIRECTION_DOWN @property def is_closed(self): """Return if the cover is closed.""" return self.tc.is_closed() @property def assumed_state(self): """Return True because covers can be stopped midway.""" return True async def async_set_cover_position(self, **kwargs): """Move the cover to a specific position.""" if ATTR_POSITION in kwargs: position = kwargs[ATTR_POSITION] _LOGGER.debug('async_set_cover_position: %d', position) await self.set_position(position) async def async_close_cover(self, **kwargs): """Turn the device close.""" _LOGGER.debug('async_close_cover') self.tc.start_travel_down() self.start_auto_updater() await self._async_handle_command(SERVICE_CLOSE_COVER) async def async_open_cover(self, **kwargs): """Turn the device open.""" _LOGGER.debug('async_open_cover') self.tc.start_travel_up() self.start_auto_updater() await self._async_handle_command(SERVICE_OPEN_COVER) async def async_stop_cover(self, **kwargs): """Turn the device stop.""" _LOGGER.debug('async_stop_cover') self._handle_my_button() await self._async_handle_command(SERVICE_STOP_COVER) async def set_position(self, position): _LOGGER.debug('set_position') """Move cover to a designated position.""" current_position = self.tc.current_position() _LOGGER.debug('set_position :: current_position: %d, new_position: %d', current_position, position) command = None if position < current_position: command = SERVICE_CLOSE_COVER elif position > current_position: command = SERVICE_OPEN_COVER if command is not None: self.start_auto_updater() self.tc.start_travel(position) _LOGGER.debug('set_position :: command %s', command) await self._async_handle_command(command) return def start_auto_updater(self): """Start the autoupdater to update HASS while cover is moving.""" _LOGGER.debug('start_auto_updater') if self._unsubscribe_auto_updater is None: _LOGGER.debug('init _unsubscribe_auto_updater') interval = timedelta(seconds=0.1) self._unsubscribe_auto_updater = async_track_time_interval( self.hass, self.auto_updater_hook, interval) @callback def auto_updater_hook(self, now): """Call for the autoupdater.""" _LOGGER.debug('auto_updater_hook') self.async_schedule_update_ha_state() if self.position_reached(): _LOGGER.debug('auto_updater_hook :: position_reached') self.stop_auto_updater() self.hass.async_create_task(self.auto_stop_if_necessary()) def stop_auto_updater(self): """Stop the autoupdater.""" _LOGGER.debug('stop_auto_updater') if self._unsubscribe_auto_updater is not None: self._unsubscribe_auto_updater() self._unsubscribe_auto_updater = None def position_reached(self): """Return if cover has reached its final position.""" return self.tc.position_reached() async def auto_stop_if_necessary(self): """Do auto stop if necessary.""" if self.position_reached(): _LOGGER.debug('auto_stop_if_necessary :: calling stop command') await self._async_handle_command(SERVICE_STOP_COVER) self.tc.stop() async def _async_handle_command(self, command, *args): if command == "close_cover": cmd = "DOWN" self._state = False await self.hass.services.async_call( "homeassistant", "turn_off", {"entity_id": self._open_switch_entity_id}, False) await self.hass.services.async_call( "homeassistant", "turn_on", {"entity_id": self._close_switch_entity_id}, False) elif command == "open_cover": cmd = "UP" self._state = True await self.hass.services.async_call( "homeassistant", "turn_off", {"entity_id": self._close_switch_entity_id}, False) await self.hass.services.async_call( "homeassistant", "turn_on", {"entity_id": self._open_switch_entity_id}, False) elif command == "stop_cover": cmd = "STOP" self._state = True await self.hass.services.async_call( "homeassistant", "turn_off", {"entity_id": self._close_switch_entity_id}, False) await self.hass.services.async_call( "homeassistant", "turn_off", {"entity_id": self._open_switch_entity_id}, False) _LOGGER.debug('_async_handle_command :: %s', cmd) # Update state of entity self.async_write_ha_state()
class RTSRflinkCover(RflinkCommand, CoverEntity, RestoreEntity): """RFLink entity which can switch on/stop/off (eg: cover).""" def __init__(self, entity_id, rts_my_position, travel_time_down, travel_time_up, **device_config): """Initialize the cover.""" from xknx.devices import TravelCalculator self._rts_my_position = rts_my_position self._travel_time_down = travel_time_down self._travel_time_up = travel_time_up self._require_stop_cover = False # self.async_register_callbacks() self._unsubscribe_auto_updater = None super().__init__(entity_id, None, **device_config) self.tc = TravelCalculator(self._travel_time_down, self._travel_time_up) async def async_added_to_hass(self): await super().async_added_to_hass() """ Only cover's position matters. """ """ The rest is calculated from this attribute.""" old_state = await self.async_get_last_state() _LOGGER.debug('async_added_to_hass :: oldState %s', old_state) if (old_state is not None and self.tc is not None and old_state.attributes.get(ATTR_CURRENT_POSITION) is not None): self.tc.set_position( self.shift_position( int(old_state.attributes.get(ATTR_CURRENT_POSITION)))) def _handle_event(self, event): """Adjust state if RFLink picks up a remote command for this device.""" self.cancel_queued_send_commands() _LOGGER.debug('_handle_event %s', event) # this must be wrong. ON command closes cover # command = event['command'] command = event.get(EVENT_KEY_COMMAND) if command in ['on', 'allon', 'up']: self._require_stop_cover = False self.tc.start_travel_up() self.start_auto_updater() elif command in ['off', 'alloff', 'down']: self._require_stop_cover = False self.tc.start_travel_down() self.start_auto_updater() elif command in ['stop']: self._handle_my_button() def _handle_my_button(self): """Handle the MY button press""" self._require_stop_cover = False if self.tc.is_traveling(): _LOGGER.debug('_handle_my_button :: button stops cover') self.tc.stop() self.stop_auto_updater() elif self._rts_my_position is not None: _LOGGER.debug('_handle_my_button :: button sends to MY') self.tc.start_travel(self.shift_position(self._rts_my_position)) self.start_auto_updater() @property def device_state_attributes(self): """Return the device state attributes.""" attr = {} super_attr = super().device_state_attributes if super_attr is not None: attr.update(super_attr) if self._rts_my_position is not None: attr[CONF_MY_POSITION] = self._rts_my_position if self._travel_time_down is not None: attr[CONF_TRAVELLING_TIME_DOWN] = self._travel_time_down if self._travel_time_up is not None: attr[CONF_TRAVELLING_TIME_UP] = self._travel_time_up return attr @property def current_cover_position(self): """Return the current position of the cover.""" return self.shift_position(self.tc.current_position()) @property def is_opening(self): """Return if the cover is opening or not.""" return self.tc.is_opening() @property def is_closing(self): """Return if the cover is closing or not.""" return self.tc.is_closing() @property def is_closed(self): """Return if the cover is closed.""" return self.tc.is_closed() @property def assumed_state(self): """Return True because covers can be stopped midway.""" return True async def async_set_cover_position(self, **kwargs): """Move the cover to a specific position.""" if ATTR_POSITION in kwargs: position = kwargs[ATTR_POSITION] _LOGGER.debug('async_set_cover_position: %d', position) await self.set_position(position) async def async_close_cover(self, **kwargs): """Turn the device close.""" _LOGGER.debug('async_close_cover') self.tc.start_travel_down() self._require_stop_cover = False self.start_auto_updater() await self._async_handle_command(SERVICE_CLOSE_COVER) async def async_open_cover(self, **kwargs): """Turn the device open.""" _LOGGER.debug('async_open_cover') self.tc.start_travel_up() self._require_stop_cover = False self.start_auto_updater() await self._async_handle_command(SERVICE_OPEN_COVER) async def async_stop_cover(self, **kwargs): """Turn the device stop.""" _LOGGER.debug('async_stop_cover') self._handle_my_button() await self._async_handle_command(SERVICE_STOP_COVER) async def set_position(self, position): _LOGGER.debug('set_position') """Move cover to a designated position.""" current_position = self.shift_position(self.tc.current_position()) _LOGGER.debug('set_position :: current_position: %d, new_position: %d', current_position, position) command = None if position < current_position: command = SERVICE_CLOSE_COVER elif position > current_position: command = SERVICE_OPEN_COVER if command is not None: self._require_stop_cover = True self.start_auto_updater() self.tc.start_travel(self.shift_position(position)) _LOGGER.debug('set_position :: command %s', command) await self._async_handle_command(command) return def start_auto_updater(self): """Start the autoupdater to update HASS while cover is moving.""" _LOGGER.debug('start_auto_updater') if self._unsubscribe_auto_updater is None: _LOGGER.debug('init _unsubscribe_auto_updater') # self._unsubscribe_auto_updater = async_track_utc_time_change(self.hass, self.auto_updater_hook) interval = timedelta(seconds=0.1) self._unsubscribe_auto_updater = async_track_time_interval( self.hass, self.auto_updater_hook, interval) @callback def auto_updater_hook(self, now): """Call for the autoupdater.""" _LOGGER.debug('auto_updater_hook') self.async_write_ha_state() if self.position_reached(): _LOGGER.debug('auto_updater_hook :: position_reached') self.stop_auto_updater() self.hass.async_create_task(self.auto_stop_if_necessary()) def stop_auto_updater(self): """Stop the autoupdater.""" _LOGGER.debug('stop_auto_updater') if self._unsubscribe_auto_updater is not None: self._unsubscribe_auto_updater() self._unsubscribe_auto_updater = None def position_reached(self): """Return if cover has reached its final position.""" return self.tc.position_reached() async def auto_stop_if_necessary(self): """Do auto stop if necessary.""" # If device does not support auto_positioning, # we have to stop the device when position is reached. # unless device was traveling to fully open # or fully closed state if self.position_reached(): if (self._require_stop_cover and not self.tc.is_closed() and not self.tc.is_open()): _LOGGER.debug('auto_stop_if_necessary :: calling stop command') await self._async_handle_command(SERVICE_STOP_COVER) self.tc.stop() def shift_position(self, position): """Calculate 100 complement position""" try: return 100 - position except TypeError: return None
class CoverTimeBased(CoverEntity, RestoreEntity): def __init__(self, device_id, name, travel_time_down, travel_time_up, move_group_address, stop_group_address, watch_move_group_addresses, watch_stop_group_addresses, send_stop_at_ends): """Initialize the cover.""" from xknx.devices import TravelCalculator self._travel_time_down = travel_time_down self._travel_time_up = travel_time_up self._move_group_address = move_group_address self._stop_group_address = stop_group_address self._watch_move_group_addresses = watch_move_group_addresses self._watch_stop_group_addresses = watch_stop_group_addresses self._send_stop_at_ends = send_stop_at_ends self._assume_uncertain_position = True self._target_position = 0 self._processing_known_position = False if name: self._name = name else: self._name = device_id self._unsubscribe_auto_updater = None self.tc = TravelCalculator(self._travel_time_down, self._travel_time_up) async def _knx_event_handler(self, event): # Exemple of what is in event.data : {'address': '10/4/252', 'data': 0} group_address = event.data['address'] payload = event.data['data'] if group_address in self._watch_move_group_addresses: if payload == KNX_PAYLOAD_COVER_DOWN: await self.set_known_action(action='close') else: await self.set_known_action(action='open') elif group_address in self._watch_stop_group_addresses: await self.set_known_action(action='stop') async def async_added_to_hass(self): """ Only cover position and confidence in that matters.""" """ The rest is calculated from this attribute. """ # RECOVER STATE old_state = await self.async_get_last_state() _LOGGER.debug(self._name + ': ' + 'async_added_to_hass :: oldState %s', old_state) if (old_state is not None and self.tc is not None and old_state.attributes.get(ATTR_CURRENT_POSITION) is not None): self.tc.set_position( int(old_state.attributes.get(ATTR_CURRENT_POSITION))) if (old_state is not None and old_state.attributes.get(ATTR_UNCONFIRMED_STATE) is not None): if type(old_state.attributes.get(ATTR_UNCONFIRMED_STATE)) == bool: self._assume_uncertain_position = old_state.attributes.get( ATTR_UNCONFIRMED_STATE) else: self._assume_uncertain_position = str( old_state.attributes.get(ATTR_UNCONFIRMED_STATE)) == str( True) # SETUP KNX EVENT LISTENER self.hass.bus.async_listen("knx_event", self._knx_event_handler) def _handle_my_button(self): """Handle the MY button press""" if self.tc.is_traveling(): _LOGGER.debug(self._name + ': ' + '_handle_my_button :: button stops cover') self.tc.stop() self.stop_auto_updater() @property def unconfirmed_state(self): """Return the assume state as a string to persist through restarts .""" return str(self._assume_uncertain_position) @property def name(self): """Return the name of the cover.""" return self._name @property def device_state_attributes(self): """Return the device state attributes.""" attr = {} if self._travel_time_down is not None: attr[CONF_TRAVELLING_TIME_DOWN] = self._travel_time_down if self._travel_time_up is not None: attr[CONF_TRAVELLING_TIME_UP] = self._travel_time_up attr[ATTR_UNCONFIRMED_STATE] = str(self._assume_uncertain_position) return attr @property def current_cover_position(self): """Return the current position of the cover.""" return self.tc.current_position() @property def is_opening(self): """Return if the cover is opening or not.""" from xknx.devices import TravelStatus return self.tc.is_traveling( ) and self.tc.travel_direction == TravelStatus.DIRECTION_UP @property def is_closing(self): """Return if the cover is closing or not.""" from xknx.devices import TravelStatus return self.tc.is_traveling( ) and self.tc.travel_direction == TravelStatus.DIRECTION_DOWN @property def is_closed(self): """Return if the cover is closed.""" return self.tc.is_closed() @property def assumed_state(self): """Return True unless we have set position with confidence through send_know_position service.""" return self._assume_uncertain_position async def async_set_cover_position(self, **kwargs): """Move the cover to a specific position.""" if ATTR_POSITION in kwargs: self._target_position = kwargs[ATTR_POSITION] _LOGGER.debug(self._name + ': ' + 'async_set_cover_position: %d', self._target_position) await self.set_position(self._target_position) async def async_close_cover(self, **kwargs): """Turn the device close.""" _LOGGER.debug(self._name + ': ' + 'async_close_cover') await self._async_handle_command(SERVICE_CLOSE_COVER) self.tc.start_travel_down() self._target_position = 0 self.start_auto_updater() async def async_open_cover(self, **kwargs): """Turn the device open.""" _LOGGER.debug(self._name + ': ' + 'async_open_cover') await self._async_handle_command(SERVICE_OPEN_COVER) self.tc.start_travel_up() self._target_position = 100 self.start_auto_updater() async def async_stop_cover(self, **kwargs): """Turn the device stop.""" _LOGGER.debug(self._name + ': ' + 'async_stop_cover') await self._async_handle_command(SERVICE_STOP_COVER) self._handle_my_button() async def set_position(self, position): _LOGGER.debug(self._name + ': ' + 'set_position') """Move cover to a designated position.""" current_position = self.tc.current_position() _LOGGER.debug( self._name + ': ' + 'set_position :: current_position: %d, new_position: %d', current_position, position) command = None if position < current_position: command = SERVICE_OPEN_COVER elif position > current_position: command = SERVICE_CLOSE_COVER if command is not None: self.start_auto_updater() self.tc.start_travel(position) _LOGGER.debug(self._name + ': ' + 'set_position :: command %s', command) await self._async_handle_command(command) return def start_auto_updater(self): """Start the autoupdater to update HASS while cover is moving.""" _LOGGER.debug(self._name + ': ' + 'start_auto_updater') if self._unsubscribe_auto_updater is None: _LOGGER.debug(self._name + ': ' + 'init _unsubscribe_auto_updater') interval = timedelta(seconds=0.1) self._unsubscribe_auto_updater = async_track_time_interval( self.hass, self.auto_updater_hook, interval) @callback def auto_updater_hook(self, now): """Call for the autoupdater.""" _LOGGER.debug(self._name + ': ' + 'auto_updater_hook') self.async_schedule_update_ha_state() if self.position_reached(): _LOGGER.debug(self._name + ': ' + 'auto_updater_hook :: position_reached') self.stop_auto_updater() self.hass.async_create_task(self.auto_stop_if_necessary()) def stop_auto_updater(self): """Stop the autoupdater.""" _LOGGER.debug(self._name + ': ' + 'stop_auto_updater') if self._unsubscribe_auto_updater is not None: self._unsubscribe_auto_updater() self._unsubscribe_auto_updater = None def position_reached(self): """Return if cover has reached its final position.""" return self.tc.position_reached() async def set_known_action(self, **kwargs): """We want to do a few things when we get a position""" action = kwargs[ATTR_ACTION] if action not in ["open", "close", "stop"]: raise ValueError("action must be one of open, close or cover.") if action == "stop": self._handle_my_button() return if action == "open": self.tc.start_travel_up() self._target_position = 100 if action == "close": self.tc.start_travel_down() self._target_position = 0 self.start_auto_updater() async def set_known_position(self, **kwargs): """We want to do a few things when we get a position""" position = kwargs[ATTR_POSITION] confident = kwargs[ ATTR_CONFIDENT] if ATTR_CONFIDENT in kwargs else False position_type = kwargs[ ATTR_POSITION_TYPE] if ATTR_POSITION_TYPE in kwargs else ATTR_POSITION_TYPE_TARGET if position_type not in [ ATTR_POSITION_TYPE_TARGET, ATTR_POSITION_TYPE_CURRENT ]: raise ValueError(ATTR_POSITION_TYPE + " must be one of %s, %s", ATTR_POSITION_TYPE_TARGET, ATTR_POSITION_TYPE_CURRENT) _LOGGER.debug( self._name + ': ' + 'set_known_position :: position %d, confident %s, position_type %s, self.tc.is_traveling%s', position, str(confident), position_type, str(self.tc.is_traveling())) self._assume_uncertain_position = not confident self._processing_known_position = True if position_type == ATTR_POSITION_TYPE_TARGET: self._target_position = position position = self.current_cover_position if self.tc.is_traveling(): self.tc.set_position(position) self.tc.start_travel(self._target_position) self.start_auto_updater() else: if position_type == ATTR_POSITION_TYPE_TARGET: self.tc.start_travel(self._target_position) self.start_auto_updater() else: _LOGGER.debug( self._name + ': ' + 'set_known_position :: non_traveling position %d, confident %s, position_type %s', position, str(confident), position_type) self.tc.set_position(position) async def auto_stop_if_necessary(self): """Do auto stop if necessary.""" current_position = self.tc.current_position() if self.position_reached() and not self._processing_known_position: self.tc.stop() if (current_position > 0) and (current_position < 100): _LOGGER.debug( self._name + ': ' + 'auto_stop_if_necessary :: current_position between 1 and 99 :: calling stop command' ) await self._async_handle_command(SERVICE_STOP_COVER) else: if self._send_stop_at_ends: _LOGGER.debug( self._name + ': ' + 'auto_stop_if_necessary :: send_stop_at_ends :: calling stop command' ) await self._async_handle_command(SERVICE_STOP_COVER) async def _async_handle_command(self, command, *args): """We have cover.* triggered command. Reset assumed state and known_position processsing and execute""" self._assume_uncertain_position = True self._processing_known_position = False if command == SERVICE_CLOSE_COVER: cmd = "DOWN" self._state = False await self.hass.services.async_call( "knx", "send", { "address": self._move_group_address, "payload": KNX_PAYLOAD_COVER_DOWN }, False) elif command == SERVICE_OPEN_COVER: cmd = "UP" self._state = True await self.hass.services.async_call( "knx", "send", { "address": self._move_group_address, "payload": KNX_PAYLOAD_COVER_UP }, False) elif command == SERVICE_STOP_COVER: cmd = "STOP" self._state = True await self.hass.services.async_call( "knx", "send", { "address": self._stop_group_address, "payload": KNX_PAYLOAD_COVER_STOP }, False) _LOGGER.debug(self._name + ': ' + '_async_handle_command :: %s', cmd) # Update state of entity self.async_write_ha_state()
class RTSRflinkCover(RflinkCover): """RFLink entity which can switch on/stop/off (eg: cover) with time based controls.""" def __init__(self, device_id, rts_my_position, travel_time_down, travel_time_up, **device_config): """Initialize the cover.""" self._rts_my_position = rts_my_position self._travel_time_down = travel_time_down self._travel_time_up = travel_time_up # self.async_register_callbacks() self._unsubscribe_auto_updater = None super().__init__(device_id, None, **device_config) self.travel_calc = TravelCalculator(self._travel_time_down, self._travel_time_up) async def async_added_to_hass(self): """Restore RFLink cover state (OPEN/CLOSE/CURRENT_POSITION).""" await super().async_added_to_hass() old_state = await self.async_get_last_state() if (old_state is not None and self.travel_calc is not None and old_state.attributes.get(ATTR_CURRENT_POSITION) is not None): self.travel_calc.set_position( int(old_state.attributes.get(ATTR_CURRENT_POSITION))) def _handle_event(self, event): """Adjust state if RFLink picks up a remote command for this device.""" self.cancel_queued_send_commands() _LOGGER.debug("_handle_event %s", event) # this must be wrong. ON command closes cover command = event.get(EVENT_KEY_COMMAND) if command in ["on", "allon", "up"]: self.travel_calc.start_travel_up() self.start_auto_updater() elif command in ["off", "alloff", "down"]: self.travel_calc.start_travel_down() self.start_auto_updater() elif command in ["stop"]: self._handle_my_button() def _handle_my_button(self): """Handle the MY button press.""" if self.travel_calc.is_traveling(): _LOGGER.debug("_handle_my_button :: button stops cover") self.travel_calc.stop() self.stop_auto_updater() elif self._rts_my_position is not None: _LOGGER.debug("_handle_my_button :: button sends to MY") self.travel_calc.start_travel(self._rts_my_position) self.start_auto_updater() @property def device_state_attributes(self): """Return the device state attributes.""" attr = {} super_attr = super().device_state_attributes if super_attr is not None: attr.update(super_attr) if self._rts_my_position is not None: attr[CONF_MY_POSITION] = self._rts_my_position if self._travel_time_down is not None: attr[CONF_TRAVELLING_TIME_DOWN] = self._travel_time_down if self._travel_time_up is not None: attr[CONF_TRAVELLING_TIME_UP] = self._travel_time_up return attr @property def supported_features(self): """Flag supported features.""" return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION | SUPPORT_STOP @property def current_cover_position(self): """Return the current position of the cover.""" return self.travel_calc.current_position() @property def is_opening(self): """Return if the cover is opening or not.""" return (self.travel_calc.is_traveling() and self.travel_calc.travel_direction == TravelStatus.DIRECTION_UP) @property def is_closing(self): """Return if the cover is closing or not.""" return (self.travel_calc.is_traveling() and self.travel_calc.travel_direction == TravelStatus.DIRECTION_DOWN) @property def is_closed(self): """Return if the cover is closed.""" return self.travel_calc.is_closed() async def async_set_cover_position(self, **kwargs): """Move the cover to a specific position.""" if ATTR_POSITION in kwargs: position = kwargs[ATTR_POSITION] _LOGGER.debug("async_set_cover_position: %d", position) await self.set_position(position) async def async_close_cover(self, **kwargs): """Turn the device close.""" _LOGGER.debug("async_close_cover") await self._async_handle_command(SERVICE_CLOSE_COVER) self.travel_calc.start_travel_down() self.start_auto_updater() async def async_open_cover(self, **kwargs): """Turn the device open.""" _LOGGER.debug("async_open_cover") await self._async_handle_command(SERVICE_OPEN_COVER) self.travel_calc.start_travel_up() self.start_auto_updater() async def async_stop_cover(self, **kwargs): """Turn the device stop.""" _LOGGER.debug("async_stop_cover") await self._async_handle_command(SERVICE_STOP_COVER) self._handle_my_button() async def set_position(self, position): """Move cover to a designated position.""" current_position = self.travel_calc.current_position() _LOGGER.debug( "set_position :: current_position: %d, new_position: %d", current_position, position, ) command = None if position < current_position: command = SERVICE_CLOSE_COVER elif position > current_position: command = SERVICE_OPEN_COVER if command is not None: await self._async_handle_command(command) self.start_auto_updater() self.travel_calc.start_travel(position) _LOGGER.debug("set_position :: command %s", command) return def start_auto_updater(self): """Start the autoupdater to update HASS while cover is moving.""" _LOGGER.debug("start_auto_updater") if self._unsubscribe_auto_updater is None: _LOGGER.debug("init _unsubscribe_auto_updater") interval = timedelta(seconds=0.1) self._unsubscribe_auto_updater = async_track_time_interval( self.hass, self.auto_updater_hook, interval) @callback def auto_updater_hook(self, now): """Call for the autoupdater.""" _LOGGER.debug("auto_updater_hook") self.async_schedule_update_ha_state() if self.position_reached(): _LOGGER.debug("auto_updater_hook :: position_reached") self.stop_auto_updater() self.hass.async_create_task(self.auto_stop_if_necessary()) def stop_auto_updater(self): """Stop the autoupdater.""" if self._unsubscribe_auto_updater is not None: self._unsubscribe_auto_updater() self._unsubscribe_auto_updater = None def position_reached(self): """Return if cover has reached its final position.""" return self.travel_calc.position_reached() async def auto_stop_if_necessary(self): """Do auto stop if necessary.""" # If device does not support auto_positioning, # we have to stop the device when position is reached. # unless device was traveling to fully open # or fully closed state current_position = self.travel_calc.current_position() if self.position_reached(): self.travel_calc.stop() if 0 < current_position < 100: _LOGGER.debug("auto_stop_if_necessary :: calling stop command") await self._async_handle_command(SERVICE_STOP_COVER)