Example #1
0
    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())
Example #2
0
    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())
Example #3
0
 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 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)
Example #5
0
    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_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
            self.assertFalse(travelcalculator.is_opening())
            self.assertFalse(travelcalculator.is_closing())

            travelcalculator.set_position(80)
            self.assertFalse(travelcalculator.is_opening())
            self.assertFalse(travelcalculator.is_closing())

            mock_time.return_value = 1580000000.0
            travelcalculator.start_travel_down()
            self.assertFalse(travelcalculator.is_opening())
            self.assertTrue(travelcalculator.is_closing())

            mock_time.return_value = 1580000004.0
            self.assertFalse(travelcalculator.is_opening())
            self.assertTrue(travelcalculator.is_closing())

            mock_time.return_value = 1580000005.0
            self.assertFalse(travelcalculator.is_opening())
            self.assertFalse(travelcalculator.is_closing())
            # up direction
            travelcalculator.start_travel(50)
            self.assertTrue(travelcalculator.is_opening())
            self.assertFalse(travelcalculator.is_closing())

            mock_time.return_value = 1580000030.0
            self.assertFalse(travelcalculator.is_opening())
            self.assertFalse(travelcalculator.is_closing())
    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)
            self.assertEqual(travelcalculator.travel_direction,
                             TravelStatus.DIRECTION_DOWN)

            # change direction after two seconds
            mock_time.return_value = 1580000002.0
            self.assertEqual(travelcalculator.current_position(), 64)
            travelcalculator.start_travel(48)
            self.assertEqual(travelcalculator.travel_direction,
                             TravelStatus.DIRECTION_UP)

            self.assertEqual(travelcalculator.current_position(), 64)
            self.assertFalse(travelcalculator.position_reached())

            mock_time.return_value = 1580000004.0
            self.assertEqual(travelcalculator.current_position(), 56)
            self.assertFalse(travelcalculator.position_reached())

            mock_time.return_value = 1580000006.0
            self.assertEqual(travelcalculator.current_position(), 48)
            self.assertTrue(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)

            self.assertEqual(travelcalculator.travel_direction,
                             TravelStatus.DIRECTION_UP)

            # stop aftert two seconds
            mock_time.return_value = 1580000002.0
            travelcalculator.stop()

            mock_time.return_value = 1580000004.0
            self.assertEqual(travelcalculator.current_position(), 76)
            self.assertTrue(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
            self.assertEqual(travelcalculator.current_position(), 74)
            self.assertFalse(travelcalculator.position_reached())

            mock_time.return_value = 1580000009.0
            self.assertEqual(travelcalculator.current_position(), 68)
            self.assertTrue(travelcalculator.position_reached())
Example #9
0
    def test_travel_up(self):
        """Test travel down."""
        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
            self.assertEqual(travelcalculator.current_position(), 70)
            self.assertFalse(travelcalculator.position_reached())
            self.assertEqual(travelcalculator.travel_direction,
                             TravelStatus.DIRECTION_UP)

            mock_time.return_value = 1580000002.0
            self.assertEqual(travelcalculator.current_position(), 66)
            self.assertFalse(travelcalculator.position_reached())

            mock_time.return_value = 1580000004.0
            self.assertEqual(travelcalculator.current_position(), 62)
            self.assertFalse(travelcalculator.position_reached())

            mock_time.return_value = 1580000008.0
            self.assertEqual(travelcalculator.current_position(), 54)
            self.assertFalse(travelcalculator.position_reached())

            mock_time.return_value = 1580000010.0
            # position reached
            self.assertEqual(travelcalculator.current_position(), 50)
            self.assertTrue(travelcalculator.position_reached())

            mock_time.return_value = 1580000020.0
            self.assertEqual(travelcalculator.current_position(), 50)
            self.assertTrue(travelcalculator.position_reached())
Example #10
0
    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()
Example #11
0
    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())
Example #12
0
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()
Example #13
0
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()
Example #15
0
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)