Repository: XKNX/xknx
Pull request #445 (2020-10-02 22:52:40)

disable counter when context_timeout is 0
- enable use of ignore_internal_state without counter function
- don't run callbacks twice (2nd from finishing context_timeout immediately)

> cyberjunky on HA discord:
> I have windows sensors sending their stats every 10 minutes, the state doesn't change but the attribute counter: goes from 1 to 0 in 2 packets, so it triggers automation... and i have to code around it, ending up double automations (for on and off) it should be optional...

coveralls: 
[![Coverage Status](https://coveralls.io/builds/33900974/badge)](https://coveralls.io/builds/33900974)

Coverage remained the same at 93.441% when pulling **223c7f0407d8e4b8e71652ca88379a595eabf626 on farmio:binary-sensor-disable-counter** into **53050ba06f2fe185d8683284e83891790d2e0e43 on XKNX:master**.

farmio: I would even say 0 should be default for context_timeout. For most BinarySensors double-tap function is not needed. So we should avoid extra lag it adds and possible confusions from double callbacks. 

Patch:
diff --git a/docs/binary_sensor.md b/docs/binary_sensor.md
index 57f0e6bf..9c9041d3 100644
--- a/docs/binary_sensor.md
+++ b/docs/binary_sensor.md
@@ -23,7 +23,9 @@ binarysensor = BinarySensor(xknx, 'TestInput', group_address_state='1/2/3', devi
 - `name` is the name of the object.
 - `group_address_state` is the KNX group address of the sensor device.
 - `sync_state` defines if the value should be actively read from the bus. If `False` no GroupValueRead telegrams will be sent to its group address. Defaults to `True`
-- `reset_after` may be used to reset the internal state to `OFF` again after given time in ms. Defaults to `None`
+- `ignore_internal_state` allows callback call regardless of the current binary sensor state. Defaults to `True`
+- `context_timeout` time in seconds telegrams should be counted towards the current context to increment the counter. To be used with `ignore_internal_state=True`. Defaults to `None`
+- `reset_after` may be used to reset the internal state to `OFF` again after given time in sec. Defaults to `None`
 - `device_class` may be used to store the type of sensor, e.g. "motion" for motion detectors.
 
 ## [](#header-2)Example
diff --git a/docs/migration_ha_0116.md b/docs/migration_ha_0116.md
index 53a49f8a..6f545c2e 100644
--- a/docs/migration_ha_0116.md
+++ b/docs/migration_ha_0116.md
@@ -16,7 +16,7 @@ We've changed the default behavior of the `ignore_internal_state` attribute. It
 If you encounter issues with your current automations please set it to `False` again. We've analysed the current state
 and it appears that most of the people using it have it set to `True`.
 
-The binary sensor now has an additional `context_timeout` attribute which can be used in order to define the time period
+The binary sensor now has an additional `context_timeout` attribute which allows to define a time period
 in which your clicks should be counted towards the current context (i.e. incrementing the counter variable that you can
 use in your automations).
 
@@ -54,6 +54,7 @@ Your new binary sensor will now look like:
 
 - name: cover_abstell
   state_address: "2/0/33"
+  context_timeout: 1.0
 
 ```
 
@@ -96,4 +97,4 @@ automation:
 
 If you intend to use the `counter` feature (counter > 1) make sure you also enable `ignore_internal_state`
 for your binary_sensor and set the `context_timeout` attribute to the time in between you want it to react to your
-sensor clicks (defaults to 1 - which should be fine). Otherwise the counter will _not_ work correctly.
\ No newline at end of file
+sensor clicks (defaults to None - which disables this feature). Otherwise the counter will _not_ work correctly.
\ No newline at end of file
diff --git a/home-assistant-plugin/custom_components/xknx/factory.py b/home-assistant-plugin/custom_components/xknx/factory.py
index 080e4864..157565c4 100644
--- a/home-assistant-plugin/custom_components/xknx/factory.py
+++ b/home-assistant-plugin/custom_components/xknx/factory.py
@@ -246,7 +246,7 @@ def _create_binary_sensor(knx_module: XKNX, config: ConfigType) -> XknxBinarySen
         sync_state=config[BinarySensorSchema.CONF_SYNC_STATE],
         device_class=config.get(CONF_DEVICE_CLASS),
         ignore_internal_state=config[BinarySensorSchema.CONF_IGNORE_INTERNAL_STATE],
-        context_timeout=config[BinarySensorSchema.CONF_CONTEXT_TIMEOUT],
+        context_timeout=config.get(BinarySensorSchema.CONF_CONTEXT_TIMEOUT),
         reset_after=config.get(BinarySensorSchema.CONF_RESET_AFTER),
     )
 
diff --git a/home-assistant-plugin/custom_components/xknx/schema.py b/home-assistant-plugin/custom_components/xknx/schema.py
index b33d57ff..a224d9cb 100644
--- a/home-assistant-plugin/custom_components/xknx/schema.py
+++ b/home-assistant-plugin/custom_components/xknx/schema.py
@@ -102,10 +102,10 @@ class BinarySensorSchema:
                     cv.string,
                 ),
                 vol.Optional(CONF_IGNORE_INTERNAL_STATE, default=True): cv.boolean,
-                vol.Optional(CONF_CONTEXT_TIMEOUT, default=1.0): vol.All(
+                vol.Required(CONF_STATE_ADDRESS): cv.string,
+                vol.Optional(CONF_CONTEXT_TIMEOUT): vol.All(
                     vol.Coerce(float), vol.Range(min=0, max=10)
                 ),
-                vol.Required(CONF_STATE_ADDRESS): cv.string,
                 vol.Optional(CONF_DEVICE_CLASS): cv.string,
                 vol.Optional(CONF_RESET_AFTER): cv.positive_int,
             }
diff --git a/xknx/devices/binary_sensor.py b/xknx/devices/binary_sensor.py
index d6f5175c..f1909de0 100644
--- a/xknx/devices/binary_sensor.py
+++ b/xknx/devices/binary_sensor.py
@@ -10,6 +10,7 @@ A BinarySensor may also have Actions attached which are executed after state was
 """
 import asyncio
 import time
+from typing import List, Optional
 
 from xknx.remote_value import RemoteValueSwitch
 
@@ -21,20 +22,17 @@ class BinarySensor(Device):
     """Class for binary sensor."""
 
     # pylint: disable=too-many-instance-attributes
-
-    DEFAULT_CONTEXT_TIMEOUT = 1
-
     def __init__(
         self,
         xknx,
         name,
         group_address_state=None,
-        sync_state=True,
-        ignore_internal_state=False,
-        device_class=None,
-        reset_after=None,
-        actions=None,
-        context_timeout=DEFAULT_CONTEXT_TIMEOUT,
+        sync_state: bool = True,
+        ignore_internal_state: bool = True,
+        device_class: str = None,
+        reset_after: Optional[float] = None,
+        actions: List[Action] = None,
+        context_timeout: Optional[float] = None,
         device_updated_cb=None,
     ):
         """Initialize BinarySensor class."""
@@ -81,10 +79,11 @@ class BinarySensor(Device):
     def from_config(cls, xknx, name, config):
         """Initialize object from configuration structure."""
         group_address_state = config.get("group_address_state")
-        context_timeout = config.get("context_timeout", 1)
+        context_timeout = config.get("context_timeout")
+        reset_after = config.get("reset_after")
         sync_state = config.get("sync_state", True)
         device_class = config.get("device_class")
-        ignore_internal_state = config.get("ignore_internal_state", False)
+        ignore_internal_state = config.get("ignore_internal_state", True)
         actions = []
         if "actions" in config:
             for action in config["actions"]:
@@ -98,6 +97,7 @@ class BinarySensor(Device):
             sync_state=sync_state,
             ignore_internal_state=ignore_internal_state,
             context_timeout=context_timeout,
+            reset_after=reset_after,
             device_class=device_class,
             actions=actions,
         )
@@ -106,13 +106,13 @@ class BinarySensor(Device):
         """Update the internal state from RemoteValue (Callback)."""
         await self._set_internal_state(self.remote_value.value)
 
-    async def _set_internal_state(self, state):
+    async def _set_internal_state(self, state: bool):
         """Set the internal state of the device. If state was changed after_update hooks and connected Actions are executed."""
         if state != self.state or self.ignore_internal_state:
             self.state = state
             self.bump_and_get_counter(state)
 
-            if self.ignore_internal_state:
+            if self.ignore_internal_state and self._context_timeout:
                 if self._context_task:
                     self._context_task.cancel()
                 self._context_task = asyncio.create_task(
@@ -144,10 +144,10 @@ class BinarySensor(Device):
         """Return current counter for sensor."""
         return self._count_set_on if self.state else self._count_set_off
 
-    def bump_and_get_counter(self, state):
+    def bump_and_get_counter(self, state: bool) -> int:
         """Bump counter and return the number of times a state was set to the same value within CONTEXT_TIMEOUT."""
 
-        def within_same_context():
+        def within_same_context() -> bool:
             """Check if state change was within same context (e.g. 'Button was pressed twice')."""
             if self._last_set is None:
                 self._last_set = time.time()
@@ -157,7 +157,7 @@ class BinarySensor(Device):
             self._last_set = new_set_time
             return time_diff < self._context_timeout
 
-        if within_same_context():
+        if self._context_timeout and within_same_context():
             if state:
                 self._count_set_on = self._count_set_on + 1
                 return self._count_set_on
@@ -180,18 +180,18 @@ class BinarySensor(Device):
                 if self._reset_task:
                     self._reset_task.cancel()
                 self._reset_task = asyncio.create_task(
-                    self._reset_state(self.reset_after / 1000)
+                    self._reset_state(self.reset_after)
                 )
 
-    async def _reset_state(self, wait_seconds):
+    async def _reset_state(self, wait_seconds: float):
         await asyncio.sleep(wait_seconds)
         await self._set_internal_state(False)
 
-    def is_on(self):
+    def is_on(self) -> bool:
         """Return if binary sensor is 'on'."""
         return bool(self.state)
 
-    def is_off(self):
+    def is_off(self) -> bool:
         """Return if binary sensor is 'off'."""
         return not self.state
 
diff --git a/xknx/remote_value/remote_value.py b/xknx/remote_value/remote_value.py
index f63cc03e..52e299fd 100644
--- a/xknx/remote_value/remote_value.py
+++ b/xknx/remote_value/remote_value.py
@@ -7,7 +7,6 @@ Remote value can be :
 - or a group of both representing the same value.
 """
 import logging
-from typing import List
 
 from xknx.exceptions import CouldNotParseTelegram
 from xknx.telegram import GroupAddress, Telegram, TelegramType
@@ -28,14 +27,10 @@ class RemoteValue:
         device_name=None,
         feature_name=None,
         after_update_cb=None,
-        passive_group_addresses: List[str] = None,
     ):
         """Initialize RemoteValue class."""
         # pylint: disable=too-many-arguments
         self.xknx = xknx
-        self.passive_group_addresses = RemoteValue.get_passive_group_addresses(
-            passive_group_addresses
-        )
         if group_address is not None:
             group_address = GroupAddress(group_address)
         if group_address_state is not None:
@@ -63,11 +58,7 @@ class RemoteValue:
     @property
     def initialized(self):
         """Evaluate if remote value is initialized with group address."""
-        return bool(
-            self.group_address_state
-            or self.group_address
-            or self.passive_group_addresses
-        )
+        return bool(self.group_address_state or self.group_address)
 
     @property
     def readable(self):
@@ -81,14 +72,7 @@ class RemoteValue:
 
     def has_group_address(self, group_address):
         """Test if device has given group address."""
-
-        def _internal_addresses():
-            """Yield all group_addresses."""
-            yield self.group_address
-            yield self.group_address_state
-            yield from self.passive_group_addresses
-
-        return group_address in _internal_addresses()
+        return group_address in [self.group_address, self.group_address_state]
 
     def payload_valid(self, payload):
         """Test if telegram payload may be parsed - to be implemented in derived class.."""
@@ -235,10 +219,3 @@ class RemoteValue:
             if key not in self.__dict__:
                 return False
         return True
-
-    @staticmethod
-    def get_passive_group_addresses(passive_group_addresses: List[str]) -> List:
-        """Obtain passive state group addresses."""
-        if passive_group_addresses is None:
-            return []
-        return [GroupAddress(ga) for ga in passive_group_addresses]
diff --git a/xknx/remote_value/remote_value_climate_mode.py b/xknx/remote_value/remote_value_climate_mode.py
index 751615b1..0d7d2cae 100644
--- a/xknx/remote_value/remote_value_climate_mode.py
+++ b/xknx/remote_value/remote_value_climate_mode.py
@@ -4,7 +4,6 @@ Module for managing an climate mode remote values.
 DPT .
 """
 from enum import Enum
-from typing import List
 
 from xknx.dpt import (
     DPTArray,
@@ -39,7 +38,6 @@ class RemoteValueClimateMode(RemoteValue):
         feature_name="Climate Mode",
         climate_mode_type=None,
         after_update_cb=None,
-        passive_group_addresses: List[str] = None,
     ):
         """Initialize remote value of KNX climate mode."""
         # pylint: disable=too-many-arguments
@@ -51,7 +49,6 @@ class RemoteValueClimateMode(RemoteValue):
             device_name=device_name,
             feature_name=feature_name,
             after_update_cb=after_update_cb,
-            passive_group_addresses=passive_group_addresses,
         )
         if not isinstance(climate_mode_type, self.ClimateModeType):
             raise ConversionError(
diff --git a/xknx/remote_value/remote_value_color_rgb.py b/xknx/remote_value/remote_value_color_rgb.py
index 9fa8f47c..796b63cc 100644
--- a/xknx/remote_value/remote_value_color_rgb.py
+++ b/xknx/remote_value/remote_value_color_rgb.py
@@ -3,8 +3,6 @@ Module for managing an RGB remote value.
 
 DPT 232.600.
 """
-from typing import List
-
 from xknx.dpt import DPTArray
 from xknx.exceptions import ConversionError
 
@@ -22,7 +20,6 @@ class RemoteValueColorRGB(RemoteValue):
         device_name=None,
         feature_name="Color RGB",
         after_update_cb=None,
-        passive_group_addresses: List[str] = None,
     ):
         """Initialize remote value of KNX DPT 232.600 (DPT_Color_RGB)."""
         # pylint: disable=too-many-arguments
@@ -33,7 +30,6 @@ class RemoteValueColorRGB(RemoteValue):
             device_name=device_name,
             feature_name=feature_name,
             after_update_cb=after_update_cb,
-            passive_group_addresses=passive_group_addresses,
         )
 
     def payload_valid(self, payload):
diff --git a/xknx/remote_value/remote_value_color_rgbw.py b/xknx/remote_value/remote_value_color_rgbw.py
index 28bd041e..7c541822 100644
--- a/xknx/remote_value/remote_value_color_rgbw.py
+++ b/xknx/remote_value/remote_value_color_rgbw.py
@@ -3,8 +3,6 @@ Module for managing an RGBW remote value.
 
 DPT 251.600.
 """
-from typing import List
-
 from xknx.dpt import DPTArray
 from xknx.exceptions import ConversionError
 
@@ -22,7 +20,6 @@ class RemoteValueColorRGBW(RemoteValue):
         device_name=None,
         feature_name="Color RGBW",
         after_update_cb=None,
-        passive_group_addresses: List[str] = None,
     ):
         """Initialize remote value of KNX DPT 251.600 (DPT_Color_RGBW)."""
         # pylint: disable=too-many-arguments
@@ -33,7 +30,6 @@ class RemoteValueColorRGBW(RemoteValue):
             device_name=device_name,
             feature_name=feature_name,
             after_update_cb=after_update_cb,
-            passive_group_addresses=passive_group_addresses,
         )
         self.previous_value = (0, 0, 0, 0)
 
diff --git a/xknx/remote_value/remote_value_datetime.py b/xknx/remote_value/remote_value_datetime.py
index 0b2ada73..2ce02672 100644
--- a/xknx/remote_value/remote_value_datetime.py
+++ b/xknx/remote_value/remote_value_datetime.py
@@ -5,7 +5,6 @@ DPT 10.001, 11.001 and 19.001
 """
 from enum import Enum
 import time
-from typing import List
 
 from xknx.dpt import DPTArray, DPTDate, DPTDateTime, DPTTime
 from xknx.exceptions import ConversionError
@@ -34,7 +33,6 @@ class RemoteValueDateTime(RemoteValue):
         device_name=None,
         feature_name="DateTime",
         after_update_cb=None,
-        passive_group_addresses: List[str] = None,
     ):
         """Initialize RemoteValueSensor class."""
         # pylint: disable=too-many-arguments
@@ -55,7 +53,6 @@ class RemoteValueDateTime(RemoteValue):
             device_name=device_name,
             feature_name=feature_name,
             after_update_cb=after_update_cb,
-            passive_group_addresses=passive_group_addresses,
         )
 
     def payload_valid(self, payload):
diff --git a/xknx/remote_value/remote_value_dpt_2_byte_unsigned.py b/xknx/remote_value/remote_value_dpt_2_byte_unsigned.py
index 7e536075..f6b79315 100644
--- a/xknx/remote_value/remote_value_dpt_2_byte_unsigned.py
+++ b/xknx/remote_value/remote_value_dpt_2_byte_unsigned.py
@@ -3,8 +3,6 @@ Module for managing a DTP 7001 remote value.
 
 DPT 7.001.
 """
-from typing import List
-
 from xknx.dpt import DPT2ByteUnsigned, DPTArray
 
 from .remote_value import RemoteValue
@@ -21,7 +19,6 @@ class RemoteValueDpt2ByteUnsigned(RemoteValue):
         device_name=None,
         feature_name="Value",
         after_update_cb=None,
-        passive_group_addresses: List[str] = None,
     ):
         """Initialize remote value of KNX DPT 7.001."""
         # pylint: disable=too-many-arguments
@@ -32,7 +29,6 @@ class RemoteValueDpt2ByteUnsigned(RemoteValue):
             device_name=device_name,
             feature_name=feature_name,
             after_update_cb=after_update_cb,
-            passive_group_addresses=passive_group_addresses,
         )
 
     def payload_valid(self, payload):
diff --git a/xknx/remote_value/remote_value_scaling.py b/xknx/remote_value/remote_value_scaling.py
index ed8b52e4..91af884c 100644
--- a/xknx/remote_value/remote_value_scaling.py
+++ b/xknx/remote_value/remote_value_scaling.py
@@ -3,8 +3,6 @@ Module for managing a Scaling remote value.
 
 DPT 5.001.
 """
-from typing import List
-
 from xknx.dpt import DPTArray
 
 from .remote_value import RemoteValue
@@ -23,7 +21,6 @@ class RemoteValueScaling(RemoteValue):
         after_update_cb=None,
         range_from=0,
         range_to=100,
-        passive_group_addresses: List[str] = None,
     ):
         """Initialize remote value of KNX DPT 5.001 (DPT_Scaling)."""
         # pylint: disable=too-many-arguments
@@ -34,7 +31,6 @@ class RemoteValueScaling(RemoteValue):
             device_name=device_name,
             feature_name=feature_name,
             after_update_cb=after_update_cb,
-            passive_group_addresses=passive_group_addresses,
         )
         self.range_from = range_from
         self.range_to = range_to
diff --git a/xknx/remote_value/remote_value_sensor.py b/xknx/remote_value/remote_value_sensor.py
index 06a75397..7cf96af6 100644
--- a/xknx/remote_value/remote_value_sensor.py
+++ b/xknx/remote_value/remote_value_sensor.py
@@ -4,8 +4,6 @@ Module for managing a remote value typically used within a sensor.
 The module maps a given value_type to a DPT class and uses this class
 for serialization and deserialization of the KNX value.
 """
-from typing import List
-
 from xknx.dpt import DPTArray, DPTBase
 from xknx.exceptions import ConversionError
 
@@ -25,7 +23,6 @@ class RemoteValueSensor(RemoteValue):
         device_name=None,
         feature_name="Value",
         after_update_cb=None,
-        passive_group_addresses: List[str] = None,
     ):
         """Initialize RemoteValueSensor class."""
         # pylint: disable=too-many-arguments
@@ -43,7 +40,6 @@ class RemoteValueSensor(RemoteValue):
             device_name=device_name,
             feature_name=feature_name,
             after_update_cb=after_update_cb,
-            passive_group_addresses=passive_group_addresses,
         )
 
     def payload_valid(self, payload):
diff --git a/xknx/remote_value/remote_value_setpoint_shift.py b/xknx/remote_value/remote_value_setpoint_shift.py
index d0cae883..d6c54be4 100644
--- a/xknx/remote_value/remote_value_setpoint_shift.py
+++ b/xknx/remote_value/remote_value_setpoint_shift.py
@@ -3,8 +3,6 @@ Module for managing setpoint shifting.
 
 DPT 6.010.
 """
-from typing import List
-
 from xknx.remote_value import RemoteValue1Count
 
 
@@ -19,7 +17,6 @@ class RemoteValueSetpointShift(RemoteValue1Count):
         device_name=None,
         after_update_cb=None,
         setpoint_shift_step=0.1,
-        passive_group_addresses: List[str] = None,
     ):
         """Initialize RemoteValueSetpointShift class."""
         # pylint: disable=too-many-arguments
@@ -30,7 +27,6 @@ class RemoteValueSetpointShift(RemoteValue1Count):
             device_name=device_name,
             feature_name="Setpoint shift value",
             after_update_cb=after_update_cb,
-            passive_group_addresses=passive_group_addresses,
         )
 
         self.setpoint_shift_step = setpoint_shift_step
diff --git a/xknx/remote_value/remote_value_step.py b/xknx/remote_value/remote_value_step.py
index e2243043..a9ac00d8 100644
--- a/xknx/remote_value/remote_value_step.py
+++ b/xknx/remote_value/remote_value_step.py
@@ -4,7 +4,6 @@ Module for managing an DPT Step remote value.
 DPT 1.007.
 """
 from enum import Enum
-from typing import List
 
 from xknx.dpt import DPTBinary
 from xknx.exceptions import ConversionError, CouldNotParseTelegram
@@ -30,7 +29,6 @@ class RemoteValueStep(RemoteValue):
         feature_name="Step",
         after_update_cb=None,
         invert=False,
-        passive_group_addresses: List[str] = None,
     ):
         """Initialize remote value of KNX DPT 1.007."""
         # pylint: disable=too-many-arguments
@@ -41,7 +39,6 @@ class RemoteValueStep(RemoteValue):
             device_name=device_name,
             feature_name=feature_name,
             after_update_cb=after_update_cb,
-            passive_group_addresses=passive_group_addresses,
         )
         self.invert = invert
 
diff --git a/xknx/remote_value/remote_value_switch.py b/xknx/remote_value/remote_value_switch.py
index 66cfac28..a583516b 100644
--- a/xknx/remote_value/remote_value_switch.py
+++ b/xknx/remote_value/remote_value_switch.py
@@ -3,8 +3,6 @@ Module for managing an DPT Switch remote value.
 
 DPT 1.001.
 """
-from typing import List
-
 from xknx.dpt import DPTBinary
 from xknx.exceptions import ConversionError, CouldNotParseTelegram
 
@@ -24,7 +22,6 @@ class RemoteValueSwitch(RemoteValue):
         feature_name="State",
         after_update_cb=None,
         invert=False,
-        passive_group_addresses: List[str] = None,
     ):
         """Initialize remote value of KNX DPT 1.001."""
         # pylint: disable=too-many-arguments
@@ -36,7 +33,6 @@ class RemoteValueSwitch(RemoteValue):
             device_name=device_name,
             feature_name=feature_name,
             after_update_cb=after_update_cb,
-            passive_group_addresses=passive_group_addresses,
         )
         self.invert = invert
 
diff --git a/xknx/remote_value/remote_value_updown.py b/xknx/remote_value/remote_value_updown.py
index 9c4502d3..3910617c 100644
--- a/xknx/remote_value/remote_value_updown.py
+++ b/xknx/remote_value/remote_value_updown.py
@@ -4,7 +4,6 @@ Module for managing an DPT Up/Down remote value.
 DPT 1.008.
 """
 from enum import Enum
-from typing import List
 
 from xknx.dpt import DPTBinary
 from xknx.exceptions import ConversionError, CouldNotParseTelegram
@@ -31,7 +30,6 @@ class RemoteValueUpDown(RemoteValue):
         feature_name="Up/Down",
         after_update_cb=None,
         invert=False,
-        passive_group_addresses: List[str] = None,
     ):
         """Initialize remote value of KNX DPT 1.008."""
         # pylint: disable=too-many-arguments
@@ -42,7 +40,6 @@ class RemoteValueUpDown(RemoteValue):
             device_name=device_name,
             feature_name=feature_name,
             after_update_cb=after_update_cb,
-            passive_group_addresses=passive_group_addresses,
         )
         self.invert = invert
 


Test patch:
diff --git a/test/devices_tests/binary_sensor_test.py b/test/devices_tests/binary_sensor_test.py
index c4f5a7d5..c7bad9c8 100644
--- a/test/devices_tests/binary_sensor_test.py
+++ b/test/devices_tests/binary_sensor_test.py
@@ -62,16 +62,16 @@ class TestBinarySensor(unittest.TestCase):
     def test_process_reset_after(self):
         """Test process / reading telegrams from telegram queue."""
         xknx = XKNX()
-        reset_after_ms = 0.01
+        reset_after_sec = 0.001
         binaryinput = BinarySensor(
-            xknx, "TestInput", "1/2/3", reset_after=reset_after_ms
+            xknx, "TestInput", "1/2/3", reset_after=reset_after_sec
         )
         telegram_on = Telegram(
             group_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
         )
 
         self.loop.run_until_complete(binaryinput.process(telegram_on))
-        self.loop.run_until_complete(asyncio.sleep(reset_after_ms * 2 / 1000))
+        self.loop.run_until_complete(asyncio.sleep(reset_after_sec * 2))
         self.assertEqual(binaryinput.state, False)
 
     def test_process_action(self):
@@ -130,11 +130,9 @@ class TestBinarySensor(unittest.TestCase):
         ) as mock_sleep:
             mock_time.return_value = 1599076123.0
             self.loop.run_until_complete(binary_sensor.process(telegram_on))
-
             self.loop.run_until_complete(
                 xknx.devices.process(xknx.telegrams.get_nowait())
             )
-
             self.assertEqual(binary_sensor.state, True)
             self.assertEqual(switch.state, True)
 
@@ -142,17 +140,13 @@ class TestBinarySensor(unittest.TestCase):
             self.loop.run_until_complete(
                 xknx.devices.process(xknx.telegrams.get_nowait())
             )
-
             self.assertEqual(switch.state, False)
             self.assertEqual(binary_sensor.state, True)
-            # add a second to the time to avoid double tap behaviour here
-            mock_time.return_value += BinarySensor.DEFAULT_CONTEXT_TIMEOUT
-            self.loop.run_until_complete(binary_sensor.process(telegram_on))
 
+            self.loop.run_until_complete(binary_sensor.process(telegram_on))
             self.loop.run_until_complete(
                 xknx.devices.process(xknx.telegrams.get_nowait())
             )
-
             self.assertEqual(binary_sensor.state, True)
             self.assertEqual(switch.state, True)
 
@@ -197,9 +191,42 @@ class TestBinarySensor(unittest.TestCase):
     #
     def test_process_callback(self):
         """Test after_update_callback after state of switch was changed."""
-        # pylint: disable=no-self-use
+        # pylint: disable=protected-access
+        xknx = XKNX()
+        switch = BinarySensor(
+            xknx, "TestInput", group_address_state="1/2/3", ignore_internal_state=False
+        )
+
+        after_update_callback = Mock()
+
+        async def async_after_update_callback(device):
+            """Async callback."""
+            after_update_callback(device)
+
+        switch.register_device_updated_cb(async_after_update_callback)
+
+        telegram = Telegram(group_address=GroupAddress("1/2/3"), payload=DPTBinary(1))
+        self.loop.run_until_complete(switch.process(telegram))
+        # no _context_task started because ignore_internal_state is False
+        self.assertIsNone(switch._context_task)
+        after_update_callback.assert_called_once_with(switch)
+
+        after_update_callback.reset_mock()
+        # send same telegram again
+        self.loop.run_until_complete(switch.process(telegram))
+        after_update_callback.assert_not_called()
+
+    def test_process_callback_ignore_internal_state(self):
+        """Test after_update_callback after state of switch was changed."""
+        # pylint: disable=protected-access
         xknx = XKNX()
-        switch = BinarySensor(xknx, "TestInput", group_address_state="1/2/3")
+        switch = BinarySensor(
+            xknx,
+            "TestInput",
+            group_address_state="1/2/3",
+            ignore_internal_state=True,
+            context_timeout=0.001,
+        )
 
         after_update_callback = Mock()
 
@@ -210,9 +237,60 @@ class TestBinarySensor(unittest.TestCase):
         switch.register_device_updated_cb(async_after_update_callback)
 
         telegram = Telegram(group_address=GroupAddress("1/2/3"), payload=DPTBinary(1))
+        self.assertEqual(switch.counter, 0)
+
         self.loop.run_until_complete(switch.process(telegram))
+        after_update_callback.assert_not_called()
+        self.assertEqual(switch.counter, 1)
+        self.loop.run_until_complete(switch._context_task)
+        after_update_callback.assert_called_with(switch)
+        # once with counter 1 and once with counter 0
+        self.assertEqual(after_update_callback.call_count, 2)
 
+        after_update_callback.reset_mock()
+        # send same telegram again
+        self.loop.run_until_complete(switch.process(telegram))
+        self.assertEqual(switch.counter, 1)
+        self.loop.run_until_complete(switch.process(telegram))
+        self.assertEqual(switch.counter, 2)
+        after_update_callback.assert_not_called()
+
+        self.loop.run_until_complete(switch._context_task)
         after_update_callback.assert_called_with(switch)
+        # once with counter 2 and once with counter 0
+        self.assertEqual(after_update_callback.call_count, 2)
+        self.assertEqual(switch.counter, 0)
+
+    def test_process_callback_ignore_internal_state_no_counter(self):
+        """Test after_update_callback after state of switch was changed."""
+        # pylint: disable=protected-access
+        xknx = XKNX()
+        switch = BinarySensor(
+            xknx,
+            "TestInput",
+            group_address_state="1/2/3",
+            ignore_internal_state=True,
+            context_timeout=0,
+        )
+
+        after_update_callback = Mock()
+
+        async def async_after_update_callback(device):
+            """Async callback."""
+            after_update_callback(device)
+
+        switch.register_device_updated_cb(async_after_update_callback)
+
+        telegram = Telegram(group_address=GroupAddress("1/2/3"), payload=DPTBinary(1))
+        self.loop.run_until_complete(switch.process(telegram))
+        # no _context_task started because context_timeout is False
+        self.assertIsNone(switch._context_task)
+        after_update_callback.assert_called_once_with(switch)
+
+        after_update_callback.reset_mock()
+        # send same telegram again
+        self.loop.run_until_complete(switch.process(telegram))
+        after_update_callback.assert_called_once_with(switch)
 
     #
     # TEST COUNTER
@@ -220,7 +298,9 @@ class TestBinarySensor(unittest.TestCase):
     def test_counter(self):
         """Test counter functionality."""
         xknx = XKNX()
-        switch = BinarySensor(xknx, "TestInput", group_address_state="1/2/3")
+        switch = BinarySensor(
+            xknx, "TestInput", group_address_state="1/2/3", context_timeout=1
+        )
         with patch("time.time") as mock_time:
             mock_time.return_value = 1517000000.0
             self.assertEqual(switch.bump_and_get_counter(True), 1)
diff --git a/test/remote_value_tests/remote_value_test.py b/test/remote_value_tests/remote_value_test.py
index cd87890a..d04153cc 100644
--- a/test/remote_value_tests/remote_value_test.py
+++ b/test/remote_value_tests/remote_value_test.py
@@ -139,27 +139,6 @@ class TestRemoteValue(unittest.TestCase):
                 "State",
             )
 
-    def test_process_listening_address(self):
-        """Test if listening group addresses are processed."""
-        xknx = XKNX()
-        remote_value = RemoteValue(
-            xknx, group_address="1/2/3", passive_group_addresses=["1/1/1"]
-        )
-        self.assertTrue(remote_value.writable)
-        self.assertFalse(remote_value.readable)
-        # RemoteValue is initialized with only passive group address
-        self.assertTrue(remote_value.initialized)
-        with patch("xknx.remote_value.RemoteValue.payload_valid") as patch_valid:
-            patch_valid.return_value = True
-            test_payload = DPTArray((0x01, 0x02))
-            telegram = Telegram(GroupAddress("1/1/1"), payload=test_payload)
-            self.assertTrue(
-                self.loop.run_until_complete(
-                    asyncio.Task(remote_value.process(telegram))
-                )
-            )
-            self.assertEqual(remote_value.payload, test_payload)
-
     def test_eq(self):
         """Test __eq__ operator."""
         xknx = XKNX()