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)
def test_set_position_after_travel(self): """Set explicit position after start_travel should stop traveling.""" travelcalculator = TravelCalculator(25, 50) travelcalculator.start_travel(30) travelcalculator.set_position(80) self.assertTrue(travelcalculator.position_reached()) self.assertEqual(travelcalculator.current_position(), 80)
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)
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 __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)
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)
def __init__(self, device_id, name, travel_time_down, travel_time_up, open_script_entity_id, close_script_entity_id, stop_script_entity_id, 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._open_script_entity_id = open_script_entity_id self._close_script_entity_id = close_script_entity_id self._stop_script_entity_id = stop_script_entity_id 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)
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
def test_travel_down(self): """Test travel up.""" travelcalculator = TravelCalculator(25, 50) travelcalculator.set_position(60) travelcalculator.time_set_from_outside = 1000 travelcalculator.start_travel(40) # time not changed, still at beginning self.assertEqual(travelcalculator.current_position(), 60) self.assertFalse(travelcalculator.position_reached()) self.assertEqual( travelcalculator.travel_direction, TravelStatus.DIRECTION_DOWN) travelcalculator.time_set_from_outside = 1000 + 1 self.assertEqual(travelcalculator.current_position(), 56) self.assertFalse(travelcalculator.position_reached()) travelcalculator.time_set_from_outside = 1000 + 2 self.assertEqual(travelcalculator.current_position(), 52) self.assertFalse(travelcalculator.position_reached()) travelcalculator.time_set_from_outside = 1000 + 4 self.assertEqual(travelcalculator.current_position(), 44) self.assertFalse(travelcalculator.position_reached()) travelcalculator.time_set_from_outside = 1000 + 5 # position reached self.assertEqual(travelcalculator.current_position(), 40) self.assertTrue(travelcalculator.position_reached()) travelcalculator.time_set_from_outside = 1000 + 10 self.assertEqual(travelcalculator.current_position(), 40) self.assertTrue(travelcalculator.position_reached())
def test_set_position(self): """Test set position.""" travelcalculator = TravelCalculator(25, 50) travelcalculator.set_position(70) self.assertTrue(travelcalculator.position_reached()) self.assertEqual(travelcalculator.current_position(), 70)
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_time_default(self): """Test default time settings (no time set from outside).""" travelcalculator = TravelCalculator(25, 50) self.assertLess( abs(time.time()-travelcalculator.current_time()), 0.001)
def test_stop(self): """Test stopping.""" travelcalculator = TravelCalculator(25, 50) travelcalculator.set_position(60) travelcalculator.time_set_from_outside = 1000 travelcalculator.start_travel(80) self.assertEqual( travelcalculator.travel_direction, TravelStatus.DIRECTION_UP) # stop aftert two seconds travelcalculator.time_set_from_outside = 1000 + 2 travelcalculator.stop() travelcalculator.time_set_from_outside = 1000 + 4 self.assertEqual(travelcalculator.current_position(), 64) self.assertTrue(travelcalculator.position_reached()) # restart after 1 additional second (3 seconds) travelcalculator.time_set_from_outside = 1000 + 5 travelcalculator.start_travel(68) # running up for 6 seconds travelcalculator.time_set_from_outside = 1000 + 6 self.assertEqual(travelcalculator.current_position(), 66) self.assertFalse(travelcalculator.position_reached()) travelcalculator.time_set_from_outside = 1000 + 7 self.assertEqual(travelcalculator.current_position(), 68) self.assertTrue(travelcalculator.position_reached())
def test_time_default(self): """Test default time settings (no time set from outside).""" travelcalculator = TravelCalculator(25, 50) self.assertLess(abs(time.time() - travelcalculator.current_time()), 0.001)
def test_change_direction(self): """Test changing direction while travelling.""" travelcalculator = TravelCalculator(25, 50) travelcalculator.set_position(60) travelcalculator.time_set_from_outside = 1000 travelcalculator.start_travel(80) self.assertEqual(travelcalculator.travel_direction, TravelStatus.DIRECTION_UP) # change direction after two seconds travelcalculator.time_set_from_outside = 1000 + 2 self.assertEqual(travelcalculator.current_position(), 64) travelcalculator.start_travel(48) self.assertEqual(travelcalculator.travel_direction, TravelStatus.DIRECTION_DOWN) self.assertEqual(travelcalculator.current_position(), 64) self.assertFalse(travelcalculator.position_reached()) travelcalculator.time_set_from_outside = 1000 + 4 self.assertEqual(travelcalculator.current_position(), 56) self.assertFalse(travelcalculator.position_reached()) travelcalculator.time_set_from_outside = 1000 + 6 self.assertEqual(travelcalculator.current_position(), 48) self.assertTrue(travelcalculator.position_reached())
def test_stop(self): """Test stopping.""" travelcalculator = TravelCalculator(25, 50) travelcalculator.set_position(60) travelcalculator.time_set_from_outside = 1000 travelcalculator.start_travel(80) self.assertEqual(travelcalculator.travel_direction, TravelStatus.DIRECTION_UP) # stop aftert two seconds travelcalculator.time_set_from_outside = 1000 + 2 travelcalculator.stop() travelcalculator.time_set_from_outside = 1000 + 4 self.assertEqual(travelcalculator.current_position(), 64) self.assertTrue(travelcalculator.position_reached()) # restart after 1 additional second (3 seconds) travelcalculator.time_set_from_outside = 1000 + 5 travelcalculator.start_travel(68) # running up for 6 seconds travelcalculator.time_set_from_outside = 1000 + 6 self.assertEqual(travelcalculator.current_position(), 66) self.assertFalse(travelcalculator.position_reached()) travelcalculator.time_set_from_outside = 1000 + 7 self.assertEqual(travelcalculator.current_position(), 68) self.assertTrue(travelcalculator.position_reached())
def test_travel_up(self): """Test travel up.""" travelcalculator = TravelCalculator(25, 50) with patch("time.time") as mock_time: mock_time.return_value = 1580000000.0 travelcalculator.set_position(70) travelcalculator.start_travel(50) # time not changed, still at beginning assert travelcalculator.current_position() == 70 assert not travelcalculator.position_reached() assert travelcalculator.travel_direction == TravelStatus.DIRECTION_UP mock_time.return_value = 1580000002.0 assert travelcalculator.current_position() == 66 assert not travelcalculator.position_reached() mock_time.return_value = 1580000004.0 assert travelcalculator.current_position() == 62 assert not travelcalculator.position_reached() mock_time.return_value = 1580000008.0 assert travelcalculator.current_position() == 54 assert not travelcalculator.position_reached() mock_time.return_value = 1580000010.0 # position reached assert travelcalculator.current_position() == 50 assert travelcalculator.position_reached() mock_time.return_value = 1580000020.0 assert travelcalculator.current_position() == 50 assert travelcalculator.position_reached()
def test_travel_down(self): """Test travel down.""" travelcalculator = TravelCalculator(25, 50) with patch("time.time") as mock_time: mock_time.return_value = 1580000000.0 travelcalculator.set_position(40) travelcalculator.start_travel(60) # time not changed, still at beginning assert travelcalculator.current_position() == 40 assert not travelcalculator.position_reached() assert travelcalculator.travel_direction == TravelStatus.DIRECTION_DOWN mock_time.return_value = 1580000001.0 assert travelcalculator.current_position() == 44 assert not travelcalculator.position_reached() mock_time.return_value = 1580000002.0 assert travelcalculator.current_position() == 48 assert not travelcalculator.position_reached() mock_time.return_value = 1580000004.0 assert travelcalculator.current_position() == 56 assert not travelcalculator.position_reached() mock_time.return_value = 1580000005.0 # position reached assert travelcalculator.current_position() == 60 assert travelcalculator.position_reached() mock_time.return_value = 1580000010.0 assert travelcalculator.current_position() == 60 assert travelcalculator.position_reached()
def test_is_opening_closing(self): """Test reports is_opening and is_closing.""" travelcalculator = TravelCalculator(25, 50) with patch("time.time") as mock_time: mock_time.return_value = 1580000000.0 assert not travelcalculator.is_opening() assert not travelcalculator.is_closing() travelcalculator.set_position(80) assert not travelcalculator.is_opening() assert not travelcalculator.is_closing() mock_time.return_value = 1580000000.0 travelcalculator.start_travel_down() assert not travelcalculator.is_opening() assert travelcalculator.is_closing() mock_time.return_value = 1580000004.0 assert not travelcalculator.is_opening() assert travelcalculator.is_closing() mock_time.return_value = 1580000005.0 assert not travelcalculator.is_opening() assert not travelcalculator.is_closing() # up direction travelcalculator.start_travel(50) assert travelcalculator.is_opening() assert not travelcalculator.is_closing() mock_time.return_value = 1580000030.0 assert not travelcalculator.is_opening() assert not travelcalculator.is_closing()
def test_set_position(self): """Test set position.""" travelcalculator = TravelCalculator(25, 50) travelcalculator.set_position(70) assert travelcalculator.position_reached() assert travelcalculator.current_position() == 70
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_travel_down_with_updates(self): """Test travel down with position updates from bus.""" travelcalculator = TravelCalculator(25, 50) with patch("time.time") as mock_time: mock_time.return_value = 1580000000.0 travelcalculator.set_position(40) travelcalculator.start_travel(100) # 15 seconds to reach 100 # time not changed, still at beginning assert travelcalculator.current_position() == 40 assert not travelcalculator.position_reached() assert travelcalculator.travel_direction == TravelStatus.DIRECTION_DOWN mock_time.return_value = 1580000002.0 assert travelcalculator.current_position() == 48 assert not travelcalculator.position_reached() # update from bus matching calculation travelcalculator.update_position(48) assert travelcalculator.current_position() == 48 assert not travelcalculator.position_reached() mock_time.return_value = 1580000010.0 assert travelcalculator.current_position() == 80 assert not travelcalculator.position_reached() # update from bus not matching calculation takes precedence (1 second slower) travelcalculator.update_position(76) assert travelcalculator.current_position() == 76 assert not travelcalculator.position_reached() # travel time extended by 1 second due to update from bus mock_time.return_value = 1580000015.0 assert travelcalculator.current_position() == 96 assert not travelcalculator.position_reached() mock_time.return_value = 1580000015.0 + 1 assert travelcalculator.current_position() == 100 assert travelcalculator.position_reached()
def test_travel_full_down(self): """Test travelling to the full down position.""" travelcalculator = TravelCalculator(25, 50) travelcalculator.set_position(80) travelcalculator.time_set_from_outside = 1000 travelcalculator.start_travel_down() travelcalculator.time_set_from_outside = 1019 self.assertFalse(travelcalculator.position_reached()) self.assertFalse(travelcalculator.is_closed()) self.assertFalse(travelcalculator.is_open()) travelcalculator.time_set_from_outside = 1020 self.assertTrue(travelcalculator.position_reached()) self.assertTrue(travelcalculator.is_closed()) self.assertFalse(travelcalculator.is_open())
def test_change_direction(self): """Test changing direction while travelling.""" travelcalculator = TravelCalculator(25, 50) travelcalculator.set_position(60) travelcalculator.time_set_from_outside = 1000 travelcalculator.start_travel(80) self.assertEqual( travelcalculator.travel_direction, TravelStatus.DIRECTION_UP) # change direction after two seconds travelcalculator.time_set_from_outside = 1000 + 2 self.assertEqual(travelcalculator.current_position(), 64) travelcalculator.start_travel(48) self.assertEqual( travelcalculator.travel_direction, TravelStatus.DIRECTION_DOWN) self.assertEqual(travelcalculator.current_position(), 64) self.assertFalse(travelcalculator.position_reached()) travelcalculator.time_set_from_outside = 1000 + 4 self.assertEqual(travelcalculator.current_position(), 56) self.assertFalse(travelcalculator.position_reached()) travelcalculator.time_set_from_outside = 1000 + 6 self.assertEqual(travelcalculator.current_position(), 48) self.assertTrue(travelcalculator.position_reached())
def test_time_set_from_outside(self): """Test setting the current time from outside.""" travelcalculator = TravelCalculator(25, 50) travelcalculator.time_set_from_outside = 1000 self.assertEqual(travelcalculator.current_time(), 1000)
def test_change_direction(self): """Test changing direction while travelling.""" travelcalculator = TravelCalculator(50, 25) with patch("time.time") as mock_time: mock_time.return_value = 1580000000.0 travelcalculator.set_position(60) travelcalculator.start_travel(80) assert travelcalculator.travel_direction == TravelStatus.DIRECTION_DOWN # change direction after two seconds mock_time.return_value = 1580000002.0 assert travelcalculator.current_position() == 64 travelcalculator.start_travel(48) assert travelcalculator.travel_direction == TravelStatus.DIRECTION_UP assert travelcalculator.current_position() == 64 assert not travelcalculator.position_reached() mock_time.return_value = 1580000004.0 assert travelcalculator.current_position() == 56 assert not travelcalculator.position_reached() mock_time.return_value = 1580000006.0 assert travelcalculator.current_position() == 48 assert travelcalculator.position_reached()
def test_stop(self): """Test stopping.""" travelcalculator = TravelCalculator(25, 50) with patch("time.time") as mock_time: mock_time.return_value = 1580000000.0 travelcalculator.set_position(80) travelcalculator.start_travel(60) assert travelcalculator.travel_direction == TravelStatus.DIRECTION_UP # stop aftert two seconds mock_time.return_value = 1580000002.0 travelcalculator.stop() mock_time.return_value = 1580000004.0 assert travelcalculator.current_position() == 76 assert travelcalculator.position_reached() # restart after 1 additional second (3 seconds) mock_time.return_value = 1580000005.0 travelcalculator.start_travel(68) # running up for 6 seconds mock_time.return_value = 1580000006.0 assert travelcalculator.current_position() == 74 assert not travelcalculator.position_reached() mock_time.return_value = 1580000009.0 assert travelcalculator.current_position() == 68 assert travelcalculator.position_reached()
def test_travel_up_with_updates(self): """Test travel up with position updates from bus.""" travelcalculator = TravelCalculator(25, 50) with patch("time.time") as mock_time: mock_time.return_value = 1580000000.0 travelcalculator.set_position(70) travelcalculator.start_travel(50) # 10 seconds to reach 50 mock_time.return_value = 1580000005.0 assert travelcalculator.current_position() == 60 assert not travelcalculator.position_reached() # update from bus not matching calculation takes precedence (1 second faster) travelcalculator.update_position(58) assert travelcalculator.current_position() == 58 assert not travelcalculator.position_reached() # position reached 1 second earlier than predicted mock_time.return_value = 1580000010.0 - 1 assert travelcalculator.current_position() == 50 assert travelcalculator.position_reached()
def test_travel_up(self): """Test travel down.""" travelcalculator = TravelCalculator(25, 50) travelcalculator.set_position(50) travelcalculator.time_set_from_outside = 1000 travelcalculator.start_travel(70) # time not changed, still at beginning self.assertEqual(travelcalculator.current_position(), 50) self.assertFalse(travelcalculator.position_reached()) self.assertEqual( travelcalculator.travel_direction, TravelStatus.DIRECTION_UP) travelcalculator.time_set_from_outside = 1000 + 2 self.assertEqual(travelcalculator.current_position(), 54) self.assertFalse(travelcalculator.position_reached()) travelcalculator.time_set_from_outside = 1000 + 4 self.assertEqual(travelcalculator.current_position(), 58) self.assertFalse(travelcalculator.position_reached()) travelcalculator.time_set_from_outside = 1000 + 8 self.assertEqual(travelcalculator.current_position(), 66) self.assertFalse(travelcalculator.position_reached()) travelcalculator.time_set_from_outside = 1000 + 10 # position reached self.assertEqual(travelcalculator.current_position(), 70) self.assertTrue(travelcalculator.position_reached()) travelcalculator.time_set_from_outside = 1000 + 20 self.assertEqual(travelcalculator.current_position(), 70) self.assertTrue(travelcalculator.position_reached())
def test_travel_down(self): """Test travel up.""" travelcalculator = TravelCalculator(25, 50) travelcalculator.set_position(60) travelcalculator.time_set_from_outside = 1000 travelcalculator.start_travel(40) # time not changed, still at beginning self.assertEqual(travelcalculator.current_position(), 60) self.assertFalse(travelcalculator.position_reached()) self.assertEqual(travelcalculator.travel_direction, TravelStatus.DIRECTION_DOWN) travelcalculator.time_set_from_outside = 1000 + 1 self.assertEqual(travelcalculator.current_position(), 56) self.assertFalse(travelcalculator.position_reached()) travelcalculator.time_set_from_outside = 1000 + 2 self.assertEqual(travelcalculator.current_position(), 52) self.assertFalse(travelcalculator.position_reached()) travelcalculator.time_set_from_outside = 1000 + 4 self.assertEqual(travelcalculator.current_position(), 44) self.assertFalse(travelcalculator.position_reached()) travelcalculator.time_set_from_outside = 1000 + 5 # position reached self.assertEqual(travelcalculator.current_position(), 40) self.assertTrue(travelcalculator.position_reached()) travelcalculator.time_set_from_outside = 1000 + 10 self.assertEqual(travelcalculator.current_position(), 40) self.assertTrue(travelcalculator.position_reached())
def test_travel_up(self): """Test travel down.""" travelcalculator = TravelCalculator(25, 50) travelcalculator.set_position(50) travelcalculator.time_set_from_outside = 1000 travelcalculator.start_travel(70) # time not changed, still at beginning self.assertEqual(travelcalculator.current_position(), 50) self.assertFalse(travelcalculator.position_reached()) self.assertEqual(travelcalculator.travel_direction, TravelStatus.DIRECTION_UP) travelcalculator.time_set_from_outside = 1000 + 2 self.assertEqual(travelcalculator.current_position(), 54) self.assertFalse(travelcalculator.position_reached()) travelcalculator.time_set_from_outside = 1000 + 4 self.assertEqual(travelcalculator.current_position(), 58) self.assertFalse(travelcalculator.position_reached()) travelcalculator.time_set_from_outside = 1000 + 8 self.assertEqual(travelcalculator.current_position(), 66) self.assertFalse(travelcalculator.position_reached()) travelcalculator.time_set_from_outside = 1000 + 10 # position reached self.assertEqual(travelcalculator.current_position(), 70) self.assertTrue(travelcalculator.position_reached()) travelcalculator.time_set_from_outside = 1000 + 20 self.assertEqual(travelcalculator.current_position(), 70) self.assertTrue(travelcalculator.position_reached())
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()
def test_travel_full_down(self): """Test travelling to the full down position.""" travelcalculator = TravelCalculator(25, 50) with patch("time.time") as mock_time: mock_time.return_value = 1580000000.0 travelcalculator.set_position(20) travelcalculator.start_travel_down() mock_time.return_value = 1580000019.0 assert not travelcalculator.position_reached() assert not travelcalculator.is_closed() assert not travelcalculator.is_open() mock_time.return_value = 1580000020.0 assert travelcalculator.position_reached() assert travelcalculator.is_closed() assert not travelcalculator.is_open()