Repository: XKNX/xknx
Pull request #1236 (2023-04-01 08:17:12)

Accept DPTArray or DPTBinary in DPTBase.from_knx() instead of raw tuple[int]
<!--
  You are awesome! Thanks for contributing to our project!
  Please, DO NOT DELETE ANY TEXT from this template!.
-->
## Description
<!--
Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context.
List any dependencies that are required for this change.
-->
- Accept `DPTArray` or `DPTBinary` in `DPTBase.from_knx()` instead of raw `tuple[int]`.
- Remove payload_valid() from RemoteValue and remove payload type form its generics parameters.

Second part or #1231

Fixes # (issue)

## Type of change
<!--
Please tick the applicable options.
NOTE: Ticking multiple options most likely indicates
that your change is to big and it is suggested to split it into several smaller PRs.
-->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [x] Code quality improvements to existing code or addition of tests

## Checklist

- [ ] The documentation has been adjusted accordingly
- [x] Tests have been added that prove the fix is effective or that the feature works
- [x] The changes are documented in the changelog (docs/changelog.md)

codecov[bot]: ## [Codecov](https://codecov.io/gh/XKNX/xknx/pull/1236?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) Report
> Merging [#1236](https://codecov.io/gh/XKNX/xknx/pull/1236?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) (dd0daec) into [main](https://codecov.io/gh/XKNX/xknx/commit/5720d9c843c657d40d2bf44bae2d62419dcef1ad?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) (5720d9c) will **decrease** coverage by `0.02%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/XKNX/xknx/pull/1236/graphs/tree.svg?width=650&height=150&src=pr&token=irWbIygS84&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX)](https://codecov.io/gh/XKNX/xknx/pull/1236?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX)

```diff
@@            Coverage Diff             @@
##             main    #1236      +/-   ##
==========================================
- Coverage   96.59%   96.58%   -0.02%     
==========================================
  Files         148      149       +1     
  Lines        9543     9479      -64     
==========================================
- Hits         9218     9155      -63     
+ Misses        325      324       -1     
```


| [Impacted Files](https://codecov.io/gh/XKNX/xknx/pull/1236?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) | Coverage Δ | |
|---|---|---|
| [xknx/devices/climate\_mode.py](https://codecov.io/gh/XKNX/xknx/pull/1236?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9kZXZpY2VzL2NsaW1hdGVfbW9kZS5weQ==) | `100.00% <ø> (ø)` | |
| [xknx/core/state\_updater.py](https://codecov.io/gh/XKNX/xknx/pull/1236?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9jb3JlL3N0YXRlX3VwZGF0ZXIucHk=) | `83.47% <100.00%> (ø)` | |
| [xknx/devices/climate.py](https://codecov.io/gh/XKNX/xknx/pull/1236?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9kZXZpY2VzL2NsaW1hdGUucHk=) | `100.00% <100.00%> (ø)` | |
| [xknx/devices/cover.py](https://codecov.io/gh/XKNX/xknx/pull/1236?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9kZXZpY2VzL2NvdmVyLnB5) | `99.46% <100.00%> (ø)` | |
| [xknx/devices/device.py](https://codecov.io/gh/XKNX/xknx/pull/1236?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9kZXZpY2VzL2RldmljZS5weQ==) | `100.00% <100.00%> (ø)` | |
| [xknx/devices/expose\_sensor.py](https://codecov.io/gh/XKNX/xknx/pull/1236?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9kZXZpY2VzL2V4cG9zZV9zZW5zb3IucHk=) | `100.00% <100.00%> (ø)` | |
| [xknx/devices/fan.py](https://codecov.io/gh/XKNX/xknx/pull/1236?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9kZXZpY2VzL2Zhbi5weQ==) | `100.00% <100.00%> (ø)` | |
| [xknx/devices/light.py](https://codecov.io/gh/XKNX/xknx/pull/1236?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9kZXZpY2VzL2xpZ2h0LnB5) | `99.60% <100.00%> (ø)` | |
| [xknx/devices/sensor.py](https://codecov.io/gh/XKNX/xknx/pull/1236?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9kZXZpY2VzL3NlbnNvci5weQ==) | `96.66% <100.00%> (ø)` | |
| [xknx/devices/weather.py](https://codecov.io/gh/XKNX/xknx/pull/1236?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9kZXZpY2VzL3dlYXRoZXIucHk=) | `100.00% <100.00%> (ø)` | |
| ... and [38 more](https://codecov.io/gh/XKNX/xknx/pull/1236?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) | |


Patch:
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index 4a5b20f8..f3254729 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -16,7 +16,7 @@ jobs:
       # - No PRs marked as no-stale
       # - No issues marked as no-stale or help-wanted
       - name: 90 days stale issues & PRs policy
-        uses: actions/stale@v7
+        uses: actions/stale@v8
         with:
           repo-token: ${{ secrets.GITHUB_TOKEN }}
           days-before-stale: 90
diff --git a/docs/changelog.md b/docs/changelog.md
index 721efcfc..116fe833 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -14,7 +14,10 @@ nav_order: 2
 
 ### Internals
 
-- Return `DPTArray` or `DPTBinary` from `DPTBase.to_knx()` instead of `tuple[int, ...]`.
+- Refactor DPTBase transcoder classes
+  - Accept `DPTArray` or `DPTBinary` in `DPTBase.from_knx()` instead of raw `tuple[int]`.
+  - Return `DPTArray` or `DPTBinary` from `DPTBase.to_knx()` instead of `tuple[int, ...]`.
+  - Remove payload_valid() from RemoteValue and remove payload type form its generics parameters.
 
 # 2.7.0 IP Device Management 2023-03-15
 
diff --git a/xknx/core/state_updater.py b/xknx/core/state_updater.py
index 67504050..15218106 100644
--- a/xknx/core/state_updater.py
+++ b/xknx/core/state_updater.py
@@ -126,7 +126,7 @@ class StateUpdater:
 
     def register_remote_value(
         self,
-        remote_value: RemoteValue[Any, Any],
+        remote_value: RemoteValue[Any],
         tracker_options: TrackerOptionType = True,
     ) -> None:
         """Register a RemoteValue to initialize its state and/or track for expiration."""
@@ -162,11 +162,11 @@ class StateUpdater:
         if self.started:
             tracker.start()
 
-    def unregister_remote_value(self, remote_value: RemoteValue[Any, Any]) -> None:
+    def unregister_remote_value(self, remote_value: RemoteValue[Any]) -> None:
         """Unregister a RemoteValue from StateUpdater."""
         self._workers.pop(id(remote_value)).stop()
 
-    def update_received(self, remote_value: RemoteValue[Any, Any]) -> None:
+    def update_received(self, remote_value: RemoteValue[Any]) -> None:
         """Reset the timer when a state update was received."""
         if self.started and id(remote_value) in self._workers:
             self._workers[id(remote_value)].update_received()
diff --git a/xknx/devices/climate.py b/xknx/devices/climate.py
index cd5708e6..2200a7d5 100644
--- a/xknx/devices/climate.py
+++ b/xknx/devices/climate.py
@@ -136,7 +136,7 @@ class Climate(Device):
 
         self.mode = mode
 
-    def _iter_remote_values(self) -> Iterator[RemoteValue[Any, Any]]:
+    def _iter_remote_values(self) -> Iterator[RemoteValue[Any]]:
         """Iterate the devices RemoteValue classes."""
         yield self.temperature
         yield self.target_temperature
diff --git a/xknx/devices/climate_mode.py b/xknx/devices/climate_mode.py
index 29871bbd..e2ed4e92 100644
--- a/xknx/devices/climate_mode.py
+++ b/xknx/devices/climate_mode.py
@@ -174,7 +174,7 @@ class ClimateMode(Device):
 
     def _iter_remote_values(
         self,
-    ) -> Iterator[RemoteValue[Any, Any]]:
+    ) -> Iterator[RemoteValue[Any]]:
         """Iterate climate mode RemoteValue classes."""
         return chain(
             self._iter_operation_remote_values(),
@@ -183,7 +183,7 @@ class ClimateMode(Device):
 
     def _iter_operation_remote_values(
         self,
-    ) -> Iterator[RemoteValueClimateModeBase[Any, HVACOperationMode]]:
+    ) -> Iterator[RemoteValueClimateModeBase[HVACOperationMode]]:
         return chain(
             self._iter_binary_operation_modes(),
             self._iter_byte_operation_modes(),
@@ -191,7 +191,7 @@ class ClimateMode(Device):
 
     def _iter_binary_operation_modes(
         self,
-    ) -> Iterator[RemoteValueClimateModeBase[Any, HVACOperationMode]]:
+    ) -> Iterator[RemoteValueClimateModeBase[HVACOperationMode]]:
         """Iterate DPT 1 binary operation modes."""
         yield from (
             self.remote_value_operation_mode_comfort,
@@ -202,7 +202,7 @@ class ClimateMode(Device):
 
     def _iter_byte_operation_modes(
         self,
-    ) -> Iterator[RemoteValueClimateModeBase[Any, HVACOperationMode]]:
+    ) -> Iterator[RemoteValueClimateModeBase[HVACOperationMode]]:
         """Iterate normal DPT 20.102 operation mode remote values."""
         yield from (
             self.remote_value_operation_mode,
@@ -211,7 +211,7 @@ class ClimateMode(Device):
 
     def _iter_controller_remote_values(
         self,
-    ) -> Iterator[RemoteValueClimateModeBase[Any, HVACControllerMode]]:
+    ) -> Iterator[RemoteValueClimateModeBase[HVACControllerMode]]:
         """Iterate DPT 20.105 controller remote values."""
         yield self.remote_value_controller_mode
         yield self.remote_value_heat_cool
@@ -242,7 +242,7 @@ class ClimateMode(Device):
                 "operation (preset) mode not supported", str(operation_mode)
             )
 
-        rv_operation: RemoteValueClimateModeBase[Any, HVACOperationMode]
+        rv_operation: RemoteValueClimateModeBase[HVACOperationMode]
         for rv_operation in self._iter_operation_remote_values():
             if (
                 rv_operation.writable
@@ -262,7 +262,7 @@ class ClimateMode(Device):
                 "controller (HVAC) mode not supported", str(controller_mode)
             )
 
-        rv_controller: RemoteValueClimateModeBase[Any, HVACControllerMode]
+        rv_controller: RemoteValueClimateModeBase[HVACControllerMode]
         for rv_controller in self._iter_controller_remote_values():
             if (
                 rv_controller.writable
diff --git a/xknx/devices/cover.py b/xknx/devices/cover.py
index 489c1364..00e66288 100644
--- a/xknx/devices/cover.py
+++ b/xknx/devices/cover.py
@@ -143,7 +143,7 @@ class Cover(Device):
         self._periodic_update_task: Task | None = None
         self._travel_direction_tilt: TravelStatus | None = None
 
-    def _iter_remote_values(self) -> Iterator[RemoteValue[Any, Any]]:
+    def _iter_remote_values(self) -> Iterator[RemoteValue[Any]]:
         """Iterate the devices RemoteValue classes."""
         yield self.updown
         yield self.step
diff --git a/xknx/devices/device.py b/xknx/devices/device.py
index e421839f..72a5f3cc 100644
--- a/xknx/devices/device.py
+++ b/xknx/devices/device.py
@@ -62,7 +62,7 @@ class Device(ABC):
                 self.xknx.task_registry.unregister(task.name)
 
     @abstractmethod
-    def _iter_remote_values(self) -> Iterator[RemoteValue[Any, Any]]:
+    def _iter_remote_values(self) -> Iterator[RemoteValue[Any]]:
         """Iterate the devices RemoteValue classes."""
         # yield self.remote_value
         # yield from (<list all used RemoteValue instances>)
diff --git a/xknx/devices/expose_sensor.py b/xknx/devices/expose_sensor.py
index 9ad51d65..c0294c72 100644
--- a/xknx/devices/expose_sensor.py
+++ b/xknx/devices/expose_sensor.py
@@ -75,7 +75,7 @@ class ExposeSensor(Device):
         self._cooldown_latest_value = self.sensor_value.value
         await super().after_update()
 
-    def _iter_remote_values(self) -> Iterator[RemoteValue[Any, Any]]:
+    def _iter_remote_values(self) -> Iterator[RemoteValue[Any]]:
         """Iterate the devices RemoteValue classes."""
         yield self.sensor_value
 
diff --git a/xknx/devices/fan.py b/xknx/devices/fan.py
index c56b3fe2..e181eab2 100644
--- a/xknx/devices/fan.py
+++ b/xknx/devices/fan.py
@@ -109,7 +109,7 @@ class Fan(Device):
             after_update_cb=self.after_update,
         )
 
-    def _iter_remote_values(self) -> Iterator[RemoteValue[Any, Any]]:
+    def _iter_remote_values(self) -> Iterator[RemoteValue[Any]]:
         """Iterate the devices RemoteValue classes."""
         yield from (self.switch, self.speed, self.oscillation)
 
diff --git a/xknx/devices/light.py b/xknx/devices/light.py
index 2a533ce3..4f27374d 100644
--- a/xknx/devices/light.py
+++ b/xknx/devices/light.py
@@ -317,14 +317,14 @@ class Light(Device):
         self._individual_color_debounce_telegram_counter: int
         self._reset_individual_color_debounce_telegrams()
 
-    def _iter_remote_values(self) -> Iterator[RemoteValue[Any, Any]]:
+    def _iter_remote_values(self) -> Iterator[RemoteValue[Any]]:
         """Iterate the devices RemoteValue classes."""
         return chain(
             self._iter_instant_remote_values(),
             self._iter_debounce_remote_values(),
         )
 
-    def _iter_instant_remote_values(self) -> Iterator[RemoteValue[Any, Any]]:
+    def _iter_instant_remote_values(self) -> Iterator[RemoteValue[Any]]:
         """Iterate the devices RemoteValue classes calling after_update_cb immediately."""
         yield self.switch
         yield self.brightness
@@ -336,7 +336,7 @@ class Light(Device):
         yield self.tunable_white
         yield self.color_temperature
 
-    def _iter_debounce_remote_values(self) -> Iterator[RemoteValue[Any, Any]]:
+    def _iter_debounce_remote_values(self) -> Iterator[RemoteValue[Any]]:
         """Iterate the devices RemoteValue classes debouncing after_update_cb."""
         for color in self._iter_individual_colors():
             yield color.switch
diff --git a/xknx/devices/sensor.py b/xknx/devices/sensor.py
index d9b9a9de..9e7acf23 100644
--- a/xknx/devices/sensor.py
+++ b/xknx/devices/sensor.py
@@ -67,7 +67,7 @@ class Sensor(Device):
             )
         self.always_callback = always_callback
 
-    def _iter_remote_values(self) -> Iterator[RemoteValue[Any, Any]]:
+    def _iter_remote_values(self) -> Iterator[RemoteValue[Any]]:
         """Iterate the devices RemoteValue classes."""
         yield self.sensor_value
 
diff --git a/xknx/devices/weather.py b/xknx/devices/weather.py
index 804792a8..6907a9c1 100644
--- a/xknx/devices/weather.py
+++ b/xknx/devices/weather.py
@@ -230,7 +230,7 @@ class Weather(Device):
             after_update_cb=self.after_update,
         )
 
-    def _iter_remote_values(self) -> Iterator[RemoteValue[Any, Any]]:
+    def _iter_remote_values(self) -> Iterator[RemoteValue[Any]]:
         """Iterate the devices remote values."""
         yield self._temperature
         yield self._brightness_south
diff --git a/xknx/dpt/__init__.py b/xknx/dpt/__init__.py
index b3067d03..3c6cdf2c 100644
--- a/xknx/dpt/__init__.py
+++ b/xknx/dpt/__init__.py
@@ -189,4 +189,4 @@ from .dpt_hvac_mode import DPTControllerStatus, DPTHVACContrMode, DPTHVACMode
 from .dpt_scaling import DPTAngle, DPTScaling
 from .dpt_string import DPTLatin1, DPTString
 from .dpt_time import DPTTime
-from .payload import DPTArray, DPTBinary, DPTPayloadT
+from .payload import DPTArray, DPTBinary
diff --git a/xknx/dpt/dpt.py b/xknx/dpt/dpt.py
index 050c0e39..8187c2e3 100644
--- a/xknx/dpt/dpt.py
+++ b/xknx/dpt/dpt.py
@@ -6,7 +6,7 @@ from collections.abc import Iterator
 from inspect import isabstract
 from typing import Any, TypeVar, cast
 
-from xknx.exceptions import ConversionError
+from xknx.exceptions import CouldNotParseTelegram
 
 from .payload import DPTArray, DPTBinary
 
@@ -50,7 +50,8 @@ class DPTBase(ABC):
 
     """
 
-    payload_length: int = cast(int, None)
+    payload_type: type[DPTArray | DPTBinary]
+    payload_length: int = cast(int, None)  # only used for DPTArray
     dpt_main_number: int | None = None
     dpt_sub_number: int | None = None
     value_type: str | None = None
@@ -59,27 +60,51 @@ class DPTBase(ABC):
 
     @classmethod
     @abstractmethod
-    def from_knx(cls, raw: tuple[int, ...]) -> Any:
-        """Parse/deserialize from KNX/IP raw data (big endian)."""
+    def from_knx(cls, payload: DPTArray | DPTBinary) -> Any:
+        """
+        Parse/deserialize from KNX/IP payload data.
+
+        Raise `CouldNotParseTelegram` for wrong payload
+        or `ConversionError` for unparsable value.
+        """
+        # raw = cls.validate_payload(payload)
+
+    @classmethod
+    def validate_payload(cls, payload: DPTArray | DPTBinary) -> tuple[int, ...]:
+        """
+        Test if payload has the correct length and type for given DPT.
+
+        Return tuple of raw values.
+        Raise CouldNotParseTelegram if payload type or length is invalid for DPT.
+        """
+        if cls.payload_type is DPTArray and isinstance(payload, DPTArray):
+            if cls.payload_length == len(payload.value):
+                return payload.value
+
+            raise CouldNotParseTelegram(
+                f"Invalid payload length for {cls.__name__}",
+                payload=payload,
+                expected_length=cls.payload_length,
+            )
+
+        if cls.payload_type is DPTBinary and isinstance(payload, DPTBinary):
+            # wrap in tuple for consistent return signature
+            return (payload.value,)
+
+        raise CouldNotParseTelegram(
+            f"Invalid payload type for {cls.__name__}",
+            payload=payload,
+            expected_type=cls.payload_type,
+        )
 
     @classmethod
     @abstractmethod
     def to_knx(cls, value: Any) -> DPTArray | DPTBinary:
-        """Serialize to KNX/IP raw data."""
+        """
+        Serialize to KNX/IP raw data.
 
-    @classmethod
-    def test_bytesarray(cls, raw: tuple[int, ...]) -> None:
-        """Test if array of raw bytes has the correct length and values of correct type."""
-        if cls.payload_length is None:
-            raise NotImplementedError(f"payload_length has to be defined for: {cls}")
-        if (
-            not isinstance(raw, (tuple, list))
-            or len(raw) != cls.payload_length
-            or any(not isinstance(byte, int) for byte in raw)
-            or any(byte < 0 for byte in raw)
-            or any(byte > 255 for byte in raw)
-        ):
-            raise ConversionError("Invalid raw bytes", raw=raw)
+        Raise `ConversionError` for unparsable value.
+        """
 
     @classmethod
     def __recursive_subclasses__(cls: T) -> Iterator[T]:
@@ -152,14 +177,15 @@ class DPTBase(ABC):
 class DPTNumeric(DPTBase):
     """Base class for KNX data point types decoding numeric values."""
 
+    payload_type = DPTArray
     value_min: int | float
     value_max: int | float
     resolution: int | float
 
     @classmethod
     @abstractmethod
-    def from_knx(cls, raw: tuple[int, ...]) -> int | float:
-        """Parse/deserialize from KNX/IP raw data (big endian)."""
+    def from_knx(cls, payload: DPTArray | DPTBinary) -> int | float:
+        """Parse/deserialize from KNX/IP payload data."""
 
     @classmethod
     @abstractmethod
diff --git a/xknx/dpt/dpt_1byte_signed.py b/xknx/dpt/dpt_1byte_signed.py
index 129d10a1..aeedaeae 100644
--- a/xknx/dpt/dpt_1byte_signed.py
+++ b/xknx/dpt/dpt_1byte_signed.py
@@ -4,7 +4,7 @@ from __future__ import annotations
 from xknx.exceptions import ConversionError
 
 from .dpt import DPTNumeric
-from .payload import DPTArray
+from .payload import DPTArray, DPTBinary
 
 
 class DPTSignedRelativeValue(DPTNumeric):
@@ -24,9 +24,9 @@ class DPTSignedRelativeValue(DPTNumeric):
     resolution = 1
 
     @classmethod
-    def from_knx(cls, raw: tuple[int, ...]) -> int:
+    def from_knx(cls, payload: DPTArray | DPTBinary) -> int:
         """Parse/deserialize from KNX/IP raw data."""
-        cls.test_bytesarray(raw)
+        raw = cls.validate_payload(payload)
         if raw[0] > cls.value_max:
             return raw[0] - 0x100
         return raw[0]
diff --git a/xknx/dpt/dpt_1byte_uint.py b/xknx/dpt/dpt_1byte_uint.py
index 1e2b7a5e..59fd1f82 100644
--- a/xknx/dpt/dpt_1byte_uint.py
+++ b/xknx/dpt/dpt_1byte_uint.py
@@ -4,7 +4,7 @@ from __future__ import annotations
 from xknx.exceptions import ConversionError
 
 from .dpt import DPTNumeric
-from .payload import DPTArray
+from .payload import DPTArray, DPTBinary
 
 
 class DPTValue1ByteUnsigned(DPTNumeric):
@@ -24,15 +24,13 @@ class DPTValue1ByteUnsigned(DPTNumeric):
     resolution = 1
 
     @classmethod
-    def from_knx(cls, raw: tuple[int, ...]) -> int:
+    def from_knx(cls, payload: DPTArray | DPTBinary) -> int:
         """Parse/deserialize from KNX/IP raw data."""
-        cls.test_bytesarray(raw)
-
-        value = raw[0]
+        value = cls.validate_payload(payload)[0]
 
         if not cls._test_boundaries(value):
             raise ConversionError(
-                f"Could not parse {cls.__name__}", value=value, raw=raw
+                f"Could not parse {cls.__name__}", value=value, payload=payload
             )
 
         return value
@@ -120,15 +118,13 @@ class DPTSceneNumber(DPTValue1ByteUnsigned):
     value_max = 64
 
     @classmethod
-    def from_knx(cls, raw: tuple[int, ...]) -> int:
+    def from_knx(cls, payload: DPTArray | DPTBinary) -> int:
         """Parse/deserialize from KNX/IP raw data."""
-        cls.test_bytesarray(raw)
-
-        value = raw[0] + 1
+        value = cls.validate_payload(payload)[0] + 1
 
         if not cls._test_boundaries(value):
             raise ConversionError(
-                f"Could not parse {cls.__name__}", value=value, raw=raw
+                f"Could not parse {cls.__name__}", value=value, payload=payload
             )
 
         return value
diff --git a/xknx/dpt/dpt_2byte_float.py b/xknx/dpt/dpt_2byte_float.py
index 048e3af9..8cf74985 100644
--- a/xknx/dpt/dpt_2byte_float.py
+++ b/xknx/dpt/dpt_2byte_float.py
@@ -8,7 +8,7 @@ from __future__ import annotations
 from xknx.exceptions import ConversionError
 
 from .dpt import DPTNumeric
-from .payload import DPTArray
+from .payload import DPTArray, DPTBinary
 
 
 class DPT2ByteFloat(DPTNumeric):
@@ -28,9 +28,9 @@ class DPT2ByteFloat(DPTNumeric):
     resolution = 0.01
 
     @classmethod
-    def from_knx(cls, raw: tuple[int, ...]) -> float:
+    def from_knx(cls, payload: DPTArray | DPTBinary) -> float:
         """Parse/deserialize from KNX/IP raw data."""
-        cls.test_bytesarray(raw)
+        raw = cls.validate_payload(payload)
         data = (raw[0] * 256) + raw[1]
         exponent = (data >> 11) & 0x0F
         significand = data & 0x7FF
diff --git a/xknx/dpt/dpt_2byte_signed.py b/xknx/dpt/dpt_2byte_signed.py
index eb05d8e5..5dad75d3 100644
--- a/xknx/dpt/dpt_2byte_signed.py
+++ b/xknx/dpt/dpt_2byte_signed.py
@@ -11,7 +11,7 @@ import struct
 from xknx.exceptions import ConversionError
 
 from .dpt import DPTNumeric
-from .payload import DPTArray
+from .payload import DPTArray, DPTBinary
 
 
 class DPT2ByteSigned(DPTNumeric):
@@ -33,12 +33,12 @@ class DPT2ByteSigned(DPTNumeric):
     _struct_format = ">h"
 
     @classmethod
-    def from_knx(cls, raw: tuple[int, ...]) -> int:
+    def from_knx(cls, payload: DPTArray | DPTBinary) -> int:
         """Parse/deserialize from KNX/IP raw data."""
-        cls.test_bytesarray(raw)
+        raw = cls.validate_payload(payload)
 
         try:
-            return struct.unpack(cls._struct_format, bytes(raw))[0]  # type: ignore
+            return struct.unpack(cls._struct_format, bytes(raw))[0]  # type: ignore[no-any-return]
         except struct.error:
             raise ConversionError(f"Could not parse {cls.__name__}", raw=raw)
 
diff --git a/xknx/dpt/dpt_2byte_uint.py b/xknx/dpt/dpt_2byte_uint.py
index 5553c714..79231185 100644
--- a/xknx/dpt/dpt_2byte_uint.py
+++ b/xknx/dpt/dpt_2byte_uint.py
@@ -4,7 +4,7 @@ from __future__ import annotations
 from xknx.exceptions import ConversionError
 
 from .dpt import DPTNumeric
-from .payload import DPTArray
+from .payload import DPTArray, DPTBinary
 
 
 class DPT2ByteUnsigned(DPTNumeric):
@@ -26,9 +26,9 @@ class DPT2ByteUnsigned(DPTNumeric):
     resolution = 1
 
     @classmethod
-    def from_knx(cls, raw: tuple[int, ...]) -> int:
+    def from_knx(cls, payload: DPTArray | DPTBinary) -> int:
         """Parse/deserialize from KNX/IP raw data."""
-        cls.test_bytesarray(raw)
+        raw = cls.validate_payload(payload)
         return (raw[0] * 256) + raw[1]
 
     @classmethod
diff --git a/xknx/dpt/dpt_4bit_control.py b/xknx/dpt/dpt_4bit_control.py
index 64ad8f15..7a3b6b7c 100644
--- a/xknx/dpt/dpt_4bit_control.py
+++ b/xknx/dpt/dpt_4bit_control.py
@@ -20,7 +20,7 @@ from typing import Any
 from xknx.exceptions import ConversionError
 
 from .dpt import DPTBase
-from .payload import DPTBinary
+from .payload import DPTArray, DPTBinary
 
 
 class DPTControlStepCode(DPTBase, ABC):
@@ -31,6 +31,7 @@ class DPTControlStepCode(DPTBase, ABC):
     APCI_STEPCODEMASK = 0x07
     APCI_MAX_VALUE = APCI_CONTROLMASK | APCI_STEPCODEMASK
 
+    payload_type = DPTBinary
     payload_length = 1
 
     @classmethod
@@ -46,12 +47,6 @@ class DPTControlStepCode(DPTBase, ABC):
         step_code = value & cls.APCI_STEPCODEMASK
         return control, step_code
 
-    @classmethod
-    def _test_boundaries(cls, raw: int) -> bool:
-        """Test if raw KNX data is within defined range for this object."""
-        if isinstance(raw, int):
-            return 0 <= raw <= cls.APCI_MAX_VALUE
-
     @classmethod
     def _test_values(cls, step_code: int) -> bool:
         """Test if input values are valid."""
@@ -84,13 +79,13 @@ class DPTControlStepCode(DPTBase, ABC):
         return DPTBinary(cls._encode(control, step_code))
 
     @classmethod
-    def from_knx(cls, raw: tuple[int, ...]) -> Any:
+    def from_knx(cls, payload: DPTArray | DPTBinary) -> Any:
         """Parse/deserialize from KNX/IP raw data."""
-        if not isinstance(raw, tuple) or not cls._test_boundaries(raw[0]):
+        raw = cls.validate_payload(payload)[0]
+        if raw > cls.APCI_MAX_VALUE:
             raise ConversionError(f"Can't parse {cls.__name__}", raw=raw)
 
-        control, step_code = cls._decode(raw[0])
-
+        control, step_code = cls._decode(raw)
         return {"control": control, "step_code": step_code}
 
 
@@ -145,9 +140,9 @@ class DPTControlStepwise(DPTControlStepCode):
         return super().to_knx(cls._from_increment(value))
 
     @classmethod
-    def from_knx(cls, raw: tuple[int, ...]) -> int:
+    def from_knx(cls, payload: DPTArray | DPTBinary) -> int:
         """Parse/deserialize from KNX/IP raw data."""
-        return cls._to_increment(super().from_knx(raw))
+        return cls._to_increment(super().from_knx(payload))
 
 
 class DPTControlStepwiseDimming(DPTControlStepwise):
@@ -210,9 +205,9 @@ class DPTControlStartStop(DPTControlStepCode):
         return super().to_knx(values)
 
     @classmethod
-    def from_knx(cls, raw: tuple[int, ...]) -> Direction:
+    def from_knx(cls, payload: DPTArray | DPTBinary) -> Direction:
         """Convert current payload to value."""
-        values = super().from_knx(raw)
+        values = super().from_knx(payload)
         if values["step_code"] == 0:
             return cls.Direction(2)  # STOP
         if values["control"] == 0:
diff --git a/xknx/dpt/dpt_4byte_float.py b/xknx/dpt/dpt_4byte_float.py
index 13910edf..1b136c50 100644
--- a/xknx/dpt/dpt_4byte_float.py
+++ b/xknx/dpt/dpt_4byte_float.py
@@ -12,7 +12,7 @@ from typing import cast
 from xknx.exceptions import ConversionError
 
 from .dpt import DPTNumeric
-from .payload import DPTArray
+from .payload import DPTArray, DPTBinary
 
 
 class DPT4ByteFloat(DPTNumeric):
@@ -37,9 +37,9 @@ class DPT4ByteFloat(DPTNumeric):
     resolution = 0.0000001
 
     @classmethod
-    def from_knx(cls, raw: tuple[int, ...]) -> float:
+    def from_knx(cls, payload: DPTArray | DPTBinary) -> float:
         """Parse/deserialize from KNX/IP raw data (big endian)."""
-        cls.test_bytesarray(raw)
+        raw = cls.validate_payload(payload)
         try:
             raw_float = cast(float, struct.unpack(">f", bytes(raw))[0])
         except struct.error:
diff --git a/xknx/dpt/dpt_4byte_int.py b/xknx/dpt/dpt_4byte_int.py
index 9c153a44..82e88b90 100644
--- a/xknx/dpt/dpt_4byte_int.py
+++ b/xknx/dpt/dpt_4byte_int.py
@@ -12,7 +12,7 @@ import struct
 from xknx.exceptions import ConversionError
 
 from .dpt import DPTNumeric
-from .payload import DPTArray
+from .payload import DPTArray, DPTBinary
 
 
 class DPT4ByteUnsigned(DPTNumeric):
@@ -34,12 +34,12 @@ class DPT4ByteUnsigned(DPTNumeric):
     _struct_format = ">I"
 
     @classmethod
-    def from_knx(cls, raw: tuple[int, ...]) -> int:
+    def from_knx(cls, payload: DPTArray | DPTBinary) -> int:
         """Parse/deserialize from KNX/IP raw data."""
-        cls.test_bytesarray(raw)
+        raw = cls.validate_payload(payload)
 
         try:
-            return struct.unpack(cls._struct_format, bytes(raw))[0]  # type: ignore
+            return struct.unpack(cls._struct_format, bytes(raw))[0]  # type: ignore[no-any-return]
         except struct.error:
             raise ConversionError(f"Could not parse {cls.__name__}", raw=raw)
 
diff --git a/xknx/dpt/dpt_color.py b/xknx/dpt/dpt_color.py
index 2922605d..debc1f2d 100644
--- a/xknx/dpt/dpt_color.py
+++ b/xknx/dpt/dpt_color.py
@@ -6,7 +6,7 @@ from typing import NamedTuple
 from xknx.exceptions import ConversionError
 
 from .dpt import DPTBase
-from .payload import DPTArray
+from .payload import DPTArray, DPTBinary
 
 
 class XYYColor(NamedTuple):
@@ -25,12 +25,13 @@ class XYYColor(NamedTuple):
 class DPTColorXYY(DPTBase):
     """Abstraction for KNX 6 octet color xyY (DPT 242.600)."""
 
+    payload_type = DPTArray
     payload_length = 6
 
     @classmethod
-    def from_knx(cls, raw: tuple[int, ...]) -> XYYColor:
+    def from_knx(cls, payload: DPTArray | DPTBinary) -> XYYColor:
         """Parse/deserialize from KNX/IP raw data."""
-        cls.test_bytesarray(raw)
+        raw = cls.validate_payload(payload)
 
         x_axis_int = raw[0] << 8 | raw[1]
         y_axis_int = raw[2] << 8 | raw[3]
diff --git a/xknx/dpt/dpt_date.py b/xknx/dpt/dpt_date.py
index ed257415..80da70b2 100644
--- a/xknx/dpt/dpt_date.py
+++ b/xknx/dpt/dpt_date.py
@@ -6,18 +6,19 @@ import time
 from xknx.exceptions import ConversionError
 
 from .dpt import DPTBase
-from .payload import DPTArray
+from .payload import DPTArray, DPTBinary
 
 
 class DPTDate(DPTBase):
     """Abstraction for KNX 3 octet date (DPT 11.001)."""
 
+    payload_type = DPTArray
     payload_length = 3
 
     @classmethod
-    def from_knx(cls, raw: tuple[int, ...]) -> time.struct_time:
+    def from_knx(cls, payload: DPTArray | DPTBinary) -> time.struct_time:
         """Parse/deserialize from KNX/IP raw data."""
-        cls.test_bytesarray(raw)
+        raw = cls.validate_payload(payload)
 
         day = raw[0] & 0x1F
         month = raw[1] & 0x0F
diff --git a/xknx/dpt/dpt_datetime.py b/xknx/dpt/dpt_datetime.py
index 2a3fc863..c2a68c99 100644
--- a/xknx/dpt/dpt_datetime.py
+++ b/xknx/dpt/dpt_datetime.py
@@ -6,18 +6,19 @@ import time
 from xknx.exceptions import ConversionError
 
 from .dpt import DPTBase
-from .payload import DPTArray
+from .payload import DPTArray, DPTBinary
 
 
 class DPTDateTime(DPTBase):
     """Abstraction for KNX 8 octet datetime (DPT 19.001)."""
 
+    payload_type = DPTArray
     payload_length = 8
 
     @classmethod
-    def from_knx(cls, raw: tuple[int, ...]) -> time.struct_time:
+    def from_knx(cls, payload: DPTArray | DPTBinary) -> time.struct_time:
         """Parse/deserialize from KNX/IP raw data."""
-        cls.test_bytesarray(raw)
+        raw = cls.validate_payload(payload)
 
         year = raw[0] + 1900
         month = raw[1] & 0x0F
diff --git a/xknx/dpt/dpt_hvac_mode.py b/xknx/dpt/dpt_hvac_mode.py
index 3982beb2..53c8dd99 100644
--- a/xknx/dpt/dpt_hvac_mode.py
+++ b/xknx/dpt/dpt_hvac_mode.py
@@ -7,7 +7,7 @@ from typing import Generic, TypeVar
 from xknx.exceptions import ConversionError
 
 from .dpt import DPTBase
-from .payload import DPTArray
+from .payload import DPTArray, DPTBinary
 
 HVACModeT = TypeVar("HVACModeT", "HVACControllerMode", "HVACOperationMode")
 
@@ -46,12 +46,13 @@ class _DPTClimateMode(DPTBase, Generic[HVACModeT]):
 
     SUPPORTED_MODES: dict[int, HVACModeT] = {}
 
+    payload_type = DPTArray
     payload_length = 1
 
     @classmethod
-    def from_knx(cls, raw: tuple[int, ...]) -> HVACModeT:
+    def from_knx(cls, payload: DPTArray | DPTBinary) -> HVACModeT:
         """Parse/deserialize from KNX/IP raw data."""
-        cls.test_bytesarray(raw)
+        raw = cls.validate_payload(payload)
         try:
             return cls.SUPPORTED_MODES[raw[0]]
         except KeyError:
@@ -126,9 +127,9 @@ class DPTControllerStatus(_DPTClimateMode[HVACOperationMode]):
     }
 
     @classmethod
-    def from_knx(cls, raw: tuple[int, ...]) -> HVACOperationMode:
+    def from_knx(cls, payload: DPTArray | DPTBinary) -> HVACOperationMode:
         """Parse/deserialize from KNX/IP raw data."""
-        cls.test_bytesarray(raw)
+        raw = cls.validate_payload(payload)
         if raw[0] & 8 > 0:
             return HVACOperationMode.FROST_PROTECTION
         if raw[0] & 4 > 0:
diff --git a/xknx/dpt/dpt_scaling.py b/xknx/dpt/dpt_scaling.py
index 281be9b0..d814d9a3 100644
--- a/xknx/dpt/dpt_scaling.py
+++ b/xknx/dpt/dpt_scaling.py
@@ -4,7 +4,7 @@ from __future__ import annotations
 from xknx.exceptions import ConversionError
 
 from .dpt import DPTNumeric
-from .payload import DPTArray
+from .payload import DPTArray, DPTBinary
 
 
 class DPTScaling(DPTNumeric):
@@ -25,19 +25,16 @@ class DPTScaling(DPTNumeric):
     resolution = 1
 
     @classmethod
-    def from_knx(cls, raw: tuple[int, ...]) -> int:
+    def from_knx(cls, payload: DPTArray | DPTBinary) -> int:
         """Parse/deserialize from KNX/IP raw data."""
-        cls.test_bytesarray(raw)
-
-        knx_value = raw[0]
+        knx_value = cls.validate_payload(payload)[0]
         delta = cls.value_max - cls.value_min
         value = round((knx_value / 255) * delta) + cls.value_min
 
         if not cls._test_boundaries(value):
             raise ConversionError(
-                f"Could not parse {cls.__name__}", value=value, raw=raw
+                f"Could not parse {cls.__name__}", value=value, payload=payload
             )
-
         return value
 
     @classmethod
diff --git a/xknx/dpt/dpt_string.py b/xknx/dpt/dpt_string.py
index c01ceea8..f5209bee 100644
--- a/xknx/dpt/dpt_string.py
+++ b/xknx/dpt/dpt_string.py
@@ -4,7 +4,7 @@ from __future__ import annotations
 from xknx.exceptions import ConversionError
 
 from .dpt import DPTBase
-from .payload import DPTArray
+from .payload import DPTArray, DPTBinary
 
 
 class DPTString(DPTBase):
@@ -14,6 +14,7 @@ class DPTString(DPTBase):
     DPT 16.000
     """
 
+    payload_type = DPTArray
     payload_length = 14
     dpt_main_number = 16
     dpt_sub_number = 0
@@ -23,9 +24,9 @@ class DPTString(DPTBase):
     _encoding = "ascii"
 
     @classmethod
-    def from_knx(cls, raw: tuple[int, ...]) -> str:
+    def from_knx(cls, payload: DPTArray | DPTBinary) -> str:
         """Parse/deserialize from KNX/IP raw data."""
-        cls.test_bytesarray(raw)
+        raw = cls.validate_payload(payload)
         return bytes(byte for byte in raw if byte != 0x00).decode(
             cls._encoding, errors="replace"
         )
diff --git a/xknx/dpt/dpt_time.py b/xknx/dpt/dpt_time.py
index 1d2f3393..e3d4aa37 100644
--- a/xknx/dpt/dpt_time.py
+++ b/xknx/dpt/dpt_time.py
@@ -6,7 +6,7 @@ import time
 from xknx.exceptions import ConversionError
 
 from .dpt import DPTBase
-from .payload import DPTArray
+from .payload import DPTArray, DPTBinary
 
 
 class DPTTime(DPTBase):
@@ -16,12 +16,13 @@ class DPTTime(DPTBase):
     DPT 10.001
     """
 
+    payload_type = DPTArray
     payload_length = 3
 
     @classmethod
-    def from_knx(cls, raw: tuple[int, ...]) -> time.struct_time:
+    def from_knx(cls, payload: DPTArray | DPTBinary) -> time.struct_time:
         """Parse/deserialize from KNX/IP raw data."""
-        cls.test_bytesarray(raw)
+        raw = cls.validate_payload(payload)
 
         weekday = (raw[0] & 0xE0) >> 5
         hours = raw[0] & 0x1F
diff --git a/xknx/dpt/payload.py b/xknx/dpt/payload.py
index 9ed8c664..658f608a 100644
--- a/xknx/dpt/payload.py
+++ b/xknx/dpt/payload.py
@@ -1,8 +1,6 @@
 """Implementation of KNX raw payload abstractions."""
 from __future__ import annotations
 
-from typing import TypeVar, Union
-
 from xknx.exceptions import ConversionError
 
 
@@ -65,6 +63,3 @@ class DPTArray:
     def __str__(self) -> str:
         """Return object as readable string."""
         return f'<DPTArray value="[{",".join(hex(b) for b in self.value)}]" />'
-
-
-DPTPayloadT = TypeVar("DPTPayloadT", bound=Union[DPTArray, DPTBinary])
diff --git a/xknx/remote_value/remote_value.py b/xknx/remote_value/remote_value.py
index 3449656b..ae254d69 100644
--- a/xknx/remote_value/remote_value.py
+++ b/xknx/remote_value/remote_value.py
@@ -13,7 +13,7 @@ from collections.abc import Awaitable, Iterator
 import logging
 from typing import TYPE_CHECKING, Callable, Generic, TypeVar, Union
 
-from xknx.dpt import DPTArray, DPTBinary, DPTPayloadT
+from xknx.dpt import DPTArray, DPTBinary
 from xknx.exceptions import ConversionError, CouldNotParseTelegram
 from xknx.telegram import GroupAddress, Telegram
 from xknx.telegram.address import (
@@ -34,7 +34,7 @@ GroupAddressesType = Union["DeviceAddressableType", list["DeviceAddressableType"
 ValueT = TypeVar("ValueT")
 
 
-class RemoteValue(ABC, Generic[DPTPayloadT, ValueT]):
+class RemoteValue(ABC, Generic[ValueT]):
     """Class for managing remote knx value."""
 
     def __init__(
@@ -138,16 +138,11 @@ class RemoteValue(ABC, Generic[DPTPayloadT, ValueT]):
         return group_address in remote_value_addresses()
 
     @abstractmethod
-    def payload_valid(self, payload: DPTArray | DPTBinary | None) -> DPTPayloadT:
-        """Return payload if telegram payload may be parsed - to be implemented in derived class."""
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
-    @abstractmethod
-    def from_knx(self, payload: DPTPayloadT) -> ValueT:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> ValueT:
         """Convert current payload to value - to be implemented in derived class."""
 
     @abstractmethod
-    def to_knx(self, value: ValueT) -> DPTPayloadT:
+    def to_knx(self, value: ValueT) -> DPTArray | DPTBinary:
         """Convert value to payload - to be implemented in derived class."""
 
     async def process(self, telegram: Telegram, always_callback: bool = False) -> bool:
@@ -173,8 +168,7 @@ class RemoteValue(ABC, Generic[DPTPayloadT, ValueT]):
             )
 
         try:
-            _new_payload = self.payload_valid(telegram.payload.value)
-            decoded_payload = self.from_knx(_new_payload)
+            decoded_payload = self.from_knx(telegram.payload.value)
         except (ConversionError, CouldNotParseTelegram) as err:
             logger.warning(
                 "Can not process %s for %s - %s: %s",
diff --git a/xknx/remote_value/remote_value_1count.py b/xknx/remote_value/remote_value_1count.py
index e995df42..41be6102 100644
--- a/xknx/remote_value/remote_value_1count.py
+++ b/xknx/remote_value/remote_value_1count.py
@@ -6,24 +6,17 @@ DPT 6.010.
 from __future__ import annotations
 
 from xknx.dpt import DPTArray, DPTBinary, DPTValue1Count
-from xknx.exceptions import CouldNotParseTelegram
 
 from .remote_value import RemoteValue
 
 
-class RemoteValue1Count(RemoteValue[DPTArray, int]):
+class RemoteValue1Count(RemoteValue[int]):
     """Abstraction for remote value of KNX 6.010 (DPT_Value_1_Count)."""
 
-    def payload_valid(self, payload: DPTArray | DPTBinary | None) -> DPTArray:
-        """Test if telegram payload may be parsed."""
-        if isinstance(payload, DPTArray) and len(payload.value) == 1:
-            return payload
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
     def to_knx(self, value: int) -> DPTArray:
         """Convert value to payload."""
         return DPTValue1Count.to_knx(value)
 
-    def from_knx(self, payload: DPTArray) -> int:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> int:
         """Convert current payload to value."""
-        return DPTValue1Count.from_knx(payload.value)
+        return DPTValue1Count.from_knx(payload)
diff --git a/xknx/remote_value/remote_value_climate_mode.py b/xknx/remote_value/remote_value_climate_mode.py
index 6bd004c5..5f688bbe 100644
--- a/xknx/remote_value/remote_value_climate_mode.py
+++ b/xknx/remote_value/remote_value_climate_mode.py
@@ -15,7 +15,6 @@ from xknx.dpt import (
     DPTControllerStatus,
     DPTHVACContrMode,
     DPTHVACMode,
-    DPTPayloadT,
 )
 from xknx.dpt.dpt_hvac_mode import HVACControllerMode, HVACModeT, HVACOperationMode
 from xknx.exceptions import ConversionError, CouldNotParseTelegram
@@ -26,7 +25,7 @@ if TYPE_CHECKING:
     from xknx.xknx import XKNX
 
 
-class RemoteValueClimateModeBase(RemoteValue[DPTPayloadT, Optional[HVACModeT]]):
+class RemoteValueClimateModeBase(RemoteValue[Optional[HVACModeT]]):
     """Base class for binary climate mode remote values."""
 
     @abstractmethod
@@ -36,7 +35,7 @@ class RemoteValueClimateModeBase(RemoteValue[DPTPayloadT, Optional[HVACModeT]]):
         """Return a list of all supported operation modes."""
 
 
-class RemoteValueOperationMode(RemoteValueClimateModeBase[DPTArray, HVACOperationMode]):
+class RemoteValueOperationMode(RemoteValueClimateModeBase[HVACOperationMode]):
     """Abstraction for remote value of KNX climate operation modes."""
 
     class ClimateModeType(Enum):
@@ -81,24 +80,16 @@ class RemoteValueOperationMode(RemoteValueClimateModeBase[DPTArray, HVACOperatio
         """Return a list of all supported operation modes."""
         return list(self._climate_mode_transcoder.SUPPORTED_MODES.values())
 
-    def payload_valid(self, payload: DPTArray | DPTBinary | None) -> DPTArray:
-        """Test if telegram payload may be parsed."""
-        if isinstance(payload, DPTArray) and len(payload.value) == 1:
-            return payload
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
     def to_knx(self, value: Any) -> DPTArray:
         """Convert value to payload."""
         return self._climate_mode_transcoder.to_knx(value)
 
-    def from_knx(self, payload: DPTArray) -> HVACOperationMode | None:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> HVACOperationMode | None:
         """Convert current payload to value."""
-        return self._climate_mode_transcoder.from_knx(payload.value)
+        return self._climate_mode_transcoder.from_knx(payload)
 
 
-class RemoteValueControllerMode(
-    RemoteValueClimateModeBase[DPTArray, HVACControllerMode]
-):
+class RemoteValueControllerMode(RemoteValueClimateModeBase[HVACControllerMode]):
     """Abstraction for remote value of KNX climate controller modes."""
 
     def __init__(
@@ -126,24 +117,16 @@ class RemoteValueControllerMode(
         """Return a list of all supported operation modes."""
         return list(DPTHVACContrMode.SUPPORTED_MODES.values())
 
-    def payload_valid(self, payload: DPTArray | DPTBinary | None) -> DPTArray:
-        """Test if telegram payload may be parsed."""
-        if isinstance(payload, DPTArray) and len(payload.value) == 1:
-            return payload
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
     def to_knx(self, value: Any) -> DPTArray:
         """Convert value to payload."""
         return DPTHVACContrMode.to_knx(value)
 
-    def from_knx(self, payload: DPTArray) -> HVACControllerMode | None:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> HVACControllerMode | None:
         """Convert current payload to value."""
-        return DPTHVACContrMode.from_knx(payload.value)
+        return DPTHVACContrMode.from_knx(payload)
 
 
-class RemoteValueBinaryOperationMode(
-    RemoteValueClimateModeBase[DPTBinary, HVACOperationMode]
-):
+class RemoteValueBinaryOperationMode(RemoteValueClimateModeBase[HVACOperationMode]):
     """Abstraction for remote value of split up KNX climate modes."""
 
     def __init__(
@@ -183,12 +166,6 @@ class RemoteValueBinaryOperationMode(
             after_update_cb=after_update_cb,
         )
 
-    def payload_valid(self, payload: DPTArray | DPTBinary | None) -> DPTBinary:
-        """Test if telegram payload may be parsed."""
-        if isinstance(payload, DPTBinary):
-            return payload
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
     def to_knx(self, value: Any) -> DPTBinary:
         """Convert value to payload."""
         if isinstance(value, HVACOperationMode):
@@ -210,23 +187,21 @@ class RemoteValueBinaryOperationMode(
             HVACOperationMode.STANDBY,
         ]
 
-    def from_knx(self, payload: DPTPayloadT) -> HVACOperationMode | None:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> HVACOperationMode | None:
         """Convert current payload to value."""
-        if payload == DPTBinary(1):
+        if payload.value == 1:
             return self.operation_mode
-        if payload == DPTBinary(0):
+        if payload.value == 0:
             return None
         raise CouldNotParseTelegram(
-            "payload invalid",
+            "Payload invalid",
             payload=str(payload),
             device_name=self.device_name,
             feature_name=self.feature_name,
         )
 
 
-class RemoteValueBinaryHeatCool(
-    RemoteValueClimateModeBase[DPTBinary, HVACControllerMode]
-):
+class RemoteValueBinaryHeatCool(RemoteValueClimateModeBase[HVACControllerMode]):
     """Abstraction for remote value of heat/cool controller mode."""
 
     def __init__(
@@ -266,12 +241,6 @@ class RemoteValueBinaryHeatCool(
             after_update_cb=after_update_cb,
         )
 
-    def payload_valid(self, payload: DPTArray | DPTBinary | None) -> DPTBinary:
-        """Test if telegram payload may be parsed."""
-        if isinstance(payload, DPTBinary):
-            return payload
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
     def supported_operation_modes(self) -> list[HVACControllerMode]:
         """Return a list of the configured operation mode."""
         return [HVACControllerMode.HEAT, HVACControllerMode.COOL]
@@ -288,11 +257,11 @@ class RemoteValueBinaryHeatCool(
             feature_name=self.feature_name,
         )
 
-    def from_knx(self, payload: DPTPayloadT) -> HVACControllerMode | None:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> HVACControllerMode | None:
         """Convert current payload to value."""
-        if payload == DPTBinary(1):
+        if payload.value == 1:
             return self.controller_mode
-        if payload == DPTBinary(0):
+        if payload.value == 0:
             # return the other operation mode
             return next(
                 (
@@ -303,7 +272,7 @@ class RemoteValueBinaryHeatCool(
                 None,
             )
         raise CouldNotParseTelegram(
-            "payload invalid",
+            "Payload invalid",
             payload=str(payload),
             device_name=self.device_name,
             feature_name=self.feature_name,
diff --git a/xknx/remote_value/remote_value_color_rgb.py b/xknx/remote_value/remote_value_color_rgb.py
index bb53732e..a1aa9970 100644
--- a/xknx/remote_value/remote_value_color_rgb.py
+++ b/xknx/remote_value/remote_value_color_rgb.py
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
     from xknx.xknx import XKNX
 
 
-class RemoteValueColorRGB(RemoteValue[DPTArray, tuple[int, int, int]]):
+class RemoteValueColorRGB(RemoteValue[tuple[int, int, int]]):
     """Abstraction for remote value of KNX DPT 232.600 (DPT_Color_RGB)."""
 
     def __init__(
@@ -41,12 +41,6 @@ class RemoteValueColorRGB(RemoteValue[DPTArray, tuple[int, int, int]]):
             after_update_cb=after_update_cb,
         )
 
-    def payload_valid(self, payload: DPTArray | DPTBinary | None) -> DPTArray:
-        """Test if telegram payload may be parsed."""
-        if isinstance(payload, DPTArray) and len(payload.value) == 3:
-            return payload
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
     def to_knx(self, value: Sequence[int]) -> DPTArray:
         """Convert value to payload."""
         if not isinstance(value, (list, tuple)):
@@ -72,6 +66,9 @@ class RemoteValueColorRGB(RemoteValue[DPTArray, tuple[int, int, int]]):
 
         return DPTArray(list(value))
 
-    def from_knx(self, payload: DPTArray) -> tuple[int, int, int]:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> tuple[int, int, int]:
         """Convert current payload to value."""
+        if not (isinstance(payload, DPTArray) and len(payload.value) == 3):
+            raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
+
         return payload.value[0], payload.value[1], payload.value[2]
diff --git a/xknx/remote_value/remote_value_color_rgbw.py b/xknx/remote_value/remote_value_color_rgbw.py
index 275cd633..2df24c7c 100644
--- a/xknx/remote_value/remote_value_color_rgbw.py
+++ b/xknx/remote_value/remote_value_color_rgbw.py
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
     from xknx.xknx import XKNX
 
 
-class RemoteValueColorRGBW(RemoteValue[DPTArray, tuple[int, int, int, int]]):
+class RemoteValueColorRGBW(RemoteValue[tuple[int, int, int, int]]):
     """Abstraction for remote value of KNX DPT 251.600 (DPT_Color_RGBW)."""
 
     def __init__(
@@ -42,12 +42,6 @@ class RemoteValueColorRGBW(RemoteValue[DPTArray, tuple[int, int, int, int]]):
         )
         self.previous_value: tuple[int, int, int, int] = (0, 0, 0, 0)
 
-    def payload_valid(self, payload: DPTArray | DPTBinary | None) -> DPTArray:
-        """Test if telegram payload may be parsed."""
-        if isinstance(payload, DPTArray) and len(payload.value) == 6:
-            return payload
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
     def to_knx(self, value: Sequence[int]) -> DPTArray:
         """
         Convert value (4-6 bytes) to payload (6 bytes).
@@ -99,13 +93,16 @@ class RemoteValueColorRGBW(RemoteValue[DPTArray, tuple[int, int, int, int]]):
             return DPTArray(list(rgbw) + [0x00] + list(value[4:]))
         return DPTArray(value)
 
-    def from_knx(self, payload: DPTArray) -> tuple[int, int, int, int]:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> tuple[int, int, int, int]:
         """
         Convert current payload to value. Always 4 byte (RGBW).
 
         If one element is invalid, use the previous value. All previous element
         values are initialized to 0.
         """
+        if not (isinstance(payload, DPTArray) and len(payload.value) == 6):
+            raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
+
         _result = list(self.previous_value)
         for i in range(len(payload.value) - 2):
             if payload.value[5] & (0x08 >> i):  # R,G,B,W value valid?
diff --git a/xknx/remote_value/remote_value_color_xyy.py b/xknx/remote_value/remote_value_color_xyy.py
index 05e3ec23..5084ad93 100644
--- a/xknx/remote_value/remote_value_color_xyy.py
+++ b/xknx/remote_value/remote_value_color_xyy.py
@@ -9,7 +9,6 @@ from typing import TYPE_CHECKING
 
 from xknx.dpt import DPTArray, DPTBinary
 from xknx.dpt.dpt_color import DPTColorXYY, XYYColor
-from xknx.exceptions import CouldNotParseTelegram
 
 from .remote_value import AsyncCallbackType, GroupAddressesType, RemoteValue
 
@@ -17,11 +16,9 @@ if TYPE_CHECKING:
     from xknx.xknx import XKNX
 
 
-class RemoteValueColorXYY(RemoteValue[DPTArray, XYYColor]):
+class RemoteValueColorXYY(RemoteValue[XYYColor]):
     """Abstraction for remote value of KNX DPT 242.600 (DPT_Colour_xyY)."""
 
-    PAYLOAD_LENGTH = 6
-
     def __init__(
         self,
         xknx: XKNX,
@@ -43,16 +40,10 @@ class RemoteValueColorXYY(RemoteValue[DPTArray, XYYColor]):
             after_update_cb=after_update_cb,
         )
 
-    def payload_valid(self, payload: DPTArray | DPTBinary | None) -> DPTArray:
-        """Test if telegram payload may be parsed."""
-        if isinstance(payload, DPTArray) and len(payload.value) == self.PAYLOAD_LENGTH:
-            return payload
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
     def to_knx(self, value: XYYColor) -> DPTArray:
         """Convert value to payload."""
         return DPTColorXYY.to_knx(value)
 
-    def from_knx(self, payload: DPTArray) -> XYYColor:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> XYYColor:
         """Convert current payload to value."""
-        return DPTColorXYY.from_knx(payload.value)
+        return DPTColorXYY.from_knx(payload)
diff --git a/xknx/remote_value/remote_value_control.py b/xknx/remote_value/remote_value_control.py
index 848688b4..b494ceac 100644
--- a/xknx/remote_value/remote_value_control.py
+++ b/xknx/remote_value/remote_value_control.py
@@ -9,7 +9,7 @@ from __future__ import annotations
 from typing import TYPE_CHECKING, Any
 
 from xknx.dpt import DPTArray, DPTBinary, DPTControlStepCode
-from xknx.exceptions import ConversionError, CouldNotParseTelegram
+from xknx.exceptions import ConversionError
 
 from .remote_value import AsyncCallbackType, GroupAddressesType, RemoteValue
 
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
     from xknx.xknx import XKNX
 
 
-class RemoteValueControl(RemoteValue[DPTBinary, Any]):
+class RemoteValueControl(RemoteValue[Any]):
     """Abstraction for remote value used for controlling."""
 
     def __init__(
@@ -50,20 +50,13 @@ class RemoteValueControl(RemoteValue[DPTBinary, Any]):
             after_update_cb=after_update_cb,
         )
 
-    def payload_valid(self, payload: DPTArray | DPTBinary | None) -> DPTBinary:
-        """Test if telegram payload may be parsed."""
-        if isinstance(payload, DPTBinary):
-            return payload
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
     def to_knx(self, value: Any) -> DPTBinary:
         """Convert value to payload."""
         return self.dpt_class.to_knx(value)
 
-    def from_knx(self, payload: DPTBinary) -> Any:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> Any:
         """Convert current payload to value."""
-        # TODO: DPTBinary.value is int - DPTBase.from_knx requires Tuple[int, ...] - maybe use bytes
-        return self.dpt_class.from_knx((payload.value,))
+        return self.dpt_class.from_knx(payload)
 
     @property
     def unit_of_measurement(self) -> str | None:
diff --git a/xknx/remote_value/remote_value_datetime.py b/xknx/remote_value/remote_value_datetime.py
index 6ccdc695..0ac007d4 100644
--- a/xknx/remote_value/remote_value_datetime.py
+++ b/xknx/remote_value/remote_value_datetime.py
@@ -10,7 +10,7 @@ import time
 from typing import TYPE_CHECKING
 
 from xknx.dpt import DPTArray, DPTBinary, DPTDate, DPTDateTime, DPTTime
-from xknx.exceptions import ConversionError, CouldNotParseTelegram
+from xknx.exceptions import ConversionError
 
 from .remote_value import AsyncCallbackType, GroupAddressesType, RemoteValue
 
@@ -26,7 +26,7 @@ class DateTimeType(Enum):
     TIME = DPTTime
 
 
-class RemoteValueDateTime(RemoteValue[DPTArray, time.struct_time]):
+class RemoteValueDateTime(RemoteValue[time.struct_time]):
     """Abstraction for remote value of KNX 10.001, 11.001 and 19.001 time and date objects."""
 
     def __init__(
@@ -62,19 +62,10 @@ class RemoteValueDateTime(RemoteValue[DPTArray, time.struct_time]):
             after_update_cb=after_update_cb,
         )
 
-    def payload_valid(self, payload: DPTArray | DPTBinary | None) -> DPTArray:
-        """Test if telegram payload may be parsed."""
-        if (
-            isinstance(payload, DPTArray)
-            and len(payload.value) == self.dpt_class.payload_length
-        ):
-            return payload
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
     def to_knx(self, value: time.struct_time) -> DPTArray:
         """Convert value to payload."""
         return self.dpt_class.to_knx(value)
 
-    def from_knx(self, payload: DPTArray) -> time.struct_time:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> time.struct_time:
         """Convert current payload to value."""
-        return self.dpt_class.from_knx(payload.value)
+        return self.dpt_class.from_knx(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 744e6437..eda8d50e 100644
--- a/xknx/remote_value/remote_value_dpt_2_byte_unsigned.py
+++ b/xknx/remote_value/remote_value_dpt_2_byte_unsigned.py
@@ -8,7 +8,6 @@ from __future__ import annotations
 from typing import TYPE_CHECKING
 
 from xknx.dpt import DPT2ByteUnsigned, DPTArray, DPTBinary
-from xknx.exceptions import CouldNotParseTelegram
 
 from .remote_value import AsyncCallbackType, GroupAddressesType, RemoteValue
 
@@ -16,7 +15,7 @@ if TYPE_CHECKING:
     from xknx.xknx import XKNX
 
 
-class RemoteValueDpt2ByteUnsigned(RemoteValue[DPTArray, int]):
+class RemoteValueDpt2ByteUnsigned(RemoteValue[int]):
     """Abstraction for remote value of KNX DPT 7.001."""
 
     def __init__(
@@ -40,16 +39,10 @@ class RemoteValueDpt2ByteUnsigned(RemoteValue[DPTArray, int]):
             after_update_cb=after_update_cb,
         )
 
-    def payload_valid(self, payload: DPTArray | DPTBinary | None) -> DPTArray:
-        """Test if telegram payload may be parsed."""
-        if isinstance(payload, DPTArray) and len(payload.value) == 2:
-            return payload
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
     def to_knx(self, value: int) -> DPTArray:
         """Convert value to payload."""
         return DPT2ByteUnsigned.to_knx(value)
 
-    def from_knx(self, payload: DPTArray) -> int:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> int:
         """Convert current payload to value."""
-        return DPT2ByteUnsigned.from_knx(payload.value)
+        return DPT2ByteUnsigned.from_knx(payload)
diff --git a/xknx/remote_value/remote_value_dpt_value_1_ucount.py b/xknx/remote_value/remote_value_dpt_value_1_ucount.py
index 87404101..f55e7a7d 100644
--- a/xknx/remote_value/remote_value_dpt_value_1_ucount.py
+++ b/xknx/remote_value/remote_value_dpt_value_1_ucount.py
@@ -6,24 +6,17 @@ DPT 5.010.
 from __future__ import annotations
 
 from xknx.dpt import DPTArray, DPTBinary, DPTValue1Ucount
-from xknx.exceptions import CouldNotParseTelegram
 
 from .remote_value import RemoteValue
 
 
-class RemoteValueDptValue1Ucount(RemoteValue[DPTArray, int]):
+class RemoteValueDptValue1Ucount(RemoteValue[int]):
     """Abstraction for remote value of KNX DPT 5.010."""
 
-    def payload_valid(self, payload: DPTArray | DPTBinary | None) -> DPTArray:
-        """Test if telegram payload may be parsed."""
-        if isinstance(payload, DPTArray) and len(payload.value) == 1:
-            return payload
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
     def to_knx(self, value: int) -> DPTArray:
         """Convert value to payload."""
         return DPTValue1Ucount.to_knx(value)
 
-    def from_knx(self, payload: DPTArray) -> int:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> int:
         """Convert current payload to value."""
-        return DPTValue1Ucount.from_knx(payload.value)
+        return DPTValue1Ucount.from_knx(payload)
diff --git a/xknx/remote_value/remote_value_raw.py b/xknx/remote_value/remote_value_raw.py
index a1e43915..620453a9 100644
--- a/xknx/remote_value/remote_value_raw.py
+++ b/xknx/remote_value/remote_value_raw.py
@@ -6,7 +6,7 @@ for serialization and deserialization of the KNX value.
 """
 from __future__ import annotations
 
-from typing import TYPE_CHECKING, Union
+from typing import TYPE_CHECKING
 
 from xknx.dpt.dpt import DPTArray, DPTBinary
 from xknx.exceptions import ConversionError, CouldNotParseTelegram
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
     from xknx.xknx import XKNX
 
 
-class RemoteValueRaw(RemoteValue[Union[DPTArray, DPTBinary], int]):
+class RemoteValueRaw(RemoteValue[int]):
     """Abstraction for raw values."""
 
     def __init__(
@@ -43,16 +43,6 @@ class RemoteValueRaw(RemoteValue[Union[DPTArray, DPTBinary], int]):
             after_update_cb=after_update_cb,
         )
 
-    def payload_valid(
-        self, payload: DPTArray | DPTBinary | None
-    ) -> DPTArray | DPTBinary:
-        """Test if telegram payload may be parsed."""
-        if isinstance(payload, DPTBinary) and self.payload_length == 0:
-            return payload
-        if isinstance(payload, DPTArray) and len(payload.value) == self.payload_length:
-            return payload
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
     def to_knx(self, value: int) -> DPTArray | DPTBinary:
         """Convert value to payload."""
         if self.payload_length == 0:
@@ -69,9 +59,13 @@ class RemoteValueRaw(RemoteValue[Union[DPTArray, DPTBinary], int]):
 
     def from_knx(self, payload: DPTArray | DPTBinary) -> int:
         """Convert current payload to value."""
-        if isinstance(payload, DPTBinary):
+        if isinstance(payload, DPTBinary) and self.payload_length == 0:
             return payload.value
-        try:
-            return int.from_bytes(payload.value, byteorder="big")
-        except ValueError as err:
-            raise ConversionError("Could not parse payload", payload=payload) from err
+        if isinstance(payload, DPTArray) and len(payload.value) == self.payload_length:
+            try:
+                return int.from_bytes(payload.value, byteorder="big")
+            except ValueError as err:
+                raise ConversionError(
+                    "Could not parse payload", payload=payload
+                ) from err
+        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
diff --git a/xknx/remote_value/remote_value_scaling.py b/xknx/remote_value/remote_value_scaling.py
index 7559aef9..a8239524 100644
--- a/xknx/remote_value/remote_value_scaling.py
+++ b/xknx/remote_value/remote_value_scaling.py
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
     from xknx.xknx import XKNX
 
 
-class RemoteValueScaling(RemoteValue[DPTArray, int]):
+class RemoteValueScaling(RemoteValue[int]):
     """Abstraction for remote value of KNX DPT 5.001 (DPT_Scaling)."""
 
     def __init__(
@@ -44,20 +44,16 @@ class RemoteValueScaling(RemoteValue[DPTArray, int]):
         self.range_from = range_from
         self.range_to = range_to
 
-    def payload_valid(self, payload: DPTArray | DPTBinary | None) -> DPTArray:
-        """Test if telegram payload may be parsed."""
-        if isinstance(payload, DPTArray) and len(payload.value) == 1:
-            return payload
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
     def to_knx(self, value: float) -> DPTArray:
         """Convert value to payload."""
         knx_value = self._calc_to_knx(self.range_from, self.range_to, value)
         return DPTArray(knx_value)
 
-    def from_knx(self, payload: DPTArray) -> int:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> int:
         """Convert current payload to value."""
-        return self._calc_from_knx(self.range_from, self.range_to, payload.value[0])
+        if isinstance(payload, DPTArray) and len(payload.value) == 1:
+            return self._calc_from_knx(self.range_from, self.range_to, payload.value[0])
+        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
 
     @property
     def unit_of_measurement(self) -> str | None:
diff --git a/xknx/remote_value/remote_value_scene_number.py b/xknx/remote_value/remote_value_scene_number.py
index 2c5a2fce..c1e32c38 100644
--- a/xknx/remote_value/remote_value_scene_number.py
+++ b/xknx/remote_value/remote_value_scene_number.py
@@ -6,24 +6,17 @@ DPT 17.001.
 from __future__ import annotations
 
 from xknx.dpt import DPTArray, DPTBinary, DPTSceneNumber
-from xknx.exceptions import CouldNotParseTelegram
 
 from .remote_value import RemoteValue
 
 
-class RemoteValueSceneNumber(RemoteValue[DPTArray, int]):
+class RemoteValueSceneNumber(RemoteValue[int]):
     """Abstraction for remote value of KNX DPT 17.001 (DPT_Scene_Number)."""
 
-    def payload_valid(self, payload: DPTArray | DPTBinary | None) -> DPTArray:
-        """Test if telegram payload may be parsed."""
-        if isinstance(payload, DPTArray) and len(payload.value) == 1:
-            return payload
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
     def to_knx(self, value: int) -> DPTArray:
         """Convert value to payload."""
         return DPTSceneNumber.to_knx(value)
 
-    def from_knx(self, payload: DPTArray) -> int:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> int:
         """Convert current payload to value."""
-        return DPTSceneNumber.from_knx(payload.value)
+        return DPTSceneNumber.from_knx(payload)
diff --git a/xknx/remote_value/remote_value_sensor.py b/xknx/remote_value/remote_value_sensor.py
index 2c4ede2b..8f5e4fe7 100644
--- a/xknx/remote_value/remote_value_sensor.py
+++ b/xknx/remote_value/remote_value_sensor.py
@@ -10,7 +10,7 @@ from abc import abstractmethod
 from typing import TYPE_CHECKING, TypeVar, Union
 
 from xknx.dpt import DPTArray, DPTBase, DPTBinary, DPTNumeric, DPTString
-from xknx.exceptions import ConversionError, CouldNotParseTelegram
+from xknx.exceptions import ConversionError
 
 from .remote_value import AsyncCallbackType, GroupAddressesType, RemoteValue
 
@@ -20,7 +20,7 @@ if TYPE_CHECKING:
 ValueT = TypeVar("ValueT")
 
 
-class _RemoteValueGeneric(RemoteValue[DPTArray, ValueT]):
+class _RemoteValueGeneric(RemoteValue[ValueT]):
     """Abstraction for generic DPT types."""
 
     dpt_base_class: type[DPTBase]
@@ -60,21 +60,12 @@ class _RemoteValueGeneric(RemoteValue[DPTArray, ValueT]):
             after_update_cb=after_update_cb,
         )
 
-    def payload_valid(self, payload: DPTArray | DPTBinary | None) -> DPTArray:
-        """Test if telegram payload may be parsed."""
-        if (
-            isinstance(payload, DPTArray)
-            and len(payload.value) == self.dpt_class.payload_length
-        ):
-            return payload
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
     def to_knx(self, value: ValueT) -> DPTArray:
         """Convert value to payload."""
         return self.dpt_class.to_knx(value)  # type: ignore[return-value]
 
     @abstractmethod
-    def from_knx(self, payload: DPTArray) -> ValueT:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> ValueT:
         """Convert current payload to value."""
 
     @property
@@ -94,9 +85,9 @@ class RemoteValueSensor(_RemoteValueGeneric[Union[int, float, str]]):
     dpt_base_class = DPTBase
     dpt_class: type[DPTBase]
 
-    def from_knx(self, payload: DPTArray) -> int | float | str:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> int | float | str:
         """Convert current payload to value."""
-        return self.dpt_class.from_knx(payload.value)  # type: ignore
+        return self.dpt_class.from_knx(payload)  # type: ignore[no-any-return]
 
 
 class RemoteValueNumeric(_RemoteValueGeneric[Union[int, float]]):
@@ -105,9 +96,9 @@ class RemoteValueNumeric(_RemoteValueGeneric[Union[int, float]]):
     dpt_base_class = DPTNumeric
     dpt_class: type[DPTNumeric]
 
-    def from_knx(self, payload: DPTArray) -> int | float:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> int | float:
         """Convert current payload to value."""
-        return self.dpt_class.from_knx(payload.value)
+        return self.dpt_class.from_knx(payload)
 
 
 class RemoteValueString(_RemoteValueGeneric[str]):
@@ -117,6 +108,6 @@ class RemoteValueString(_RemoteValueGeneric[str]):
     dpt_class: type[DPTString]
     _default_dpt_class = DPTString
 
-    def from_knx(self, payload: DPTArray) -> str:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> str:
         """Convert current payload to value."""
-        return self.dpt_class.from_knx(payload.value)
+        return self.dpt_class.from_knx(payload)
diff --git a/xknx/remote_value/remote_value_setpoint_shift.py b/xknx/remote_value/remote_value_setpoint_shift.py
index e9204f13..e9977bd3 100644
--- a/xknx/remote_value/remote_value_setpoint_shift.py
+++ b/xknx/remote_value/remote_value_setpoint_shift.py
@@ -24,7 +24,7 @@ class SetpointShiftMode(Enum):
     DPT9002 = DPTTemperature
 
 
-class RemoteValueSetpointShift(RemoteValue[DPTArray, float]):
+class RemoteValueSetpointShift(RemoteValue[float]):
     """Abstraction for remote value of KNX DPT 6.010."""
 
     def __init__(
@@ -54,21 +54,6 @@ class RemoteValueSetpointShift(RemoteValue[DPTArray, float]):
         )
         self.setpoint_shift_step = setpoint_shift_step
 
-    def payload_valid(self, payload: DPTArray | DPTBinary | None) -> DPTArray:
-        """Test if telegram payload may be parsed."""
-        if isinstance(payload, DPTArray):
-            payload_length = len(payload.value)
-            if self.dpt_class is None:
-                if payload_length == DPTTemperature.payload_length:
-                    self.dpt_class = DPTTemperature
-                    return payload
-                if payload_length == DPTValue1Count.payload_length:
-                    self.dpt_class = DPTValue1Count
-                    return payload
-            elif payload_length == self.dpt_class.payload_length:
-                return payload
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
     def to_knx(self, value: float) -> DPTArray:
         """Convert value to payload."""
         if self.dpt_class is None:
@@ -80,10 +65,24 @@ class RemoteValueSetpointShift(RemoteValue[DPTArray, float]):
             return DPTValue1Count.to_knx(converted_value)
         return DPTTemperature.to_knx(value)
 
-    def from_knx(self, payload: DPTArray) -> float:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> float:
         """Convert current payload to value."""
-        assert self.dpt_class is not None  # checked by payload_valid() from process()
-        payload_value = self.dpt_class.from_knx(payload.value)
+        if self.dpt_class is None:
+            self.dpt_class = self._determine_dpt_class(payload)
+
+        payload_value = self.dpt_class.from_knx(payload)
         if self.dpt_class == DPTValue1Count:
             return payload_value * self.setpoint_shift_step
         return payload_value
+
+    def _determine_dpt_class(
+        self, payload: DPTArray | DPTBinary
+    ) -> type[DPTValue1Count | DPTTemperature]:
+        """Test if telegram payload may be parsed."""
+        if isinstance(payload, DPTArray):
+            payload_length = len(payload.value)
+            if payload_length == DPTTemperature.payload_length:
+                return DPTTemperature
+            if payload_length == DPTValue1Count.payload_length:
+                return DPTValue1Count
+        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
diff --git a/xknx/remote_value/remote_value_step.py b/xknx/remote_value/remote_value_step.py
index 682af6a6..667efac7 100644
--- a/xknx/remote_value/remote_value_step.py
+++ b/xknx/remote_value/remote_value_step.py
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
     from xknx.xknx import XKNX
 
 
-class RemoteValueStep(RemoteValue[DPTBinary, "RemoteValueStep.Direction"]):
+class RemoteValueStep(RemoteValue["RemoteValueStep.Direction"]):
     """Abstraction for remote value of KNX DPT 1.007 / DPT_Step."""
 
     class Direction(Enum):
@@ -47,12 +47,6 @@ class RemoteValueStep(RemoteValue[DPTBinary, "RemoteValueStep.Direction"]):
         )
         self.invert = invert
 
-    def payload_valid(self, payload: DPTArray | DPTBinary | None) -> DPTBinary:
-        """Test if telegram payload may be parsed."""
-        if isinstance(payload, DPTBinary):
-            return payload
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
     # from KNX Association System Specifications AS v1.5.00:
     # 1.007 DPT_Step   0 = Decrease 1 = Increase
     # 1.008 DPT_UpDown 0 = Up       1 = Down
@@ -70,14 +64,14 @@ class RemoteValueStep(RemoteValue[DPTBinary, "RemoteValueStep.Direction"]):
             feature_name=self.feature_name,
         )
 
-    def from_knx(self, payload: DPTBinary) -> RemoteValueStep.Direction:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> RemoteValueStep.Direction:
         """Convert current payload to value."""
-        if payload == DPTBinary(1):
+        if payload.value == 1:
             return self.Direction.DECREASE if self.invert else self.Direction.INCREASE
-        if payload == DPTBinary(0):
+        if payload.value == 0:
             return self.Direction.INCREASE if self.invert else self.Direction.DECREASE
         raise CouldNotParseTelegram(
-            "payload invalid",
+            "Payload invalid",
             payload=payload,
             device_name=self.device_name,
             feature_name=self.feature_name,
diff --git a/xknx/remote_value/remote_value_switch.py b/xknx/remote_value/remote_value_switch.py
index 618722a2..14b4c301 100644
--- a/xknx/remote_value/remote_value_switch.py
+++ b/xknx/remote_value/remote_value_switch.py
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
     from xknx.xknx import XKNX
 
 
-class RemoteValueSwitch(RemoteValue[DPTBinary, bool]):
+class RemoteValueSwitch(RemoteValue[bool]):
     """Abstraction for remote value of KNX DPT 1.001 / DPT_Switch."""
 
     def __init__(
@@ -42,12 +42,6 @@ class RemoteValueSwitch(RemoteValue[DPTBinary, bool]):
         )
         self.invert = bool(invert)
 
-    def payload_valid(self, payload: DPTArray | DPTBinary | None) -> DPTBinary:
-        """Test if telegram payload may be parsed."""
-        if isinstance(payload, DPTBinary):
-            return payload
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
     def to_knx(self, value: bool) -> DPTBinary:
         """Convert value to payload."""
         if isinstance(value, bool):
@@ -59,14 +53,14 @@ class RemoteValueSwitch(RemoteValue[DPTBinary, bool]):
             feature_name=self.feature_name,
         )
 
-    def from_knx(self, payload: DPTBinary) -> bool:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> bool:
         """Convert current payload to value."""
-        if payload == DPTBinary(0):
+        if payload.value == 0:
             return self.invert
-        if payload == DPTBinary(1):
+        if payload.value == 1:
             return not self.invert
         raise CouldNotParseTelegram(
-            "payload invalid",
+            "Payload invalid",
             payload=payload,
             device_name=self.device_name,
             feature_name=self.feature_name,
diff --git a/xknx/remote_value/remote_value_temp.py b/xknx/remote_value/remote_value_temp.py
index 085d531f..2d3281ae 100644
--- a/xknx/remote_value/remote_value_temp.py
+++ b/xknx/remote_value/remote_value_temp.py
@@ -6,24 +6,17 @@ DPT 9.001.
 from __future__ import annotations
 
 from xknx.dpt import DPTArray, DPTBinary, DPTTemperature
-from xknx.exceptions import CouldNotParseTelegram
 
 from .remote_value import RemoteValue
 
 
-class RemoteValueTemp(RemoteValue[DPTArray, float]):
+class RemoteValueTemp(RemoteValue[float]):
     """Abstraction for remote value of KNX 9.001 (DPT_Value_Temp)."""
 
-    def payload_valid(self, payload: DPTArray | DPTBinary | None) -> DPTArray:
-        """Test if telegram payload may be parsed."""
-        if isinstance(payload, DPTArray) and len(payload.value) == 2:
-            return payload
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
     def to_knx(self, value: float) -> DPTArray:
         """Convert value to payload."""
         return DPTTemperature.to_knx(value)
 
-    def from_knx(self, payload: DPTArray) -> float:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> float:
         """Convert current payload to value."""
-        return DPTTemperature.from_knx(payload.value)
+        return DPTTemperature.from_knx(payload)
diff --git a/xknx/remote_value/remote_value_updown.py b/xknx/remote_value/remote_value_updown.py
index 496589be..3994ee3c 100644
--- a/xknx/remote_value/remote_value_updown.py
+++ b/xknx/remote_value/remote_value_updown.py
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
     from xknx.xknx import XKNX
 
 
-class RemoteValueUpDown(RemoteValue[DPTBinary, "RemoteValueUpDown.Direction"]):
+class RemoteValueUpDown(RemoteValue["RemoteValueUpDown.Direction"]):
     """Abstraction for remote value of KNX DPT 1.008 / DPT_UpDown."""
 
     class Direction(Enum):
@@ -47,12 +47,6 @@ class RemoteValueUpDown(RemoteValue[DPTBinary, "RemoteValueUpDown.Direction"]):
         )
         self.invert = invert
 
-    def payload_valid(self, payload: DPTArray | DPTBinary | None) -> DPTBinary:
-        """Test if telegram payload may be parsed."""
-        if isinstance(payload, DPTBinary):
-            return payload
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
     def to_knx(self, value: RemoteValueUpDown.Direction) -> DPTBinary:
         """Convert value to payload."""
         if value == self.Direction.UP:
@@ -66,14 +60,14 @@ class RemoteValueUpDown(RemoteValue[DPTBinary, "RemoteValueUpDown.Direction"]):
             feature_name=self.feature_name,
         )
 
-    def from_knx(self, payload: DPTBinary) -> RemoteValueUpDown.Direction:
+    def from_knx(self, payload: DPTArray | DPTBinary) -> RemoteValueUpDown.Direction:
         """Convert current payload to value."""
-        if payload == DPTBinary(0):
+        if payload.value == 0:
             return self.Direction.DOWN if self.invert else self.Direction.UP
-        if payload == DPTBinary(1):
+        if payload.value == 1:
             return self.Direction.UP if self.invert else self.Direction.DOWN
         raise CouldNotParseTelegram(
-            "payload invalid",
+            "Payload invalid",
             payload=payload,
             device_name=self.device_name,
             feature_name=self.feature_name,
diff --git a/xknx/tools/group_communication.py b/xknx/tools/group_communication.py
index 6d8c5e07..5ba75cd8 100644
--- a/xknx/tools/group_communication.py
+++ b/xknx/tools/group_communication.py
@@ -75,7 +75,7 @@ async def read_group_value(
     xknx: XKNX,
     group_address: DeviceAddressableType,
     value_type: int | str | type[DPTBase] | None = None,
-) -> DPTArray | DPTBinary | Any | None:
+) -> int | tuple[int, ...] | Any | None:
     """Read a value from a KNX group address."""
     transcoder = _parse_dpt(value_type)
     value_reader = ValueReader(xknx, parse_device_group_address(group_address))
@@ -83,7 +83,7 @@ async def read_group_value(
     if response is not None:
         assert isinstance(response.payload, (GroupValueWrite, GroupValueResponse))
         if transcoder is not None:
-            return transcoder.from_knx(response.payload.value.value)  # type: ignore[arg-type]
+            return transcoder.from_knx(response.payload.value)
         return response.payload.value.value
     return None
 


Test patch:
diff --git a/requirements/testing.txt b/requirements/testing.txt
index fd6084d0..3da981ab 100644
--- a/requirements/testing.txt
+++ b/requirements/testing.txt
@@ -8,6 +8,6 @@ pylint==2.17.1
 pytest==7.2.2
 pytest-asyncio==0.21.0
 pytest-cov==4.0.0
-setuptools==67.6.0
+setuptools==67.6.1
 tox==4.4.8
 mypy==1.1.1
diff --git a/test/dpt_tests/dpt_1byte_signed_test.py b/test/dpt_tests/dpt_1byte_signed_test.py
index f59fb70a..9ef5636d 100644
--- a/test/dpt_tests/dpt_1byte_signed_test.py
+++ b/test/dpt_tests/dpt_1byte_signed_test.py
@@ -8,53 +8,24 @@ from xknx.exceptions import ConversionError
 class TestDPTRelativeValue:
     """Test class for KNX DPT Relative Value."""
 
-    def test_from_knx_positive(self):
-        """Test positive value from KNX."""
-        assert DPTSignedRelativeValue.from_knx((0x00,)) == 0
-        assert DPTSignedRelativeValue.from_knx((0x01,)) == 1
-        assert DPTSignedRelativeValue.from_knx((0x02,)) == 2
-        assert DPTSignedRelativeValue.from_knx((0x64,)) == 100
-        assert DPTSignedRelativeValue.from_knx((0x7F,)) == 127
-
-    def test_from_knx_negative(self):
-        """Test negative value from KNX."""
-        assert DPTSignedRelativeValue.from_knx((0x80,)) == -128
-        assert DPTSignedRelativeValue.from_knx((0x9C,)) == -100
-        assert DPTSignedRelativeValue.from_knx((0xFE,)) == -2
-        assert DPTSignedRelativeValue.from_knx((0xFF,)) == -1
-
-    def test_to_knx_positive(self):
-        """Test positive value to KNX."""
-        assert DPTSignedRelativeValue.to_knx(0) == DPTArray(
-            0x00,
-        )
-        assert DPTSignedRelativeValue.to_knx(1) == DPTArray(
-            0x01,
-        )
-        assert DPTSignedRelativeValue.to_knx(2) == DPTArray(
-            0x02,
-        )
-        assert DPTSignedRelativeValue.to_knx(100) == DPTArray(
-            0x64,
-        )
-        assert DPTSignedRelativeValue.to_knx(127) == DPTArray(
-            0x7F,
-        )
-
-    def test_to_knx_negative(self):
-        """Test negative value to KNX."""
-        assert DPTSignedRelativeValue.to_knx(-128) == DPTArray(
-            0x80,
-        )
-        assert DPTSignedRelativeValue.to_knx(-100) == DPTArray(
-            0x9C,
-        )
-        assert DPTSignedRelativeValue.to_knx(-2) == DPTArray(
-            0xFE,
-        )
-        assert DPTSignedRelativeValue.to_knx(-1) == DPTArray(
-            0xFF,
-        )
+    @pytest.mark.parametrize(
+        ("raw", "value"),
+        [
+            ((0x00,), 0),
+            ((0x01,), 1),
+            ((0x02,), 2),
+            ((0x64,), 100),
+            ((0x7F,), 127),
+            ((0x80,), -128),
+            ((0x9C,), -100),
+            ((0xFE,), -2),
+            ((0xFF,), -1),
+        ],
+    )
+    def test_transcoder(self, raw, value):
+        """Test value from and to KNX."""
+        assert DPTSignedRelativeValue.from_knx(DPTArray(raw)) == value
+        assert DPTSignedRelativeValue.to_knx(value) == DPTArray(raw)
 
     def test_assert_min_exceeded(self):
         """Test initialization with wrong value (Underflow)."""
diff --git a/test/dpt_tests/dpt_1byte_uint_test.py b/test/dpt_tests/dpt_1byte_uint_test.py
index f86ea442..1b275170 100644
--- a/test/dpt_tests/dpt_1byte_uint_test.py
+++ b/test/dpt_tests/dpt_1byte_uint_test.py
@@ -2,7 +2,7 @@
 import pytest
 
 from xknx.dpt import DPTArray, DPTTariff, DPTValue1Ucount
-from xknx.exceptions import ConversionError
+from xknx.exceptions import ConversionError, CouldNotParseTelegram
 
 
 class TestDPTValue1Ucount:
@@ -11,17 +11,17 @@ class TestDPTValue1Ucount:
     def test_value_50(self):
         """Test parsing and streaming of DPTValue1Ucount 50."""
         assert DPTValue1Ucount.to_knx(50) == DPTArray(0x32)
-        assert DPTValue1Ucount.from_knx((0x32,)) == 50
+        assert DPTValue1Ucount.from_knx(DPTArray((0x32,))) == 50
 
     def test_value_max(self):
         """Test parsing and streaming of DPTValue1Ucount 255."""
         assert DPTValue1Ucount.to_knx(255) == DPTArray(0xFF)
-        assert DPTValue1Ucount.from_knx((0xFF,)) == 255
+        assert DPTValue1Ucount.from_knx(DPTArray((0xFF,))) == 255
 
     def test_value_min(self):
         """Test parsing and streaming of DPTValue1Ucount 0."""
         assert DPTValue1Ucount.to_knx(0) == DPTArray(0x00)
-        assert DPTValue1Ucount.from_knx((0x00,)) == 0
+        assert DPTValue1Ucount.from_knx(DPTArray((0x00,))) == 0
 
     def test_to_knx_min_exceeded(self):
         """Test parsing of DPTValue1Ucount with wrong value (underflow)."""
@@ -40,17 +40,17 @@ class TestDPTValue1Ucount:
 
     def test_from_knx_wrong_parameter(self):
         """Test parsing of DPTValue1Ucount with wrong value (3 byte array)."""
-        with pytest.raises(ConversionError):
-            DPTValue1Ucount.from_knx((0x01, 0x02, 0x03))
+        with pytest.raises(CouldNotParseTelegram):
+            DPTValue1Ucount.from_knx(DPTArray((0x01, 0x02, 0x03)))
 
     def test_from_knx_wrong_value(self):
         """Test parsing of DPTValue1Ucount with value which exceeds limits."""
         with pytest.raises(ConversionError):
-            DPTValue1Ucount.from_knx((0x256,))
+            DPTValue1Ucount.from_knx(DPTArray((0x256,)))
 
     def test_from_knx_wrong_parameter2(self):
         """Test parsing of DPTValue1Ucount with wrong value (array containing string)."""
-        with pytest.raises(ConversionError):
+        with pytest.raises(CouldNotParseTelegram):
             DPTValue1Ucount.from_knx("0x23")
 
 
@@ -60,4 +60,4 @@ class TestDPTTariff:
     def test_from_knx_max_exceeded(self):
         """Test parsing of DPTTariff with wrong value (overflow)."""
         with pytest.raises(ConversionError):
-            DPTTariff.from_knx((0xFF,))
+            DPTTariff.from_knx(DPTArray((0xFF,)))
diff --git a/test/dpt_tests/dpt_2byte_signed_test.py b/test/dpt_tests/dpt_2byte_signed_test.py
index 9b9527fb..37ac54c3 100644
--- a/test/dpt_tests/dpt_2byte_signed_test.py
+++ b/test/dpt_tests/dpt_2byte_signed_test.py
@@ -5,7 +5,7 @@ from unittest.mock import patch
 import pytest
 
 from xknx.dpt import DPT2ByteSigned, DPTArray
-from xknx.exceptions import ConversionError
+from xknx.exceptions import ConversionError, CouldNotParseTelegram
 
 
 class TestDPT2ByteSigned:
@@ -29,29 +29,29 @@ class TestDPT2ByteSigned:
     def test_signed_value_max_value(self):
         """Test DPT2ByteSigned parsing and streaming."""
         assert DPT2ByteSigned.to_knx(32767) == DPTArray((0x7F, 0xFF))
-        assert DPT2ByteSigned.from_knx((0x7F, 0xFF)) == 32767
+        assert DPT2ByteSigned.from_knx(DPTArray((0x7F, 0xFF))) == 32767
 
     def test_signed_value_min_value(self):
         """Test DPT2ByteSigned parsing and streaming with null values."""
         assert DPT2ByteSigned.to_knx(-20480) == DPTArray((0xB0, 0x00))
-        assert DPT2ByteSigned.from_knx((0xB0, 0x00)) == -20480
+        assert DPT2ByteSigned.from_knx(DPTArray((0xB0, 0x00))) == -20480
 
     def test_signed_value_0123(self):
         """Test DPT2ByteSigned parsing and streaming."""
         assert DPT2ByteSigned.to_knx(291) == DPTArray((0x01, 0x23))
-        assert DPT2ByteSigned.from_knx((0x01, 0x23)) == 291
+        assert DPT2ByteSigned.from_knx(DPTArray((0x01, 0x23))) == 291
 
     def test_signed_wrong_value_from_knx(self):
         """Test DPT2ByteSigned parsing with wrong value."""
-        with pytest.raises(ConversionError):
-            DPT2ByteSigned.from_knx((0xFF, 0x4E, 0x12))
+        with pytest.raises(CouldNotParseTelegram):
+            DPT2ByteSigned.from_knx(DPTArray((0xFF, 0x4E, 0x12)))
 
     def test_from_knx_unpack_error(self):
         """Test DPT2ByteSigned parsing with unpack error."""
         with patch("struct.unpack") as unpack_mock:
             unpack_mock.side_effect = struct.error()
             with pytest.raises(ConversionError):
-                DPT2ByteSigned.from_knx((0x01, 0x23))
+                DPT2ByteSigned.from_knx(DPTArray((0x01, 0x23)))
 
     def test_to_knx_pack_error(self):
         """Test serializing DPT2ByteSigned with pack error."""
diff --git a/test/dpt_tests/dpt_2byte_uint_test.py b/test/dpt_tests/dpt_2byte_uint_test.py
index 5b3e645b..e827515e 100644
--- a/test/dpt_tests/dpt_2byte_uint_test.py
+++ b/test/dpt_tests/dpt_2byte_uint_test.py
@@ -2,7 +2,7 @@
 import pytest
 
 from xknx.dpt import DPTArray, DPTUElCurrentmA
-from xknx.exceptions import ConversionError
+from xknx.exceptions import ConversionError, CouldNotParseTelegram
 
 
 class TestDPT2byte:
@@ -31,29 +31,29 @@ class TestDPT2byte:
     def test_current_value_max_value(self):
         """Test DPTUElCurrentmA parsing and streaming."""
         assert DPTUElCurrentmA.to_knx(65535) == DPTArray((0xFF, 0xFF))
-        assert DPTUElCurrentmA.from_knx((0xFF, 0xFF)) == 65535
+        assert DPTUElCurrentmA.from_knx(DPTArray((0xFF, 0xFF))) == 65535
 
     def test_current_value_min_value(self):
         """Test DPTUElCurrentmA parsing and streaming with null values."""
         assert DPTUElCurrentmA.to_knx(0) == DPTArray((0x00, 0x00))
-        assert DPTUElCurrentmA.from_knx((0x00, 0x00)) == 0
+        assert DPTUElCurrentmA.from_knx(DPTArray((0x00, 0x00))) == 0
 
     def test_current_value_38(self):
         """Test DPTUElCurrentmA parsing and streaming 38mA."""
         assert DPTUElCurrentmA.to_knx(38) == DPTArray((0x00, 0x26))
-        assert DPTUElCurrentmA.from_knx((0x00, 0x26)) == 38
+        assert DPTUElCurrentmA.from_knx(DPTArray((0x00, 0x26))) == 38
 
     def test_current_value_78(self):
         """Test DPTUElCurrentmA parsing and streaming 78mA."""
         assert DPTUElCurrentmA.to_knx(78) == DPTArray((0x00, 0x4E))
-        assert DPTUElCurrentmA.from_knx((0x00, 0x4E)) == 78
+        assert DPTUElCurrentmA.from_knx(DPTArray((0x00, 0x4E))) == 78
 
     def test_current_value_1234(self):
         """Test DPTUElCurrentmA parsing and streaming 4660mA."""
         assert DPTUElCurrentmA.to_knx(4660) == DPTArray((0x12, 0x34))
-        assert DPTUElCurrentmA.from_knx((0x12, 0x34)) == 4660
+        assert DPTUElCurrentmA.from_knx(DPTArray((0x12, 0x34))) == 4660
 
     def test_current_wrong_value_from_knx(self):
         """Test DPTUElCurrentmA parsing with wrong value."""
-        with pytest.raises(ConversionError):
-            DPTUElCurrentmA.from_knx((0xFF, 0x4E, 0x12))
+        with pytest.raises(CouldNotParseTelegram):
+            DPTUElCurrentmA.from_knx(DPTArray((0xFF, 0x4E, 0x12)))
diff --git a/test/dpt_tests/dpt_4bit_control_test.py b/test/dpt_tests/dpt_4bit_control_test.py
index 5fd46ea3..13365abf 100644
--- a/test/dpt_tests/dpt_4bit_control_test.py
+++ b/test/dpt_tests/dpt_4bit_control_test.py
@@ -9,7 +9,7 @@ from xknx.dpt import (
     DPTControlStepCode,
     DPTControlStepwise,
 )
-from xknx.exceptions import ConversionError
+from xknx.exceptions import ConversionError, CouldNotParseTelegram
 
 
 class TestDPTControlStepCode:
@@ -61,13 +61,13 @@ class TestDPTControlStepCode:
         for raw in range(16):
             control = 1 if raw >> 3 else 0
             valueref = {"control": control, "step_code": raw & 0x07}
-            value = DPTControlStepCode.from_knx((raw,))
+            value = DPTControlStepCode.from_knx(DPTBinary((raw,)))
             assert value == valueref
 
     def test_from_knx_wrong_value(self):
         """Test parsing invalid DPTControlStepCode type from KNX."""
         with pytest.raises(ConversionError):
-            DPTControlStepCode.from_knx((0x1F,))
+            DPTControlStepCode.from_knx(DPTBinary((0x1F,)))
 
     def test_unit(self):
         """Test unit_of_measurement function."""
@@ -77,52 +77,64 @@ class TestDPTControlStepCode:
 class TestDPTControlStepwise:
     """Test class for DPTControlStepwise objects."""
 
-    def test_to_knx(self):
+    @pytest.mark.parametrize(
+        ("raw", "value"),
+        [
+            (0xF, 1),
+            (0xE, 3),
+            (0xD, 6),
+            (0xC, 12),
+            (0xB, 25),
+            (0xA, 50),
+            (0x9, 100),
+            (0x7, -1),
+            (0x6, -3),
+            (0x5, -6),
+            (0x4, -12),
+            (0x3, -25),
+            (0x2, -50),
+            (0x1, -100),
+            (0x0, 0),
+        ],
+    )
+    def test_to_knx(self, raw, value):
         """Test serializing values to DPTControlStepwise."""
-        assert DPTControlStepwise.to_knx(1) == DPTBinary(0xF)
-        assert DPTControlStepwise.to_knx(3) == DPTBinary(0xE)
-        assert DPTControlStepwise.to_knx(6) == DPTBinary(0xD)
-        assert DPTControlStepwise.to_knx(12) == DPTBinary(0xC)
-        assert DPTControlStepwise.to_knx(25) == DPTBinary(0xB)
-        assert DPTControlStepwise.to_knx(50) == DPTBinary(0xA)
-        assert DPTControlStepwise.to_knx(100) == DPTBinary(0x9)
-        assert DPTControlStepwise.to_knx(-1) == DPTBinary(0x7)
-        assert DPTControlStepwise.to_knx(-3) == DPTBinary(0x6)
-        assert DPTControlStepwise.to_knx(-6) == DPTBinary(0x5)
-        assert DPTControlStepwise.to_knx(-12) == DPTBinary(0x4)
-        assert DPTControlStepwise.to_knx(-25) == DPTBinary(0x3)
-        assert DPTControlStepwise.to_knx(-50) == DPTBinary(0x2)
-        assert DPTControlStepwise.to_knx(-100) == DPTBinary(0x1)
-        assert DPTControlStepwise.to_knx(0) == DPTBinary(0x0)
+        assert DPTControlStepwise.to_knx(value) == DPTBinary(raw)
 
     def test_to_knx_wrong_type(self):
         """Test serializing wrong type to DPTControlStepwise."""
         with pytest.raises(ConversionError):
             DPTControlStepwise.to_knx("")
 
-    def test_from_knx(self):
+    @pytest.mark.parametrize(
+        ("raw", "value"),
+        [
+            (0xF, 1),
+            (0xE, 3),
+            (0xD, 6),
+            (0xC, 12),
+            (0xB, 25),
+            (0xA, 50),
+            (0x9, 100),
+            (0x8, 0),
+            (0x7, -1),
+            (0x6, -3),
+            (0x5, -6),
+            (0x4, -12),
+            (0x3, -25),
+            (0x2, -50),
+            (0x1, -100),
+            (0x0, 0),
+        ],
+    )
+    def test_from_knx(self, raw, value):
         """Test parsing DPTControlStepwise types from KNX."""
-        assert DPTControlStepwise.from_knx((0xF,)) == 1
-        assert DPTControlStepwise.from_knx((0xE,)) == 3
-        assert DPTControlStepwise.from_knx((0xD,)) == 6
-        assert DPTControlStepwise.from_knx((0xC,)) == 12
-        assert DPTControlStepwise.from_knx((0xB,)) == 25
-        assert DPTControlStepwise.from_knx((0xA,)) == 50
-        assert DPTControlStepwise.from_knx((0x9,)) == 100
-        assert DPTControlStepwise.from_knx((0x8,)) == 0
-        assert DPTControlStepwise.from_knx((0x7,)) == -1
-        assert DPTControlStepwise.from_knx((0x6,)) == -3
-        assert DPTControlStepwise.from_knx((0x5,)) == -6
-        assert DPTControlStepwise.from_knx((0x4,)) == -12
-        assert DPTControlStepwise.from_knx((0x3,)) == -25
-        assert DPTControlStepwise.from_knx((0x2,)) == -50
-        assert DPTControlStepwise.from_knx((0x1,)) == -100
-        assert DPTControlStepwise.from_knx((0x0,)) == 0
+        assert DPTControlStepwise.from_knx(DPTBinary(raw)) == value
 
     def test_from_knx_wrong_value(self):
         """Test parsing invalid DPTControlStepwise type from KNX."""
         with pytest.raises(ConversionError):
-            DPTControlStepwise.from_knx((0x1F,))
+            DPTControlStepwise.from_knx(DPTBinary(0x1F))
 
     def test_unit(self):
         """Test unit_of_measurement function."""
@@ -158,11 +170,14 @@ class TestDPTControlStartStop:
                 expected_direction = DPTControlStartStopDimming.Direction.STOP
             elif i < 8:
                 expected_direction = DPTControlStartStopDimming.Direction.DECREASE
-            assert DPTControlStartStopDimming.from_knx((i,)) == expected_direction
+            assert (
+                DPTControlStartStopDimming.from_knx(DPTBinary((i,)))
+                == expected_direction
+            )
 
     def test_mode_from_knx_wrong_value(self):
         """Test serializing invalid data type to KNX."""
-        with pytest.raises(ConversionError):
+        with pytest.raises(CouldNotParseTelegram):
             DPTControlStartStopDimming.from_knx(1)
 
     def test_direction_names(self):
diff --git a/test/dpt_tests/dpt_4byte_int_test.py b/test/dpt_tests/dpt_4byte_int_test.py
index d204c03c..d70bad1a 100644
--- a/test/dpt_tests/dpt_4byte_int_test.py
+++ b/test/dpt_tests/dpt_4byte_int_test.py
@@ -5,7 +5,7 @@ from unittest.mock import patch
 import pytest
 
 from xknx.dpt import DPT4ByteSigned, DPT4ByteUnsigned, DPTArray
-from xknx.exceptions import ConversionError
+from xknx.exceptions import ConversionError, CouldNotParseTelegram
 
 
 class TestDPT4Byte:
@@ -32,22 +32,24 @@ class TestDPT4Byte:
     def test_unsigned_value_max_value(self):
         """Test DPT4ByteUnsigned parsing and streaming."""
         assert DPT4ByteUnsigned.to_knx(4294967295) == DPTArray((0xFF, 0xFF, 0xFF, 0xFF))
-        assert DPT4ByteUnsigned.from_knx((0xFF, 0xFF, 0xFF, 0xFF)) == 4294967295
+        assert (
+            DPT4ByteUnsigned.from_knx(DPTArray((0xFF, 0xFF, 0xFF, 0xFF))) == 4294967295
+        )
 
     def test_unsigned_value_min_value(self):
         """Test parsing and streaming with null values."""
         assert DPT4ByteUnsigned.to_knx(0) == DPTArray((0x00, 0x00, 0x00, 0x00))
-        assert DPT4ByteUnsigned.from_knx((0x00, 0x00, 0x00, 0x00)) == 0
+        assert DPT4ByteUnsigned.from_knx(DPTArray((0x00, 0x00, 0x00, 0x00))) == 0
 
     def test_unsigned_value_01234567(self):
         """Test DPT4ByteUnsigned parsing and streaming."""
         assert DPT4ByteUnsigned.to_knx(19088743) == DPTArray((0x01, 0x23, 0x45, 0x67))
-        assert DPT4ByteUnsigned.from_knx((0x01, 0x23, 0x45, 0x67)) == 19088743
+        assert DPT4ByteUnsigned.from_knx(DPTArray((0x01, 0x23, 0x45, 0x67))) == 19088743
 
     def test_unsigned_wrong_value_from_knx(self):
         """Test DPT4ByteUnsigned parsing with wrong value."""
-        with pytest.raises(ConversionError):
-            DPT4ByteUnsigned.from_knx((0xFF, 0x4E, 0x12))
+        with pytest.raises(CouldNotParseTelegram):
+            DPT4ByteUnsigned.from_knx(DPTArray((0xFF, 0x4E, 0x12)))
 
     # ####################################################################
     # DPT4ByteSigned
@@ -70,29 +72,31 @@ class TestDPT4Byte:
     def test_signed_value_max_value(self):
         """Test DPT4ByteSigned parsing and streaming."""
         assert DPT4ByteSigned.to_knx(2147483647) == DPTArray((0x7F, 0xFF, 0xFF, 0xFF))
-        assert DPT4ByteSigned.from_knx((0x7F, 0xFF, 0xFF, 0xFF)) == 2147483647
+        assert DPT4ByteSigned.from_knx(DPTArray((0x7F, 0xFF, 0xFF, 0xFF))) == 2147483647
 
     def test_signed_value_min_value(self):
         """Test DPT4ByteSigned parsing and streaming with null values."""
         assert DPT4ByteSigned.to_knx(-2147483648) == DPTArray((0x80, 0x00, 0x00, 0x00))
-        assert DPT4ByteSigned.from_knx((0x80, 0x00, 0x00, 0x00)) == -2147483648
+        assert (
+            DPT4ByteSigned.from_knx(DPTArray((0x80, 0x00, 0x00, 0x00))) == -2147483648
+        )
 
     def test_signed_value_01234567(self):
         """Test DPT4ByteSigned parsing and streaming."""
         assert DPT4ByteSigned.to_knx(19088743) == DPTArray((0x01, 0x23, 0x45, 0x67))
-        assert DPT4ByteSigned.from_knx((0x01, 0x23, 0x45, 0x67)) == 19088743
+        assert DPT4ByteSigned.from_knx(DPTArray((0x01, 0x23, 0x45, 0x67))) == 19088743
 
     def test_signed_wrong_value_from_knx(self):
         """Test DPT4ByteSigned parsing with wrong value."""
-        with pytest.raises(ConversionError):
-            DPT4ByteSigned.from_knx((0xFF, 0x4E, 0x12))
+        with pytest.raises(CouldNotParseTelegram):
+            DPT4ByteSigned.from_knx(DPTArray((0xFF, 0x4E, 0x12)))
 
     def test_from_knx_unpack_error(self):
         """Test DPT4ByteSigned parsing with unpack error."""
         with patch("struct.unpack") as unpack_mock:
             unpack_mock.side_effect = struct.error()
             with pytest.raises(ConversionError):
-                DPT4ByteSigned.from_knx((0x01, 0x23, 0x45, 0x67))
+                DPT4ByteSigned.from_knx(DPTArray((0x01, 0x23, 0x45, 0x67)))
 
     def test_to_knx_pack_error(self):
         """Test serializing DPT4ByteSigned with pack error."""
diff --git a/test/dpt_tests/dpt_color_test.py b/test/dpt_tests/dpt_color_test.py
index 26f8b256..e4c8b9ef 100644
--- a/test/dpt_tests/dpt_color_test.py
+++ b/test/dpt_tests/dpt_color_test.py
@@ -2,7 +2,7 @@
 import pytest
 
 from xknx.dpt.dpt_color import DPTArray, DPTColorXYY, XYYColor
-from xknx.exceptions import ConversionError
+from xknx.exceptions import ConversionError, CouldNotParseTelegram
 
 
 class TestDPTColorXYY:
@@ -27,7 +27,7 @@ class TestDPTColorXYY:
         assert DPTColorXYY.to_knx(((1, 1), 255)) == DPTArray(
             (0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03)
         )
-        assert DPTColorXYY.from_knx((0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03)) == (
+        assert DPTColorXYY.from_knx(DPTArray((0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03))) == (
             (1, 1),
             255,
         )
@@ -37,19 +37,25 @@ class TestDPTColorXYY:
         assert DPTColorXYY.to_knx(((0, 0), 0)) == DPTArray(
             (0x00, 0x00, 0x00, 0x00, 0x00, 0x03)
         )
-        assert DPTColorXYY.from_knx((0x00, 0x00, 0x00, 0x00, 0x00, 0x03)) == ((0, 0), 0)
+        assert DPTColorXYY.from_knx(DPTArray((0x00, 0x00, 0x00, 0x00, 0x00, 0x03))) == (
+            (0, 0),
+            0,
+        )
 
     def test_xyycolor_value_none_value(self):
         """Test DPTColorXYY parsing and streaming with null values."""
         assert DPTColorXYY.to_knx((None, 0)) == DPTArray(
             (0x00, 0x00, 0x00, 0x00, 0x00, 0x01)
         )
-        assert DPTColorXYY.from_knx((0x00, 0x00, 0x00, 0x00, 0x00, 0x01)) == (None, 0)
+        assert DPTColorXYY.from_knx(DPTArray((0x00, 0x00, 0x00, 0x00, 0x00, 0x01))) == (
+            None,
+            0,
+        )
 
         assert DPTColorXYY.to_knx(((0, 0), None)) == DPTArray(
             (0x00, 0x00, 0x00, 0x00, 0x00, 0x02)
         )
-        assert DPTColorXYY.from_knx((0x00, 0x00, 0x00, 0x00, 0x00, 0x02)) == (
+        assert DPTColorXYY.from_knx(DPTArray((0x00, 0x00, 0x00, 0x00, 0x00, 0x02))) == (
             (0, 0),
             None,
         )
@@ -57,7 +63,7 @@ class TestDPTColorXYY:
         assert DPTColorXYY.to_knx((None, None)) == DPTArray(
             (0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
         )
-        assert DPTColorXYY.from_knx((0x00, 0x00, 0x00, 0x00, 0x00, 0x00)) == (
+        assert DPTColorXYY.from_knx(DPTArray((0x00, 0x00, 0x00, 0x00, 0x00, 0x00))) == (
             None,
             None,
         )
@@ -67,16 +73,16 @@ class TestDPTColorXYY:
         assert DPTColorXYY.to_knx(((0.2, 0.2), 128)) == DPTArray(
             (0x33, 0x33, 0x33, 0x33, 0x80, 0x03)
         )
-        assert DPTColorXYY.from_knx((0x33, 0x33, 0x33, 0x33, 0x80, 0x03)) == (
+        assert DPTColorXYY.from_knx(DPTArray((0x33, 0x33, 0x33, 0x33, 0x80, 0x03))) == (
             (0.2, 0.2),
             128,
         )
         assert DPTColorXYY.to_knx(
             XYYColor(color=(0.8, 0.8), brightness=204)
         ) == DPTArray((0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x03))
-        assert DPTColorXYY.from_knx((0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x03)) == XYYColor(
-            color=(0.8, 0.8), brightness=204
-        )
+        assert DPTColorXYY.from_knx(
+            DPTArray((0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x03))
+        ) == XYYColor(color=(0.8, 0.8), brightness=204)
 
     def test_xyycolor_wrong_value_to_knx(self):
         """Test DPTColorXYY parsing with wrong value."""
@@ -91,5 +97,5 @@ class TestDPTColorXYY:
 
     def test_xyycolor_wrong_value_from_knx(self):
         """Test DPTColorXYY parsing with wrong value."""
-        with pytest.raises(ConversionError):
-            DPTColorXYY.from_knx((0xFF, 0x4E, 0x12))
+        with pytest.raises(CouldNotParseTelegram):
+            DPTColorXYY.from_knx(DPTArray((0xFF, 0x4E, 0x12)))
diff --git a/test/dpt_tests/dpt_date_test.py b/test/dpt_tests/dpt_date_test.py
index f804d7d7..0fed3cac 100644
--- a/test/dpt_tests/dpt_date_test.py
+++ b/test/dpt_tests/dpt_date_test.py
@@ -4,7 +4,7 @@ import time
 import pytest
 
 from xknx.dpt import DPTArray, DPTDate
-from xknx.exceptions import ConversionError
+from xknx.exceptions import ConversionError, CouldNotParseTelegram
 
 
 class TestDPTDate:
@@ -12,19 +12,19 @@ class TestDPTDate:
 
     def test_from_knx(self):
         """Test parsing of DPTDate object from binary values. Example 1."""
-        assert DPTDate.from_knx((0x04, 0x01, 0x02)) == time.strptime(
+        assert DPTDate.from_knx(DPTArray((0x04, 0x01, 0x02))) == time.strptime(
             "2002-01-04", "%Y-%m-%d"
         )
 
     def test_from_knx_old_date(self):
         """Test parsing of DPTDate object from binary values. Example 2."""
-        assert DPTDate.from_knx((0x1F, 0x01, 0x5A)) == time.strptime(
+        assert DPTDate.from_knx(DPTArray((0x1F, 0x01, 0x5A))) == time.strptime(
             "1990-01-31", "%Y-%m-%d"
         )
 
     def test_from_knx_future_date(self):
         """Test parsing of DPTDate object from binary values. Example 3."""
-        assert DPTDate.from_knx((0x04, 0x0C, 0x59)) == time.strptime(
+        assert DPTDate.from_knx(DPTArray((0x04, 0x0C, 0x59))) == time.strptime(
             "2089-12-4", "%Y-%m-%d"
         )
 
@@ -45,8 +45,8 @@ class TestDPTDate:
 
     def test_from_knx_wrong_parameter(self):
         """Test parsing from DPTDate object from wrong binary values."""
-        with pytest.raises(ConversionError):
-            DPTDate.from_knx((0xF8, 0x23))
+        with pytest.raises(CouldNotParseTelegram):
+            DPTDate.from_knx(DPTArray((0xF8, 0x23)))
 
     def test_to_knx_wrong_parameter(self):
         """Test parsing from DPTDate object from wrong string value."""
@@ -56,9 +56,9 @@ class TestDPTDate:
     def test_from_knx_wrong_range_month(self):
         """Test Exception when parsing DPTDAte from KNX with wrong month."""
         with pytest.raises(ConversionError):
-            DPTDate.from_knx((0x04, 0x00, 0x59))
+            DPTDate.from_knx(DPTArray((0x04, 0x00, 0x59)))
 
     def test_from_knx_wrong_range_year(self):
         """Test Exception when parsing DPTDate from KNX with wrong year."""
         with pytest.raises(ConversionError):
-            DPTDate.from_knx((0x04, 0x01, 0x64))
+            DPTDate.from_knx(DPTArray((0x04, 0x01, 0x64)))
diff --git a/test/dpt_tests/dpt_datetime_test.py b/test/dpt_tests/dpt_datetime_test.py
index 0730fb93..364d18e1 100644
--- a/test/dpt_tests/dpt_datetime_test.py
+++ b/test/dpt_tests/dpt_datetime_test.py
@@ -4,7 +4,7 @@ import time
 import pytest
 
 from xknx.dpt import DPTArray, DPTDateTime
-from xknx.exceptions import ConversionError
+from xknx.exceptions import ConversionError, CouldNotParseTelegram
 
 
 class TestDPTDateTime:
@@ -16,7 +16,7 @@ class TestDPTDateTime:
     def test_from_knx(self):
         """Test parsing of DPTDateTime object from binary values. Example 1."""
         assert DPTDateTime.from_knx(
-            (0x75, 0x0B, 0x1C, 0x17, 0x07, 0x18, 0x20, 0x80)
+            DPTArray((0x75, 0x0B, 0x1C, 0x17, 0x07, 0x18, 0x20, 0x80))
         ) == time.strptime("2017-11-28 23:7:24", "%Y-%m-%d %H:%M:%S")
 
     def test_to_knx(self):
@@ -32,7 +32,7 @@ class TestDPTDateTime:
     def test_from_knx_date_in_past(self):
         """Test parsing of DPTDateTime object from binary values. Example 1."""
         assert DPTDateTime.from_knx(
-            (0x00, 0x1, 0x1, 0x20, 0x00, 0x00, 0x00, 0x00)
+            DPTArray((0x00, 0x1, 0x1, 0x20, 0x00, 0x00, 0x00, 0x00))
         ) == time.strptime("1900 1 1 0 0 0", "%Y %m %d %H %M %S")
 
     def test_to_knx_date_in_past(self):
@@ -48,7 +48,7 @@ class TestDPTDateTime:
     def test_from_knx_date_in_future(self):
         """Test parsing of DPTDateTime object from binary values. Example 1."""
         assert DPTDateTime.from_knx(
-            (0xFF, 0x0C, 0x1F, 0xF7, 0x3B, 0x3B, 0x20, 0x80)
+            DPTArray((0xFF, 0x0C, 0x1F, 0xF7, 0x3B, 0x3B, 0x20, 0x80))
         ) == time.strptime("2155-12-31 0 23:59:59", "%Y-%m-%d %w %H:%M:%S")
 
     def test_to_knx_date_in_future(self):
@@ -63,14 +63,16 @@ class TestDPTDateTime:
     #
     def test_from_knx_wrong_size(self):
         """Test parsing DPTDateTime from KNX with wrong binary values (wrong size)."""
-        with pytest.raises(ConversionError):
-            DPTDateTime.from_knx((0xF8, 0x23))
+        with pytest.raises(CouldNotParseTelegram):
+            DPTDateTime.from_knx(DPTArray((0xF8, 0x23)))
 
     def test_from_knx_wrong_bytes(self):
         """Test parsing DPTDateTime from KNX with wrong binary values (wrong bytes)."""
         with pytest.raises(ConversionError):
             # (second byte exceeds value...)
-            DPTDateTime.from_knx((0xFF, 0x0D, 0x1F, 0xF7, 0x3B, 0x3B, 0x00, 0x00))
+            DPTDateTime.from_knx(
+                DPTArray((0xFF, 0x0D, 0x1F, 0xF7, 0x3B, 0x3B, 0x00, 0x00))
+            )
 
     #
     # TEST WRONG PARAMETER
diff --git a/test/dpt_tests/dpt_float_test.py b/test/dpt_tests/dpt_float_test.py
index e4520809..b4955099 100644
--- a/test/dpt_tests/dpt_float_test.py
+++ b/test/dpt_tests/dpt_float_test.py
@@ -22,7 +22,7 @@ from xknx.dpt import (
     DPTTemperature,
     DPTVoltage,
 )
-from xknx.exceptions import ConversionError
+from xknx.exceptions import ConversionError, CouldNotParseTelegram
 
 
 class TestDPTFloat:
@@ -34,35 +34,39 @@ class TestDPTFloat:
     def test_value_from_documentation(self):
         """Test parsing and streaming of DPT2ByteFloat -30.00. Example from the internet[tm]."""
         assert DPT2ByteFloat.to_knx(-30.00) == DPTArray((0x8A, 0x24))
-        assert DPT2ByteFloat.from_knx((0x8A, 0x24)) == -30.00
+        assert DPT2ByteFloat.from_knx(DPTArray((0x8A, 0x24))) == -30.00
 
     def test_value_taken_from_live_thermostat(self):
         """Test parsing and streaming of DPT2ByteFloat 19.96."""
         assert DPT2ByteFloat.to_knx(16.96) == DPTArray((0x06, 0xA0))
-        assert DPT2ByteFloat.from_knx((0x06, 0xA0)) == 16.96
+        assert DPT2ByteFloat.from_knx(DPTArray((0x06, 0xA0))) == 16.96
 
     def test_zero_value(self):
         """Test parsing and streaming of DPT2ByteFloat zero value."""
         assert DPT2ByteFloat.to_knx(0.00) == DPTArray((0x00, 0x00))
-        assert DPT2ByteFloat.from_knx((0x00, 0x00)) == 0.00
+        assert DPT2ByteFloat.from_knx(DPTArray((0x00, 0x00))) == 0.00
 
     def test_room_temperature(self):
         """Test parsing and streaming of DPT2ByteFloat 21.00. Room temperature."""
         assert DPT2ByteFloat.to_knx(21.00) == DPTArray((0x0C, 0x1A))
-        assert DPT2ByteFloat.from_knx((0x0C, 0x1A)) == 21.00
+        assert DPT2ByteFloat.from_knx(DPTArray((0x0C, 0x1A))) == 21.00
 
     def test_high_temperature(self):
         """Test parsing and streaming of DPT2ByteFloat 500.00, 499.84, 500.16. Testing rounding issues."""
         assert DPT2ByteFloat.to_knx(500.00) == DPTArray((0x2E, 0x1A))
-        assert round(abs(DPT2ByteFloat.from_knx((0x2E, 0x1A)) - 499.84), 7) == 0
-        assert round(abs(DPT2ByteFloat.from_knx((0x2E, 0x1B)) - 500.16), 7) == 0
+        assert (
+            round(abs(DPT2ByteFloat.from_knx(DPTArray((0x2E, 0x1A))) - 499.84), 7) == 0
+        )
+        assert (
+            round(abs(DPT2ByteFloat.from_knx(DPTArray((0x2E, 0x1B))) - 500.16), 7) == 0
+        )
         assert DPT2ByteFloat.to_knx(499.84) == DPTArray((0x2E, 0x1A))
         assert DPT2ByteFloat.to_knx(500.16) == DPTArray((0x2E, 0x1B))
 
     def test_minor_negative_temperature(self):
         """Test parsing and streaming of DPT2ByteFloat -10.00. Testing negative values."""
         assert DPT2ByteFloat.to_knx(-10.00) == DPTArray((0x84, 0x18))
-        assert DPT2ByteFloat.from_knx((0x84, 0x18)) == -10.00
+        assert DPT2ByteFloat.from_knx(DPTArray((0x84, 0x18))) == -10.00
 
     def test_very_cold_temperature(self):
         """
@@ -71,37 +75,37 @@ class TestDPTFloat:
         Testing rounding issues of negative values.
         """
         assert DPT2ByteFloat.to_knx(-1000.00) == DPTArray((0xB1, 0xE6))
-        assert DPT2ByteFloat.from_knx((0xB1, 0xE6)) == -999.68
-        assert DPT2ByteFloat.from_knx((0xB1, 0xE5)) == -1000.32
+        assert DPT2ByteFloat.from_knx(DPTArray((0xB1, 0xE6))) == -999.68
+        assert DPT2ByteFloat.from_knx(DPTArray((0xB1, 0xE5))) == -1000.32
         assert DPT2ByteFloat.to_knx(-999.68) == DPTArray((0xB1, 0xE6))
         assert DPT2ByteFloat.to_knx(-1000.32) == DPTArray((0xB1, 0xE5))
 
     def test_max(self):
         """Test parsing and streaming of DPT2ByteFloat with maximum value."""
         assert DPT2ByteFloat.to_knx(DPT2ByteFloat.value_max) == DPTArray((0x7F, 0xFF))
-        assert DPT2ByteFloat.from_knx((0x7F, 0xFF)) == DPT2ByteFloat.value_max
+        assert DPT2ByteFloat.from_knx(DPTArray((0x7F, 0xFF))) == DPT2ByteFloat.value_max
 
     def test_close_to_limit(self):
         """Test parsing and streaming of DPT2ByteFloat with numeric limit."""
         assert DPT2ByteFloat.to_knx(20.48) == DPTArray((0x0C, 0x00))
-        assert DPT2ByteFloat.from_knx((0x0C, 0x00)) == 20.48
+        assert DPT2ByteFloat.from_knx(DPTArray((0x0C, 0x00))) == 20.48
         assert DPT2ByteFloat.to_knx(-20.48) == DPTArray((0x80, 0x00))
-        assert DPT2ByteFloat.from_knx((0x80, 0x00)) == -20.48
+        assert DPT2ByteFloat.from_knx(DPTArray((0x80, 0x00))) == -20.48
 
     def test_min(self):
         """Test parsing and streaming of DPT2ByteFloat with minimum value."""
         assert DPT2ByteFloat.to_knx(DPT2ByteFloat.value_min) == DPTArray((0xF8, 0x00))
-        assert DPT2ByteFloat.from_knx((0xF8, 0x00)) == DPT2ByteFloat.value_min
+        assert DPT2ByteFloat.from_knx(DPTArray((0xF8, 0x00))) == DPT2ByteFloat.value_min
 
     def test_close_to_max(self):
         """Test parsing and streaming of DPT2ByteFloat with maximum value -1."""
         assert DPT2ByteFloat.to_knx(670433.28) == DPTArray((0x7F, 0xFE))
-        assert DPT2ByteFloat.from_knx((0x7F, 0xFE)) == 670433.28
+        assert DPT2ByteFloat.from_knx(DPTArray((0x7F, 0xFE))) == 670433.28
 
     def test_close_to_min(self):
         """Test parsing and streaming of DPT2ByteFloat with minimum value +1."""
         assert DPT2ByteFloat.to_knx(-670760.96) == DPTArray((0xF8, 0x01))
-        assert DPT2ByteFloat.from_knx((0xF8, 0x01)) == -670760.96
+        assert DPT2ByteFloat.from_knx(DPTArray((0xF8, 0x01))) == -670760.96
 
     def test_to_knx_min_exceeded(self):
         """Test parsing of DPT2ByteFloat with wrong value (underflow)."""
@@ -120,13 +124,8 @@ class TestDPTFloat:
 
     def test_from_knx_wrong_parameter(self):
         """Test parsing of DPT2ByteFloat with wrong value (wrong number of bytes)."""
-        with pytest.raises(ConversionError):
-            DPT2ByteFloat.from_knx((0xF8, 0x01, 0x23))
-
-    def test_from_knx_wrong_parameter2(self):
-        """Test parsing of DPT2ByteFloat with wrong value (second parameter is a string)."""
-        with pytest.raises(ConversionError):
-            DPT2ByteFloat.from_knx((0xF8, "0x23"))
+        with pytest.raises(CouldNotParseTelegram):
+            DPT2ByteFloat.from_knx(DPTArray((0xF8, 0x01, 0x23)))
 
     #
     # DPTTemperature
@@ -146,7 +145,7 @@ class TestDPTFloat:
     def test_temperature_assert_min_exceeded_from_knx(self):
         """Testing parsing of DPTTemperature with wrong value."""
         with pytest.raises(ConversionError):
-            DPTTemperature.from_knx((0xB1, 0xE6))  # -1000
+            DPTTemperature.from_knx(DPTArray((0xB1, 0xE6)))  # -1000
 
     #
     # DPTLux
@@ -211,9 +210,9 @@ class TestDPTFloat:
     #
     def test_4byte_float_values_from_power_meter(self):
         """Test parsing DPT4ByteFloat value from power meter."""
-        assert DPT4ByteFloat.from_knx((0x43, 0xC6, 0x80, 00)) == 397
+        assert DPT4ByteFloat.from_knx(DPTArray((0x43, 0xC6, 0x80, 00))) == 397
         assert DPT4ByteFloat.to_knx(397) == DPTArray((0x43, 0xC6, 0x80, 00))
-        assert DPT4ByteFloat.from_knx((0x42, 0x38, 0x00, 00)) == 46
+        assert DPT4ByteFloat.from_knx(DPTArray((0x42, 0x38, 0x00, 00))) == 46
         assert DPT4ByteFloat.to_knx(46) == DPTArray((0x42, 0x38, 0x00, 00))
 
     def test_14_033(self):
@@ -222,13 +221,13 @@ class TestDPTFloat:
 
     def test_14_055(self):
         """Test DPTPhaseAngleDeg object."""
-        assert DPT4ByteFloat.from_knx((0x42, 0xEF, 0x00, 0x00)) == 119.5
+        assert DPT4ByteFloat.from_knx(DPTArray((0x42, 0xEF, 0x00, 0x00))) == 119.5
         assert DPT4ByteFloat.to_knx(119.5) == DPTArray((0x42, 0xEF, 0x00, 0x00))
         assert DPTPhaseAngleDeg.unit == "°"
 
     def test_14_057(self):
         """Test DPT4ByteFloat object."""
-        assert DPT4ByteFloat.from_knx((0x3F, 0x71, 0xEB, 0x86)) == 0.9450001
+        assert DPT4ByteFloat.from_knx(DPTArray((0x3F, 0x71, 0xEB, 0x86))) == 0.9450001
         assert DPT4ByteFloat.to_knx(0.945000052452) == DPTArray(
             (0x3F, 0x71, 0xEB, 0x86)
         )
@@ -236,26 +235,28 @@ class TestDPTFloat:
 
     def test_4byte_float_values_from_voltage_meter(self):
         """Test parsing DPT4ByteFloat from voltage meter."""
-        assert DPT4ByteFloat.from_knx((0x43, 0x65, 0xE3, 0xD7)) == 229.89
+        assert DPT4ByteFloat.from_knx(DPTArray((0x43, 0x65, 0xE3, 0xD7))) == 229.89
         assert DPT4ByteFloat.to_knx(229.89) == DPTArray((0x43, 0x65, 0xE3, 0xD7))
 
     def test_4byte_float_zero_value(self):
         """Test parsing and streaming of DPT4ByteFloat zero value."""
-        assert DPT4ByteFloat.from_knx((0x00, 0x00, 0x00, 0x00)) == 0.00
+        assert DPT4ByteFloat.from_knx(DPTArray((0x00, 0x00, 0x00, 0x00))) == 0.00
         assert DPT4ByteFloat.to_knx(0.00) == DPTArray((0x00, 0x00, 0x00, 0x00))
 
     def test_4byte_float_special_value(self):
         """Test parsing and streaming of DPT4ByteFloat special value."""
-        assert math.isnan(DPT4ByteFloat.from_knx((0x7F, 0xC0, 0x00, 0x00)))
+        assert math.isnan(DPT4ByteFloat.from_knx(DPTArray((0x7F, 0xC0, 0x00, 0x00))))
         assert DPT4ByteFloat.to_knx(float("nan")) == DPTArray((0x7F, 0xC0, 0x00, 0x00))
 
-        assert math.isinf(DPT4ByteFloat.from_knx((0x7F, 0x80, 0x00, 0x00)))
+        assert math.isinf(DPT4ByteFloat.from_knx(DPTArray((0x7F, 0x80, 0x00, 0x00))))
         assert DPT4ByteFloat.to_knx(float("inf")) == DPTArray((0x7F, 0x80, 0x00, 0x00))
 
-        assert DPT4ByteFloat.from_knx((0xFF, 0x80, 0x00, 0x00)) == float("-inf")
+        assert DPT4ByteFloat.from_knx(DPTArray((0xFF, 0x80, 0x00, 0x00))) == float(
+            "-inf"
+        )
         assert DPT4ByteFloat.to_knx(float("-inf")) == DPTArray((0xFF, 0x80, 0x00, 0x00))
 
-        assert DPT4ByteFloat.from_knx((0x80, 0x00, 0x00, 0x00)) == float("-0")
+        assert DPT4ByteFloat.from_knx(DPTArray((0x80, 0x00, 0x00, 0x00))) == float("-0")
         assert DPT4ByteFloat.to_knx(float("-0")) == DPTArray((0x80, 0x00, 0x00, 0x00))
 
     def test_4byte_float_to_knx_wrong_parameter(self):
@@ -265,20 +266,15 @@ class TestDPTFloat:
 
     def test_4byte_float_from_knx_wrong_parameter(self):
         """Test parsing of DPT4ByteFloat with wrong value (wrong number of bytes)."""
-        with pytest.raises(ConversionError):
-            DPT4ByteFloat.from_knx((0xF8, 0x01, 0x23))
-
-    def test_4byte_float_from_knx_wrong_parameter2(self):
-        """Test parsing of DPT4ByteFloat with wrong value (second parameter is a string)."""
-        with pytest.raises(ConversionError):
-            DPT4ByteFloat.from_knx((0xF8, "0x23", 0x00, 0x00))
+        with pytest.raises(CouldNotParseTelegram):
+            DPT4ByteFloat.from_knx(DPTArray((0xF8, 0x01, 0x23)))
 
     def test_4byte_flaot_from_knx_unpack_error(self):
         """Test DPT4ByteFloat parsing with unpack error."""
         with patch("struct.unpack") as unpack_mock:
             unpack_mock.side_effect = struct.error()
             with pytest.raises(ConversionError):
-                DPT4ByteFloat.from_knx((0x01, 0x23, 0x02, 0x02))
+                DPT4ByteFloat.from_knx(DPTArray((0x01, 0x23, 0x02, 0x02)))
 
     #
     # DPTElectricCurrent
diff --git a/test/dpt_tests/dpt_hvac_mode_test.py b/test/dpt_tests/dpt_hvac_mode_test.py
index 281a3d2c..69978b84 100644
--- a/test/dpt_tests/dpt_hvac_mode_test.py
+++ b/test/dpt_tests/dpt_hvac_mode_test.py
@@ -3,7 +3,7 @@ import pytest
 
 from xknx.dpt import DPTArray, DPTControllerStatus, DPTHVACMode
 from xknx.dpt.dpt_hvac_mode import HVACOperationMode
-from xknx.exceptions import ConversionError
+from xknx.exceptions import ConversionError, CouldNotParseTelegram
 
 
 class TestDPTControllerStatus:
@@ -26,11 +26,14 @@ class TestDPTControllerStatus:
 
     def test_mode_from_knx(self):
         """Test parsing DPTHVACMode from KNX."""
-        assert DPTHVACMode.from_knx((0x00,)) == HVACOperationMode.AUTO
-        assert DPTHVACMode.from_knx((0x01,)) == HVACOperationMode.COMFORT
-        assert DPTHVACMode.from_knx((0x02,)) == HVACOperationMode.STANDBY
-        assert DPTHVACMode.from_knx((0x03,)) == HVACOperationMode.NIGHT
-        assert DPTHVACMode.from_knx((0x04,)) == HVACOperationMode.FROST_PROTECTION
+        assert DPTHVACMode.from_knx(DPTArray((0x00,))) == HVACOperationMode.AUTO
+        assert DPTHVACMode.from_knx(DPTArray((0x01,))) == HVACOperationMode.COMFORT
+        assert DPTHVACMode.from_knx(DPTArray((0x02,))) == HVACOperationMode.STANDBY
+        assert DPTHVACMode.from_knx(DPTArray((0x03,))) == HVACOperationMode.NIGHT
+        assert (
+            DPTHVACMode.from_knx(DPTArray((0x04,)))
+            == HVACOperationMode.FROST_PROTECTION
+        )
 
     def test_controller_status_to_knx(self):
         """Test serializing DPTControllerStatus to KNX."""
@@ -54,38 +57,52 @@ class TestDPTControllerStatus:
 
     def test_controller_status_from_knx(self):
         """Test parsing DPTControllerStatus from KNX."""
-        assert DPTControllerStatus.from_knx((0x21,)) == HVACOperationMode.COMFORT
-        assert DPTControllerStatus.from_knx((0x22,)) == HVACOperationMode.STANDBY
-        assert DPTControllerStatus.from_knx((0x24,)) == HVACOperationMode.NIGHT
         assert (
-            DPTControllerStatus.from_knx((0x28,)) == HVACOperationMode.FROST_PROTECTION
+            DPTControllerStatus.from_knx(DPTArray((0x21,))) == HVACOperationMode.COMFORT
+        )
+        assert (
+            DPTControllerStatus.from_knx(DPTArray((0x22,))) == HVACOperationMode.STANDBY
+        )
+        assert (
+            DPTControllerStatus.from_knx(DPTArray((0x24,))) == HVACOperationMode.NIGHT
+        )
+        assert (
+            DPTControllerStatus.from_knx(DPTArray((0x28,)))
+            == HVACOperationMode.FROST_PROTECTION
         )
 
     def test_controller_status_from_knx_other_bits_set(self):
         """Test parsing DPTControllerStatus from KNX."""
-        assert DPTControllerStatus.from_knx((0x21,)) == HVACOperationMode.COMFORT
-        assert DPTControllerStatus.from_knx((0x23,)) == HVACOperationMode.STANDBY
-        assert DPTControllerStatus.from_knx((0x27,)) == HVACOperationMode.NIGHT
         assert (
-            DPTControllerStatus.from_knx((0x2F,)) == HVACOperationMode.FROST_PROTECTION
+            DPTControllerStatus.from_knx(DPTArray((0x21,))) == HVACOperationMode.COMFORT
+        )
+        assert (
+            DPTControllerStatus.from_knx(DPTArray((0x23,))) == HVACOperationMode.STANDBY
+        )
+        assert (
+            DPTControllerStatus.from_knx(DPTArray((0x27,))) == HVACOperationMode.NIGHT
+        )
+        assert (
+            DPTControllerStatus.from_knx(DPTArray((0x2F,)))
+            == HVACOperationMode.FROST_PROTECTION
         )
 
     def test_mode_from_knx_wrong_value(self):
         """Test parsing of DPTControllerStatus with wrong value)."""
-        with pytest.raises(ConversionError):
-            DPTHVACMode.from_knx((1, 2))
+        with pytest.raises(CouldNotParseTelegram):
+            DPTHVACMode.from_knx(DPTArray((1, 2)))
 
     def test_mode_from_knx_wrong_code(self):
         """Test parsing of DPTHVACMode with wrong code."""
         with pytest.raises(ConversionError):
-            DPTHVACMode.from_knx((0x05,))
+            DPTHVACMode.from_knx(DPTArray((0x05,)))
 
     def test_controller_status_from_knx_wrong_value(self):
         """Test parsing of DPTControllerStatus with wrong value)."""
-        with pytest.raises(ConversionError):
-            DPTControllerStatus.from_knx((1, 2))
+        with pytest.raises(CouldNotParseTelegram):
+            DPTControllerStatus.from_knx(DPTArray((1, 2)))
 
     def test_controller_status_from_knx_wrong_code(self):
         """Test parsing of DPTControllerStatus with wrong code."""
         with pytest.raises(ConversionError):
-            DPTControllerStatus.from_knx((0x00,))
+            DPTControllerStatus.from_knx(DPTArray((0x00,)))
diff --git a/test/dpt_tests/dpt_scaling_test.py b/test/dpt_tests/dpt_scaling_test.py
index bfd0a2b2..e880bb38 100644
--- a/test/dpt_tests/dpt_scaling_test.py
+++ b/test/dpt_tests/dpt_scaling_test.py
@@ -2,31 +2,25 @@
 import pytest
 
 from xknx.dpt import DPTAngle, DPTArray, DPTScaling
-from xknx.exceptions import ConversionError
+from xknx.exceptions import ConversionError, CouldNotParseTelegram
 
 
 class TestDPTScaling:
     """Test class for KNX scaling value."""
 
-    def test_value_30_pct(self):
-        """Test parsing and streaming of DPTScaling 30%."""
-        assert DPTScaling.to_knx(30) == DPTArray((0x4C,))
-        assert DPTScaling.from_knx((0x4C,)) == 30
-
-    def test_value_99_pct(self):
-        """Test parsing and streaming of DPTScaling 99%."""
-        assert DPTScaling.to_knx(99) == DPTArray((0xFC,))
-        assert DPTScaling.from_knx((0xFC,)) == 99
-
-    def test_value_max(self):
-        """Test parsing and streaming of DPTScaling 100%."""
-        assert DPTScaling.to_knx(100) == DPTArray((0xFF,))
-        assert DPTScaling.from_knx((0xFF,)) == 100
-
-    def test_value_min(self):
-        """Test parsing and streaming of DPTScaling 0."""
-        assert DPTScaling.to_knx(0) == DPTArray((0x00,))
-        assert DPTScaling.from_knx((0x00,)) == 0
+    @pytest.mark.parametrize(
+        ("raw", "value"),
+        [
+            ((0x4C,), 30),
+            ((0xFC,), 99),
+            ((0xFF,), 100),
+            ((0x00,), 0),
+        ],
+    )
+    def test_transcoder(self, raw, value):
+        """Test parsing and streaming of DPTScaling."""
+        assert DPTScaling.to_knx(value) == DPTArray(raw)
+        assert DPTScaling.from_knx(DPTArray(raw)) == value
 
     def test_to_knx_min_exceeded(self):
         """Test parsing of DPTScaling with wrong value (underflow)."""
@@ -45,42 +39,31 @@ class TestDPTScaling:
 
     def test_from_knx_wrong_parameter(self):
         """Test parsing of DPTScaling with wrong value (3 byte array)."""
-        with pytest.raises(ConversionError):
-            DPTScaling.from_knx((0x01, 0x02, 0x03))
+        with pytest.raises(CouldNotParseTelegram):
+            DPTScaling.from_knx(DPTArray((0x01, 0x02, 0x03)))
 
     def test_from_knx_wrong_value(self):
         """Test parsing of DPTScaling with value which exceeds limits."""
         with pytest.raises(ConversionError):
-            DPTScaling.from_knx((0x256,))
-
-    def test_from_knx_wrong_parameter2(self):
-        """Test parsing of DPTScaling with wrong value (array containing string)."""
-        with pytest.raises(ConversionError):
-            DPTScaling.from_knx("0x23")
+            DPTScaling.from_knx(DPTArray((0x256,)))
 
 
 class TestDPTAngle:
     """Test class for KNX scaling value."""
 
-    def test_value_30_deg(self):
-        """Test parsing and streaming of DPTAngle 30°."""
-        assert DPTAngle.to_knx(30) == DPTArray((0x15,))
-        assert DPTAngle.from_knx((0x15,)) == 30
-
-    def test_value_270_deg(self):
-        """Test parsing and streaming of DPTAngle 270°."""
-        assert DPTAngle.to_knx(270) == DPTArray((0xBF,))
-        assert DPTAngle.from_knx((0xBF,)) == 270
-
-    def test_value_max(self):
-        """Test parsing and streaming of DPTAngle 360°."""
-        assert DPTAngle.to_knx(360) == DPTArray((0xFF,))
-        assert DPTAngle.from_knx((0xFF,)) == 360
-
-    def test_value_min(self):
-        """Test parsing and streaming of DPTAngle 0°."""
-        assert DPTAngle.to_knx(0) == DPTArray((0x00,))
-        assert DPTAngle.from_knx((0x00,)) == 0
+    @pytest.mark.parametrize(
+        ("raw", "value"),
+        [
+            ((0x15,), 30),
+            ((0xBF,), 270),
+            ((0xFF,), 360),
+            ((0x00,), 0),
+        ],
+    )
+    def test_transcoder(self, raw, value):
+        """Test parsing and streaming of DPTAngle."""
+        assert DPTAngle.to_knx(value) == DPTArray(raw)
+        assert DPTAngle.from_knx(DPTArray(raw)) == value
 
     def test_to_knx_min_exceeded(self):
         """Test parsing of DPTAngle with wrong value (underflow)."""
diff --git a/test/dpt_tests/dpt_scene_number_test.py b/test/dpt_tests/dpt_scene_number_test.py
index 878d36d9..a5da19dd 100644
--- a/test/dpt_tests/dpt_scene_number_test.py
+++ b/test/dpt_tests/dpt_scene_number_test.py
@@ -2,26 +2,24 @@
 import pytest
 
 from xknx.dpt import DPTArray, DPTSceneNumber
-from xknx.exceptions import ConversionError
+from xknx.exceptions import ConversionError, CouldNotParseTelegram
 
 
 class TestDPTSceneNumber:
     """Test class for KNX scaling value."""
 
-    def test_value_50(self):
-        """Test parsing and streaming of DPTSceneNumber 50."""
-        assert DPTSceneNumber.to_knx(50) == DPTArray((0x31,))
-        assert DPTSceneNumber.from_knx((0x31,)) == 50
-
-    def test_value_max(self):
-        """Test parsing and streaming of DPTSceneNumber 64."""
-        assert DPTSceneNumber.to_knx(64) == DPTArray((0x3F,))
-        assert DPTSceneNumber.from_knx((0x3F,)) == 64
-
-    def test_value_min(self):
-        """Test parsing and streaming of DPTSceneNumber 0."""
-        assert DPTSceneNumber.to_knx(1) == DPTArray((0x00,))
-        assert DPTSceneNumber.from_knx((0x00,)) == 1
+    @pytest.mark.parametrize(
+        ("raw", "value"),
+        [
+            ((0x31,), 50),
+            ((0x3F,), 64),
+            ((0x00,), 1),
+        ],
+    )
+    def test_transcoder(self, raw, value):
+        """Test parsing and streaming of DPTSceneNumber."""
+        assert DPTSceneNumber.to_knx(value) == DPTArray(raw)
+        assert DPTSceneNumber.from_knx(DPTArray(raw)) == value
 
     def test_to_knx_min_exceeded(self):
         """Test parsing of DPTSceneNumber with wrong value (underflow)."""
@@ -40,15 +38,10 @@ class TestDPTSceneNumber:
 
     def test_from_knx_wrong_parameter(self):
         """Test parsing of DPTSceneNumber with wrong value (3 byte array)."""
-        with pytest.raises(ConversionError):
-            DPTSceneNumber.from_knx((0x01, 0x02, 0x03))
+        with pytest.raises(CouldNotParseTelegram):
+            DPTSceneNumber.from_knx(DPTArray((0x01, 0x02, 0x03)))
 
     def test_from_knx_wrong_value(self):
         """Test parsing of DPTSceneNumber with value which exceeds limits."""
         with pytest.raises(ConversionError):
-            DPTSceneNumber.from_knx((0x64,))
-
-    def test_from_knx_wrong_parameter2(self):
-        """Test parsing of DPTSceneNumber with wrong value (array containing string)."""
-        with pytest.raises(ConversionError):
-            DPTSceneNumber.from_knx("0x23")
+            DPTSceneNumber.from_knx(DPTArray((0x64,)))
diff --git a/test/dpt_tests/dpt_string_test.py b/test/dpt_tests/dpt_string_test.py
index b8a15ae1..4b354640 100644
--- a/test/dpt_tests/dpt_string_test.py
+++ b/test/dpt_tests/dpt_string_test.py
@@ -2,7 +2,7 @@
 import pytest
 
 from xknx.dpt import DPTArray, DPTLatin1, DPTString
-from xknx.exceptions import ConversionError
+from xknx.exceptions import ConversionError, CouldNotParseTelegram
 
 
 class TestDPTString:
@@ -33,7 +33,7 @@ class TestDPTString:
     def test_values(self, string, raw, test_dpt):
         """Test parsing and streaming strings."""
         assert test_dpt.to_knx(string) == DPTArray(raw)
-        assert test_dpt.from_knx(raw) == string
+        assert test_dpt.from_knx(DPTArray(raw)) == string
 
     @pytest.mark.parametrize(
         "string,knx_string,raw",
@@ -53,7 +53,7 @@ class TestDPTString:
     def test_to_knx_ascii_invalid_chars(self, string, knx_string, raw):
         """Test streaming ASCII string with invalid chars."""
         assert DPTString.to_knx(string) == DPTArray(raw)
-        assert DPTString.from_knx(raw) == knx_string
+        assert DPTString.from_knx(DPTArray(raw)) == knx_string
 
     @pytest.mark.parametrize(
         "string,raw",
@@ -71,7 +71,7 @@ class TestDPTString:
     def test_to_knx_latin_1(self, string, raw):
         """Test streaming Latin-1 strings."""
         assert DPTLatin1.to_knx(string) == DPTArray(raw)
-        assert DPTLatin1.from_knx(raw) == string
+        assert DPTLatin1.from_knx(DPTArray(raw)) == string
 
     def test_to_knx_too_long(self):
         """Test serializing DPTString to KNX with wrong value (to long)."""
@@ -87,8 +87,8 @@ class TestDPTString:
     )
     def test_from_knx_wrong_parameter_length(self, raw):
         """Test parsing of KNX string with wrong elements length."""
-        with pytest.raises(ConversionError):
-            DPTString.from_knx(raw)
+        with pytest.raises(CouldNotParseTelegram):
+            DPTString.from_knx(DPTArray(raw))
 
     def test_no_unit_of_measurement(self):
         """Test for no unit set for DPT 16"""
diff --git a/test/dpt_tests/dpt_test.py b/test/dpt_tests/dpt_test.py
index e1a0c9de..6896b45c 100644
--- a/test/dpt_tests/dpt_test.py
+++ b/test/dpt_tests/dpt_test.py
@@ -11,78 +11,7 @@ from xknx.dpt import (
     DPTString,
     DPTTemperature,
 )
-from xknx.exceptions import ConversionError
-
-
-class TestDPT:
-    """Test class for KNX binary/integer objects."""
-
-    def test_compare_binary(self):
-        """Test comparison of DPTBinary objects."""
-        assert DPTBinary(0) == DPTBinary(0)
-        assert DPTBinary(0) == DPTBinary(False)
-        assert DPTBinary(1) == DPTBinary(True)
-        assert DPTBinary(2) == DPTBinary(2)
-        assert DPTBinary(1) != DPTBinary(4)
-        assert DPTBinary(2) != DPTBinary(0)
-        assert DPTBinary(0) != DPTBinary(2)
-
-    def test_compare_array(self):
-        """Test comparison of DPTArray objects."""
-        assert DPTArray(()) == DPTArray(())
-        assert DPTArray([1]) == DPTArray((1,))
-        assert DPTArray([1, 2, 3]) == DPTArray([1, 2, 3])
-        assert DPTArray([1, 2, 3]) == DPTArray((1, 2, 3))
-        assert DPTArray((1, 2, 3)) == DPTArray([1, 2, 3])
-        assert DPTArray((1, 2, 3)) != DPTArray([1, 2, 3, 4])
-        assert DPTArray((1, 2, 3, 4)) != DPTArray([1, 2, 3])
-        assert DPTArray((1, 2, 3)) != DPTArray([1, 2, 4])
-
-    def test_compare_none(self):
-        """Test comparison DPTArray objects with None."""
-        assert DPTArray(()) is not None
-        assert None is not DPTArray(())
-        assert DPTBinary(0) is not None
-        assert None is not DPTBinary(0)
-        assert DPTArray((1, 2, 3)) is not None
-        assert None is not DPTArray((1, 2, 3))
-        assert DPTBinary(1) is not None
-        assert None is not DPTBinary(1)
-
-    def test_compare_array_binary(self):
-        """Test comparison of empty DPTArray objects with DPTBinary objects."""
-        assert DPTArray(()) != DPTBinary(0)
-        assert DPTBinary(0) != DPTArray(())
-        assert DPTBinary(0) != DPTArray(0)
-        assert DPTBinary(1) != DPTArray(1)
-        assert DPTArray((1, 2, 3)) != DPTBinary(2)
-        assert DPTBinary(2) != DPTArray((1, 2, 3))
-        assert DPTArray((2,)) != DPTBinary(2)
-        assert DPTBinary(2) != DPTArray((2,))
-
-    def test_dpt_binary_assign(self):
-        """Test initialization of DPTBinary objects."""
-        assert DPTBinary(8).value == 8
-
-    def test_dpt_binary_assign_limit_exceeded(self):
-        """Test initialization of DPTBinary objects with wrong value (value exceeded)."""
-        with pytest.raises(ConversionError):
-            DPTBinary(DPTBinary.APCI_BITMASK + 1)
-
-    def test_dpt_init_with_string(self):
-        """Teest initialization of DPTBinary object with wrong value (wrong type)."""
-        with pytest.raises(TypeError):
-            DPTBinary("bla")
-
-    def test_dpt_array_init_with_string(self):
-        """Test initialization of DPTArray object with wrong value (wrong type)."""
-        with pytest.raises(TypeError):
-            DPTArray("bla")
-
-    def test_dpt_representation(self):
-        """Test representation of DPTBinary and DPTArray."""
-        assert DPTBinary(True).__repr__() == "DPTBinary(0x1)"
-        assert DPTArray((5, 15)).__repr__() == "DPTArray((0x5, 0xf))"
+from xknx.exceptions import CouldNotParseTelegram
 
 
 class TestDPTBase:
@@ -161,6 +90,50 @@ class TestDPTBase:
         assert DPT2ByteFloat.parse_transcoder("temperature") == DPTTemperature
 
 
+class TestDPTBaseSubclass:
+    """Test subclass of transcoder base object."""
+
+    @pytest.mark.parametrize("dpt_class", DPTBase.dpt_class_tree())
+    def test_required_values(self, dpt_class):
+        """Test required class variables are set for definitions."""
+        assert dpt_class.payload_type in (DPTArray, DPTBinary)
+        assert dpt_class.payload_length is not None
+
+    def test_validate_payload_array(self):
+        """Test validate_payload method."""
+
+        class DPTArrayTest(DPTBase):
+            payload_type = DPTArray
+            payload_length = 2
+
+        with pytest.raises(CouldNotParseTelegram):
+            DPTArrayTest.validate_payload(DPTArray((1,)))
+        with pytest.raises(CouldNotParseTelegram):
+            DPTArrayTest.validate_payload(DPTArray((1, 1, 1)))
+        with pytest.raises(CouldNotParseTelegram):
+            DPTArrayTest.validate_payload(DPTBinary(1))
+        with pytest.raises(CouldNotParseTelegram):
+            DPTArrayTest.validate_payload("why?")
+
+        assert DPTArrayTest.validate_payload(DPTArray((1, 1))) == (1, 1)
+
+    def test_validate_payload_binary(self):
+        """Test validate_payload method."""
+
+        class DPTBinaryTest(DPTBase):
+            payload_type = DPTBinary
+            payload_length = 1
+
+        with pytest.raises(CouldNotParseTelegram):
+            DPTBinaryTest.validate_payload(DPTArray(1))
+        with pytest.raises(CouldNotParseTelegram):
+            DPTBinaryTest.validate_payload(DPTArray((1, 1)))
+        with pytest.raises(CouldNotParseTelegram):
+            DPTBinaryTest.validate_payload("why?")
+
+        assert DPTBinaryTest.validate_payload(DPTBinary(1)) == (1,)
+
+
 class TestDPTNumeric:
     """Test class for numeric transcoder base object."""
 
diff --git a/test/dpt_tests/dpt_time_test.py b/test/dpt_tests/dpt_time_test.py
index c5a5e240..3e165fd7 100644
--- a/test/dpt_tests/dpt_time_test.py
+++ b/test/dpt_tests/dpt_time_test.py
@@ -4,7 +4,7 @@ import time
 import pytest
 
 from xknx.dpt import DPTArray, DPTTime
-from xknx.exceptions import ConversionError
+from xknx.exceptions import ConversionError, CouldNotParseTelegram
 
 
 class TestDPTTime:
@@ -15,7 +15,7 @@ class TestDPTTime:
     #
     def test_from_knx(self):
         """Test parsing of DPTTime object from binary values. Example 1."""
-        assert DPTTime.from_knx((0x4D, 0x17, 0x2A)) == time.strptime(
+        assert DPTTime.from_knx(DPTArray((0x4D, 0x17, 0x2A))) == time.strptime(
             "13 23 42 2", "%H %M %S %w"
         )
 
@@ -34,7 +34,7 @@ class TestDPTTime:
 
     def test_from_knx_max(self):
         """Test parsing of DPTTime object from binary values. Example 2."""
-        assert DPTTime.from_knx((0xF7, 0x3B, 0x3B)) == time.strptime(
+        assert DPTTime.from_knx(DPTArray((0xF7, 0x3B, 0x3B))) == time.strptime(
             "23 59 59 0", "%H %M %S %w"
         )
 
@@ -48,7 +48,9 @@ class TestDPTTime:
 
     def test_from_knx_min(self):
         """Test parsing of DPTTime object from binary values. Example 3."""
-        assert DPTTime.from_knx((0x0, 0x0, 0x0)) == time.strptime("0 0 0", "%H %M %S")
+        assert DPTTime.from_knx(DPTArray((0x0, 0x0, 0x0))) == time.strptime(
+            "0 0 0", "%H %M %S"
+        )
 
     #
     # TEST INITIALIZATION
@@ -59,19 +61,14 @@ class TestDPTTime:
 
     def test_from_knx_wrong_size(self):
         """Test parsing from DPTTime object from wrong binary values (wrong size)."""
-        with pytest.raises(ConversionError):
-            DPTTime.from_knx((0xF8, 0x23))
+        with pytest.raises(CouldNotParseTelegram):
+            DPTTime.from_knx(DPTArray((0xF8, 0x23)))
 
     def test_from_knx_wrong_bytes(self):
         """Test parsing from DPTTime object from wrong binary values (wrong bytes)."""
         with pytest.raises(ConversionError):
             # this parameter exceeds limit
-            DPTTime.from_knx((0xF7, 0x3B, 0x3C))
-
-    def test_from_knx_wrong_type(self):
-        """Test parsing from DPTTime object from wrong binary values (wrong type)."""
-        with pytest.raises(ConversionError):
-            DPTTime.from_knx((0xF8, "0x23"))
+            DPTTime.from_knx(DPTArray((0xF7, 0x3B, 0x3C)))
 
     def test_to_knx_wrong_parameter(self):
         """Test parsing from DPTTime object from wrong string value."""
diff --git a/test/dpt_tests/payload_test.py b/test/dpt_tests/payload_test.py
new file mode 100644
index 00000000..1079ade9
--- /dev/null
+++ b/test/dpt_tests/payload_test.py
@@ -0,0 +1,76 @@
+"""Unit test for KNX payload objects."""
+import pytest
+
+from xknx.dpt import DPTArray, DPTBinary
+from xknx.exceptions import ConversionError
+
+
+class TestDPT:
+    """Test class for KNX binary/integer objects."""
+
+    def test_compare_binary(self):
+        """Test comparison of DPTBinary objects."""
+        assert DPTBinary(0) == DPTBinary(0)
+        assert DPTBinary(0) == DPTBinary(False)
+        assert DPTBinary(1) == DPTBinary(True)
+        assert DPTBinary(2) == DPTBinary(2)
+        assert DPTBinary(1) != DPTBinary(4)
+        assert DPTBinary(2) != DPTBinary(0)
+        assert DPTBinary(0) != DPTBinary(2)
+
+    def test_compare_array(self):
+        """Test comparison of DPTArray objects."""
+        assert DPTArray(()) == DPTArray(())
+        assert DPTArray([1]) == DPTArray((1,))
+        assert DPTArray([1, 2, 3]) == DPTArray([1, 2, 3])
+        assert DPTArray([1, 2, 3]) == DPTArray((1, 2, 3))
+        assert DPTArray((1, 2, 3)) == DPTArray([1, 2, 3])
+        assert DPTArray((1, 2, 3)) != DPTArray([1, 2, 3, 4])
+        assert DPTArray((1, 2, 3, 4)) != DPTArray([1, 2, 3])
+        assert DPTArray((1, 2, 3)) != DPTArray([1, 2, 4])
+
+    def test_compare_none(self):
+        """Test comparison DPTArray objects with None."""
+        assert DPTArray(()) is not None
+        assert None is not DPTArray(())
+        assert DPTBinary(0) is not None
+        assert None is not DPTBinary(0)
+        assert DPTArray((1, 2, 3)) is not None
+        assert None is not DPTArray((1, 2, 3))
+        assert DPTBinary(1) is not None
+        assert None is not DPTBinary(1)
+
+    def test_compare_array_binary(self):
+        """Test comparison of empty DPTArray objects with DPTBinary objects."""
+        assert DPTArray(()) != DPTBinary(0)
+        assert DPTBinary(0) != DPTArray(())
+        assert DPTBinary(0) != DPTArray(0)
+        assert DPTBinary(1) != DPTArray(1)
+        assert DPTArray((1, 2, 3)) != DPTBinary(2)
+        assert DPTBinary(2) != DPTArray((1, 2, 3))
+        assert DPTArray((2,)) != DPTBinary(2)
+        assert DPTBinary(2) != DPTArray((2,))
+
+    def test_dpt_binary_assign(self):
+        """Test initialization of DPTBinary objects."""
+        assert DPTBinary(8).value == 8
+
+    def test_dpt_binary_assign_limit_exceeded(self):
+        """Test initialization of DPTBinary objects with wrong value (value exceeded)."""
+        with pytest.raises(ConversionError):
+            DPTBinary(DPTBinary.APCI_BITMASK + 1)
+
+    def test_dpt_init_with_string(self):
+        """Teest initialization of DPTBinary object with wrong value (wrong type)."""
+        with pytest.raises(TypeError):
+            DPTBinary("bla")
+
+    def test_dpt_array_init_with_string(self):
+        """Test initialization of DPTArray object with wrong value (wrong type)."""
+        with pytest.raises(TypeError):
+            DPTArray("bla")
+
+    def test_dpt_representation(self):
+        """Test representation of DPTBinary and DPTArray."""
+        assert DPTBinary(True).__repr__() == "DPTBinary(0x1)"
+        assert DPTArray((5, 15)).__repr__() == "DPTArray((0x5, 0xf))"
diff --git a/test/remote_value_tests/remote_value_datetime_test.py b/test/remote_value_tests/remote_value_datetime_test.py
index 8d7e0ada..a0bc41d1 100644
--- a/test/remote_value_tests/remote_value_datetime_test.py
+++ b/test/remote_value_tests/remote_value_datetime_test.py
@@ -35,19 +35,9 @@ class TestRemoteValueDateTime:
         )
         assert array.value == (0x75, 0x0B, 0x1C, 0x57, 0x07, 0x18, 0x20, 0x80)
 
-    def test_payload_valid(self):
-        """Testing KNX/Byte representation of DPTDateTime object."""
-        xknx = XKNX()
-        rv_datetime = RemoteValueDateTime(xknx, value_type="datetime")
-        assert rv_datetime.payload_valid(
-            DPTArray((0x75, 0x0B, 0x1C, 0x57, 0x07, 0x18, 0x20, 0x80))
-        )
-
     def test_payload_invalid(self):
         """Testing KNX/Byte representation of DPTDateTime object."""
         xknx = XKNX()
         rv_datetime = RemoteValueDateTime(xknx, value_type="datetime")
         with pytest.raises(CouldNotParseTelegram):
-            rv_datetime.payload_valid(
-                DPTArray((0x0B, 0x1C, 0x57, 0x07, 0x18, 0x20, 0x80))
-            )
+            rv_datetime.from_knx(DPTArray((0x0B, 0x1C, 0x57, 0x07, 0x18, 0x20, 0x80)))
diff --git a/test/remote_value_tests/remote_value_sensor_test.py b/test/remote_value_tests/remote_value_sensor_test.py
index 16a85f3b..0098181b 100644
--- a/test/remote_value_tests/remote_value_sensor_test.py
+++ b/test/remote_value_tests/remote_value_sensor_test.py
@@ -39,20 +39,18 @@ class TestRemoteValueSensor:
         for dpt_class in DPTBase.__recursive_subclasses__():
             assert isinstance(dpt_class.payload_length, int)
 
-    def test_payload_valid(self):
-        """Test payload_valid method."""
+    def test_payload_invalid(self):
+        """Test invalid payloads."""
         xknx = XKNX()
         remote_value = RemoteValueSensor(xknx=xknx, value_type="pulse")
-        valid_payload = DPTValue1Ucount.to_knx(1)
 
         assert remote_value.dpt_class == DPTValue1Ucount
         with pytest.raises(CouldNotParseTelegram):
-            remote_value.payload_valid(None)
+            remote_value.from_knx(None)
         with pytest.raises(CouldNotParseTelegram):
-            remote_value.payload_valid(DPTArray((1, 2, 3, 4)))
+            remote_value.from_knx(DPTArray((1, 2, 3, 4)))
         with pytest.raises(CouldNotParseTelegram):
-            remote_value.payload_valid(DPTBinary(1))
-        assert remote_value.payload_valid(valid_payload) == valid_payload
+            remote_value.from_knx(DPTBinary(1))
 
 
 class TestRemoteValueNumeric:
diff --git a/test/remote_value_tests/remote_value_setepoint_shift_test.py b/test/remote_value_tests/remote_value_setepoint_shift_test.py
index fc5fdc13..10060bf7 100644
--- a/test/remote_value_tests/remote_value_setepoint_shift_test.py
+++ b/test/remote_value_tests/remote_value_setepoint_shift_test.py
@@ -21,25 +21,26 @@ class TestRemoteValueSetpointShift:
         dpt_9_payload = DPTTemperature.to_knx(1)
 
         with pytest.raises(CouldNotParseTelegram):
-            remote_value.payload_valid(DPTBinary(0))
+            remote_value.from_knx(DPTBinary(0))
         with pytest.raises(CouldNotParseTelegram):
-            remote_value.payload_valid(DPTArray((0, 1, 2)))
+            remote_value.from_knx(DPTArray((0, 1, 2)))
 
         # DPT 6 - payload_length 1
         assert remote_value.dpt_class is None
-        assert remote_value.payload_valid(dpt_6_payload) == dpt_6_payload
+        # default setpoint_shift_step = 0.1
+        assert remote_value.from_knx(dpt_6_payload) == 0.1
         assert remote_value.dpt_class == SetpointShiftMode.DPT6010.value
         with pytest.raises(CouldNotParseTelegram):
             # DPT 9 is invalid now
-            remote_value.payload_valid(dpt_9_payload)
+            remote_value.from_knx(dpt_9_payload)
 
         remote_value.dpt_class = None
         # DPT 9 - payload_length 2
-        assert remote_value.payload_valid(dpt_9_payload) == dpt_9_payload
+        assert remote_value.from_knx(dpt_9_payload) == 1
         assert remote_value.dpt_class == SetpointShiftMode.DPT9002.value
         with pytest.raises(CouldNotParseTelegram):
             # DPT 6 is invalid now
-            remote_value.payload_valid(dpt_6_payload)
+            remote_value.from_knx(dpt_6_payload)
 
     def test_payload_valid_preassigned_mode(self):
         """Test if setpoint_shift_mode is assigned properly by payload length."""
@@ -55,25 +56,25 @@ class TestRemoteValueSetpointShift:
 
         assert remote_value_6.dpt_class == DPTValue1Count
         with pytest.raises(CouldNotParseTelegram):
-            remote_value_6.payload_valid(None)
+            remote_value_6.from_knx(None)
         with pytest.raises(CouldNotParseTelegram):
-            remote_value_6.payload_valid(dpt_9_payload)
+            remote_value_6.from_knx(dpt_9_payload)
         with pytest.raises(CouldNotParseTelegram):
-            remote_value_6.payload_valid(DPTArray((1, 2, 3, 4)))
+            remote_value_6.from_knx(DPTArray((1, 2, 3, 4)))
         with pytest.raises(CouldNotParseTelegram):
-            remote_value_6.payload_valid(DPTBinary(1))
-        assert remote_value_6.payload_valid(dpt_6_payload) == dpt_6_payload
+            remote_value_6.from_knx(DPTBinary(1))
+        assert remote_value_6.from_knx(dpt_6_payload) == 0.1
 
         assert remote_value_9.dpt_class == DPTTemperature
         with pytest.raises(CouldNotParseTelegram):
-            remote_value_9.payload_valid(None)
+            remote_value_9.from_knx(None)
         with pytest.raises(CouldNotParseTelegram):
-            remote_value_9.payload_valid(dpt_6_payload)
+            remote_value_9.from_knx(dpt_6_payload)
         with pytest.raises(CouldNotParseTelegram):
-            remote_value_9.payload_valid(DPTArray((1, 2, 3)))
+            remote_value_9.from_knx(DPTArray((1, 2, 3)))
         with pytest.raises(CouldNotParseTelegram):
-            remote_value_9.payload_valid(DPTBinary(1))
-        assert remote_value_9.payload_valid(dpt_9_payload) == dpt_9_payload
+            remote_value_9.from_knx(DPTBinary(1))
+        assert remote_value_9.from_knx(dpt_9_payload) == 1
 
     def test_to_knx_uninitialized(self):
         """Test to_knx raising ConversionError."""
@@ -105,13 +106,13 @@ class TestRemoteValueSetpointShift:
         """Test from_knx for uninitialized setpoint_shift_mode."""
         xknx = XKNX()
         remote_value = RemoteValueSetpointShift(xknx=xknx)
-        with pytest.raises(AssertionError):
+        with pytest.raises(CouldNotParseTelegram):
             remote_value.from_knx(1)
         # assign DPT 9.002 mode
-        remote_value.payload_valid(DPTArray((0x00, 0x64)))
+        assert remote_value.from_knx(DPTArray((0x00, 0x64))) == 1
         assert remote_value.from_knx(DPTArray((0x07, 0xD0))) == 20
         # wrong payload length raises, once assigned
-        with pytest.raises(ConversionError):
+        with pytest.raises(CouldNotParseTelegram):
             remote_value.from_knx(DPTArray((10,)))
 
     def test_from_knx_dpt_6(self):
diff --git a/test/remote_value_tests/remote_value_test.py b/test/remote_value_tests/remote_value_test.py
index f212f2d8..ab0815fe 100644
--- a/test/remote_value_tests/remote_value_test.py
+++ b/test/remote_value_tests/remote_value_test.py
@@ -90,10 +90,9 @@ class TestRemoteValue:
         """Test if exception is raised when processing telegram with unsupported payload type."""
         xknx = XKNX()
         remote_value = RemoteValue(xknx)
-        with patch("xknx.remote_value.RemoteValue.payload_valid") as patch_valid, patch(
+        with patch(
             "xknx.remote_value.RemoteValue.has_group_address"
         ) as patch_has_group_address:
-            patch_valid.return_value = False
             patch_has_group_address.return_value = True
 
             telegram = Telegram(
@@ -105,19 +104,6 @@ class TestRemoteValue:
             ):
                 await remote_value.process(telegram)
 
-    async def test_process_invalid_payload(self):
-        """Test if exception is raised when processing telegram with invalid payload."""
-        xknx = XKNX()
-        remote_value = RemoteValue(xknx)
-        with patch(
-            "xknx.remote_value.RemoteValue.has_group_address", remote_value=True
-        ):
-            telegram = Telegram(
-                destination_address=GroupAddress("1/2/1"),
-                payload=GroupValueWrite(DPTArray((0x01, 0x02))),
-            )
-            assert await remote_value.process(telegram) is False
-
     async def test_process_unsupported_payload(self):
         """Test warning is logged when processing telegram with unsupported payload."""
         xknx = XKNX()
@@ -126,14 +112,13 @@ class TestRemoteValue:
             destination_address=GroupAddress("1/2/1"),
             payload=GroupValueWrite(DPTArray((0x01, 0x02))),
         )
-        with patch("xknx.remote_value.RemoteValue.payload_valid") as patch_valid, patch(
+        with patch(
             "xknx.remote_value.RemoteValue.has_group_address"
         ) as patch_has_group_address, patch(
             "xknx.remote_value.RemoteValue.from_knx"
         ) as patch_from, patch(
             "logging.Logger.warning"
         ) as mock_warning:
-            patch_valid.return_value = telegram.payload.value
             patch_has_group_address.return_value = True
             patch_from.side_effect = ConversionError("TestError")
 
@@ -151,10 +136,7 @@ class TestRemoteValue:
         xknx = XKNX()
         remote_value = RemoteValueSwitch(xknx, group_address_state="1/2/3")
 
-        with patch("xknx.remote_value.RemoteValue.payload_valid") as patch_valid, patch(
-            "xknx.core.ValueReader.read", new_callable=AsyncMock
-        ) as patch_read:
-            patch_valid.return_value = True
+        with patch("xknx.core.ValueReader.read", new_callable=AsyncMock) as patch_read:
             telegram = Telegram(
                 destination_address=GroupAddress("1/2/3"),
                 payload=GroupValueWrite(DPTBinary(1)),
@@ -171,10 +153,9 @@ class TestRemoteValue:
         xknx = XKNX()
         remote_value = RemoteValueSwitch(xknx, group_address_state="1/2/3")
 
-        with patch("xknx.remote_value.RemoteValue.payload_valid") as patch_valid, patch(
+        with patch(
             "xknx.core.ValueReader.read", new_callable=AsyncMock
         ) as patch_read, patch("logging.Logger.warning") as mock_warning:
-            patch_valid.return_value = False
             patch_read.return_value = None
 
             await remote_value.read_state(wait_for_result=True)
@@ -235,15 +216,14 @@ class TestRemoteValue:
         assert not remote_value.readable
         # RemoteValue is initialized with only passive group address
         assert remote_value.initialized
-        with patch("xknx.remote_value.RemoteValue.payload_valid") as patch_always_valid:
-            patch_always_valid.side_effect = lambda payload: payload
-            test_payload = DPTArray((0x01, 0x02))
-            telegram = Telegram(
-                destination_address=GroupAddress("1/1/1"),
-                payload=GroupValueWrite(test_payload),
-            )
-            assert await remote_value.process(telegram)
-            assert remote_value.telegram.payload.value == test_payload
+
+        test_payload = DPTArray((0x01, 0x02))
+        telegram = Telegram(
+            destination_address=GroupAddress("1/1/1"),
+            payload=GroupValueWrite(test_payload),
+        )
+        assert await remote_value.process(telegram)
+        assert remote_value.telegram.payload.value == test_payload
 
     def test_eq(self):
         """Test __eq__ operator."""