Repository: XKNX/xknx

Issue:
is_open / is_closed not correctly reported if position is inverted
**Description of problem:**
The cover device directs the is_open and is_closed methods to the TravelCalculator. The problem is, that the TravelCalculator doesn't know about inverted position. So, is_open returns false if the position is 0. But with inverted position 0 is open.

I'm not sure where to fix it... should we change the TravelCalculator to correctly report open/closed? Or should we change the cover device which could check itself if current_position == open_position (which is correctly stored in the device).

Please give an advice and I could submit a PR

- [x] using xknx standalone
- [x] using Home-Assistant knx plugin

**Version information:**
- xknx / Home-Assistant release with the issue: 0.104.2
- last working xknx / Home-Assistant release (if known):

**KNX installation:**
Using a cover actuator from Jung.

**Problem-relevant `xknx.yaml` or `configuration.yaml` entries (fill out even if it seems unimportant):**
- platform: knx
  name: bath
  move_long_address: '8/2/1'
  move_short_address: '8/1/1'
  position_address: '8/3/1'
  position_state_address: '8/4/1'
  invert_position: true

**Traceback (if applicable):**


Discussion:

Patch:
diff --git a/home-assistant-plugin/custom_components/xknx/cover.py b/home-assistant-plugin/custom_components/xknx/cover.py
index 4c678b43..5177378f 100644
--- a/home-assistant-plugin/custom_components/xknx/cover.py
+++ b/home-assistant-plugin/custom_components/xknx/cover.py
@@ -143,14 +143,30 @@ class KNXCover(CoverDevice):
 
     @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():
@@ -166,8 +182,8 @@ class KNXCover(CoverDevice):
     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)
+            knx_position = 100 - kwargs[ATTR_POSITION]
+            await self.device.set_position(knx_position)
             self.start_auto_updater()
 
     async def async_stop_cover(self, **kwargs):
@@ -177,16 +193,22 @@ class KNXCover(CoverDevice):
 
     @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 HASS while cover is moving."""
diff --git a/xknx/devices/cover.py b/xknx/devices/cover.py
index 5961f615..1d34b145 100644
--- a/xknx/devices/cover.py
+++ b/xknx/devices/cover.py
@@ -47,18 +47,16 @@ class Cover(Device):
             xknx,
             group_address_long,
             device_name=self.name,
-            after_update_cb=self.after_update,
-            invert=invert_position)
+            after_update_cb=self.after_update)
 
         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
+        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,
@@ -68,8 +66,8 @@ class Cover(Device):
             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,
@@ -179,9 +177,9 @@ class Cover(Device):
         if not self.position.group_address:
             current_position = self.current_position()
             if position < current_position:
-                await self.updown.down()
-            elif position > current_position:
                 await self.updown.up()
+            elif position > current_position:
+                await self.updown.down()
             self.travelcalculator.start_travel(position)
             return
 
@@ -267,6 +265,14 @@ 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_position(self):
         """Return if cover supports direct positioning."""
diff --git a/xknx/devices/travelcalculator.py b/xknx/devices/travelcalculator.py
index 3055618b..16ef08af 100644
--- a/xknx/devices/travelcalculator.py
+++ b/xknx/devices/travelcalculator.py
@@ -46,11 +46,9 @@ class TravelCalculator:
         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."""
@@ -68,14 +66,14 @@ class TravelCalculator:
     def start_travel(self, travel_to_position):
         """Start traveling to position."""
         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."""
@@ -95,6 +93,14 @@ class TravelCalculator:
         """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 +119,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 +131,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 +142,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 bd3f76a3..5ab7fe34 100644
--- a/test/devices_tests/cover_test.py
+++ b/test/devices_tests/cover_test.py
@@ -154,6 +154,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 +193,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 +213,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)))
 
@@ -264,8 +267,9 @@ 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)))
 
     def test_position_without_position_address_down(self):
         """Test moving cover down - with no absolute positioning supported."""
@@ -281,7 +285,7 @@ class TestCover(unittest.TestCase):
         self.assertEqual(xknx.telegrams.qsize(), 1)
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(telegram,
-                         Telegram(GroupAddress('1/2/1'), payload=DPTBinary(0)))
+                         Telegram(GroupAddress('1/2/1'), payload=DPTBinary(1)))
 
     def test_angle(self):
         """Test changing angle."""
@@ -327,7 +331,7 @@ class TestCover(unittest.TestCase):
             group_address_short='1/2/2',
             group_address_position='1/2/3',
             group_address_position_state='1/2/4')
-        telegram = Telegram(GroupAddress('1/2/4'), payload=DPTArray(42))
+        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)
 
@@ -343,7 +347,7 @@ 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_callback(self):
         """Test process / reading telegrams from telegram queue. Test if callback is executed."""
@@ -386,24 +390,57 @@ 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 open covers (up)
+            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
@@ -510,7 +547,7 @@ class TestCover(unittest.TestCase):
 
         with patch('time.time') as mock_time:
             mock_time.return_value = 1517000000.0
-            self.loop.run_until_complete(asyncio.Task(cover.set_up()))
+            self.loop.run_until_complete(asyncio.Task(cover.set_down()))
             mock_time.return_value = 1517000001.0
             self.assertEqual(cover.state_addresses(), [])
 
diff --git a/test/devices_tests/travelcalculator_test.py b/test/devices_tests/travelcalculator_test.py
index b53aacdd..7e8a6b1b 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,19 +15,6 @@ class TestTravelCalculator(unittest.TestCase):
     #
     # INIT
     #
-    def test_time_default(self):
-        """Test default time settings (no time set from outside)."""
-        travelcalculator = TravelCalculator(25, 50)
-        self.assertLess(
-            abs(time.time()-travelcalculator.current_time()),
-            0.001)
-
-    def test_time_set_from_outside(self):
-        """Test setting the current time from outside."""
-        travelcalculator = TravelCalculator(25, 50)
-        travelcalculator.time_set_from_outside = 1000
-        self.assertEqual(travelcalculator.current_time(), 1000)
-
     def test_set_position(self):
         """Test set position."""
         travelcalculator = TravelCalculator(25, 50)
@@ -46,187 +33,222 @@ 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()
+            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 = 1014
-        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."""
         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())
 
-        travelcalculator.set_position(20)
-        self.assertFalse(travelcalculator.is_traveling())
+            travelcalculator.set_position(80)
+            self.assertFalse(travelcalculator.is_traveling())
 
-        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())
 
-        travelcalculator.time_set_from_outside = 1005
-        self.assertFalse(travelcalculator.is_traveling())
+            mock_time.return_value = 1580000005.0
+            self.assertFalse(travelcalculator.is_traveling())
+
+    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())