Repository: XKNX/xknx

Issue:
Rework is_open for covers
**Description of problem:**
I already created an issue in the HA repository (https://github.com/home-assistant/core/issues/34330), but I realized the problem lies in XKNX. 

Problem shortcut: My garage door can be controlled with knx. It just supports up/down and **cannot** report the current position of the garage door. In the current implementation, I cannot open / close the garage door via HA, because the reported state is not correct in 50% of the time. Same problem occurs with central covers like in my HA issue.
![image](https://user-images.githubusercontent.com/9930385/88324506-13078c80-cd24-11ea-856e-427cab364b10.png)

Even for covers which can report the current position, xknx uses the time in the travelcalculator and not the actual position of the cover.

With boils down to:
**The cover is_open and is_closed is not correct for knx covers with and without position state address.**

My proposal:
- knx cover **with** `group_address_position_state`: Use the actual position comparison with 100% or 0% to determine is_open and is_closed

- knx cover **without** `group_address_position_state`: Return `None` for both is_open and is_closed. The travelcalculator is a nice effort, but it doesn't help if the position was changed outside of XKNX. Therefore I believe it can't be used to determine the state at all.

I can provide the implementation if it has a chance to get it pulled ;)

- [ ] using xknx standalone
- [x] using Home-Assistant knx plugin

**Version information:**
- xknx / Home-Assistant release with the issue: 102

**KNX installation:**


**Problem-relevant `xknx.yaml` or `configuration.yaml` entries (fill out even if it seems unimportant):**

**Traceback (if applicable):**


Discussion:

Patch:
diff --git a/changelog.md b/changelog.md
index af3d0bbc..05721e89 100644
--- a/changelog.md
+++ b/changelog.md
@@ -10,6 +10,8 @@
 
 ### New Features
 
+- Cover: add optional `group_address_stop` for manual stopping
+- Cover: start travel calculator when up/down telegram from bus is received
 - HA integration: `knx.send` service takes `type` attribute to allow sending DPT encoded values like `sensor`
 - HA integration: `sensor` and `expose` accept int and float values for `type` (parsed as DPT numbers)
 - new StateUpdater: Devices `sync_state` can be set to `init` to just initialize state on startup, `expire [minutes]` to read the state from the KNX bus when it was not updated for [minutes] or `every [minutes]` to update it regularly every [minutes]
@@ -24,9 +26,12 @@
 - enable multicast on macOS and fix a bug where unknown cemi frames raise a TypeError on routing connections
 - BinarySensor: reset_after is now implemented as asyncio.Task to prevent blocking the loop
 - ClimateMode: binary climate modes should be fully functional now (sending, receiving and syncing)
+- Cover: position update from bus does update current position, but not target position (when moving)
 
 ### Internals
 
+- Cover travelcalculator doesn't start from 0% but is initialized by first movement or status telegram
+- Cover uses 0% for open cover and 100% for closed cover now
 - DPT classes can now be searched via value_type string or dpt number from any parent class (DPTBase for all) to be used in Sensor
 - Use RemoteValue class in BinarySensor, DateTime and ClimateMode device
 - use time.struct_time for internal time and date representation
diff --git a/home-assistant-plugin/custom_components/xknx/__init__.py b/home-assistant-plugin/custom_components/xknx/__init__.py
index 19a75823..fd16f23a 100644
--- a/home-assistant-plugin/custom_components/xknx/__init__.py
+++ b/home-assistant-plugin/custom_components/xknx/__init__.py
@@ -209,9 +209,7 @@ class KNXModule:
             return self.connection_config_tunneling()
         if CONF_XKNX_ROUTING in self.config[DOMAIN]:
             return self.connection_config_routing()
-        # return None to let xknx use config from xknx.yaml connection block if given
-        #   otherwise it will use default ConnectionConfig (Automatic)
-        return None
+        return self.connection_config_auto()
 
     def connection_config_routing(self):
         """Return the connection_config if routing is configured."""
@@ -235,6 +233,11 @@ class KNXModule:
             auto_reconnect=True,
         )
 
+    def connection_config_auto(self):
+        """Return the connection_config if auto is configured."""
+        # pylint: disable=no-self-use
+        return ConnectionConfig()
+
     def register_callbacks(self):
         """Register callbacks within XKNX object."""
         if (
diff --git a/home-assistant-plugin/custom_components/xknx/binary_sensor.py b/home-assistant-plugin/custom_components/xknx/binary_sensor.py
index 551666a8..54bb8930 100644
--- a/home-assistant-plugin/custom_components/xknx/binary_sensor.py
+++ b/home-assistant-plugin/custom_components/xknx/binary_sensor.py
@@ -33,20 +33,17 @@ AUTOMATION_SCHEMA = vol.Schema(
 
 AUTOMATIONS_SCHEMA = vol.All(cv.ensure_list, [AUTOMATION_SCHEMA])
 
-PLATFORM_SCHEMA = vol.All(
-    cv.deprecated("significant_bit"),
-    PLATFORM_SCHEMA.extend(
-        {
-            vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
-            vol.Optional(CONF_SYNC_STATE, default=True):
-                vol.Any(vol.All(vol.Coerce(int), vol.Range(min=2, max=1440)), cv.boolean, cv.string),
-            vol.Optional(CONF_IGNORE_INTERNAL_STATE, default=False): cv.boolean,
-            vol.Required(CONF_STATE_ADDRESS): cv.string,
-            vol.Optional(CONF_DEVICE_CLASS): cv.string,
-            vol.Optional(CONF_RESET_AFTER): cv.positive_int,
-            vol.Optional(CONF_AUTOMATION): AUTOMATIONS_SCHEMA,
-        }
-    )
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
+    {
+        vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
+        vol.Optional(CONF_SYNC_STATE, default=True):
+            vol.Any(vol.All(vol.Coerce(int), vol.Range(min=2, max=1440)), cv.boolean, cv.string),
+        vol.Optional(CONF_IGNORE_INTERNAL_STATE, default=False): cv.boolean,
+        vol.Required(CONF_STATE_ADDRESS): cv.string,
+        vol.Optional(CONF_DEVICE_CLASS): cv.string,
+        vol.Optional(CONF_RESET_AFTER): cv.positive_int,
+        vol.Optional(CONF_AUTOMATION): AUTOMATIONS_SCHEMA,
+    }
 )
 
 
diff --git a/home-assistant-plugin/custom_components/xknx/cover.py b/home-assistant-plugin/custom_components/xknx/cover.py
index a49365e9..9b9f9f19 100644
--- a/home-assistant-plugin/custom_components/xknx/cover.py
+++ b/home-assistant-plugin/custom_components/xknx/cover.py
@@ -22,6 +22,7 @@ from . import ATTR_DISCOVER_DEVICES, DATA_XKNX
 
 CONF_MOVE_LONG_ADDRESS = "move_long_address"
 CONF_MOVE_SHORT_ADDRESS = "move_short_address"
+CONF_STOP_ADDRESS = "stop_address"
 CONF_POSITION_ADDRESS = "position_address"
 CONF_POSITION_STATE_ADDRESS = "position_state_address"
 CONF_ANGLE_ADDRESS = "angle_address"
@@ -38,6 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
         vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
         vol.Optional(CONF_MOVE_LONG_ADDRESS): cv.string,
         vol.Optional(CONF_MOVE_SHORT_ADDRESS): cv.string,
+        vol.Optional(CONF_STOP_ADDRESS): cv.string,
         vol.Optional(CONF_POSITION_ADDRESS): cv.string,
         vol.Optional(CONF_POSITION_STATE_ADDRESS): cv.string,
         vol.Optional(CONF_ANGLE_ADDRESS): cv.string,
@@ -80,6 +82,7 @@ def async_add_entities_config(hass, config, async_add_entities):
         name=config[CONF_NAME],
         group_address_long=config.get(CONF_MOVE_LONG_ADDRESS),
         group_address_short=config.get(CONF_MOVE_SHORT_ADDRESS),
+        group_address_stop=config.get(CONF_STOP_ADDRESS),
         group_address_position_state=config.get(CONF_POSITION_STATE_ADDRESS),
         group_address_angle=config.get(CONF_ANGLE_ADDRESS),
         group_address_angle_state=config.get(CONF_ANGLE_STATE_ADDRESS),
@@ -109,6 +112,8 @@ class KNXCover(CoverEntity):
         async def after_update_callback(device):
             """Call after device was updated."""
             self.async_write_ha_state()
+            if self.device.is_traveling():
+                self.start_auto_updater()
 
         self.device.register_device_updated_cb(after_update_callback)
 
@@ -139,40 +144,53 @@ class KNXCover(CoverEntity):
     def supported_features(self):
         """Flag supported features."""
         supported_features = (
-            SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION | SUPPORT_STOP
+            SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
         )
+        if self.device.supports_stop:
+            supported_features |= SUPPORT_STOP
         if self.device.supports_angle:
             supported_features |= SUPPORT_SET_TILT_POSITION
         return supported_features
 
     @property
     def current_cover_position(self):
-        """Return the current position of the cover."""
-        return self.device.current_position()
+        """Return current position of cover.
+        None is unknown, 0 is closed, 100 is fully open.
+        """
+        # In KNX 0 is open, 100 is closed.
+        try:
+            return 100 - self.device.current_position()
+        except TypeError:
+            return None
 
     @property
     def is_closed(self):
         """Return if the cover is closed."""
         return self.device.is_closed()
 
+    @property
+    def is_opening(self):
+        """Return if the cover is opening or not."""
+        return self.device.is_opening()
+
+    @property
+    def is_closing(self):
+        """Return if the cover is closing or not."""
+        return self.device.is_closing()
+
     async def async_close_cover(self, **kwargs):
         """Close the cover."""
-        if not self.device.is_closed():
-            await self.device.set_down()
-            self.start_auto_updater()
+        await self.device.set_down()
 
     async def async_open_cover(self, **kwargs):
         """Open the cover."""
-        if not self.device.is_open():
-            await self.device.set_up()
-            self.start_auto_updater()
+        await self.device.set_up()
 
     async def async_set_cover_position(self, **kwargs):
         """Move the cover to a specific position."""
         if ATTR_POSITION in kwargs:
-            position = kwargs[ATTR_POSITION]
-            await self.device.set_position(position)
-            self.start_auto_updater()
+            knx_position = 100 - kwargs[ATTR_POSITION]
+            await self.device.set_position(knx_position)
 
     async def async_stop_cover(self, **kwargs):
         """Stop the cover."""
@@ -181,16 +199,22 @@ class KNXCover(CoverEntity):
 
     @property
     def current_cover_tilt_position(self):
-        """Return current tilt position of cover."""
+        """Return current position of cover tilt.
+        None is unknown, 0 is closed, 100 is fully open.
+        """
+        # In KNX 0 is open, 100 is closed.
         if not self.device.supports_angle:
             return None
-        return self.device.current_angle()
+        try:
+            return 100 - self.device.current_angle()
+        except TypeError:
+            return None
 
     async def async_set_cover_tilt_position(self, **kwargs):
         """Move the cover tilt to a specific position."""
         if ATTR_TILT_POSITION in kwargs:
-            tilt_position = kwargs[ATTR_TILT_POSITION]
-            await self.device.set_angle(tilt_position)
+            knx_tilt_position = 100 - kwargs[ATTR_TILT_POSITION]
+            await self.device.set_angle(knx_tilt_position)
 
     def start_auto_updater(self):
         """Start the autoupdater to update Home Assistant while cover is moving."""
diff --git a/xknx/devices/cover.py b/xknx/devices/cover.py
index edf20d06..2f6643cf 100644
--- a/xknx/devices/cover.py
+++ b/xknx/devices/cover.py
@@ -8,7 +8,7 @@ It provides functionality for
 * Cover will also predict the current position.
 """
 from xknx.remote_value import (
-    RemoteValueScaling, RemoteValueStep, RemoteValueUpDown)
+    RemoteValueScaling, RemoteValueStep, RemoteValueSwitch, RemoteValueUpDown)
 
 from .device import Device
 from .travelcalculator import TravelCalculator
@@ -30,6 +30,7 @@ class Cover(Device):
                  name,
                  group_address_long=None,
                  group_address_short=None,
+                 group_address_stop=None,
                  group_address_position=None,
                  group_address_position_state=None,
                  group_address_angle=None,
@@ -42,35 +43,41 @@ class Cover(Device):
         """Initialize Cover class."""
         # pylint: disable=too-many-arguments
         super().__init__(xknx, name, device_updated_cb)
-
+        # self.after_update for position changes is called after updating the
+        # travelcalculator (in process_group_write and set_*) - angle changes
+        # are updated from RemoteValue objects
         self.updown = RemoteValueUpDown(
             xknx,
             group_address_long,
             device_name=self.name,
-            after_update_cb=self.after_update,
-            invert=invert_position)
+            after_update_cb=None)
 
         self.step = RemoteValueStep(
             xknx,
             group_address_short,
             device_name=self.name,
-            after_update_cb=self.after_update,
-            invert=invert_position)
+            after_update_cb=self.after_update)
 
-        position_range_from = 0 if invert_position else 100
-        position_range_to = 100 if invert_position else 0
+        self.stop_ = RemoteValueSwitch(
+            xknx,
+            group_address=group_address_stop,
+            device_name=self.name,
+            after_update_cb=None)
+
+        position_range_from = 100 if invert_position else 0
+        position_range_to = 0 if invert_position else 100
         self.position = RemoteValueScaling(
             xknx,
             group_address_position,
             group_address_position_state,
             device_name=self.name,
             feature_name="Position",
-            after_update_cb=self.after_update,
+            after_update_cb=None,
             range_from=position_range_from,
             range_to=position_range_to)
 
-        angle_range_from = 0 if invert_angle else 100
-        angle_range_to = 100 if invert_angle else 0
+        angle_range_from = 100 if invert_angle else 0
+        angle_range_to = 0 if invert_angle else 100
         self.angle = RemoteValueScaling(
             xknx,
             group_address_angle,
@@ -92,6 +99,7 @@ class Cover(Device):
         """Iterate the devices RemoteValue classes."""
         yield from (self.updown,
                     self.step,
+                    self.stop_,
                     self.position,
                     self.angle)
 
@@ -102,6 +110,8 @@ class Cover(Device):
             config.get('group_address_long')
         group_address_short = \
             config.get('group_address_short')
+        group_address_stop = \
+            config.get('group_address_stop')
         group_address_position = \
             config.get('group_address_position')
         group_address_position_state = \
@@ -124,6 +134,7 @@ class Cover(Device):
             name,
             group_address_long=group_address_long,
             group_address_short=group_address_short,
+            group_address_stop=group_address_stop,
             group_address_position=group_address_position,
             group_address_position_state=group_address_position_state,
             group_address_angle=group_address_angle,
@@ -138,14 +149,16 @@ class Cover(Device):
         return '<Cover name="{0}" ' \
             'updown="{1}" ' \
             'step="{2}" ' \
-            'position="{3}" ' \
-            'angle="{4}" '\
-            'travel_time_down="{5}" ' \
-            'travel_time_up="{6}" />' \
+            'stop="{3}" ' \
+            'position="{4}" ' \
+            'angle="{5}" '\
+            'travel_time_down="{6}" ' \
+            'travel_time_up="{7}" />' \
             .format(
                 self.name,
                 self.updown.group_addr_str(),
                 self.step.group_addr_str(),
+                self.stop_.group_addr_str(),
                 self.position.group_addr_str(),
                 self.angle.group_addr_str(),
                 self.travel_time_down,
@@ -155,11 +168,13 @@ class Cover(Device):
         """Move cover down."""
         await self.updown.down()
         self.travelcalculator.start_travel_down()
+        await self.after_update()
 
     async def set_up(self):
         """Move cover up."""
         await self.updown.up()
         self.travelcalculator.start_travel_up()
+        await self.after_update()
 
     async def set_short_down(self):
         """Move cover short down."""
@@ -171,24 +186,38 @@ class Cover(Device):
 
     async def stop(self):
         """Stop cover."""
-        # Thats the KNX way of doing this. electrical engineers ... m-)
-        await self.step.increase()
+        if self.stop_.writable:
+            await self.stop_.on()
+        elif self.step.writable:
+            await self.step.increase()
+        else:
+            self.xknx.logger.warning('Stop not supported for device %s', self.get_name())
+            return
         self.travelcalculator.stop()
+        await self.after_update()
 
     async def set_position(self, position):
         """Move cover to a desginated postion."""
-        # No direct positioning group address defined
-        if not self.position.group_address:
+        if not self.position.writable:
+            # No direct positioning group address defined
+            # fully open or close is always possible even if current position is not known
             current_position = self.current_position()
-            if position < current_position:
-                await self.updown.down()
-            elif position > current_position:
+            if current_position is None:
+                if position == self.travelcalculator.position_open:
+                    await self.updown.up()
+                elif position == self.travelcalculator.position_closed:
+                    await self.updown.down()
+                else:
+                    self.xknx.logger.warning("Current position unknown. Initialize cover by moving to end position.")
+                    return
+            elif position < current_position:
                 await self.updown.up()
-            self.travelcalculator.start_travel(position)
-            return
-
-        await self.position.set(position)
+            elif position > current_position:
+                await self.updown.down()
+        else:
+            await self.position.set(position)
         self.travelcalculator.start_travel(position)
+        await self.after_update()
 
     async def set_angle(self, angle):
         """Move cover to designated angle."""
@@ -196,7 +225,6 @@ class Cover(Device):
             self.xknx.logger.warning('Angle not supported for device %s', self.get_name())
             return
         await self.angle.set(angle)
-        await self.after_update()
 
     async def auto_stop_if_necessary(self):
         """Do auto stop if necessary."""
@@ -205,7 +233,8 @@ class Cover(Device):
         # unless device was traveling to fully open
         # or fully closed state
         if (
-                not self.position.group_address and
+                self.supports_stop and
+                not self.position.writable and
                 self.position_reached() and
                 not self.is_open() and
                 not self.is_closed()):
@@ -228,18 +257,33 @@ class Cover(Device):
 
     async def sync(self):
         """Read states of device from KNX bus."""
-        # TODO: if not self.travelcalculator.is_traveling():
-        # when Cover is traveling, requesting state will return false results
-        # but this is manual syncing so it may be ok
-        # should state updater wait for cover to stop?
         await self.position.read_state()
         await self.angle.read_state()
 
     async def process_group_write(self, telegram):
         """Process incoming GROUP WRITE telegram."""
-        position_processed = await self.position.process(telegram)
-        if position_processed:
-            self.travelcalculator.set_position(self.position.value)
+        if await self.updown.process(telegram):
+            if self.updown.value == RemoteValueUpDown.Direction.UP:
+                self.travelcalculator.start_travel_up()
+            else:
+                self.travelcalculator.start_travel_down()
+            # call after_update to account for travelcalculator changes
+            await self.after_update()
+        # stop from bus
+        if await self.stop_.process(telegram) or await self.step.process(telegram):
+            if self.is_traveling():
+                self.travelcalculator.stop()
+                await self.after_update()
+
+        if await self.position.process(telegram):
+            # distinction between new target position and position update from bus
+            if telegram.group_address == self.position.group_address_state:
+                if self.is_traveling():
+                    self.travelcalculator.update_position(self.position.value)
+                else:
+                    self.travelcalculator.set_position(self.position.value)
+            else:
+                self.travelcalculator.start_travel(self.position.value)
             await self.after_update()
 
         await self.angle.process(telegram)
@@ -268,6 +312,19 @@ class Cover(Device):
         """Return if cover is closed."""
         return self.travelcalculator.is_closed()
 
+    def is_opening(self):
+        """Return if the cover is opening or not."""
+        return self.travelcalculator.is_opening()
+
+    def is_closing(self):
+        """Return if the cover is closing or not."""
+        return self.travelcalculator.is_closing()
+
+    @property
+    def supports_stop(self):
+        """Return if cover supports manual stopping."""
+        return self.stop_.writable or self.step.writable
+
     @property
     def supports_position(self):
         """Return if cover supports direct positioning."""
diff --git a/xknx/devices/travelcalculator.py b/xknx/devices/travelcalculator.py
index 3055618b..5de7d1d6 100644
--- a/xknx/devices/travelcalculator.py
+++ b/xknx/devices/travelcalculator.py
@@ -42,24 +42,29 @@ class TravelCalculator:
         self.travel_time_down = travel_time_down
         self.travel_time_up = travel_time_up
 
-        self.travel_to_position = 0
+        self.travel_to_position = None
         self.travel_started_time = 0
         self.travel_direction = TravelStatus.STOPPED
 
-        # 0 is closed, 100 is fully open
-        self.position_closed = 0
-        self.position_open = 100
-
-        self.time_set_from_outside = None
+        # 100 is closed, 0 is fully open
+        self.position_closed = 100
+        self.position_open = 0
 
     def set_position(self, position):
-        """Set known position of cover."""
-        self.last_known_position = position
+        """Set position and target of cover."""
         self.travel_to_position = position
-        self.position_type = PositionType.CONFIRMED
+        self.update_position(position)
+
+    def update_position(self, position):
+        """Update known position of cover."""
+        self.last_known_position = position
+        if position == self.travel_to_position:
+            self.position_type = PositionType.CONFIRMED
 
     def stop(self):
         """Stop traveling."""
+        if self.position_type == PositionType.UNKNOWN:
+            return
         self.last_known_position = self.current_position()
         self.travel_to_position = self.last_known_position
         self.position_type = PositionType.CALCULATED
@@ -67,15 +72,18 @@ class TravelCalculator:
 
     def start_travel(self, travel_to_position):
         """Start traveling to position."""
+        if self.position_type == PositionType.UNKNOWN:
+            self.set_position(travel_to_position)
+            return
         self.stop()
-        self.travel_started_time = self.current_time()
+        self.travel_started_time = time.time()
         self.travel_to_position = travel_to_position
         self.position_type = PositionType.CALCULATED
 
         self.travel_direction = \
-            TravelStatus.DIRECTION_UP \
+            TravelStatus.DIRECTION_DOWN \
             if travel_to_position > self.last_known_position else \
-            TravelStatus.DIRECTION_DOWN
+            TravelStatus.DIRECTION_UP
 
     def start_travel_up(self):
         """Start traveling up."""
@@ -89,12 +97,22 @@ class TravelCalculator:
         """Return current (calculated or known) position."""
         if self.position_type == PositionType.CALCULATED:
             return self._calculate_position()
+        if self.position_type == PositionType.UNKNOWN:
+            return None
         return self.last_known_position
 
     def is_traveling(self):
         """Return if cover is traveling."""
         return self.current_position() != self.travel_to_position
 
+    def is_opening(self):
+        """Return if the cover is opening."""
+        return self.is_traveling() and self.travel_direction == TravelStatus.DIRECTION_UP
+
+    def is_closing(self):
+        """Return if the cover is closing."""
+        return self.is_traveling() and self.travel_direction == TravelStatus.DIRECTION_DOWN
+
     def position_reached(self):
         """Return if cover has reached designated position."""
         return self.current_position() == self.travel_to_position
@@ -113,10 +131,10 @@ class TravelCalculator:
 
         def position_reached_or_exceeded(relative_position):
             """Return if designated position was reached."""
-            if relative_position >= 0 \
+            if relative_position <= 0 \
                     and self.travel_direction == TravelStatus.DIRECTION_DOWN:
                 return True
-            if relative_position <= 0 \
+            if relative_position >= 0 \
                     and self.travel_direction == TravelStatus.DIRECTION_UP:
                 return True
             return False
@@ -125,9 +143,10 @@ class TravelCalculator:
             return self.travel_to_position
 
         travel_time = self._calculate_travel_time(relative_position)
-        if self.current_time() > self.travel_started_time + travel_time:
+        if time.time() > self.travel_started_time + travel_time:
             return self.travel_to_position
-        progress = (self.current_time()-self.travel_started_time)/travel_time
+
+        progress = (time.time()-self.travel_started_time)/travel_time
         position = self.last_known_position + relative_position * progress
         return int(position)
 
@@ -135,23 +154,16 @@ class TravelCalculator:
         """Calculate time to travel to relative position."""
         travel_direction = \
             TravelStatus.DIRECTION_UP \
-            if relative_position > 0 else \
+            if relative_position < 0 else \
             TravelStatus.DIRECTION_DOWN
         travel_time_full = \
             self.travel_time_up \
             if travel_direction == TravelStatus.DIRECTION_UP else \
             self.travel_time_down
-        travel_range = self.position_open - self.position_closed
+        travel_range = self.position_closed - self.position_open
 
         return travel_time_full * abs(relative_position) / travel_range
 
-    def current_time(self):
-        """Get current time. May be modified from outside (for unit tests)."""
-        # time_set_from_outside is  used within unit tests
-        if self.time_set_from_outside is not None:
-            return self.time_set_from_outside
-        return time.time()
-
     def __eq__(self, other):
         """Equal operator."""
         return self.__dict__ == other.__dict__


Test patch:
diff --git a/test/devices_tests/cover_test.py b/test/devices_tests/cover_test.py
index a4f77870..e4107ec8 100644
--- a/test/devices_tests/cover_test.py
+++ b/test/devices_tests/cover_test.py
@@ -25,8 +25,40 @@ class TestCover(unittest.TestCase):
         self.loop.close()
 
     #
-    # SUPPORTS_POSITION/ANGLE
+    # SUPPORTS STOP/POSITION/ANGLE
     #
+    def test_supports_stop_true(self):
+        """Test support_position_true."""
+        xknx = XKNX(loop=self.loop)
+        cover_short_stop = Cover(
+            xknx,
+            'Children.Venetian',
+            group_address_long='1/4/14',
+            group_address_short='1/4/15')
+        self.assertTrue(cover_short_stop.supports_stop)
+
+        cover_manual_stop = Cover(
+            xknx,
+            'Children.Venetian',
+            group_address_long='1/4/14',
+            group_address_stop='1/4/15')
+        self.assertTrue(cover_manual_stop.supports_stop)
+
+    def test_supports_stop_false(self):
+        """Test support_position_true."""
+        xknx = XKNX(loop=self.loop)
+        cover = Cover(
+            xknx,
+            'Children.Venetian',
+            group_address_long='1/4/14',
+            group_address_position='1/4/16',
+            group_address_angle='1/4/18')
+        self.assertFalse(cover.supports_stop)
+        with patch('logging.Logger.warning') as mock_warn:
+            self.loop.run_until_complete(asyncio.Task(cover.stop()))
+            self.assertEqual(xknx.telegrams.qsize(), 0)
+            mock_warn.assert_called_with('Stop not supported for device %s', 'Children.Venetian')
+
     def test_supports_position_true(self):
         """Test support_position_true."""
         xknx = XKNX(loop=self.loop)
@@ -154,6 +186,7 @@ class TestCover(unittest.TestCase):
         self.loop.run_until_complete(asyncio.Task(cover.set_up()))
         self.assertEqual(xknx.telegrams.qsize(), 1)
         telegram = xknx.telegrams.get_nowait()
+        # DPT 1.008 - 0:up 1:down
         self.assertEqual(telegram,
                          Telegram(GroupAddress('1/2/1'), payload=DPTBinary(0)))
 
@@ -192,6 +225,7 @@ class TestCover(unittest.TestCase):
         self.loop.run_until_complete(asyncio.Task(cover.set_short_up()))
         self.assertEqual(xknx.telegrams.qsize(), 1)
         telegram = xknx.telegrams.get_nowait()
+        # DPT 1.008 - 0:up 1:down
         self.assertEqual(telegram,
                          Telegram(GroupAddress('1/2/2'), payload=DPTBinary(0)))
 
@@ -211,6 +245,7 @@ class TestCover(unittest.TestCase):
         self.loop.run_until_complete(asyncio.Task(cover.set_short_down()))
         self.assertEqual(xknx.telegrams.qsize(), 1)
         telegram = xknx.telegrams.get_nowait()
+        # DPT 1.008 - 0:up 1:down
         self.assertEqual(telegram,
                          Telegram(GroupAddress('1/2/2'), payload=DPTBinary(1)))
 
@@ -220,18 +255,31 @@ class TestCover(unittest.TestCase):
     def test_stop(self):
         """Test stopping cover."""
         xknx = XKNX(loop=self.loop)
-        cover = Cover(
+        cover_short_stop = Cover(
             xknx, 'TestCover',
             group_address_long='1/2/1',
             group_address_short='1/2/2',
             group_address_position='1/2/3',
             group_address_position_state='1/2/4')
-        self.loop.run_until_complete(asyncio.Task(cover.stop()))
+        self.loop.run_until_complete(asyncio.Task(cover_short_stop.stop()))
         self.assertEqual(xknx.telegrams.qsize(), 1)
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(telegram,
                          Telegram(GroupAddress('1/2/2'), payload=DPTBinary(1)))
 
+        cover_manual_stop = Cover(
+            xknx, 'TestCover',
+            group_address_long='1/2/1',
+            group_address_short='1/2/2',
+            group_address_stop='1/2/0',
+            group_address_position='1/2/3',
+            group_address_position_state='1/2/4')
+        self.loop.run_until_complete(asyncio.Task(cover_manual_stop.stop()))
+        self.assertEqual(xknx.telegrams.qsize(), 1)
+        telegram = xknx.telegrams.get_nowait()
+        self.assertEqual(telegram,
+                         Telegram(GroupAddress('1/2/0'), payload=DPTBinary(1)))
+
     #
     # TEST POSITION
     #
@@ -264,8 +312,10 @@ class TestCover(unittest.TestCase):
         self.loop.run_until_complete(asyncio.Task(cover.set_position(50)))
         self.assertEqual(xknx.telegrams.qsize(), 1)
         telegram = xknx.telegrams.get_nowait()
+        # DPT 1.008 - 0:up 1:down
         self.assertEqual(telegram,
-                         Telegram(GroupAddress('1/2/1'), payload=DPTBinary(1)))
+                         Telegram(GroupAddress('1/2/1'), payload=DPTBinary(0)))
+        self.assertEqual(cover.travelcalculator.travel_to_position, 50)
 
     def test_position_without_position_address_down(self):
         """Test moving cover down - with no absolute positioning supported."""
@@ -280,9 +330,53 @@ class TestCover(unittest.TestCase):
         self.loop.run_until_complete(asyncio.Task(cover.set_position(80)))
         self.assertEqual(xknx.telegrams.qsize(), 1)
         telegram = xknx.telegrams.get_nowait()
+        self.assertEqual(telegram,
+                         Telegram(GroupAddress('1/2/1'), payload=DPTBinary(1)))
+        self.assertEqual(cover.travelcalculator.travel_to_position, 80)
+
+    def test_position_without_position_address_uninitialized_up(self):
+        """Test moving uninitialized cover to absolute position - with no absolute positioning supported."""
+        xknx = XKNX(loop=self.loop)
+        cover = Cover(
+            xknx,
+            'TestCover',
+            group_address_long='1/2/1',
+            group_address_short='1/2/2',
+            group_address_position_state='1/2/4')
+
+        with patch('logging.Logger.warning') as mock_warn:
+            self.loop.run_until_complete(asyncio.Task(cover.set_position(50)))
+            self.assertEqual(xknx.telegrams.qsize(), 0)
+            mock_warn.assert_called_with('Current position unknown. Initialize cover by moving to end position.')
+
+        self.loop.run_until_complete(asyncio.Task(cover.set_position(0)))
+        self.assertEqual(xknx.telegrams.qsize(), 1)
+        telegram = xknx.telegrams.get_nowait()
         self.assertEqual(telegram,
                          Telegram(GroupAddress('1/2/1'), payload=DPTBinary(0)))
 
+    def test_position_without_position_address_uninitialized_down(self):
+        """Test moving uninitialized cover to absolute position - with no absolute positioning supported."""
+        xknx = XKNX(loop=self.loop)
+        cover = Cover(
+            xknx,
+            'TestCover',
+            group_address_long='1/2/1',
+            group_address_short='1/2/2',
+            group_address_position_state='1/2/4')
+
+        with patch('logging.Logger.warning') as mock_warn:
+            self.loop.run_until_complete(asyncio.Task(cover.set_position(50)))
+            self.assertEqual(xknx.telegrams.qsize(), 0)
+            mock_warn.assert_called_with('Current position unknown. Initialize cover by moving to end position.')
+
+        self.loop.run_until_complete(asyncio.Task(cover.set_position(100)))
+        print(cover.travelcalculator.position_closed)
+        self.assertEqual(xknx.telegrams.qsize(), 1)
+        telegram = xknx.telegrams.get_nowait()
+        self.assertEqual(telegram,
+                         Telegram(GroupAddress('1/2/1'), payload=DPTBinary(1)))
+
     def test_angle(self):
         """Test changing angle."""
         xknx = XKNX(loop=self.loop)
@@ -317,7 +411,7 @@ class TestCover(unittest.TestCase):
     #
     # TEST PROCESS
     #
-    def test_process(self):
+    def test_process_position(self):
         """Test process / reading telegrams from telegram queue. Test if position is processed correctly."""
         xknx = XKNX(loop=self.loop)
         cover = Cover(
@@ -327,9 +421,29 @@ class TestCover(unittest.TestCase):
             group_address_short='1/2/2',
             group_address_position='1/2/3',
             group_address_position_state='1/2/4')
+        # initial position process - position is unknown so this is the new state - not moving
+        telegram = Telegram(GroupAddress('1/2/3'), payload=DPTArray(213))
+        self.loop.run_until_complete(asyncio.Task(cover.process(telegram)))
+        self.assertEqual(cover.current_position(), 84)
+        self.assertFalse(cover.is_traveling())
+        # state telegram updates current position - we are not moving so this is new state - not moving
         telegram = Telegram(GroupAddress('1/2/4'), payload=DPTArray(42))
         self.loop.run_until_complete(asyncio.Task(cover.process(telegram)))
+        self.assertEqual(cover.current_position(), 16)
+        self.assertFalse(cover.is_traveling())
+        self.assertEqual(cover.travelcalculator.travel_to_position, 16)
+        # new position - movement starts
+        telegram = Telegram(GroupAddress('1/2/3'), payload=DPTArray(255))
+        self.loop.run_until_complete(asyncio.Task(cover.process(telegram)))
+        self.assertEqual(cover.current_position(), 16)
+        self.assertTrue(cover.is_closing())
+        self.assertEqual(cover.travelcalculator.travel_to_position, 100)
+        # new state while moving - movement goes on; travelcalculator updated
+        telegram = Telegram(GroupAddress('1/2/4'), payload=DPTArray(213))
+        self.loop.run_until_complete(asyncio.Task(cover.process(telegram)))
         self.assertEqual(cover.current_position(), 84)
+        self.assertTrue(cover.is_closing())
+        self.assertEqual(cover.travelcalculator.travel_to_position, 100)
 
     def test_process_angle(self):
         """Test process / reading telegrams from telegram queue. Test if position is processed correctly."""
@@ -343,7 +457,65 @@ class TestCover(unittest.TestCase):
             group_address_angle_state='1/2/4')
         telegram = Telegram(GroupAddress('1/2/4'), payload=DPTArray(42))
         self.loop.run_until_complete(asyncio.Task(cover.process(telegram)))
-        self.assertEqual(cover.current_angle(), 84)
+        self.assertEqual(cover.current_angle(), 16)
+
+    def test_process_up(self):
+        """Test process / reading telegrams from telegram queue. Test if up/down is processed correctly."""
+        xknx = XKNX(loop=self.loop)
+        cover = Cover(
+            xknx,
+            'TestCover',
+            group_address_long='1/2/1',
+            group_address_short='1/2/2')
+        cover.travelcalculator.set_position(50)
+        self.assertFalse(cover.is_traveling())
+        telegram = Telegram(GroupAddress('1/2/1'), payload=DPTBinary(0))
+        self.loop.run_until_complete(asyncio.Task(cover.process(telegram)))
+        self.assertTrue(cover.is_opening())
+
+    def test_process_down(self):
+        """Test process / reading telegrams from telegram queue. Test if up/down is processed correctly."""
+        xknx = XKNX(loop=self.loop)
+        cover = Cover(
+            xknx,
+            'TestCover',
+            group_address_long='1/2/1',
+            group_address_short='1/2/2')
+        cover.travelcalculator.set_position(50)
+        self.assertFalse(cover.is_traveling())
+        telegram = Telegram(GroupAddress('1/2/1'), payload=DPTBinary(1))
+        self.loop.run_until_complete(asyncio.Task(cover.process(telegram)))
+        self.assertTrue(cover.is_closing())
+
+    def test_process_stop(self):
+        """Test process / reading telegrams from telegram queue. Test if stop is processed correctly."""
+        xknx = XKNX(loop=self.loop)
+        cover = Cover(
+            xknx,
+            'TestCover',
+            group_address_long='1/2/1',
+            group_address_stop='1/2/2',)
+        cover.travelcalculator.set_position(50)
+        self.loop.run_until_complete(asyncio.Task(cover.set_down()))
+        self.assertTrue(cover.is_traveling())
+        telegram = Telegram(GroupAddress('1/2/2'), payload=DPTBinary(1))
+        self.loop.run_until_complete(asyncio.Task(cover.process(telegram)))
+        self.assertFalse(cover.is_traveling())
+
+    def test_process_short_stop(self):
+        """Test process / reading telegrams from telegram queue. Test if stop is processed correctly."""
+        xknx = XKNX(loop=self.loop)
+        cover = Cover(
+            xknx,
+            'TestCover',
+            group_address_long='1/2/1',
+            group_address_short='1/2/2',)
+        cover.travelcalculator.set_position(50)
+        self.loop.run_until_complete(asyncio.Task(cover.set_down()))
+        self.assertTrue(cover.is_traveling())
+        telegram = Telegram(GroupAddress('1/2/2'), payload=DPTBinary(1))
+        self.loop.run_until_complete(asyncio.Task(cover.process(telegram)))
+        self.assertFalse(cover.is_traveling())
 
     def test_process_callback(self):
         """Test process / reading telegrams from telegram queue. Test if callback is executed."""
@@ -354,8 +526,11 @@ class TestCover(unittest.TestCase):
             'TestCover',
             group_address_long='1/2/1',
             group_address_short='1/2/2',
-            group_address_position='1/2/3',
-            group_address_position_state='1/2/4')
+            group_address_stop='1/2/3',
+            group_address_position='1/2/4',
+            group_address_position_state='1/2/5',
+            group_address_angle='1/2/6',
+            group_address_angle_state='1/2/7')
 
         after_update_callback = Mock()
 
@@ -363,10 +538,24 @@ class TestCover(unittest.TestCase):
             """Async callback."""
             after_update_callback(device)
         cover.register_device_updated_cb(async_after_update_callback)
-
-        telegram = Telegram(GroupAddress('1/2/4'), payload=DPTArray(42))
+        for address, payload, feature in [
+                ('1/2/1', DPTBinary(1), "long"),
+                ('1/2/2', DPTBinary(1), "short"),
+                ('1/2/4', DPTArray(42), "position"),
+                ('1/2/5', DPTArray(42), "position state"),
+                ('1/2/6', DPTArray(42), "angle"),
+                ('1/2/7', DPTArray(51), "angle state")]:
+            with self.subTest(address=address, feature=feature):
+                telegram = Telegram(GroupAddress(address), payload=payload)
+                self.loop.run_until_complete(asyncio.Task(cover.process(telegram)))
+                after_update_callback.assert_called_with(cover)
+                after_update_callback.reset_mock()
+        # Stop only when cover is travelling
+        telegram = Telegram(GroupAddress('1/2/3'), payload=DPTBinary(1))
+        self.loop.run_until_complete(asyncio.Task(cover.process(telegram)))
+        after_update_callback.assert_not_called()
+        self.loop.run_until_complete(asyncio.Task(cover.set_down()))
         self.loop.run_until_complete(asyncio.Task(cover.process(telegram)))
-
         after_update_callback.assert_called_with(cover)
 
     #
@@ -379,6 +568,7 @@ class TestCover(unittest.TestCase):
             xknx,
             'TestCover',
             group_address_long='1/2/1',
+            group_address_stop='1/2/2',
             group_address_position='1/2/3',
             group_address_position_state='1/2/4',
             travel_time_down=10,
@@ -386,24 +576,58 @@ class TestCover(unittest.TestCase):
         with patch('time.time') as mock_time:
             mock_time.return_value = 1517000000.0
             self.assertFalse(cover.is_traveling())
+            self.assertFalse(cover.is_opening())
+            self.assertFalse(cover.is_closing())
             self.assertTrue(cover.position_reached())
+            # we start with state open covers (up)
+            cover.travelcalculator.set_position(0)
+            self.loop.run_until_complete(asyncio.Task(cover.set_down()))
+            self.assertTrue(cover.is_traveling())
+            self.assertTrue(cover.is_open())
+            self.assertFalse(cover.is_closed())
+            self.assertFalse(cover.is_opening())
+            self.assertTrue(cover.is_closing())
 
+            mock_time.return_value = 1517000005.0  # 5 Seconds, half way
+            self.assertFalse(cover.position_reached())
+            self.assertTrue(cover.is_traveling())
+            self.assertFalse(cover.is_open())
+            self.assertFalse(cover.is_closed())
+            self.assertFalse(cover.is_opening())
+            self.assertTrue(cover.is_closing())
+
+            mock_time.return_value = 1517000010.0  # 10 Seconds, fully closed
+            self.assertTrue(cover.position_reached())
+            self.assertFalse(cover.is_traveling())
+            self.assertFalse(cover.is_open())
+            self.assertTrue(cover.is_closed())
+            self.assertFalse(cover.is_opening())
+            self.assertFalse(cover.is_closing())
+            # up again
             self.loop.run_until_complete(asyncio.Task(cover.set_up()))
+            self.assertFalse(cover.position_reached())
             self.assertTrue(cover.is_traveling())
             self.assertFalse(cover.is_open())
             self.assertTrue(cover.is_closed())
+            self.assertTrue(cover.is_opening())
+            self.assertFalse(cover.is_closing())
 
-            mock_time.return_value = 1517000005.0  # 5 Seconds, half way
+            mock_time.return_value = 1517000015.0  # 15 Seconds, half way
             self.assertFalse(cover.position_reached())
             self.assertTrue(cover.is_traveling())
             self.assertFalse(cover.is_open())
             self.assertFalse(cover.is_closed())
+            self.assertTrue(cover.is_opening())
+            self.assertFalse(cover.is_closing())
 
-            mock_time.return_value = 1517000010.0  # 10 Seconds, fully open
+            mock_time.return_value = 1517000016.0  # 16 Seconds, manual stop
+            self.loop.run_until_complete(asyncio.Task(cover.stop()))
             self.assertTrue(cover.position_reached())
             self.assertFalse(cover.is_traveling())
-            self.assertTrue(cover.is_open())
+            self.assertFalse(cover.is_open())
             self.assertFalse(cover.is_closed())
+            self.assertFalse(cover.is_opening())
+            self.assertFalse(cover.is_closing())
 
     #
     # TEST AUTO STOP
@@ -415,6 +639,7 @@ class TestCover(unittest.TestCase):
             xknx,
             'TestCover',
             group_address_long='1/2/1',
+            group_address_stop='1/2/2',
             travel_time_down=10,
             travel_time_up=10)
         with patch('xknx.devices.Cover.stop') as mock_stop, patch('time.time') as mock_time:
@@ -423,6 +648,9 @@ class TestCover(unittest.TestCase):
             mock_stop.return_value = fut
 
             mock_time.return_value = 1517000000.0
+            # we start with state 0 - open covers (up) this is assumed immediately
+            self.loop.run_until_complete(asyncio.Task(cover.set_position(0)))
+
             self.loop.run_until_complete(asyncio.Task(cover.set_position(50)))
 
             mock_time.return_value = 1517000001.0
diff --git a/test/devices_tests/travelcalculator_test.py b/test/devices_tests/travelcalculator_test.py
index b53aacdd..b943c07e 100644
--- a/test/devices_tests/travelcalculator_test.py
+++ b/test/devices_tests/travelcalculator_test.py
@@ -1,6 +1,6 @@
 """Unit test for TravelCalculator objects."""
-import time
 import unittest
+from unittest.mock import patch
 
 from xknx.devices import TravelCalculator, TravelStatus
 
@@ -15,18 +15,15 @@ class TestTravelCalculator(unittest.TestCase):
     #
     # INIT
     #
-    def test_time_default(self):
-        """Test default time settings (no time set from outside)."""
+    def test_init(self):
+        """Test initial status."""
         travelcalculator = TravelCalculator(25, 50)
-        self.assertLess(
-            abs(time.time()-travelcalculator.current_time()),
-            0.001)
-
-    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)
+        self.assertFalse(travelcalculator.is_closed())
+        self.assertFalse(travelcalculator.is_closing())
+        self.assertFalse(travelcalculator.is_opening())
+        self.assertFalse(travelcalculator.is_traveling())
+        self.assertTrue(travelcalculator.position_reached())
+        self.assertIsNone(travelcalculator.current_position())
 
     def test_set_position(self):
         """Test set position."""
@@ -46,187 +43,226 @@ class TestTravelCalculator(unittest.TestCase):
     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())
+        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
+            self.assertEqual(travelcalculator.current_position(), 40)
+            self.assertFalse(travelcalculator.position_reached())
+            self.assertEqual(
+                travelcalculator.travel_direction,
+                TravelStatus.DIRECTION_DOWN)
+
+            mock_time.return_value = 1580000001.0
+            self.assertEqual(travelcalculator.current_position(), 44)
+            self.assertFalse(travelcalculator.position_reached())
+
+            mock_time.return_value = 1580000002.0
+            self.assertEqual(travelcalculator.current_position(), 48)
+            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 = 1580000005.0
+            # position reached
+            self.assertEqual(travelcalculator.current_position(), 60)
+            self.assertTrue(travelcalculator.position_reached())
+
+            mock_time.return_value = 1580000010.0
+            self.assertEqual(travelcalculator.current_position(), 60)
+            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())
+        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())
 
     def test_stop(self):
         """Test stopping."""
         travelcalculator = TravelCalculator(25, 50)
-        travelcalculator.set_position(60)
+        with patch('time.time') as mock_time:
+            mock_time.return_value = 1580000000.0
+            travelcalculator.set_position(80)
+            travelcalculator.start_travel(60)
 
-        travelcalculator.time_set_from_outside = 1000
-        travelcalculator.start_travel(80)
-        self.assertEqual(
-            travelcalculator.travel_direction,
-            TravelStatus.DIRECTION_UP)
+            self.assertEqual(
+                travelcalculator.travel_direction,
+                TravelStatus.DIRECTION_UP)
 
-        # stop aftert two seconds
-        travelcalculator.time_set_from_outside = 1000 + 2
-        travelcalculator.stop()
+            # stop aftert two seconds
+            mock_time.return_value = 1580000002.0
+            travelcalculator.stop()
 
-        travelcalculator.time_set_from_outside = 1000 + 4
-        self.assertEqual(travelcalculator.current_position(), 64)
-        self.assertTrue(travelcalculator.position_reached())
+            mock_time.return_value = 1580000004.0
+            self.assertEqual(travelcalculator.current_position(), 76)
+            self.assertTrue(travelcalculator.position_reached())
 
-        # restart after 1 additional second (3 seconds)
-        travelcalculator.time_set_from_outside = 1000 + 5
-        travelcalculator.start_travel(68)
+            # restart after 1 additional second (3 seconds)
+            mock_time.return_value = 1580000005.0
+            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())
+            # running up for 6 seconds
+            mock_time.return_value = 1580000006.0
+            self.assertEqual(travelcalculator.current_position(), 74)
+            self.assertFalse(travelcalculator.position_reached())
 
-        travelcalculator.time_set_from_outside = 1000 + 7
-        self.assertEqual(travelcalculator.current_position(), 68)
-        self.assertTrue(travelcalculator.position_reached())
+            mock_time.return_value = 1580000009.0
+            self.assertEqual(travelcalculator.current_position(), 68)
+            self.assertTrue(travelcalculator.position_reached())
 
     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())
+        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_travel_full_up(self):
         """Test travelling to the full up position."""
         travelcalculator = TravelCalculator(25, 50)
-        travelcalculator.set_position(70)
+        with patch('time.time') as mock_time:
+            mock_time.return_value = 1580000000.0
+            travelcalculator.set_position(30)
+            travelcalculator.start_travel_up()
 
-        travelcalculator.time_set_from_outside = 1000
-        travelcalculator.start_travel_up()
-
-        travelcalculator.time_set_from_outside = 1014
-        self.assertFalse(travelcalculator.position_reached())
-        self.assertFalse(travelcalculator.is_closed())
-        self.assertFalse(travelcalculator.is_open())
+            mock_time.return_value = 1580000014.0
+            self.assertFalse(travelcalculator.position_reached())
+            self.assertFalse(travelcalculator.is_closed())
+            self.assertFalse(travelcalculator.is_open())
 
-        travelcalculator.time_set_from_outside = 1015
-        self.assertTrue(travelcalculator.position_reached())
-        self.assertTrue(travelcalculator.is_open())
-        self.assertFalse(travelcalculator.is_closed())
+            mock_time.return_value = 1580000015.0
+            self.assertTrue(travelcalculator.position_reached())
+            self.assertTrue(travelcalculator.is_open())
+            self.assertFalse(travelcalculator.is_closed())
 
     def test_travel_full_down(self):
         """Test travelling to the full down position."""
         travelcalculator = TravelCalculator(25, 50)
-        travelcalculator.set_position(80)
+        with patch('time.time') as mock_time:
+            mock_time.return_value = 1580000000.0
+            travelcalculator.set_position(20)
+            travelcalculator.start_travel_down()
 
-        travelcalculator.time_set_from_outside = 1000
-        travelcalculator.start_travel_down()
+            mock_time.return_value = 1580000019.0
+            self.assertFalse(travelcalculator.position_reached())
+            self.assertFalse(travelcalculator.is_closed())
+            self.assertFalse(travelcalculator.is_open())
 
-        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())
+            mock_time.return_value = 1580000020.0
+            self.assertTrue(travelcalculator.position_reached())
+            self.assertTrue(travelcalculator.is_closed())
+            self.assertFalse(travelcalculator.is_open())
 
     def test_is_traveling(self):
-        """Test if cover is traveling."""
+        """Test if cover is traveling and position reached."""
         travelcalculator = TravelCalculator(25, 50)
-        self.assertFalse(travelcalculator.is_traveling())
+        with patch('time.time') as mock_time:
+            mock_time.return_value = 1580000000.0
+            self.assertFalse(travelcalculator.is_traveling())
+            self.assertTrue(travelcalculator.position_reached())
 
-        travelcalculator.set_position(20)
-        self.assertFalse(travelcalculator.is_traveling())
+            travelcalculator.set_position(80)
+            self.assertFalse(travelcalculator.is_traveling())
+            self.assertTrue(travelcalculator.position_reached())
 
-        travelcalculator.time_set_from_outside = 1000
-        travelcalculator.start_travel_down()
+            mock_time.return_value = 1580000000.0
+            travelcalculator.start_travel_down()
 
-        travelcalculator.time_set_from_outside = 1004
-        self.assertTrue(travelcalculator.is_traveling())
+            mock_time.return_value = 1580000004.0
+            self.assertTrue(travelcalculator.is_traveling())
+            self.assertFalse(travelcalculator.position_reached())
 
-        travelcalculator.time_set_from_outside = 1005
-        self.assertFalse(travelcalculator.is_traveling())
+            mock_time.return_value = 1580000005.0
+            self.assertFalse(travelcalculator.is_traveling())
+            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())
diff --git a/test/str_test.py b/test/str_test.py
index 87adb405..b5c09ea4 100644
--- a/test/str_test.py
+++ b/test/str_test.py
@@ -116,8 +116,9 @@ class TestStringRepresentations(unittest.TestCase):
         cover = Cover(
             xknx,
             name='Rolladen',
-            group_address_long='1/2/3',
-            group_address_short='1/2/4',
+            group_address_long='1/2/2',
+            group_address_short='1/2/3',
+            group_address_stop='1/2/4',
             group_address_position='1/2/5',
             group_address_position_state='1/2/6',
             group_address_angle='1/2/7',
@@ -126,9 +127,10 @@ class TestStringRepresentations(unittest.TestCase):
             travel_time_up=10)
         self.assertEqual(
             str(cover),
-            '<Cover name="Rolladen" updown="GroupAddress("1/2/3")/None/None/None" step="GroupAddress("1/2/4")/None/None/None" position="Group'
-            'Address("1/2/5")/GroupAddress("1/2/6")/None/None" angle="GroupAddress("1/2/7")/GroupAddress("1/2/8")/None/None" travel_time_down="8'
-            '" travel_time_up="10" />')
+            '<Cover name="Rolladen" updown="GroupAddress("1/2/2")/None/None/None" step="GroupAddress("1/2/3")/None/None/None" '
+            'stop="GroupAddress("1/2/4")/None/None/None" position="GroupAddress("1/2/5")/GroupAddress("1/2/6")/None/None" '
+            'angle="GroupAddress("1/2/7")/GroupAddress("1/2/8")/None/None" '
+            'travel_time_down="8" travel_time_up="10" />')
 
     def test_fan(self):
         """Test string representation of fan object."""