Repository: XKNX/xknx
Pull request #519 (2020-11-30 23:53:38)

Refactor `Telegram.payload` to support other APCI payloads
## Description
This is the second part of #253, where `Telegram.payload` is refactored to support generic APCI payloads. In addition, `Telegram.telegramtype` and `CEMIFrame.cmd` are obsolete as well, because they can be derived from the payload.

Most of the changes are related to the testcases, where `Telegram(.., payload=DPT*())` is now (for example) `Telegram(.., payload=GroupValueWrite(DPT*()))`.

I have not tested the Home-Assistant plugin, as I don't have a setup running. It probably needs some changes.

## Type of change
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

## Checklist:
- [x] I have performed a self-review of my own code
- [ ] The documentation has been adjusted accordingly
- [x] The changes generate no new warnings
- [x] Tests have been added that prove the fix is effective or that the feature works
- [x] The changes are documented in the changelog
- [x] The Homeassistant plugin has been adjusted in case of new config options


basilfx: I have two MyPy errors left, which I don't understand:

```
xknx/telegram/apci.py:14: error: Module 'xknx.dpt' has no attribute 'DPTArray'; maybe "DPTArea"?
xknx/telegram/apci.py:14: error: Module 'xknx.dpt' has no attribute 'DPTBinary'
```

False positive?
farmio: Have you seen this: https://github.com/python/mypy/issues/7423
Maybe we should adapt our Mypy config. I think `from ... as ...` would be odd. 
coveralls: 
[![Coverage Status](https://coveralls.io/builds/35366660/badge)](https://coveralls.io/builds/35366660)

Coverage decreased (-0.1%) to 94.461% when pulling **67c3062ad661fc1e926688d4490a84372305e914 on basilfx:feature/apci_payload** into **0defb2649644ef8fe53ab7d375875c2366be245f on XKNX:master**.

farmio: when I run this now from my HA test installation I get a lot of 
```python
2020-12-01 22:12:26 WARNING (MainThread) [xknx.log] 'from_knx()' not implemented for GroupValueRead
# and this for GAs in fire_event_filter
2020-12-01 22:12:26 WARNING (Recorder) [homeassistant.components.recorder] Event is not JSON serializable: <Event knx_event[L]: data=<GroupValueResponse dpt="<DPTArray value="[0x19,0x62]" />" />, destination=5/1/0, direction=Incoming, source=1.0.54, telegramtype=GroupValueResponse>
```
also I think it would be cleaner if the APCI services could get their own module for importing them instead of importing from xknx.telegram together with higher-order classes
```python
# eg
from xknx.telegram import GroupAddress, IndividualAddress, Telegram
from xknx.telegram.apci import GroupValueRead, GroupValueWrite
# instead of
from xknx.telegram import GroupAddress, GroupValueRead, GroupValueWrite, IndividualAddress, Telegram
```
basilfx: @farmio Thanks for testing, I'll pick it up. I'm also busy with a few other improvements.
basilfx: I'm still working on the APCI unit tests.
basilfx: Unit tests are ready. Let's see what I have to add more :-)

Patch:
diff --git a/changelog.md b/changelog.md
index 43f43ced..9474a656 100644
--- a/changelog.md
+++ b/changelog.md
@@ -10,6 +10,8 @@
 
 - renamed "PhysicalAddress" to "IndividualAddress"
 - Telegram: `group_address` renamed to `destination_address`, to prepare support for other APCI services and add `source_address`
+- Telegram: remove `Telegram.telegramtype` and replace with payload object derived from `xknx.telegram.apci.APCI`.
+- CEMIFrame: remove `CEMIFrame.cmd`, which can be derived from `CEMIFrame.payload`.
 - Farewell Travis CI; Welcome Github Actions!
 
 ## 0.15.6 Bugfix for StateUpater 2020-11-26
diff --git a/examples/example_tunnel.py b/examples/example_tunnel.py
index c5450568..979adde3 100644
--- a/examples/example_tunnel.py
+++ b/examples/example_tunnel.py
@@ -5,6 +5,7 @@ from xknx import XKNX
 from xknx.dpt import DPTBinary
 from xknx.io import GatewayScanner, Tunnel
 from xknx.telegram import GroupAddress, IndividualAddress, Telegram
+from xknx.telegram.apci import GroupValueWrite
 
 
 async def main():
@@ -38,11 +39,17 @@ async def main():
     await tunnel.connect()
 
     await tunnel.send_telegram(
-        Telegram(destination_address=GroupAddress("1/0/15"), payload=DPTBinary(1))
+        Telegram(
+            destination_address=GroupAddress("1/0/15"),
+            payload=GroupValueWrite(DPTBinary(1)),
+        )
     )
     await asyncio.sleep(2)
     await tunnel.send_telegram(
-        Telegram(destination_address=GroupAddress("1/0/15"), payload=DPTBinary(0))
+        Telegram(
+            destination_address=GroupAddress("1/0/15"),
+            payload=GroupValueWrite(DPTBinary(0)),
+        )
     )
     await asyncio.sleep(2)
 
diff --git a/home-assistant-plugin/custom_components/xknx/__init__.py b/home-assistant-plugin/custom_components/xknx/__init__.py
index 4b82e1d3..44142025 100644
--- a/home-assistant-plugin/custom_components/xknx/__init__.py
+++ b/home-assistant-plugin/custom_components/xknx/__init__.py
@@ -14,6 +14,7 @@ from xknx.io import (
     ConnectionType,
 )
 from xknx.telegram import AddressFilter, GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueResponse, GroupValueWrite
 
 from homeassistant.const import (
     CONF_ENTITY_ID,
@@ -322,14 +323,20 @@ class KNXModule:
 
     async def telegram_received_cb(self, telegram):
         """Call invoked after a KNX telegram was received."""
+        data = None
+
+        # Not all telegrams have serializable data.
+        if isinstance(telegram, (GroupValueWrite, GroupValueResponse)):
+            data = telegram.dpt.value
+
         self.hass.bus.async_fire(
             "knx_event",
             {
-                "data": telegram.payload.value,
+                "data": data,
                 "destination": str(telegram.destination_address),
                 "direction": telegram.direction.value,
                 "source": str(telegram.source_address),
-                "telegramtype": telegram.telegramtype.value,
+                "telegramtype": telegram.payload.__class__.__name__,
             },
         )
 
diff --git a/xknx/core/telegram_queue.py b/xknx/core/telegram_queue.py
index 5d8eb94f..5981bdd4 100644
--- a/xknx/core/telegram_queue.py
+++ b/xknx/core/telegram_queue.py
@@ -11,7 +11,8 @@ import asyncio
 import logging
 
 from xknx.exceptions import CommunicationError, XKNXException
-from xknx.telegram import TelegramDirection, TelegramType
+from xknx.telegram import TelegramDirection
+from xknx.telegram.apci import GroupValueWrite
 
 logger = logging.getLogger("xknx.log")
 telegram_logger = logging.getLogger("xknx.telegram")
@@ -133,7 +134,7 @@ class TelegramQueue:
         telegram_logger.debug(telegram)
         if self.xknx.knxip_interface is not None:
             await self.xknx.knxip_interface.send_telegram(telegram)
-            if telegram.telegramtype == TelegramType.GROUP_WRITE:
+            if isinstance(telegram.payload, GroupValueWrite):
                 await self.xknx.devices.process(telegram)
         else:
             logger.warning("No KNXIP interface defined")
diff --git a/xknx/core/value_reader.py b/xknx/core/value_reader.py
index 6c3ea6f6..928d05d9 100644
--- a/xknx/core/value_reader.py
+++ b/xknx/core/value_reader.py
@@ -10,7 +10,8 @@ The module will
 import asyncio
 import logging
 
-from xknx.telegram import Telegram, TelegramType
+from xknx.telegram import Telegram
+from xknx.telegram.apci import GroupValueRead, GroupValueResponse, GroupValueWrite
 
 logger = logging.getLogger("xknx.log")
 
@@ -49,19 +50,14 @@ class ValueReader:
     async def send_group_read(self):
         """Send group read."""
         telegram = Telegram(
-            destination_address=self.group_address, telegramtype=TelegramType.GROUP_READ
+            destination_address=self.group_address, payload=GroupValueRead()
         )
         await self.xknx.telegrams.put(telegram)
 
     async def telegram_received(self, telegram):
         """Test if telegram has correct group address and trigger event."""
-        if (
-            telegram.destination_address == self.group_address
-            and telegram.telegramtype
-            in (
-                TelegramType.GROUP_RESPONSE,
-                TelegramType.GROUP_WRITE,
-            )
+        if telegram.destination_address == self.group_address and isinstance(
+            telegram.payload, (GroupValueResponse, GroupValueWrite)
         ):
             self.success = True
             self.received_telegram = telegram
diff --git a/xknx/devices/device.py b/xknx/devices/device.py
index 6d33b2e7..72c44bb5 100644
--- a/xknx/devices/device.py
+++ b/xknx/devices/device.py
@@ -5,7 +5,7 @@ It provides basis functionality for reading the state from the KNX bus.
 """
 import logging
 
-from xknx.telegram import TelegramType
+from xknx.telegram.apci import GroupValueRead, GroupValueResponse, GroupValueWrite
 
 logger = logging.getLogger("xknx.log")
 
@@ -51,11 +51,11 @@ class Device:
 
     async def process(self, telegram):
         """Process incoming telegram."""
-        if telegram.telegramtype == TelegramType.GROUP_WRITE:
+        if isinstance(telegram.payload, GroupValueWrite):
             await self.process_group_write(telegram)
-        elif telegram.telegramtype == TelegramType.GROUP_RESPONSE:
+        elif isinstance(telegram.payload, GroupValueResponse):
             await self.process_group_response(telegram)
-        elif telegram.telegramtype == TelegramType.GROUP_READ:
+        elif isinstance(telegram.payload, GroupValueRead):
             await self.process_group_read(telegram)
 
     async def process_group_read(self, telegram):
@@ -69,7 +69,7 @@ class Device:
 
     async def process_group_write(self, telegram):
         """Process incoming GroupValueWrite telegrams."""
-        # The dafault is, that devices dont process group writes
+        # The default is, that devices dont process group writes
 
     def get_name(self):
         """Return name of device."""
diff --git a/xknx/dpt/__init__.py b/xknx/dpt/__init__.py
index b5bfff82..0141d90e 100644
--- a/xknx/dpt/__init__.py
+++ b/xknx/dpt/__init__.py
@@ -170,3 +170,161 @@ from .dpt_hvac_mode import (
 from .dpt_scaling import DPTAngle, DPTScaling
 from .dpt_string import DPTString
 from .dpt_time import DPTTime
+
+__all__ = [
+    "DPT2ByteFloat",
+    "DPT2ByteSigned",
+    "DPT2ByteUnsigned",
+    "DPT2Ucount",
+    "DPT4ByteFloat",
+    "DPT4ByteSigned",
+    "DPT4ByteUnsigned",
+    "DPTAbsoluteTemperature",
+    "DPTAcceleration",
+    "DPTAccelerationAngular",
+    "DPTActivationEnergy",
+    "DPTActiveEnergy",
+    "DPTActiveEnergykWh",
+    "DPTActivity",
+    "DPTAmplitude",
+    "DPTAngle",
+    "DPTAngleDeg",
+    "DPTAngleRad",
+    "DPTAngularFrequency",
+    "DPTAngularMomentum",
+    "DPTAngularVelocity",
+    "DPTApparantEnergy",
+    "DPTApparantEnergykVAh",
+    "DPTArea",
+    "DPTArray",
+    "DPTBase",
+    "DPTBinary",
+    "DPTBrightness",
+    "DPTCapacitance",
+    "DPTChargeDensitySurface",
+    "DPTChargeDensityVolume",
+    "DPTColorTemperature",
+    "DPTCommonTemperature",
+    "DPTComparator",
+    "DPTCompressibility",
+    "DPTConductance",
+    "DPTControllerStatus",
+    "DPTCurrent",
+    "DPTDate",
+    "DPTDateTime",
+    "DPTDecimalFactor",
+    "DPTDeltaTimeHrs",
+    "DPTDeltaTimeMin",
+    "DPTDeltaTimeMsec",
+    "DPTDeltaTimeSec",
+    "DPTDensity",
+    "DPTElectricalConductivity",
+    "DPTElectricCharge",
+    "DPTElectricCurrent",
+    "DPTElectricCurrentDensity",
+    "DPTElectricDipoleMoment",
+    "DPTElectricDisplacement",
+    "DPTElectricFieldStrength",
+    "DPTElectricFlux",
+    "DPTElectricFluxDensity",
+    "DPTElectricPolarization",
+    "DPTElectricPotential",
+    "DPTElectricPotentialDifference",
+    "DPTElectromagneticMoment",
+    "DPTElectromotiveForce",
+    "DPTEnergy",
+    "DPTEnthalpy",
+    "DPTFlowRateM3H",
+    "DPTForce",
+    "DPTFrequency",
+    "DPTHeatCapacity",
+    "DPTHeatFlowRate",
+    "DPTHeatQuantity",
+    "DPTHumidity",
+    "DPTHVACContrMode",
+    "DPTHVACMode",
+    "DPTImpedance",
+    "DPTKelvinPerPercent",
+    "DPTLength",
+    "DPTLengthMm",
+    "DPTLightQuantity",
+    "DPTLongDeltaTimeSec",
+    "DPTLuminance",
+    "DPTLuminousFlux",
+    "DPTLuminousIntensity",
+    "DPTLux",
+    "DPTMagneticFieldStrength",
+    "DPTMagneticFlux",
+    "DPTMagneticFluxDensity",
+    "DPTMagneticMoment",
+    "DPTMagneticPolarization",
+    "DPTMagnetization",
+    "DPTMagnetomotiveForce",
+    "DPTMass",
+    "DPTMassFlux",
+    "DPTMol",
+    "DPTMomentum",
+    "DPTPartsPerMillion",
+    "DPTPercentU8",
+    "DPTPercentV16",
+    "DPTPercentV8",
+    "DPTPhaseAngleDeg",
+    "DPTPhaseAngleRad",
+    "DPTPower",
+    "DPTPower2Byte",
+    "DPTPowerDensity",
+    "DPTPowerFactor",
+    "DPTPressure",
+    "DPTPressure2Byte",
+    "DPTRainAmount",
+    "DPTReactance",
+    "DPTReactiveEnergy",
+    "DPTReactiveEnergykVARh",
+    "DPTResistance",
+    "DPTResistivity",
+    "DPTRotationAngle",
+    "DPTScaling",
+    "DPTSceneNumber",
+    "DPTSelfInductance",
+    "DPTSignedRelativeValue",
+    "DPTSolidAngle",
+    "DPTSoundIntensity",
+    "DPTSpeed",
+    "DPTStress",
+    "DPTString",
+    "DPTSurfaceTension",
+    "DPTTariff",
+    "DPTTemperature",
+    "DPTTemperatureA",
+    "DPTTemperatureDifference",
+    "DPTTemperatureDifference2Byte",
+    "DPTTemperatureF",
+    "DPTThermalCapacity",
+    "DPTThermalConductivity",
+    "DPTThermoelectricPower",
+    "DPTTime",
+    "DPTTime1",
+    "DPTTime2",
+    "DPTTimePeriod100Msec",
+    "DPTTimePeriod10Msec",
+    "DPTTimePeriodHrs",
+    "DPTTimePeriodMin",
+    "DPTTimePeriodMsec",
+    "DPTTimePeriodSec",
+    "DPTTimeSeconds",
+    "DPTTorque",
+    "DPTUElCurrentmA",
+    "DPTValue1ByteUnsigned",
+    "DPTValue1Count",
+    "DPTValue1Ucount",
+    "DPTValue2Count",
+    "DPTValue4Count",
+    "DPTVoltage",
+    "DPTVolume",
+    "DPTVolumeFlow",
+    "DPTVolumeFlux",
+    "DPTWeight",
+    "DPTWork",
+    "DPTWsp",
+    "DPTWspKmh",
+]
diff --git a/xknx/dpt/dpt.py b/xknx/dpt/dpt.py
index f462ddca..c6c42abf 100644
--- a/xknx/dpt/dpt.py
+++ b/xknx/dpt/dpt.py
@@ -1,4 +1,6 @@
 """Implementation of Basic KNX datatypes."""
+from typing import Union
+
 from xknx.exceptions import ConversionError
 
 
@@ -126,7 +128,7 @@ class DPTBinary:
     APCI_BITMASK = 0x3F
     APCI_MAX_VALUE = APCI_BITMASK
 
-    def __init__(self, value):
+    def __init__(self, value) -> None:
         """Initialize DPTBinary class."""
         if not isinstance(value, int):
             raise TypeError()
@@ -148,7 +150,7 @@ class DPTArray:
     """The DPTArray is a base class for all datatypes appended to the KNX telegram."""
 
     # pylint: disable=too-few-public-methods
-    def __init__(self, value):
+    def __init__(self, value: Union[int, list, bytes, tuple]) -> None:
         """Initialize DPTArray class."""
         if isinstance(value, int):
             self.value = (value,)
diff --git a/xknx/io/routing.py b/xknx/io/routing.py
index 57776ffb..3fbd4aa0 100644
--- a/xknx/io/routing.py
+++ b/xknx/io/routing.py
@@ -6,7 +6,6 @@ Routing uses UDP Multicast to broadcast and receive KNX/IP messages.
 import logging
 
 from xknx.knxip import (
-    APCICommand,
     CEMIFrame,
     CEMIMessageCode,
     KNXIPFrame,
@@ -49,12 +48,6 @@ class Routing:
             return
         elif knxipframe.body.cemi.src_addr == self.xknx.own_address:
             logger.debug("Ignoring own packet")
-        elif knxipframe.body.cemi.cmd not in (
-            APCICommand.GROUP_READ,
-            APCICommand.GROUP_WRITE,
-            APCICommand.GROUP_RESPONSE,
-        ):
-            logger.warning("APCI not implemented: %s", knxipframe)
         else:
             telegram = knxipframe.body.cemi.telegram
             telegram.direction = TelegramDirection.INCOMING
diff --git a/xknx/knxip/__init__.py b/xknx/knxip/__init__.py
index cb8edbaf..0b1d000b 100644
--- a/xknx/knxip/__init__.py
+++ b/xknx/knxip/__init__.py
@@ -14,7 +14,6 @@ from .header import KNXIPHeader
 from .hpai import HPAI
 from .knxip import KNXIPFrame
 from .knxip_enum import (
-    APCICommand,
     CEMIFlags,
     CEMIMessageCode,
     ConnectRequestType,
diff --git a/xknx/knxip/cemi_frame.py b/xknx/knxip/cemi_frame.py
index c26dd1e6..f3c82039 100644
--- a/xknx/knxip/cemi_frame.py
+++ b/xknx/knxip/cemi_frame.py
@@ -13,11 +13,11 @@ Documentation within:
 """
 from typing import Union
 
-from xknx.dpt import DPTArray, DPTBinary
 from xknx.exceptions import ConversionError, CouldNotParseKNXIP, UnsupportedCEMIMessage
-from xknx.telegram import GroupAddress, IndividualAddress, Telegram, TelegramType
+from xknx.telegram import GroupAddress, IndividualAddress, Telegram
+from xknx.telegram.apci import APCI
 
-from .knxip_enum import APCICommand, CEMIFlags, CEMIMessageCode
+from .knxip_enum import CEMIFlags, CEMIMessageCode
 
 
 class CEMIFrame:
@@ -30,7 +30,6 @@ class CEMIFrame:
         xknx,
         code: CEMIMessageCode = CEMIMessageCode.L_DATA_IND,
         flags: int = 0,
-        cmd: APCICommand = APCICommand.GROUP_READ,
         src_addr: IndividualAddress = IndividualAddress(None),
         dst_addr: Union[GroupAddress, IndividualAddress] = GroupAddress(None),
         mpdu_len: int = 0,
@@ -40,7 +39,6 @@ class CEMIFrame:
         self.xknx = xknx
         self.code = code
         self.flags = flags
-        self.cmd = cmd
         self.src_addr = src_addr
         self.dst_addr = dst_addr
         self.mpdu_len = mpdu_len
@@ -63,19 +61,8 @@ class CEMIFrame:
     def telegram(self) -> Telegram:
         """Return telegram."""
 
-        def resolve_telegram_type(cmd):
-            """Return telegram type from APCI Command."""
-            if cmd == APCICommand.GROUP_WRITE:
-                return TelegramType.GROUP_WRITE
-            if cmd == APCICommand.GROUP_READ:
-                return TelegramType.GROUP_READ
-            if cmd == APCICommand.GROUP_RESPONSE:
-                return TelegramType.GROUP_RESPONSE
-            raise ConversionError(f"Telegram not implemented for {self.cmd}")
-
         return Telegram(
             destination_address=self.dst_addr,
-            telegramtype=resolve_telegram_type(self.cmd),
             payload=self.payload,
             source_address=self.src_addr,
         )
@@ -105,19 +92,6 @@ class CEMIFrame:
         else:
             raise TypeError()
 
-        # TODO: use telegram.direction
-        def resolve_cmd(telegramtype: TelegramType) -> APCICommand:
-            """Resolve APCICommand from TelegramType."""
-            if telegramtype == TelegramType.GROUP_READ:
-                return APCICommand.GROUP_READ
-            if telegramtype == TelegramType.GROUP_WRITE:
-                return APCICommand.GROUP_WRITE
-            if telegramtype == TelegramType.GROUP_RESPONSE:
-                return APCICommand.GROUP_RESPONSE
-            raise TypeError()
-
-        self.cmd = resolve_cmd(telegram.telegramtype)
-
     def set_hops(self, hops: int):
         """Set hops."""
         # Resetting hops
@@ -127,13 +101,9 @@ class CEMIFrame:
 
     def calculated_length(self) -> int:
         """Get length of KNX/IP body."""
-        if self.payload is None:
-            return 11
-        if isinstance(self.payload, DPTBinary):
-            return 11
-        if isinstance(self.payload, DPTArray):
-            return 11 + len(self.payload.value)
-        raise TypeError()
+        if not isinstance(self.payload, APCI):
+            raise TypeError()
+        return 10 + self.payload.calculated_length()
 
     def from_knx(self, raw) -> int:
         """Parse/deserialize from KNX/IP raw data."""
@@ -188,35 +158,31 @@ class CEMIFrame:
         # TPCI (transport layer control information)   -> First 14 bit
         # APCI (application layer control information) -> Last  10 bit
 
-        tpci_apci = cemi[9 + addil] * 256 + cemi[10 + addil]
-
-        try:
-            self.cmd = APCICommand(tpci_apci & 0xFFC0)
-        except ValueError:
-            raise UnsupportedCEMIMessage(
-                "APCI not supported: {:#012b} in CEMI: {}".format(
-                    tpci_apci & 0xFFC0, cemi.hex()
-                )
-            )
-
-        apdu = cemi[10 + addil :]
-        if len(apdu) != self.mpdu_len:
+        apdu = cemi[9 + addil :]
+        if len(apdu) != (self.mpdu_len + 1):
             raise CouldNotParseKNXIP(
                 "APDU LEN should be {} but is {} in CEMI: {}".format(
                     self.mpdu_len, len(apdu), cemi.hex()
                 )
             )
 
-        if len(apdu) == 1:
-            apci = tpci_apci & DPTBinary.APCI_BITMASK
-            self.payload = DPTBinary(apci)
-        else:
-            self.payload = DPTArray(cemi[11 + addil :])
+        tpci_apci = (apdu[0] << 8) + apdu[1]
+
+        try:
+            self.payload = APCI.resolve_apci(tpci_apci & 0x03FF)
+        except ConversionError:
+            raise UnsupportedCEMIMessage(
+                "APCI not supported: {:#012b}".format(tpci_apci & 0x03FF)
+            )
+
+        self.payload.from_knx(apdu)
 
-        return 10 + addil + len(apdu)
+        return 10 + addil + self.mpdu_len
 
     def to_knx(self):
         """Serialize to KNX/IP raw data."""
+        if not isinstance(self.payload, APCI):
+            raise TypeError()
         if not isinstance(self.src_addr, (GroupAddress, IndividualAddress)):
             raise ConversionError("src_addr not set")
         if not isinstance(self.dst_addr, (GroupAddress, IndividualAddress)):
@@ -230,42 +196,19 @@ class CEMIFrame:
         data.append(self.flags & 255)
         data.extend(self.src_addr.to_knx())
         data.extend(self.dst_addr.to_knx())
+        data.append(self.payload.calculated_length())
+        data.extend(self.payload.to_knx())
 
-        def encode_cmd_and_payload(cmd, encoded_payload=0, appended_payload=None):
-            """Encode cmd and payload."""
-            if appended_payload is None:
-                appended_payload = []
-            data = [
-                1 + len(appended_payload),
-                (cmd.value >> 8) & 0xFF,
-                (cmd.value & 0xFF) | (encoded_payload & DPTBinary.APCI_BITMASK),
-            ]
-            data.extend(appended_payload)
-            return data
-
-        if self.payload is None:
-            data.extend(encode_cmd_and_payload(self.cmd))
-        elif isinstance(self.payload, DPTBinary):
-            data.extend(
-                encode_cmd_and_payload(self.cmd, encoded_payload=self.payload.value)
-            )
-        elif isinstance(self.payload, DPTArray):
-            data.extend(
-                encode_cmd_and_payload(self.cmd, appended_payload=self.payload.value)
-            )
-        else:
-            raise TypeError()
         return data
 
     def __str__(self):
         """Return object as readable string."""
         return (
             '<CEMIFrame SourceAddress="{}" DestinationAddress="{}" '
-            'Flags="{:16b}" Command="{}" payload="{}" />'.format(
+            'Flags="{:16b}" payload="{}" />'.format(
                 self.src_addr.__repr__(),
                 self.dst_addr.__repr__(),
                 self.flags,
-                self.cmd,
                 self.payload,
             )
         )
diff --git a/xknx/knxip/knxip_enum.py b/xknx/knxip/knxip_enum.py
index 127a76e9..f4d1e060 100644
--- a/xknx/knxip/knxip_enum.py
+++ b/xknx/knxip/knxip_enum.py
@@ -49,14 +49,6 @@ class CEMIMessageCode(Enum):
     L_RAW_CON = 0x2F
 
 
-class APCICommand(Enum):
-    """Enum class for KNX/IP APCI Commands."""
-
-    GROUP_READ = 0x00
-    GROUP_WRITE = 0x80
-    GROUP_RESPONSE = 0x40
-
-
 class CEMIFlags:
     """Enum class for KNX/IP CEMI Flags."""
 
diff --git a/xknx/remote_value/remote_value.py b/xknx/remote_value/remote_value.py
index 88f86ee3..dc7a298c 100644
--- a/xknx/remote_value/remote_value.py
+++ b/xknx/remote_value/remote_value.py
@@ -10,7 +10,8 @@ import logging
 from typing import List
 
 from xknx.exceptions import CouldNotParseTelegram
-from xknx.telegram import GroupAddress, Telegram, TelegramType
+from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueResponse, GroupValueWrite
 
 logger = logging.getLogger("xknx.log")
 
@@ -112,17 +113,37 @@ class RemoteValue:
         """Process incoming or outgoing telegram."""
         if not self.has_group_address(telegram.destination_address):
             return False
-        if not self.payload_valid(telegram.payload):
+        if not isinstance(
+            telegram.payload,
+            (
+                GroupValueWrite,
+                GroupValueResponse,
+            ),
+        ):
+            raise CouldNotParseTelegram(
+                "payload not a GroupValueWrite or GroupValueResponse",
+                payload=telegram.payload,
+                destination_address=telegram.destination_address,
+                source_address=telegram.source_address,
+                device_name=self.device_name,
+                feature_name=self.feature_name,
+            )
+        if not self.payload_valid(telegram.payload.value):
             raise CouldNotParseTelegram(
                 "payload invalid",
                 payload=telegram.payload,
-                address=telegram.destination_address,
+                destination_address=telegram.destination_address,
+                source_address=telegram.source_address,
                 device_name=self.device_name,
                 feature_name=self.feature_name,
             )
         self.xknx.state_updater.update_received(self)
-        if self.payload is None or always_callback or self.payload != telegram.payload:
-            self.payload = telegram.payload
+        if (
+            self.payload is None
+            or always_callback
+            or self.payload != telegram.payload.value
+        ):
+            self.payload = telegram.payload.value
             if self.after_update_cb is not None:
                 await self.after_update_cb()
         return True
@@ -138,10 +159,9 @@ class RemoteValue:
         """Send payload as telegram to KNX bus."""
         telegram = Telegram(
             destination_address=self.group_address,
-            telegramtype=(
-                TelegramType.GROUP_RESPONSE if response else TelegramType.GROUP_WRITE
+            payload=(
+                GroupValueResponse(payload) if response else GroupValueWrite(payload)
             ),
-            payload=payload,
         )
         await self.xknx.telegrams.put(telegram)
 
diff --git a/xknx/telegram/__init__.py b/xknx/telegram/__init__.py
index b47b5fd0..1b222e1d 100644
--- a/xknx/telegram/__init__.py
+++ b/xknx/telegram/__init__.py
@@ -8,4 +8,4 @@ Module for handling KNX primitves.
 # flake8: noqa
 from .address import GroupAddress, GroupAddressType, IndividualAddress
 from .address_filter import AddressFilter
-from .telegram import Telegram, TelegramDirection, TelegramType
+from .telegram import Telegram, TelegramDirection
diff --git a/xknx/telegram/apci.py b/xknx/telegram/apci.py
new file mode 100644
index 00000000..c5075ba2
--- /dev/null
+++ b/xknx/telegram/apci.py
@@ -0,0 +1,207 @@
+"""
+Module for serialization and deserialization of APCI payloads.
+
+APCI stands for Application Layer Protocol Control Information.
+
+An APCI payload contains a service and payload. For example, a GroupValueWrite
+is a service that takes a DPT as a value.
+"""
+from abc import ABC, abstractmethod
+from enum import Enum
+from typing import ClassVar, Optional, Union, cast
+
+from xknx.dpt import DPTArray, DPTBinary
+from xknx.exceptions import ConversionError
+
+
+def encode_cmd_and_payload(
+    cmd: "APCICommand",
+    encoded_payload: int = 0,
+    appended_payload: Optional[bytes] = None,
+) -> bytes:
+    """Encode cmd and payload."""
+    if appended_payload is None:
+        appended_payload = bytes()
+
+    data = bytearray(
+        [
+            (cmd.value >> 8) & 0xFF,
+            (cmd.value & 0xFF) | (encoded_payload & DPTBinary.APCI_BITMASK),
+        ]
+    )
+    data.extend(appended_payload)
+    return data
+
+
+class APCICommand(Enum):
+    """Enum class for APCI services."""
+
+    GROUP_READ = 0x0000
+    GROUP_RESPONSE = 0x0040
+    GROUP_WRITE = 0x0080
+
+    ESCAPE = 0x03C0
+
+
+class APCIExtendedCommand(Enum):
+    """Enum class for extended APCI services."""
+
+
+class APCI(ABC):
+    """
+    Base class for ACPI services.
+
+    This base class is only the interface for the derived classes.
+    """
+
+    code: ClassVar[APCICommand] = cast(APCICommand, None)
+
+    @abstractmethod
+    def calculated_length(self) -> int:
+        """Get length of APCI payload - to be implemented in derived class."""
+
+    @abstractmethod
+    def from_knx(self, raw: bytes) -> None:
+        """Parse/deserialize from KNX/IP raw data - to be implemented in derived class."""
+
+    @abstractmethod
+    def to_knx(self) -> bytes:
+        """Serialize to KNX/IP raw data - to be implemented in derived class."""
+
+    def __eq__(self, other: object) -> bool:
+        """Equal operator."""
+        return self.__dict__ == other.__dict__
+
+    @staticmethod
+    def resolve_apci(apci: int) -> "APCI":
+        """Return APCI instance from APCI command."""
+        extended = (apci & APCICommand.ESCAPE.value) == APCICommand.ESCAPE.value
+
+        if extended:
+            raise ConversionError(
+                f"Class not implemented for extended APCI {apci:#012b}."
+            )
+
+        apci = apci & 0x03C0
+
+        if apci == APCICommand.GROUP_READ.value:
+            return GroupValueRead()
+        if apci == APCICommand.GROUP_WRITE.value:
+            return GroupValueWrite()
+        if apci == APCICommand.GROUP_RESPONSE.value:
+            return GroupValueResponse()
+        raise ConversionError(f"Class not implemented for APCI {apci:#012b}.")
+
+
+class GroupValueRead(APCI):
+    """
+    GroupValueRead service.
+
+    Does not have any payload.
+    """
+
+    code = APCICommand.GROUP_READ
+
+    def calculated_length(self) -> int:
+        """Get length of APCI payload."""
+        return 1
+
+    def from_knx(self, raw: bytes) -> None:
+        """Parse/deserialize from KNX/IP raw data."""
+
+        # Nothing to parse, but must be implemented explicitly.
+        return
+
+    def to_knx(self) -> bytes:
+        """Serialize to KNX/IP raw data."""
+        return encode_cmd_and_payload(self.code)
+
+    def __str__(self) -> str:
+        """Return object as readable string."""
+        return "<GroupValueRead />"
+
+
+class GroupValueWrite(APCI):
+    """
+    GroupValueRead service.
+
+    Takes a value (DPT) as payload.
+    """
+
+    code = APCICommand.GROUP_WRITE
+
+    def __init__(self, value: Optional[Union[DPTBinary, DPTArray]] = None) -> None:
+        """Initialize a new instance of GroupValueWrite."""
+        self.value = value
+
+    def calculated_length(self) -> int:
+        """Get length of APCI payload."""
+        if isinstance(self.value, DPTBinary):
+            return 1
+        if isinstance(self.value, DPTArray):
+            return 1 + len(self.value.value)
+        raise TypeError()
+
+    def from_knx(self, raw: bytes) -> None:
+        """Parse/deserialize from KNX/IP raw data."""
+        if len(raw) == 2:
+            self.value = DPTBinary(raw[1] & DPTBinary.APCI_BITMASK)
+        else:
+            self.value = DPTArray(raw[2:])
+
+    def to_knx(self) -> bytes:
+        """Serialize to KNX/IP raw data."""
+        if isinstance(self.value, DPTBinary):
+            return encode_cmd_and_payload(self.code, encoded_payload=self.value.value)
+        if isinstance(self.value, DPTArray):
+            return encode_cmd_and_payload(
+                self.code, appended_payload=bytes(self.value.value)
+            )
+        raise TypeError()
+
+    def __str__(self) -> str:
+        """Return object as readable string."""
+        return f'<GroupValueWrite value="{self.value}" />'
+
+
+class GroupValueResponse(APCI):
+    """
+    GroupValueResponse service.
+
+    Takes a value (DPT) as payload.
+    """
+
+    code = APCICommand.GROUP_RESPONSE
+
+    def __init__(self, value: Optional[Union[DPTBinary, DPTArray]] = None) -> None:
+        """Initialize a new instance of GroupValueResponse."""
+        self.value = value
+
+    def calculated_length(self) -> int:
+        """Get length of APCI payload."""
+        if isinstance(self.value, DPTBinary):
+            return 1
+        if isinstance(self.value, DPTArray):
+            return 1 + len(self.value.value)
+        raise TypeError()
+
+    def from_knx(self, raw: bytes) -> None:
+        """Parse/deserialize from KNX/IP raw data."""
+        if len(raw) == 2:
+            self.value = DPTBinary(raw[1] & DPTBinary.APCI_BITMASK)
+        else:
+            self.value = DPTArray(raw[2:])
+
+    def to_knx(self) -> bytes:
+        """Serialize to KNX/IP raw data."""
+        if isinstance(self.value, DPTBinary):
+            return encode_cmd_and_payload(self.code, encoded_payload=self.value.value)
+        if isinstance(self.value, DPTArray):
+            return encode_cmd_and_payload(
+                self.code, appended_payload=bytes(self.value.value)
+            )
+        raise TypeError()
+
+    def __str__(self) -> str:
+        """Return object as readable string."""
+        return f'<GroupValueResponse value="{self.value}" />'
diff --git a/xknx/telegram/telegram.py b/xknx/telegram/telegram.py
index 759bc6e0..3b4280c2 100644
--- a/xknx/telegram/telegram.py
+++ b/xknx/telegram/telegram.py
@@ -8,10 +8,9 @@ The telegram class is the leightweight interaction object between
 
 It contains
 
-* the telegram type (e.g. GROUP_WRITE)
 * the direction (incoming or outgoing)
 * the group address (e.g. 1/2/3)
-* and the payload (e.g. "12%" or "23.23 C".
+* and the payload (e.g. GroupValueWrite("12%")).
 
 """
 from enum import Enum
@@ -27,14 +26,6 @@ class TelegramDirection(Enum):
     OUTGOING = "Outgoing"
 
 
-class TelegramType(Enum):
-    """Enum class for type of telegram."""
-
-    GROUP_READ = "GroupValueRead"
-    GROUP_WRITE = "GroupValueWrite"
-    GROUP_RESPONSE = "GroupValueResponse"
-
-
 class Telegram:
     """Class for KNX telegrams."""
 
@@ -45,14 +36,12 @@ class Telegram:
         destination_address: Union[GroupAddress, IndividualAddress] = GroupAddress(
             None
         ),
-        telegramtype: TelegramType = TelegramType.GROUP_WRITE,
         direction: TelegramDirection = TelegramDirection.OUTGOING,
         payload: Any = None,
         source_address: IndividualAddress = IndividualAddress(None),
     ) -> None:
         """Initialize Telegram class."""
         self.destination_address = destination_address
-        self.telegramtype = telegramtype
         self.direction = direction
         self.payload = payload
         self.source_address = source_address
@@ -60,10 +49,9 @@ class Telegram:
     def __str__(self) -> str:
         """Return object as readable string."""
         return (
-            '<Telegram direction="{}" telegramtype="{}" source_address="{}" '
+            '<Telegram direction="{}" source_address="{}" '
             'destination_address="{}" payload="{}" />'.format(
                 self.direction.value,
-                self.telegramtype.value,
                 self.source_address,
                 self.destination_address,
                 self.payload,


Test patch:
diff --git a/test/core_tests/telegram_queue_test.py b/test/core_tests/telegram_queue_test.py
index 4460ec29..6ab5f98c 100644
--- a/test/core_tests/telegram_queue_test.py
+++ b/test/core_tests/telegram_queue_test.py
@@ -7,6 +7,7 @@ from xknx import XKNX
 from xknx.dpt import DPTBinary
 from xknx.exceptions import CouldNotParseTelegram
 from xknx.telegram import AddressFilter, GroupAddress, Telegram, TelegramDirection
+from xknx.telegram.apci import GroupValueWrite
 
 
 class AsyncMock(MagicMock):
@@ -40,7 +41,7 @@ class TestTelegramQueue(unittest.TestCase):
         telegram_in = Telegram(
             destination_address=GroupAddress("1/2/3"),
             direction=TelegramDirection.INCOMING,
-            payload=DPTBinary(1),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
 
         self.loop.run_until_complete(xknx.telegram_queue.start())
@@ -75,14 +76,12 @@ class TestTelegramQueue(unittest.TestCase):
 
         telegram_in = Telegram(
             direction=TelegramDirection.INCOMING,
-            payload=DPTBinary(1),
-            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
 
         telegram_out = Telegram(
             direction=TelegramDirection.OUTGOING,
-            payload=DPTBinary(1),
-            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
 
         self.loop.run_until_complete(xknx.telegram_queue.start())
@@ -120,7 +119,7 @@ class TestTelegramQueue(unittest.TestCase):
         telegram = Telegram(
             destination_address=GroupAddress("1/2/3"),
             direction=TelegramDirection.INCOMING,
-            payload=DPTBinary(1),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         self.loop.run_until_complete(
             xknx.telegram_queue.process_telegram_incoming(telegram)
@@ -144,7 +143,7 @@ class TestTelegramQueue(unittest.TestCase):
         telegram = Telegram(
             destination_address=GroupAddress("1/2/3"),
             direction=TelegramDirection.INCOMING,
-            payload=DPTBinary(1),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         self.loop.run_until_complete(
             xknx.telegram_queue.process_telegram_incoming(telegram)
@@ -170,7 +169,7 @@ class TestTelegramQueue(unittest.TestCase):
         telegram = Telegram(
             destination_address=GroupAddress("1/2/3"),
             direction=TelegramDirection.INCOMING,
-            payload=DPTBinary(1),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         self.loop.run_until_complete(
             xknx.telegram_queue.process_telegram_incoming(telegram)
@@ -193,7 +192,7 @@ class TestTelegramQueue(unittest.TestCase):
         telegram = Telegram(
             destination_address=GroupAddress("1/2/3"),
             direction=TelegramDirection.INCOMING,
-            payload=DPTBinary(1),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         self.loop.run_until_complete(
             xknx.telegram_queue.process_telegram_incoming(telegram)
@@ -215,7 +214,7 @@ class TestTelegramQueue(unittest.TestCase):
         telegram = Telegram(
             destination_address=GroupAddress("1/2/3"),
             direction=TelegramDirection.OUTGOING,
-            payload=DPTBinary(1),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
 
         # log a warning if there is no KNXIP interface instanciated
@@ -249,7 +248,7 @@ class TestTelegramQueue(unittest.TestCase):
         telegram = Telegram(
             destination_address=GroupAddress("1/2/3"),
             direction=TelegramDirection.INCOMING,
-            payload=DPTBinary(1),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
 
         xknx.telegrams.put_nowait(telegram)
@@ -279,12 +278,12 @@ class TestTelegramQueue(unittest.TestCase):
         telegram_in = Telegram(
             destination_address=GroupAddress("1/2/3"),
             direction=TelegramDirection.INCOMING,
-            payload=DPTBinary(1),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         telegram_out = Telegram(
             destination_address=GroupAddress("1/2/3"),
             direction=TelegramDirection.OUTGOING,
-            payload=DPTBinary(1),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
 
         xknx.telegrams.put_nowait(telegram_in)
@@ -314,7 +313,7 @@ class TestTelegramQueue(unittest.TestCase):
         telegram = Telegram(
             destination_address=GroupAddress("1/2/3"),
             direction=TelegramDirection.INCOMING,
-            payload=DPTBinary(1),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         xknx.telegrams.put_nowait(telegram)
         self.loop.run_until_complete(xknx.telegram_queue._process_all_telegrams())
@@ -343,7 +342,7 @@ class TestTelegramQueue(unittest.TestCase):
         telegram = Telegram(
             destination_address=GroupAddress("1/2/3"),
             direction=TelegramDirection.INCOMING,
-            payload=DPTBinary(1),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         xknx.telegrams.put_nowait(telegram)
         self.loop.run_until_complete(xknx.telegram_queue._process_all_telegrams())
@@ -372,7 +371,7 @@ class TestTelegramQueue(unittest.TestCase):
         telegram = Telegram(
             destination_address=GroupAddress("1/2/3"),
             direction=TelegramDirection.INCOMING,
-            payload=DPTBinary(1),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         xknx.telegrams.put_nowait(telegram)
         self.loop.run_until_complete(xknx.telegram_queue._process_all_telegrams())
diff --git a/test/core_tests/value_reader_test.py b/test/core_tests/value_reader_test.py
index 90173922..5f5ee797 100644
--- a/test/core_tests/value_reader_test.py
+++ b/test/core_tests/value_reader_test.py
@@ -6,7 +6,8 @@ from unittest.mock import patch
 from xknx import XKNX
 from xknx.core import ValueReader
 from xknx.dpt import DPTBinary
-from xknx.telegram import GroupAddress, Telegram, TelegramDirection, TelegramType
+from xknx.telegram import GroupAddress, Telegram, TelegramDirection
+from xknx.telegram.apci import GroupValueRead, GroupValueResponse, GroupValueWrite
 
 
 class TestValueReader(unittest.TestCase):
@@ -28,9 +29,8 @@ class TestValueReader(unittest.TestCase):
         test_group_address = GroupAddress("0/0/0")
         response_telegram = Telegram(
             destination_address=test_group_address,
-            telegramtype=TelegramType.GROUP_RESPONSE,
             direction=TelegramDirection.INCOMING,
-            payload=DPTBinary(1),
+            payload=GroupValueResponse(DPTBinary(1)),
         )
 
         value_reader = ValueReader(xknx, test_group_address)
@@ -94,8 +94,7 @@ class TestValueReader(unittest.TestCase):
         self.assertEqual(
             telegram,
             Telegram(
-                destination_address=GroupAddress("0/0/0"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("0/0/0"), payload=GroupValueRead()
             ),
         )
 
@@ -105,27 +104,23 @@ class TestValueReader(unittest.TestCase):
         test_group_address = GroupAddress("0/0/0")
         expected_telegram_1 = Telegram(
             destination_address=test_group_address,
-            telegramtype=TelegramType.GROUP_RESPONSE,
             direction=TelegramDirection.INCOMING,
-            payload=DPTBinary(1),
+            payload=GroupValueResponse(DPTBinary(1)),
         )
         expected_telegram_2 = Telegram(
             destination_address=test_group_address,
-            telegramtype=TelegramType.GROUP_WRITE,
             direction=TelegramDirection.INCOMING,
-            payload=DPTBinary(1),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         telegram_wrong_address = Telegram(
             destination_address=GroupAddress("0/0/1"),
-            telegramtype=TelegramType.GROUP_RESPONSE,
             direction=TelegramDirection.INCOMING,
-            payload=DPTBinary(1),
+            payload=GroupValueResponse(DPTBinary(1)),
         )
         telegram_wrong_type = Telegram(
             destination_address=test_group_address,
-            telegramtype=TelegramType.GROUP_READ,
             direction=TelegramDirection.INCOMING,
-            payload=DPTBinary(1),
+            payload=GroupValueRead(),
         )
 
         value_reader = ValueReader(xknx, test_group_address)
diff --git a/test/devices_tests/binary_sensor_test.py b/test/devices_tests/binary_sensor_test.py
index 33649836..58a336fa 100644
--- a/test/devices_tests/binary_sensor_test.py
+++ b/test/devices_tests/binary_sensor_test.py
@@ -7,7 +7,8 @@ from xknx import XKNX
 from xknx.devices import Action, BinarySensor, Switch
 from xknx.dpt import DPTArray, DPTBinary
 from xknx.exceptions import CouldNotParseTelegram
-from xknx.telegram import GroupAddress, Telegram, TelegramType
+from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueResponse, GroupValueWrite
 
 
 class AsyncMock(MagicMock):
@@ -38,14 +39,16 @@ class TestBinarySensor(unittest.TestCase):
         self.assertEqual(binaryinput.state, None)
 
         telegram_on = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         self.loop.run_until_complete(binaryinput.process(telegram_on))
 
         self.assertEqual(binaryinput.state, True)
 
         telegram_off = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(0)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(0)),
         )
         self.loop.run_until_complete(binaryinput.process(telegram_off))
         self.assertEqual(binaryinput.state, False)
@@ -54,7 +57,8 @@ class TestBinarySensor(unittest.TestCase):
         self.assertEqual(binaryinput2.state, None)
 
         telegram_off2 = Telegram(
-            destination_address=GroupAddress("1/2/4"), payload=DPTBinary(0)
+            destination_address=GroupAddress("1/2/4"),
+            payload=GroupValueWrite(DPTBinary(0)),
         )
         self.loop.run_until_complete(binaryinput2.process(telegram_off2))
         self.assertEqual(binaryinput2.state, False)
@@ -67,14 +71,16 @@ class TestBinarySensor(unittest.TestCase):
         self.assertEqual(bs_invert.state, None)
 
         telegram_on = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(0)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(0)),
         )
         self.loop.run_until_complete(bs_invert.process(telegram_on))
 
         self.assertEqual(bs_invert.state, True)
 
         telegram_off = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         self.loop.run_until_complete(bs_invert.process(telegram_off))
         self.assertEqual(bs_invert.state, False)
@@ -92,7 +98,8 @@ class TestBinarySensor(unittest.TestCase):
             device_updated_cb=async_after_update_callback,
         )
         telegram_on = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
 
         self.loop.run_until_complete(binaryinput.process(telegram_on))
@@ -131,7 +138,8 @@ class TestBinarySensor(unittest.TestCase):
         self.assertEqual(switch.state, None)
 
         telegram_on = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         self.loop.run_until_complete(binary_sensor.process(telegram_on))
         # process outgoing telegram from queue
@@ -141,7 +149,8 @@ class TestBinarySensor(unittest.TestCase):
         self.assertEqual(switch.state, True)
 
         telegram_off = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(0)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(0)),
         )
         self.loop.run_until_complete(binary_sensor.process(telegram_off))
         self.loop.run_until_complete(switch.process(xknx.telegrams.get_nowait()))
@@ -164,7 +173,8 @@ class TestBinarySensor(unittest.TestCase):
         self.assertEqual(switch.state, None)
 
         telegram_on = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
 
         with patch("time.time") as mock_time, patch(
@@ -197,7 +207,8 @@ class TestBinarySensor(unittest.TestCase):
         xknx = XKNX()
         binary_sensor = BinarySensor(xknx, "Warning", group_address_state="1/2/3")
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTArray((0x1, 0x2, 0x3))
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTArray((0x1, 0x2, 0x3))),
         )
         with self.assertRaises(CouldNotParseTelegram):
             self.loop.run_until_complete(binary_sensor.process(telegram))
@@ -245,7 +256,8 @@ class TestBinarySensor(unittest.TestCase):
         switch.register_device_updated_cb(async_after_update_callback)
 
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         self.loop.run_until_complete(switch.process(telegram))
         # no _context_task started because ignore_internal_state is False
@@ -273,7 +285,8 @@ class TestBinarySensor(unittest.TestCase):
         switch.register_device_updated_cb(async_after_update_callback)
 
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         self.assertEqual(switch.counter, 0)
 
@@ -315,7 +328,8 @@ class TestBinarySensor(unittest.TestCase):
         switch.register_device_updated_cb(async_after_update_callback)
 
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         self.loop.run_until_complete(switch.process(telegram))
         # no _context_task started because context_timeout is False
@@ -342,12 +356,14 @@ class TestBinarySensor(unittest.TestCase):
         switch.register_device_updated_cb(async_after_update_callback)
 
         write_telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         response_telegram = Telegram(
             destination_address=GroupAddress("1/2/3"),
-            payload=DPTBinary(1),
-            telegramtype=TelegramType.GROUP_RESPONSE,
+            payload=GroupValueResponse(
+                DPTBinary(1),
+            ),
         )
         self.assertIsNone(switch.state)
         # initial GroupValueResponse changes state and runs callback
diff --git a/test/devices_tests/climate_test.py b/test/devices_tests/climate_test.py
index 7d9587b5..19dcfeec 100644
--- a/test/devices_tests/climate_test.py
+++ b/test/devices_tests/climate_test.py
@@ -19,7 +19,8 @@ from xknx.dpt import (
 )
 from xknx.dpt.dpt_hvac_mode import HVACControllerMode
 from xknx.exceptions import CouldNotParseTelegram, DeviceIllegalValue
-from xknx.telegram import GroupAddress, Telegram, TelegramType
+from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueRead, GroupValueWrite
 
 DPT_20102_MODES = [
     HVACOperationMode.AUTO,
@@ -233,7 +234,7 @@ class TestClimate(unittest.TestCase):
 
         telegram = Telegram(
             destination_address=GroupAddress("1/2/1"),
-            payload=DPTArray(DPTTemperature.to_knx(23)),
+            payload=GroupValueWrite(DPTArray(DPTTemperature.to_knx(23))),
         )
         self.loop.run_until_complete(climate.process(telegram))
         after_update_callback.assert_called_with(climate)
@@ -241,7 +242,7 @@ class TestClimate(unittest.TestCase):
 
         telegram = Telegram(
             destination_address=GroupAddress("1/2/2"),
-            payload=DPTArray(DPTTemperature.to_knx(23)),
+            payload=GroupValueWrite(DPTArray(DPTTemperature.to_knx(23))),
         )
         self.loop.run_until_complete(climate.process(telegram))
         after_update_callback.assert_called_with(climate)
@@ -249,7 +250,7 @@ class TestClimate(unittest.TestCase):
 
         telegram = Telegram(
             destination_address=GroupAddress("1/2/3"),
-            payload=DPTArray(DPTValue1Count.to_knx(-4)),
+            payload=GroupValueWrite(DPTArray(DPTValue1Count.to_knx(-4))),
         )
         self.loop.run_until_complete(climate.process(telegram))
         after_update_callback.assert_called_with(climate)
@@ -276,7 +277,8 @@ class TestClimate(unittest.TestCase):
         # Note: the climate object processes the telegram, but the cb
         # is called with the climate_mode object.
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/4"), payload=DPTArray(1)
+            destination_address=GroupAddress("1/2/4"),
+            payload=GroupValueWrite(DPTArray(1)),
         )
         self.loop.run_until_complete(climate.process(telegram))
         after_update_callback.assert_called_with(climate_mode)
@@ -303,7 +305,9 @@ class TestClimate(unittest.TestCase):
                 telegram,
                 Telegram(
                     destination_address=GroupAddress("1/2/4"),
-                    payload=DPTArray(DPTHVACMode.to_knx(operation_mode)),
+                    payload=GroupValueWrite(
+                        DPTArray(DPTHVACMode.to_knx(operation_mode))
+                    ),
                 ),
             )
 
@@ -325,7 +329,9 @@ class TestClimate(unittest.TestCase):
                 telegram,
                 Telegram(
                     destination_address=GroupAddress("1/2/4"),
-                    payload=DPTArray(DPTHVACContrMode.to_knx(controller_mode)),
+                    payload=GroupValueWrite(
+                        DPTArray(DPTHVACContrMode.to_knx(controller_mode))
+                    ),
                 ),
             )
 
@@ -367,7 +373,9 @@ class TestClimate(unittest.TestCase):
                 telegram,
                 Telegram(
                     destination_address=GroupAddress("1/2/4"),
-                    payload=DPTArray(DPTControllerStatus.to_knx(operation_mode)),
+                    payload=GroupValueWrite(
+                        DPTArray(DPTControllerStatus.to_knx(operation_mode))
+                    ),
                 ),
             )
 
@@ -393,15 +401,21 @@ class TestClimate(unittest.TestCase):
             telegrams.append(xknx.telegrams.get_nowait())
 
         test_telegrams = [
-            Telegram(destination_address=GroupAddress("1/2/4"), payload=DPTArray(1)),
             Telegram(
-                destination_address=GroupAddress("1/2/5"), payload=DPTBinary(False)
+                destination_address=GroupAddress("1/2/4"),
+                payload=GroupValueWrite(DPTArray(1)),
+            ),
+            Telegram(
+                destination_address=GroupAddress("1/2/5"),
+                payload=GroupValueWrite(DPTBinary(False)),
             ),
             Telegram(
-                destination_address=GroupAddress("1/2/6"), payload=DPTBinary(False)
+                destination_address=GroupAddress("1/2/6"),
+                payload=GroupValueWrite(DPTBinary(False)),
             ),
             Telegram(
-                destination_address=GroupAddress("1/2/7"), payload=DPTBinary(True)
+                destination_address=GroupAddress("1/2/7"),
+                payload=GroupValueWrite(DPTBinary(True)),
             ),
         ]
 
@@ -425,7 +439,8 @@ class TestClimate(unittest.TestCase):
         self.assertEqual(
             telegram,
             Telegram(
-                destination_address=GroupAddress("1/2/14"), payload=DPTBinary(True)
+                destination_address=GroupAddress("1/2/14"),
+                payload=GroupValueWrite(DPTBinary(True)),
             ),
         )
         self.loop.run_until_complete(
@@ -436,7 +451,8 @@ class TestClimate(unittest.TestCase):
         self.assertEqual(
             telegram,
             Telegram(
-                destination_address=GroupAddress("1/2/14"), payload=DPTBinary(False)
+                destination_address=GroupAddress("1/2/14"),
+                payload=GroupValueWrite(DPTBinary(False)),
             ),
         )
 
@@ -533,7 +549,10 @@ class TestClimate(unittest.TestCase):
         self.assertEqual(
             _telegram,
             # DEFAULT_TEMPERATURE_STEP is 0.1 -> payload = setpoint_shift * 10
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTArray(30)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray(30)),
+            ),
         )
         self.loop.run_until_complete(xknx.devices.process(_telegram))
 
@@ -544,7 +563,7 @@ class TestClimate(unittest.TestCase):
             _telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/2"),
-                payload=DPTArray(DPT2ByteFloat().to_knx(23.00)),
+                payload=GroupValueWrite(DPTArray(DPT2ByteFloat().to_knx(23.00))),
             ),
         )
         self.loop.run_until_complete(xknx.devices.process(_telegram))
@@ -556,7 +575,10 @@ class TestClimate(unittest.TestCase):
         _telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             _telegram,
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTArray(40)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray(40)),
+            ),
         )
         self.loop.run_until_complete(xknx.devices.process(_telegram))
         _telegram = xknx.telegrams.get_nowait()
@@ -564,7 +586,7 @@ class TestClimate(unittest.TestCase):
             _telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/2"),
-                payload=DPTArray(DPT2ByteFloat().to_knx(24.00)),
+                payload=GroupValueWrite(DPTArray(DPT2ByteFloat().to_knx(24.00))),
             ),
         )
         self.loop.run_until_complete(xknx.devices.process(_telegram))
@@ -576,7 +598,10 @@ class TestClimate(unittest.TestCase):
         _telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             _telegram,
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTArray(35)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray(35)),
+            ),
         )
         self.loop.run_until_complete(xknx.devices.process(_telegram))
         _telegram = xknx.telegrams.get_nowait()
@@ -584,7 +609,7 @@ class TestClimate(unittest.TestCase):
             _telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/2"),
-                payload=DPTArray(DPT2ByteFloat().to_knx(23.50)),
+                payload=GroupValueWrite(DPTArray(DPT2ByteFloat().to_knx(23.50))),
             ),
         )
         self.loop.run_until_complete(xknx.devices.process(_telegram))
@@ -619,7 +644,10 @@ class TestClimate(unittest.TestCase):
         self.assertEqual(
             _telegram,
             # DEFAULT_TEMPERATURE_STEP is 0.1 -> payload = setpoint_shift * 10
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTArray(10)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray(10)),
+            ),
         )
         self.loop.run_until_complete(xknx.devices.process(_telegram))
 
@@ -630,7 +658,7 @@ class TestClimate(unittest.TestCase):
             _telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/2"),
-                payload=DPTArray(DPT2ByteFloat().to_knx(23.00)),
+                payload=GroupValueWrite(DPTArray(DPT2ByteFloat().to_knx(23.00))),
             ),
         )
         self.loop.run_until_complete(xknx.devices.process(_telegram))
@@ -642,7 +670,10 @@ class TestClimate(unittest.TestCase):
         _telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             _telegram,
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTArray(0xF1)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray(0xF1)),
+            ),
         )  # -15
         self.loop.run_until_complete(xknx.devices.process(_telegram))
         _telegram = xknx.telegrams.get_nowait()
@@ -650,7 +681,7 @@ class TestClimate(unittest.TestCase):
             _telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/2"),
-                payload=DPTArray(DPT2ByteFloat().to_knx(20.50)),
+                payload=GroupValueWrite(DPTArray(DPT2ByteFloat().to_knx(20.50))),
             ),
         )
         self.loop.run_until_complete(xknx.devices.process(_telegram))
@@ -662,7 +693,10 @@ class TestClimate(unittest.TestCase):
         _telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             _telegram,
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTArray(0xE2)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray(0xE2)),
+            ),
         )  # -30
         self.loop.run_until_complete(xknx.devices.process(_telegram))
         _telegram = xknx.telegrams.get_nowait()
@@ -670,7 +704,7 @@ class TestClimate(unittest.TestCase):
             _telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/2"),
-                payload=DPTArray(DPT2ByteFloat().to_knx(19.00)),
+                payload=GroupValueWrite(DPTArray(DPT2ByteFloat().to_knx(19.00))),
             ),
         )
         self.loop.run_until_complete(xknx.devices.process(_telegram))
@@ -709,7 +743,10 @@ class TestClimate(unittest.TestCase):
         self.assertEqual(
             _telegram,
             # temperature_step is 0.5 -> payload = setpoint_shift * 2
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTArray(6)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray(6)),
+            ),
         )
 
         self.loop.run_until_complete(climate.target_temperature.set(23.00))
@@ -720,7 +757,7 @@ class TestClimate(unittest.TestCase):
             _telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/2"),
-                payload=DPTArray(DPT2ByteFloat().to_knx(23.00)),
+                payload=GroupValueWrite(DPTArray(DPT2ByteFloat().to_knx(23.00))),
             ),
         )
         self.assertEqual(climate.base_temperature, 20.00)
@@ -730,7 +767,10 @@ class TestClimate(unittest.TestCase):
         self.loop.run_until_complete(xknx.devices.process(_telegram))
         self.assertEqual(
             _telegram,
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTArray(8)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray(8)),
+            ),
         )
         _telegram = xknx.telegrams.get_nowait()
         self.loop.run_until_complete(xknx.devices.process(_telegram))
@@ -738,7 +778,7 @@ class TestClimate(unittest.TestCase):
             _telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/2"),
-                payload=DPTArray(DPT2ByteFloat().to_knx(24.00)),
+                payload=GroupValueWrite(DPTArray(DPT2ByteFloat().to_knx(24.00))),
             ),
         )
         self.assertEqual(climate.target_temperature.value, 24.00)
@@ -771,7 +811,7 @@ class TestClimate(unittest.TestCase):
             _telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/2"),
-                payload=DPTArray(DPT2ByteFloat().to_knx(21.00)),
+                payload=GroupValueWrite(DPTArray(DPT2ByteFloat().to_knx(21.00))),
             ),
         )
         self.assertFalse(climate.initialized_for_setpoint_shift_calculations)
@@ -785,7 +825,10 @@ class TestClimate(unittest.TestCase):
         self.assertEqual(
             _telegram,
             # DEFAULT_TEMPERATURE_STEP is 0.1 -> payload = setpoint_shift * 10
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTArray(10)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray(10)),
+            ),
         )
         self.assertTrue(climate.initialized_for_setpoint_shift_calculations)
         self.assertEqual(climate.base_temperature, 20.00)
@@ -799,7 +842,10 @@ class TestClimate(unittest.TestCase):
         self.assertEqual(
             _telegram,
             # DEFAULT_TEMPERATURE_STEP is 0.1 -> payload = setpoint_shift * 10
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTArray(20)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray(20)),
+            ),
         )
         _telegram = xknx.telegrams.get_nowait()
         self.loop.run_until_complete(xknx.devices.process(_telegram))
@@ -826,7 +872,7 @@ class TestClimate(unittest.TestCase):
             climate.target_temperature.process(
                 Telegram(
                     destination_address=GroupAddress("1/2/2"),
-                    payload=DPTArray(DPT2ByteFloat().to_knx(20.00)),
+                    payload=GroupValueWrite(DPTArray(DPT2ByteFloat().to_knx(20.00))),
                 )
             )
         )
@@ -841,7 +887,7 @@ class TestClimate(unittest.TestCase):
             _telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray((0x00, 0x00)),
+                payload=GroupValueWrite(DPTArray((0x00, 0x00))),
             ),
         )  # 0
         # - 0.6 °C = 19.4
@@ -853,7 +899,7 @@ class TestClimate(unittest.TestCase):
             _telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray((0x87, 0xC4)),
+                payload=GroupValueWrite(DPTArray((0x87, 0xC4))),
             ),
         )  # -0.6
         # simulate incoming new target temperature for next calculation
@@ -861,7 +907,7 @@ class TestClimate(unittest.TestCase):
             climate.target_temperature.process(
                 Telegram(
                     destination_address=GroupAddress("1/2/2"),
-                    payload=DPTArray(DPT2ByteFloat().to_knx(19.40)),
+                    payload=GroupValueWrite(DPTArray(DPT2ByteFloat().to_knx(19.40))),
                 )
             )
         )
@@ -874,7 +920,7 @@ class TestClimate(unittest.TestCase):
             _telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray((0x01, 0x5E)),
+                payload=GroupValueWrite(DPTArray((0x01, 0x5E))),
             ),
         )  # +3.5
 
@@ -927,11 +973,7 @@ class TestClimate(unittest.TestCase):
         self.assertEqual(xknx.telegrams.qsize(), 1)
         telegram1 = xknx.telegrams.get_nowait()
         self.assertEqual(
-            telegram1,
-            Telegram(
-                destination_address=GroupAddress("1/2/3"),
-                telegramtype=TelegramType.GROUP_READ,
-            ),
+            telegram1, Telegram(GroupAddress("1/2/3"), payload=GroupValueRead())
         )
 
     def test_sync_operation_mode(self):
@@ -947,11 +989,7 @@ class TestClimate(unittest.TestCase):
         self.assertEqual(xknx.telegrams.qsize(), 1)
         telegram1 = xknx.telegrams.get_nowait()
         self.assertEqual(
-            telegram1,
-            Telegram(
-                destination_address=GroupAddress("1/2/4"),
-                telegramtype=TelegramType.GROUP_READ,
-            ),
+            telegram1, Telegram(GroupAddress("1/2/4"), payload=GroupValueRead())
         )
 
     def test_sync_controller_status(self):
@@ -967,11 +1005,7 @@ class TestClimate(unittest.TestCase):
         self.assertEqual(xknx.telegrams.qsize(), 1)
         telegram1 = xknx.telegrams.get_nowait()
         self.assertEqual(
-            telegram1,
-            Telegram(
-                destination_address=GroupAddress("1/2/24"),
-                telegramtype=TelegramType.GROUP_READ,
-            ),
+            telegram1, Telegram(GroupAddress("1/2/24"), payload=GroupValueRead())
         )
 
     def test_sync_controller_mode(self):
@@ -987,11 +1021,7 @@ class TestClimate(unittest.TestCase):
         self.assertEqual(xknx.telegrams.qsize(), 1)
         telegram1 = xknx.telegrams.get_nowait()
         self.assertEqual(
-            telegram1,
-            Telegram(
-                destination_address=GroupAddress("1/2/14"),
-                telegramtype=TelegramType.GROUP_READ,
-            ),
+            telegram1, Telegram(GroupAddress("1/2/14"), payload=GroupValueRead())
         )
 
     def test_sync_operation_mode_state(self):
@@ -1013,22 +1043,13 @@ class TestClimate(unittest.TestCase):
         telegrams = []
         for _ in range(3):
             telegrams.append(xknx.telegrams.get_nowait())
-        self.assertSetEqual(
-            set(telegrams),
-            {
-                Telegram(
-                    destination_address=GroupAddress("1/2/5"),
-                    telegramtype=TelegramType.GROUP_READ,
-                ),
-                Telegram(
-                    destination_address=GroupAddress("1/2/6"),
-                    telegramtype=TelegramType.GROUP_READ,
-                ),
-                Telegram(
-                    destination_address=GroupAddress("1/2/14"),
-                    telegramtype=TelegramType.GROUP_READ,
-                ),
-            },
+        self.assertListEqual(
+            telegrams,
+            [
+                Telegram(GroupAddress("1/2/5"), payload=GroupValueRead()),
+                Telegram(GroupAddress("1/2/6"), payload=GroupValueRead()),
+                Telegram(GroupAddress("1/2/14"), payload=GroupValueRead()),
+            ],
         )
 
     def test_sync_heat_cool(self):
@@ -1044,11 +1065,7 @@ class TestClimate(unittest.TestCase):
         self.assertEqual(xknx.telegrams.qsize(), 1)
         telegram1 = xknx.telegrams.get_nowait()
         self.assertEqual(
-            telegram1,
-            Telegram(
-                destination_address=GroupAddress("1/2/15"),
-                telegramtype=TelegramType.GROUP_READ,
-            ),
+            telegram1, Telegram(GroupAddress("1/2/15"), payload=GroupValueRead())
         )
 
     #
@@ -1059,8 +1076,10 @@ class TestClimate(unittest.TestCase):
         xknx = XKNX()
         climate = Climate(xknx, "TestClimate", group_address_temperature="1/2/3")
 
-        telegram = Telegram(destination_address=GroupAddress("1/2/3"))
-        telegram.payload = DPTArray(DPTTemperature().to_knx(21.34))
+        telegram = Telegram(
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTArray(DPTTemperature().to_knx(21.34))),
+        )
         self.loop.run_until_complete(climate.process(telegram))
         self.assertEqual(climate.temperature.value, 21.34)
 
@@ -1074,15 +1093,21 @@ class TestClimate(unittest.TestCase):
             group_address_controller_status="1/2/3",
         )
         for operation_mode in DPT_20102_MODES:
-            telegram = Telegram(destination_address=GroupAddress("1/2/5"))
-            telegram.payload = DPTArray(DPTHVACMode.to_knx(operation_mode))
+            telegram = Telegram(
+                destination_address=GroupAddress("1/2/5"),
+                payload=GroupValueWrite(DPTArray(DPTHVACMode.to_knx(operation_mode))),
+            )
             self.loop.run_until_complete(climate_mode.process(telegram))
             self.assertEqual(climate_mode.operation_mode, operation_mode)
         for operation_mode in DPT_20102_MODES:
             if operation_mode == HVACOperationMode.AUTO:
                 continue
-            telegram = Telegram(destination_address=GroupAddress("1/2/3"))
-            telegram.payload = DPTArray(DPTControllerStatus.to_knx(operation_mode))
+            telegram = Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(
+                    DPTArray(DPTControllerStatus.to_knx(operation_mode))
+                ),
+            )
             self.loop.run_until_complete(climate_mode.process(telegram))
             self.assertEqual(climate_mode.operation_mode, operation_mode)
 
@@ -1093,8 +1118,12 @@ class TestClimate(unittest.TestCase):
             xknx, "TestClimate", group_address_controller_mode="1/2/5"
         )
         for _, controller_mode in DPTHVACContrMode.SUPPORTED_MODES.items():
-            telegram = Telegram(destination_address=GroupAddress("1/2/5"))
-            telegram.payload = DPTArray(DPTHVACContrMode.to_knx(controller_mode))
+            telegram = Telegram(
+                destination_address=GroupAddress("1/2/5"),
+                payload=GroupValueWrite(
+                    DPTArray(DPTHVACContrMode.to_knx(controller_mode))
+                ),
+            )
             self.loop.run_until_complete(climate_mode.process(telegram))
             self.assertEqual(climate_mode.controller_mode, controller_mode)
 
@@ -1108,7 +1137,8 @@ class TestClimate(unittest.TestCase):
             group_address_controller_status="1/2/3",
         )
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         with self.assertRaises(CouldNotParseTelegram):
             self.loop.run_until_complete(climate_mode.process(telegram))
@@ -1123,7 +1153,8 @@ class TestClimate(unittest.TestCase):
             group_address_controller_status="1/2/3",
         )
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTArray((23, 24))
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTArray((23, 24))),
         )
         with self.assertRaises(CouldNotParseTelegram):
             self.loop.run_until_complete(climate_mode.process(telegram))
@@ -1138,7 +1169,8 @@ class TestClimate(unittest.TestCase):
             group_address_controller_status="1/2/3",
         )
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/5"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/5"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         with self.assertRaises(CouldNotParseTelegram):
             self.loop.run_until_complete(climate_mode.process(telegram))
@@ -1153,7 +1185,8 @@ class TestClimate(unittest.TestCase):
             group_address_controller_status="1/2/3",
         )
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/5"), payload=DPTArray((23, 24))
+            destination_address=GroupAddress("1/2/5"),
+            payload=GroupValueWrite(DPTArray((23, 24))),
         )
         with self.assertRaises(CouldNotParseTelegram):
             self.loop.run_until_complete(climate_mode.process(telegram))
@@ -1172,8 +1205,10 @@ class TestClimate(unittest.TestCase):
 
         climate.register_device_updated_cb(async_after_update_callback)
 
-        telegram = Telegram(destination_address=GroupAddress("1/2/3"))
-        telegram.payload = DPTArray(DPTTemperature().to_knx(21.34))
+        telegram = Telegram(
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTArray(DPTTemperature().to_knx(21.34))),
+        )
         self.loop.run_until_complete(climate.process(telegram))
         after_update_callback.assert_called_with(climate)
 
@@ -1187,13 +1222,17 @@ class TestClimate(unittest.TestCase):
             group_address_heat_cool_state="1/2/15",
         )
 
-        telegram = Telegram(destination_address=GroupAddress("1/2/14"))
-        telegram.payload = DPTBinary(False)
+        telegram = Telegram(
+            destination_address=GroupAddress("1/2/14"),
+            payload=GroupValueWrite(DPTBinary(False)),
+        )
         self.loop.run_until_complete(climate_mode.process(telegram))
         self.assertEqual(climate_mode.controller_mode, HVACControllerMode.COOL)
 
-        telegram = Telegram(destination_address=GroupAddress("1/2/14"))
-        telegram.payload = DPTBinary(True)
+        telegram = Telegram(
+            destination_address=GroupAddress("1/2/14"),
+            payload=GroupValueWrite(DPTBinary(True)),
+        )
         self.loop.run_until_complete(climate_mode.process(telegram))
         self.assertEqual(climate_mode.controller_mode, HVACControllerMode.HEAT)
 
@@ -1349,16 +1388,20 @@ class TestClimate(unittest.TestCase):
         """Test process / reading telegrams from telegram queue. Test if DPT20.105 controller mode is set correctly."""
         xknx = XKNX()
         climate = Climate(xknx, "TestClimate", group_address_on_off="1/2/2")
-        telegram = Telegram(destination_address=GroupAddress("1/2/2"))
-        telegram.payload = DPTBinary(1)
+        telegram = Telegram(
+            destination_address=GroupAddress("1/2/2"),
+            payload=GroupValueWrite(DPTBinary(1)),
+        )
         self.loop.run_until_complete(climate.process(telegram))
         self.assertEqual(climate.is_on, True)
 
         climate_inv = Climate(
             xknx, "TestClimate", group_address_on_off="1/2/2", on_off_invert=True
         )
-        telegram = Telegram(destination_address=GroupAddress("1/2/2"))
-        telegram.payload = DPTBinary(1)
+        telegram = Telegram(
+            destination_address=GroupAddress("1/2/2"),
+            payload=GroupValueWrite(DPTBinary(1)),
+        )
         self.loop.run_until_complete(climate_inv.process(telegram))
         self.assertEqual(climate_inv.is_on, False)
 
@@ -1373,7 +1416,8 @@ class TestClimate(unittest.TestCase):
         self.assertEqual(
             _telegram,
             Telegram(
-                destination_address=GroupAddress("1/2/2"), payload=DPTBinary(True)
+                destination_address=GroupAddress("1/2/2"),
+                payload=GroupValueWrite(DPTBinary(True)),
             ),
         )
         self.loop.run_until_complete(climate.turn_off())
@@ -1383,7 +1427,8 @@ class TestClimate(unittest.TestCase):
         self.assertEqual(
             _telegram,
             Telegram(
-                destination_address=GroupAddress("1/2/2"), payload=DPTBinary(False)
+                destination_address=GroupAddress("1/2/2"),
+                payload=GroupValueWrite(DPTBinary(False)),
             ),
         )
 
@@ -1397,7 +1442,8 @@ class TestClimate(unittest.TestCase):
         self.assertEqual(
             _telegram,
             Telegram(
-                destination_address=GroupAddress("1/2/2"), payload=DPTBinary(False)
+                destination_address=GroupAddress("1/2/2"),
+                payload=GroupValueWrite(DPTBinary(False)),
             ),
         )
         self.loop.run_until_complete(climate_inv.turn_off())
@@ -1407,6 +1453,7 @@ class TestClimate(unittest.TestCase):
         self.assertEqual(
             _telegram,
             Telegram(
-                destination_address=GroupAddress("1/2/2"), payload=DPTBinary(True)
+                destination_address=GroupAddress("1/2/2"),
+                payload=GroupValueWrite(DPTBinary(True)),
             ),
         )
diff --git a/test/devices_tests/cover_test.py b/test/devices_tests/cover_test.py
index c7035337..cce88dcc 100644
--- a/test/devices_tests/cover_test.py
+++ b/test/devices_tests/cover_test.py
@@ -7,7 +7,8 @@ from unittest.mock import Mock, patch
 from xknx import XKNX
 from xknx.devices import Cover
 from xknx.dpt import DPTArray, DPTBinary
-from xknx.telegram import GroupAddress, Telegram, TelegramType
+from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueRead, GroupValueWrite
 
 
 class TestCover(unittest.TestCase):
@@ -129,8 +130,7 @@ class TestCover(unittest.TestCase):
         self.assertEqual(
             telegram1,
             Telegram(
-                destination_address=GroupAddress("1/2/3"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/3"), payload=GroupValueRead()
             ),
         )
 
@@ -151,8 +151,7 @@ class TestCover(unittest.TestCase):
         self.assertEqual(
             telegram1,
             Telegram(
-                destination_address=GroupAddress("1/2/4"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/4"), payload=GroupValueRead()
             ),
         )
 
@@ -173,16 +172,14 @@ class TestCover(unittest.TestCase):
         self.assertEqual(
             telegram1,
             Telegram(
-                destination_address=GroupAddress("1/2/3"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/3"), payload=GroupValueRead()
             ),
         )
         telegram2 = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram2,
             Telegram(
-                destination_address=GroupAddress("1/2/4"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/4"), payload=GroupValueRead()
             ),
         )
 
@@ -203,8 +200,7 @@ class TestCover(unittest.TestCase):
         self.assertEqual(
             telegram1,
             Telegram(
-                destination_address=GroupAddress("1/2/4"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/4"), payload=GroupValueRead()
             ),
         )
 
@@ -228,7 +224,10 @@ class TestCover(unittest.TestCase):
         # DPT 1.008 - 0:up 1:down
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/1"), payload=DPTBinary(0)),
+            Telegram(
+                destination_address=GroupAddress("1/2/1"),
+                payload=GroupValueWrite(DPTBinary(0)),
+            ),
         )
 
     #
@@ -250,7 +249,10 @@ class TestCover(unittest.TestCase):
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/1"), payload=DPTBinary(1)),
+            Telegram(
+                destination_address=GroupAddress("1/2/1"),
+                payload=GroupValueWrite(DPTBinary(1)),
+            ),
         )
 
     #
@@ -273,7 +275,10 @@ class TestCover(unittest.TestCase):
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/1"), payload=DPTBinary(0)),
+            Telegram(
+                destination_address=GroupAddress("1/2/1"),
+                payload=GroupValueWrite(DPTBinary(0)),
+            ),
         )
 
     #
@@ -296,7 +301,10 @@ class TestCover(unittest.TestCase):
         # DPT 1.008 - 0:up 1:down
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/2"), payload=DPTBinary(0)),
+            Telegram(
+                destination_address=GroupAddress("1/2/2"),
+                payload=GroupValueWrite(DPTBinary(0)),
+            ),
         )
 
     #
@@ -320,7 +328,10 @@ class TestCover(unittest.TestCase):
         # DPT 1.008 - 0:up 1:down
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/2"), payload=DPTBinary(1)),
+            Telegram(
+                destination_address=GroupAddress("1/2/2"),
+                payload=GroupValueWrite(DPTBinary(1)),
+            ),
         )
 
     #
@@ -343,7 +354,10 @@ class TestCover(unittest.TestCase):
         # DPT 1.008 - 0:up 1:down
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/2"), payload=DPTBinary(1)),
+            Telegram(
+                destination_address=GroupAddress("1/2/2"),
+                payload=GroupValueWrite(DPTBinary(1)),
+            ),
         )
 
     #
@@ -365,7 +379,10 @@ class TestCover(unittest.TestCase):
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/2"), payload=DPTBinary(1)),
+            Telegram(
+                destination_address=GroupAddress("1/2/2"),
+                payload=GroupValueWrite(DPTBinary(1)),
+            ),
         )
 
         cover_manual_stop = Cover(
@@ -382,7 +399,10 @@ class TestCover(unittest.TestCase):
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/0"), payload=DPTBinary(1)),
+            Telegram(
+                destination_address=GroupAddress("1/2/0"),
+                payload=GroupValueWrite(DPTBinary(1)),
+            ),
         )
 
     #
@@ -404,7 +424,10 @@ class TestCover(unittest.TestCase):
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTArray(0x80)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray(0x80)),
+            ),
         )
 
     def test_position_without_position_address_up(self):
@@ -424,7 +447,10 @@ class TestCover(unittest.TestCase):
         # DPT 1.008 - 0:up 1:down
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/1"), payload=DPTBinary(0)),
+            Telegram(
+                destination_address=GroupAddress("1/2/1"),
+                payload=GroupValueWrite(DPTBinary(0)),
+            ),
         )
         self.assertEqual(cover.travelcalculator.travel_to_position, 50)
         self.assertTrue(cover.is_opening())
@@ -449,7 +475,10 @@ class TestCover(unittest.TestCase):
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/1"), payload=DPTBinary(1)),
+            Telegram(
+                destination_address=GroupAddress("1/2/1"),
+                payload=GroupValueWrite(DPTBinary(1)),
+            ),
         )
         self.assertEqual(cover.travelcalculator.travel_to_position, 80)
         self.assertTrue(cover.is_closing())
@@ -480,7 +509,10 @@ class TestCover(unittest.TestCase):
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/1"), payload=DPTBinary(0)),
+            Telegram(
+                destination_address=GroupAddress("1/2/1"),
+                payload=GroupValueWrite(DPTBinary(0)),
+            ),
         )
 
     def test_position_without_position_address_uninitialized_down(self):
@@ -506,7 +538,10 @@ class TestCover(unittest.TestCase):
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/1"), payload=DPTBinary(1)),
+            Telegram(
+                destination_address=GroupAddress("1/2/1"),
+                payload=GroupValueWrite(DPTBinary(1)),
+            ),
         )
 
     def test_angle(self):
@@ -528,7 +563,8 @@ class TestCover(unittest.TestCase):
         self.assertEqual(
             telegram,
             Telegram(
-                destination_address=GroupAddress("1/4/18"), payload=DPTArray(0x80)
+                destination_address=GroupAddress("1/4/18"),
+                payload=GroupValueWrite(DPTArray(0x80)),
             ),
         )
 
@@ -564,14 +600,14 @@ class TestCover(unittest.TestCase):
         )
         # initial position process - position is unknown so this is the new state - not moving
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTArray(213)
+            GroupAddress("1/2/3"), payload=GroupValueWrite(DPTArray(213))
         )
         self.loop.run_until_complete(cover.process(telegram))
         self.assertEqual(cover.current_position(), 84)
         self.assertFalse(cover.is_traveling())
         # state telegram updates current position - we are not moving so this is new state - not moving
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/4"), payload=DPTArray(42)
+            GroupAddress("1/2/4"), payload=GroupValueWrite(DPTArray(42))
         )
         self.loop.run_until_complete(cover.process(telegram))
         self.assertEqual(cover.current_position(), 16)
@@ -579,7 +615,7 @@ class TestCover(unittest.TestCase):
         self.assertEqual(cover.travelcalculator.travel_to_position, 16)
         # new position - movement starts
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTArray(255)
+            GroupAddress("1/2/3"), payload=GroupValueWrite(DPTArray(255))
         )
         self.loop.run_until_complete(cover.process(telegram))
         self.assertEqual(cover.current_position(), 16)
@@ -587,7 +623,7 @@ class TestCover(unittest.TestCase):
         self.assertEqual(cover.travelcalculator.travel_to_position, 100)
         # new state while moving - movement goes on; travelcalculator updated
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/4"), payload=DPTArray(213)
+            GroupAddress("1/2/4"), payload=GroupValueWrite(DPTArray(213))
         )
         self.loop.run_until_complete(cover.process(telegram))
         self.assertEqual(cover.current_position(), 84)
@@ -606,7 +642,7 @@ class TestCover(unittest.TestCase):
             group_address_angle_state="1/2/4",
         )
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/4"), payload=DPTArray(42)
+            GroupAddress("1/2/4"), payload=GroupValueWrite(DPTArray(42))
         )
         self.loop.run_until_complete(cover.process(telegram))
         self.assertEqual(cover.current_angle(), 16)
@@ -620,7 +656,7 @@ class TestCover(unittest.TestCase):
         cover.travelcalculator.set_position(50)
         self.assertFalse(cover.is_traveling())
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/1"), payload=DPTBinary(0)
+            GroupAddress("1/2/1"), payload=GroupValueWrite(DPTBinary(0))
         )
         self.loop.run_until_complete(cover.process(telegram))
         self.assertTrue(cover.is_opening())
@@ -634,7 +670,7 @@ class TestCover(unittest.TestCase):
         cover.travelcalculator.set_position(50)
         self.assertFalse(cover.is_traveling())
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/1"), payload=DPTBinary(1)
+            GroupAddress("1/2/1"), payload=GroupValueWrite(DPTBinary(1))
         )
         self.loop.run_until_complete(cover.process(telegram))
         self.assertTrue(cover.is_closing())
@@ -652,7 +688,7 @@ class TestCover(unittest.TestCase):
         self.loop.run_until_complete(cover.set_down())
         self.assertTrue(cover.is_traveling())
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/2"), payload=DPTBinary(1)
+            GroupAddress("1/2/2"), payload=GroupValueWrite(DPTBinary(1))
         )
         self.loop.run_until_complete(cover.process(telegram))
         self.assertFalse(cover.is_traveling())
@@ -670,7 +706,7 @@ class TestCover(unittest.TestCase):
         self.loop.run_until_complete(cover.set_down())
         self.assertTrue(cover.is_traveling())
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/2"), payload=DPTBinary(1)
+            GroupAddress("1/2/2"), payload=GroupValueWrite(DPTBinary(1))
         )
         self.loop.run_until_complete(cover.process(telegram))
         self.assertFalse(cover.is_traveling())
@@ -708,14 +744,15 @@ class TestCover(unittest.TestCase):
         ]:
             with self.subTest(address=address, feature=feature):
                 telegram = Telegram(
-                    destination_address=GroupAddress(address), payload=payload
+                    destination_address=GroupAddress(address),
+                    payload=GroupValueWrite(payload),
                 )
                 self.loop.run_until_complete(cover.process(telegram))
                 after_update_callback.assert_called_with(cover)
                 after_update_callback.reset_mock()
         # Stop only when cover is travelling
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+            GroupAddress("1/2/3"), payload=GroupValueWrite(DPTBinary(1))
         )
         self.loop.run_until_complete(cover.process(telegram))
         after_update_callback.assert_not_called()
diff --git a/test/devices_tests/datetime_test.py b/test/devices_tests/datetime_test.py
index c47cbece..ecd9aae5 100644
--- a/test/devices_tests/datetime_test.py
+++ b/test/devices_tests/datetime_test.py
@@ -7,7 +7,8 @@ from unittest.mock import patch
 from xknx import XKNX
 from xknx.devices import DateTime
 from xknx.dpt import DPTArray
-from xknx.telegram import GroupAddress, Telegram, TelegramType
+from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueRead, GroupValueResponse, GroupValueWrite
 
 
 class TestDateTime(unittest.TestCase):
@@ -43,10 +44,10 @@ class TestDateTime(unittest.TestCase):
         self.assertEqual(xknx.telegrams.qsize(), 1)
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(telegram.destination_address, GroupAddress("1/2/3"))
-        self.assertEqual(telegram.telegramtype, TelegramType.GROUP_WRITE)
-        self.assertEqual(len(telegram.payload.value), 8)
+        self.assertEqual(len(telegram.payload.value.value), 8)
         self.assertEqual(
-            telegram.payload.value, (0x75, 0x01, 0x07, 0xE9, 0x0D, 0x0E, 0x20, 0x80)
+            telegram.payload.value.value,
+            (0x75, 0x01, 0x07, 0xE9, 0x0D, 0x0E, 0x20, 0x80),
         )
 
     #
@@ -69,9 +70,8 @@ class TestDateTime(unittest.TestCase):
 
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(telegram.destination_address, GroupAddress("1/2/3"))
-        self.assertEqual(telegram.telegramtype, TelegramType.GROUP_WRITE)
-        self.assertEqual(len(telegram.payload.value), 3)
-        self.assertEqual(telegram.payload.value, (0x07, 0x01, 0x11))
+        self.assertEqual(len(telegram.payload.value.value), 3)
+        self.assertEqual(telegram.payload.value.value, (0x07, 0x01, 0x11))
 
     #
     # SYNC Time
@@ -93,9 +93,8 @@ class TestDateTime(unittest.TestCase):
 
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(telegram.destination_address, GroupAddress("1/2/3"))
-        self.assertEqual(telegram.telegramtype, TelegramType.GROUP_WRITE)
-        self.assertEqual(len(telegram.payload.value), 3)
-        self.assertEqual(telegram.payload.value, (0xE9, 0x0D, 0x0E))
+        self.assertEqual(len(telegram.payload.value.value), 3)
+        self.assertEqual(telegram.payload.value.value, (0xE9, 0x0D, 0x0E))
 
     #
     # PROCESS
@@ -111,8 +110,7 @@ class TestDateTime(unittest.TestCase):
         )
 
         telegram_read = Telegram(
-            destination_address=GroupAddress("1/2/3"),
-            telegramtype=TelegramType.GROUP_READ,
+            destination_address=GroupAddress("1/2/3"), payload=GroupValueRead()
         )
         with patch("time.localtime") as mock_time:
             mock_time.return_value = time.struct_time([2017, 1, 7, 9, 13, 14, 6, 0, 0])
@@ -127,8 +125,7 @@ class TestDateTime(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                telegramtype=TelegramType.GROUP_RESPONSE,
-                payload=DPTArray((0xE9, 0xD, 0xE)),
+                payload=GroupValueResponse(DPTArray((0xE9, 0xD, 0xE))),
             ),
         )
 
diff --git a/test/devices_tests/device_test.py b/test/devices_tests/device_test.py
index c925e0b4..ea607f58 100644
--- a/test/devices_tests/device_test.py
+++ b/test/devices_tests/device_test.py
@@ -6,7 +6,8 @@ from unittest.mock import MagicMock, Mock, patch
 from xknx import XKNX
 from xknx.devices import Device, Sensor
 from xknx.dpt import DPTArray
-from xknx.telegram import GroupAddress, Telegram, TelegramType
+from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueRead, GroupValueResponse, GroupValueWrite
 
 
 class TestDevice(unittest.TestCase):
@@ -102,9 +103,7 @@ class TestDevice(unittest.TestCase):
             fut.set_result(None)
             mock_group_read.return_value = fut
             telegram = Telegram(
-                destination_address=GroupAddress("1/2/1"),
-                payload=DPTArray((0x01, 0x02)),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/1"), payload=GroupValueRead()
             )
             self.loop.run_until_complete(device.process(telegram))
             mock_group_read.assert_called_with(telegram)
@@ -115,8 +114,7 @@ class TestDevice(unittest.TestCase):
             mock_group_write.return_value = fut
             telegram = Telegram(
                 destination_address=GroupAddress("1/2/1"),
-                payload=DPTArray((0x01, 0x02)),
-                telegramtype=TelegramType.GROUP_WRITE,
+                payload=GroupValueWrite(DPTArray((0x01, 0x02))),
             )
             self.loop.run_until_complete(device.process(telegram))
             mock_group_write.assert_called_with(telegram)
@@ -127,8 +125,7 @@ class TestDevice(unittest.TestCase):
             mock_group_response.return_value = fut
             telegram = Telegram(
                 destination_address=GroupAddress("1/2/1"),
-                payload=DPTArray((0x01, 0x02)),
-                telegramtype=TelegramType.GROUP_RESPONSE,
+                payload=GroupValueResponse(DPTArray((0x01, 0x02))),
             )
             self.loop.run_until_complete(device.process(telegram))
             mock_group_response.assert_called_with(telegram)
diff --git a/test/devices_tests/expose_sensor_test.py b/test/devices_tests/expose_sensor_test.py
index c7fbafe1..86cbe57f 100644
--- a/test/devices_tests/expose_sensor_test.py
+++ b/test/devices_tests/expose_sensor_test.py
@@ -6,7 +6,8 @@ from unittest.mock import Mock
 from xknx import XKNX
 from xknx.devices import ExposeSensor
 from xknx.dpt import DPTArray, DPTBinary
-from xknx.telegram import GroupAddress, Telegram, TelegramType
+from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueRead, GroupValueResponse, GroupValueWrite
 
 
 class TestExposeSensor(unittest.TestCase):
@@ -73,8 +74,7 @@ class TestExposeSensor(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                telegramtype=TelegramType.GROUP_WRITE,
-                payload=DPTBinary(0),
+                payload=GroupValueWrite(DPTBinary(0)),
             ),
         )
 
@@ -92,8 +92,7 @@ class TestExposeSensor(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                telegramtype=TelegramType.GROUP_WRITE,
-                payload=DPTArray((0xBF,)),
+                payload=GroupValueWrite(DPTArray((0xBF,))),
             ),
         )
 
@@ -110,8 +109,7 @@ class TestExposeSensor(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                telegramtype=TelegramType.GROUP_WRITE,
-                payload=DPTArray((0x0C, 0x1A)),
+                payload=GroupValueWrite(DPTArray((0x0C, 0x1A))),
             ),
         )
 
@@ -126,8 +124,7 @@ class TestExposeSensor(unittest.TestCase):
         )
         expose_sensor.sensor_value.payload = DPTBinary(1)
 
-        telegram = Telegram(destination_address=GroupAddress("1/2/3"))
-        telegram.telegramtype = TelegramType.GROUP_READ
+        telegram = Telegram(GroupAddress("1/2/3"), payload=GroupValueRead())
         self.loop.run_until_complete(expose_sensor.process(telegram))
         self.assertEqual(xknx.telegrams.qsize(), 1)
         telegram = xknx.telegrams.get_nowait()
@@ -135,8 +132,7 @@ class TestExposeSensor(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                telegramtype=TelegramType.GROUP_RESPONSE,
-                payload=DPTBinary(True),
+                payload=GroupValueResponse(DPTBinary(True)),
             ),
         )
 
@@ -148,8 +144,7 @@ class TestExposeSensor(unittest.TestCase):
         )
         expose_sensor.sensor_value.payload = DPTArray((0x40,))
 
-        telegram = Telegram(destination_address=GroupAddress("1/2/3"))
-        telegram.telegramtype = TelegramType.GROUP_READ
+        telegram = Telegram(GroupAddress("1/2/3"), payload=GroupValueRead())
         self.loop.run_until_complete(expose_sensor.process(telegram))
         self.assertEqual(xknx.telegrams.qsize(), 1)
         telegram = xknx.telegrams.get_nowait()
@@ -157,8 +152,7 @@ class TestExposeSensor(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                telegramtype=TelegramType.GROUP_RESPONSE,
-                payload=DPTArray((0x40,)),
+                payload=GroupValueResponse(DPTArray((0x40,))),
             ),
         )
 
@@ -170,8 +164,7 @@ class TestExposeSensor(unittest.TestCase):
         )
         expose_sensor.sensor_value.payload = DPTArray((0x0C, 0x1A))
 
-        telegram = Telegram(destination_address=GroupAddress("1/2/3"))
-        telegram.telegramtype = TelegramType.GROUP_READ
+        telegram = Telegram(GroupAddress("1/2/3"), payload=GroupValueRead())
         self.loop.run_until_complete(expose_sensor.process(telegram))
         self.assertEqual(xknx.telegrams.qsize(), 1)
         telegram = xknx.telegrams.get_nowait()
@@ -179,8 +172,7 @@ class TestExposeSensor(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                telegramtype=TelegramType.GROUP_RESPONSE,
-                payload=DPTArray((0x0C, 0x1A)),
+                payload=GroupValueResponse(DPTArray((0x0C, 0x1A))),
             ),
         )
 
diff --git a/test/devices_tests/fan_test.py b/test/devices_tests/fan_test.py
index f9715e6b..0e7a7767 100644
--- a/test/devices_tests/fan_test.py
+++ b/test/devices_tests/fan_test.py
@@ -7,7 +7,8 @@ from xknx import XKNX
 from xknx.devices import Fan
 from xknx.dpt import DPTArray, DPTBinary
 from xknx.exceptions import CouldNotParseTelegram
-from xknx.telegram import GroupAddress, Telegram, TelegramType
+from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueRead, GroupValueWrite
 
 
 class TestFan(unittest.TestCase):
@@ -39,8 +40,7 @@ class TestFan(unittest.TestCase):
         self.assertEqual(
             telegram1,
             Telegram(
-                destination_address=GroupAddress("1/2/3"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/3"), payload=GroupValueRead()
             ),
         )
 
@@ -64,8 +64,7 @@ class TestFan(unittest.TestCase):
         self.assertEqual(
             telegram1,
             Telegram(
-                destination_address=GroupAddress("1/2/4"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/4"), payload=GroupValueRead()
             ),
         )
 
@@ -83,7 +82,10 @@ class TestFan(unittest.TestCase):
         # 140 is 55% as byte (0...255)
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTArray(140)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray(140)),
+            ),
         )
 
     #
@@ -97,7 +99,8 @@ class TestFan(unittest.TestCase):
 
         # 140 is 55% as byte (0...255)
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTArray(140)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTArray(140)),
         )
         self.loop.run_until_complete(fan.process(telegram))
         self.assertEqual(fan.current_speed, 55)
@@ -107,7 +110,8 @@ class TestFan(unittest.TestCase):
         xknx = XKNX()
         fan = Fan(xknx, name="TestFan", group_address_speed="1/2/3")
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         with self.assertRaises(CouldNotParseTelegram):
             self.loop.run_until_complete(fan.process(telegram))
@@ -118,7 +122,8 @@ class TestFan(unittest.TestCase):
         xknx = XKNX()
         fan = Fan(xknx, name="TestFan", group_address_speed="1/2/3")
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTArray((23, 24))
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTArray((23, 24))),
         )
         with self.assertRaises(CouldNotParseTelegram):
             self.loop.run_until_complete(fan.process(telegram))
diff --git a/test/devices_tests/light_test.py b/test/devices_tests/light_test.py
index 0d6e8b10..2e54fb9d 100644
--- a/test/devices_tests/light_test.py
+++ b/test/devices_tests/light_test.py
@@ -7,7 +7,8 @@ from xknx import XKNX
 from xknx.devices import Light
 from xknx.dpt import DPTArray, DPTBinary
 from xknx.exceptions import CouldNotParseTelegram
-from xknx.telegram import GroupAddress, Telegram, TelegramType
+from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueRead, GroupValueWrite
 
 
 class TestLight(unittest.TestCase):
@@ -156,33 +157,27 @@ class TestLight(unittest.TestCase):
 
         test_telegrams = [
             Telegram(
-                destination_address=GroupAddress("1/2/3"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/3"), payload=GroupValueRead()
             ),
             Telegram(
-                destination_address=GroupAddress("1/2/5"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/5"), payload=GroupValueRead()
             ),
             Telegram(
-                destination_address=GroupAddress("1/2/6"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/6"), payload=GroupValueRead()
             ),
             Telegram(
-                destination_address=GroupAddress("1/2/7"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/9"), payload=GroupValueRead()
             ),
             Telegram(
-                destination_address=GroupAddress("1/2/8"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/7"), payload=GroupValueRead()
             ),
             Telegram(
-                destination_address=GroupAddress("1/2/9"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/8"), payload=GroupValueRead()
             ),
         ]
 
-        self.assertEqual(len(set(telegrams)), 6)
-        self.assertEqual(set(telegrams), set(test_telegrams))
+        self.assertEqual(len(telegrams), 6)
+        self.assertListEqual(telegrams, test_telegrams)
 
     #
     # SYNC WITH STATE ADDRESS
@@ -216,33 +211,27 @@ class TestLight(unittest.TestCase):
 
         test_telegrams = [
             Telegram(
-                destination_address=GroupAddress("1/2/4"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/4"), payload=GroupValueRead()
             ),
             Telegram(
-                destination_address=GroupAddress("1/2/6"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/6"), payload=GroupValueRead()
             ),
             Telegram(
-                destination_address=GroupAddress("1/2/8"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/8"), payload=GroupValueRead()
             ),
             Telegram(
-                destination_address=GroupAddress("1/2/10"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/14"), payload=GroupValueRead()
             ),
             Telegram(
-                destination_address=GroupAddress("1/2/12"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/10"), payload=GroupValueRead()
             ),
             Telegram(
-                destination_address=GroupAddress("1/2/14"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/12"), payload=GroupValueRead()
             ),
         ]
 
-        self.assertEqual(len(set(telegrams)), 6)
-        self.assertEqual(set(telegrams), set(test_telegrams))
+        self.assertEqual(len(telegrams), 6)
+        self.assertListEqual(telegrams, test_telegrams)
 
     #
     # TEST SET ON
@@ -261,7 +250,10 @@ class TestLight(unittest.TestCase):
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(1)),
+            ),
         )
 
     #
@@ -281,7 +273,10 @@ class TestLight(unittest.TestCase):
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTBinary(0)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(0)),
+            ),
         )
 
     #
@@ -301,7 +296,10 @@ class TestLight(unittest.TestCase):
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/5"), payload=DPTArray(23)),
+            Telegram(
+                destination_address=GroupAddress("1/2/5"),
+                payload=GroupValueWrite(DPTArray(23)),
+            ),
         )
 
     def test_set_brightness_not_dimmable(self):
@@ -335,7 +333,7 @@ class TestLight(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/5"),
-                payload=DPTArray((23, 24, 25)),
+                payload=GroupValueWrite(DPTArray((23, 24, 25))),
             ),
         )
         self.loop.run_until_complete(xknx.devices.process(telegram))
@@ -373,7 +371,7 @@ class TestLight(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/5"),
-                payload=DPTArray((23, 24, 25, 26, 0, 15)),
+                payload=GroupValueWrite(DPTArray((23, 24, 25, 26, 0, 15))),
             ),
         )
         self.loop.run_until_complete(xknx.devices.process(telegram))
@@ -414,7 +412,10 @@ class TestLight(unittest.TestCase):
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/5"), payload=DPTArray(23)),
+            Telegram(
+                destination_address=GroupAddress("1/2/5"),
+                payload=GroupValueWrite(DPTArray(23)),
+            ),
         )
 
     def test_set_tw_unsupported(self):
@@ -448,10 +449,12 @@ class TestLight(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/5"),
-                payload=DPTArray(
-                    (
-                        0x0F,
-                        0xA0,
+                payload=GroupValueWrite(
+                    DPTArray(
+                        (
+                            0x0F,
+                            0xA0,
+                        )
                     )
                 ),
             ),
@@ -484,13 +487,15 @@ class TestLight(unittest.TestCase):
         self.assertEqual(light.state, None)
 
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         self.loop.run_until_complete(light.process(telegram))
         self.assertEqual(light.state, True)
 
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(0)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(0)),
         )
         self.loop.run_until_complete(light.process(telegram))
         self.assertEqual(light.state, False)
@@ -515,7 +520,8 @@ class TestLight(unittest.TestCase):
         light.register_device_updated_cb(async_after_update_callback)
 
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         self.loop.run_until_complete(light.process(telegram))
 
@@ -533,7 +539,8 @@ class TestLight(unittest.TestCase):
         self.assertEqual(light.current_brightness, None)
 
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/5"), payload=DPTArray(23)
+            destination_address=GroupAddress("1/2/5"),
+            payload=GroupValueWrite(DPTArray(23)),
         )
         self.loop.run_until_complete(light.process(telegram))
         self.assertEqual(light.current_brightness, 23)
@@ -548,7 +555,8 @@ class TestLight(unittest.TestCase):
             group_address_brightness="1/2/5",
         )
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/5"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/5"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         with self.assertRaises(CouldNotParseTelegram):
             self.loop.run_until_complete(light.process(telegram))
@@ -564,7 +572,8 @@ class TestLight(unittest.TestCase):
             group_address_brightness="1/2/5",
         )
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/5"), payload=DPTArray((23, 24))
+            destination_address=GroupAddress("1/2/5"),
+            payload=GroupValueWrite(DPTArray((23, 24))),
         )
         with self.assertRaises(CouldNotParseTelegram):
             self.loop.run_until_complete(light.process(telegram))
@@ -580,7 +589,8 @@ class TestLight(unittest.TestCase):
         )
         self.assertEqual(light.current_color, (None, None))
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/5"), payload=DPTArray((23, 24, 25))
+            destination_address=GroupAddress("1/2/5"),
+            payload=GroupValueWrite(DPTArray((23, 24, 25))),
         )
         self.loop.run_until_complete(light.process(telegram))
         self.assertEqual(light.current_color, ((23, 24, 25), None))
@@ -598,7 +608,7 @@ class TestLight(unittest.TestCase):
         self.assertEqual(light.current_color, (None, None))
         telegram = Telegram(
             destination_address=GroupAddress("1/2/5"),
-            payload=DPTArray((23, 24, 25, 26, 0, 15)),
+            payload=GroupValueWrite(DPTArray((23, 24, 25, 26, 0, 15))),
         )
         self.loop.run_until_complete(light.process(telegram))
         self.assertEqual(light.current_color, ([23, 24, 25], 26))
@@ -615,7 +625,8 @@ class TestLight(unittest.TestCase):
         self.assertEqual(light.current_tunable_white, None)
 
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/5"), payload=DPTArray(23)
+            destination_address=GroupAddress("1/2/5"),
+            payload=GroupValueWrite(DPTArray(23)),
         )
         self.loop.run_until_complete(light.process(telegram))
         self.assertEqual(light.current_tunable_white, 23)
@@ -630,7 +641,8 @@ class TestLight(unittest.TestCase):
             group_address_tunable_white="1/2/5",
         )
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/5"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/5"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         with self.assertRaises(CouldNotParseTelegram):
             self.loop.run_until_complete(light.process(telegram))
@@ -646,7 +658,8 @@ class TestLight(unittest.TestCase):
             group_address_tunable_white="1/2/5",
         )
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/5"), payload=DPTArray((23, 24))
+            destination_address=GroupAddress("1/2/5"),
+            payload=GroupValueWrite(DPTArray((23, 24))),
         )
         with self.assertRaises(CouldNotParseTelegram):
             self.loop.run_until_complete(light.process(telegram))
@@ -664,10 +677,12 @@ class TestLight(unittest.TestCase):
 
         telegram = Telegram(
             destination_address=GroupAddress("1/2/5"),
-            payload=DPTArray(
-                (
-                    0x0F,
-                    0xA0,
+            payload=GroupValueWrite(
+                DPTArray(
+                    (
+                        0x0F,
+                        0xA0,
+                    )
                 )
             ),
         )
@@ -684,7 +699,8 @@ class TestLight(unittest.TestCase):
             group_address_color_temperature="1/2/5",
         )
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/5"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/5"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         with self.assertRaises(CouldNotParseTelegram):
             self.loop.run_until_complete(light.process(telegram))
@@ -700,7 +716,8 @@ class TestLight(unittest.TestCase):
             group_address_color_temperature="1/2/5",
         )
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/5"), payload=DPTArray(23)
+            destination_address=GroupAddress("1/2/5"),
+            payload=GroupValueWrite(DPTArray(23)),
         )
         with self.assertRaises(CouldNotParseTelegram):
             self.loop.run_until_complete(light.process(telegram))
diff --git a/test/devices_tests/notification_test.py b/test/devices_tests/notification_test.py
index 31e5a22c..c5a1bf4e 100644
--- a/test/devices_tests/notification_test.py
+++ b/test/devices_tests/notification_test.py
@@ -7,7 +7,8 @@ from xknx import XKNX
 from xknx.devices import Notification
 from xknx.dpt import DPTArray, DPTBinary, DPTString
 from xknx.exceptions import CouldNotParseTelegram
-from xknx.telegram import GroupAddress, Telegram, TelegramType
+from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueRead, GroupValueWrite
 
 
 class TestNotification(unittest.TestCase):
@@ -37,8 +38,7 @@ class TestNotification(unittest.TestCase):
         self.assertEqual(
             telegram,
             Telegram(
-                destination_address=GroupAddress("1/2/4"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/4"), payload=GroupValueRead()
             ),
         )
 
@@ -51,14 +51,14 @@ class TestNotification(unittest.TestCase):
         notification = Notification(xknx, "Warning", group_address="1/2/3")
         telegram_set = Telegram(
             destination_address=GroupAddress("1/2/3"),
-            payload=DPTArray(DPTString().to_knx("Ein Prosit!")),
+            payload=GroupValueWrite(DPTArray(DPTString().to_knx("Ein Prosit!"))),
         )
         self.loop.run_until_complete(notification.process(telegram_set))
         self.assertEqual(notification.message, "Ein Prosit!")
 
         telegram_unset = Telegram(
             destination_address=GroupAddress("1/2/3"),
-            payload=DPTArray(DPTString().to_knx("")),
+            payload=GroupValueWrite(DPTArray(DPTString().to_knx(""))),
         )
         self.loop.run_until_complete(notification.process(telegram_unset))
         self.assertEqual(notification.message, "")
@@ -77,7 +77,7 @@ class TestNotification(unittest.TestCase):
         notification.register_device_updated_cb(async_after_update_callback)
         telegram_set = Telegram(
             destination_address=GroupAddress("1/2/3"),
-            payload=DPTArray(DPTString().to_knx("Ein Prosit!")),
+            payload=GroupValueWrite(DPTArray(DPTString().to_knx("Ein Prosit!"))),
         )
         self.loop.run_until_complete(notification.process(telegram_set))
         after_update_callback.assert_called_with(notification)
@@ -88,7 +88,8 @@ class TestNotification(unittest.TestCase):
         xknx = XKNX()
         notification = Notification(xknx, "Warning", group_address="1/2/3")
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTArray((23, 24))
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTArray((23, 24))),
         )
         with self.assertRaises(CouldNotParseTelegram):
             self.loop.run_until_complete(notification.process(telegram))
@@ -98,7 +99,8 @@ class TestNotification(unittest.TestCase):
         xknx = XKNX()
         notification = Notification(xknx, "Warning", group_address="1/2/3")
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         with self.assertRaises(CouldNotParseTelegram):
             self.loop.run_until_complete(notification.process(telegram))
@@ -117,7 +119,7 @@ class TestNotification(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray(DPTString().to_knx("Ein Prosit!")),
+                payload=GroupValueWrite(DPTArray(DPTString().to_knx("Ein Prosit!"))),
             ),
         )
         # test if message longer than 14 chars gets cropped
@@ -129,7 +131,7 @@ class TestNotification(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray(DPTString().to_knx("This is too lo")),
+                payload=GroupValueWrite(DPTArray(DPTString().to_knx("This is too lo"))),
             ),
         )
 
diff --git a/test/devices_tests/scene_test.py b/test/devices_tests/scene_test.py
index 671b9400..9898bfc6 100644
--- a/test/devices_tests/scene_test.py
+++ b/test/devices_tests/scene_test.py
@@ -8,6 +8,7 @@ from xknx import XKNX
 from xknx.devices import Scene
 from xknx.dpt import DPTArray
 from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueWrite
 
 
 class TestScene(unittest.TestCase):
@@ -46,7 +47,10 @@ class TestScene(unittest.TestCase):
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/1"), payload=DPTArray(0x16)),
+            Telegram(
+                destination_address=GroupAddress("1/2/1"),
+                payload=GroupValueWrite(DPTArray(0x16)),
+            ),
         )
 
     def test_do(self):
@@ -58,7 +62,10 @@ class TestScene(unittest.TestCase):
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/1"), payload=DPTArray(0x16)),
+            Telegram(
+                destination_address=GroupAddress("1/2/1"),
+                payload=GroupValueWrite(DPTArray(0x16)),
+            ),
         )
 
     def test_wrong_do(self):
diff --git a/test/devices_tests/sensor_expose_loop_test.py b/test/devices_tests/sensor_expose_loop_test.py
index db73175b..e910a390 100644
--- a/test/devices_tests/sensor_expose_loop_test.py
+++ b/test/devices_tests/sensor_expose_loop_test.py
@@ -5,7 +5,8 @@ import unittest
 from xknx import XKNX
 from xknx.devices import BinarySensor, ExposeSensor, Sensor
 from xknx.dpt import DPTArray, DPTBinary
-from xknx.telegram import GroupAddress, Telegram, TelegramDirection, TelegramType
+from xknx.telegram import GroupAddress, Telegram, TelegramDirection
+from xknx.telegram.apci import GroupValueWrite
 
 
 class SensorExposeLoopTest(unittest.TestCase):
@@ -1460,9 +1461,8 @@ class SensorExposeLoopTest(unittest.TestCase):
 
                 incoming_telegram = Telegram(
                     destination_address=GroupAddress("1/1/1"),
-                    telegramtype=TelegramType.GROUP_WRITE,
                     direction=TelegramDirection.INCOMING,
-                    payload=test_payload,
+                    payload=GroupValueWrite(test_payload),
                 )
                 self.loop.run_until_complete(sensor.process(incoming_telegram))
 
@@ -1482,9 +1482,8 @@ class SensorExposeLoopTest(unittest.TestCase):
                     outgoing_telegram,
                     Telegram(
                         destination_address=GroupAddress("2/2/2"),
-                        telegramtype=TelegramType.GROUP_WRITE,
                         direction=TelegramDirection.OUTGOING,
-                        payload=test_payload,
+                        payload=GroupValueWrite(test_payload),
                     ),
                 )
 
@@ -1510,9 +1509,8 @@ class SensorExposeLoopTest(unittest.TestCase):
 
                 incoming_telegram = Telegram(
                     destination_address=GroupAddress("1/1/1"),
-                    telegramtype=TelegramType.GROUP_WRITE,
                     direction=TelegramDirection.INCOMING,
-                    payload=test_payload,
+                    payload=GroupValueWrite(test_payload),
                 )
                 self.loop.run_until_complete(sensor.process(incoming_telegram))
 
@@ -1526,8 +1524,7 @@ class SensorExposeLoopTest(unittest.TestCase):
                     outgoing_telegram,
                     Telegram(
                         destination_address=GroupAddress("2/2/2"),
-                        telegramtype=TelegramType.GROUP_WRITE,
                         direction=TelegramDirection.OUTGOING,
-                        payload=test_payload,
+                        payload=GroupValueWrite(test_payload),
                     ),
                 )
diff --git a/test/devices_tests/sensor_test.py b/test/devices_tests/sensor_test.py
index 7157ce38..ad928f3c 100644
--- a/test/devices_tests/sensor_test.py
+++ b/test/devices_tests/sensor_test.py
@@ -6,7 +6,8 @@ from unittest.mock import Mock
 from xknx import XKNX
 from xknx.devices import Sensor
 from xknx.dpt import DPTArray
-from xknx.telegram import GroupAddress, Telegram, TelegramType
+from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueRead, GroupValueResponse, GroupValueWrite
 
 
 class TestSensor(unittest.TestCase):
@@ -78,11 +79,12 @@ class TestSensor(unittest.TestCase):
         #  set initial payload of sensor
         sensor.sensor_value.payload = payload
 
-        telegram = Telegram(destination_address=GroupAddress("1/2/3"), payload=payload)
+        telegram = Telegram(
+            destination_address=GroupAddress("1/2/3"), payload=GroupValueWrite(payload)
+        )
         response_telegram = Telegram(
             destination_address=GroupAddress("1/2/3"),
-            payload=payload,
-            telegramtype=TelegramType.GROUP_RESPONSE,
+            payload=GroupValueResponse(payload),
         )
 
         # verify not called when always_callback is False
@@ -2642,8 +2644,7 @@ class TestSensor(unittest.TestCase):
         self.assertEqual(
             telegram,
             Telegram(
-                destination_address=GroupAddress("1/2/3"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/3"), payload=GroupValueRead()
             ),
         )
 
@@ -2669,8 +2670,10 @@ class TestSensor(unittest.TestCase):
             xknx, "TestSensor", value_type="temperature", group_address_state="1/2/3"
         )
 
-        telegram = Telegram(destination_address=GroupAddress("1/2/3"))
-        telegram.payload = DPTArray((0x06, 0xA0))
+        telegram = Telegram(
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTArray((0x06, 0xA0))),
+        )
         self.loop.run_until_complete(sensor.process(telegram))
         self.assertEqual(sensor.sensor_value.payload, DPTArray((0x06, 0xA0)))
         self.assertEqual(sensor.resolve_state(), 16.96)
@@ -2691,7 +2694,9 @@ class TestSensor(unittest.TestCase):
 
         sensor.register_device_updated_cb(async_after_update_callback)
 
-        telegram = Telegram(destination_address=GroupAddress("1/2/3"))
-        telegram.payload = DPTArray((0x01, 0x02))
+        telegram = Telegram(
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTArray((0x01, 0x02))),
+        )
         self.loop.run_until_complete(sensor.process(telegram))
         after_update_callback.assert_called_with(sensor)
diff --git a/test/devices_tests/switch_test.py b/test/devices_tests/switch_test.py
index 2ed54db4..a26e0c23 100644
--- a/test/devices_tests/switch_test.py
+++ b/test/devices_tests/switch_test.py
@@ -6,7 +6,8 @@ from unittest.mock import MagicMock, Mock, patch
 from xknx import XKNX
 from xknx.devices import Switch
 from xknx.dpt import DPTBinary
-from xknx.telegram import GroupAddress, Telegram, TelegramType
+from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueRead, GroupValueResponse, GroupValueWrite
 
 
 class AsyncMock(MagicMock):
@@ -44,8 +45,7 @@ class TestSwitch(unittest.TestCase):
         self.assertEqual(
             telegram,
             Telegram(
-                destination_address=GroupAddress("1/2/3"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/3"), payload=GroupValueRead()
             ),
         )
 
@@ -63,8 +63,7 @@ class TestSwitch(unittest.TestCase):
         self.assertEqual(
             telegram,
             Telegram(
-                destination_address=GroupAddress("1/2/4"),
-                telegramtype=TelegramType.GROUP_READ,
+                destination_address=GroupAddress("1/2/4"), payload=GroupValueRead()
             ),
         )
 
@@ -87,10 +86,12 @@ class TestSwitch(unittest.TestCase):
         callback_mock.assert_not_called()
 
         telegram_on = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         telegram_off = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(0)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(0)),
         )
 
         self.loop.run_until_complete(switch1.process(telegram_on))
@@ -136,13 +137,11 @@ class TestSwitch(unittest.TestCase):
 
         telegram_on = Telegram(
             destination_address=GroupAddress("1/2/4"),
-            payload=DPTBinary(1),
-            telegramtype=TelegramType.GROUP_RESPONSE,
+            payload=GroupValueResponse(DPTBinary(1)),
         )
         telegram_off = Telegram(
             destination_address=GroupAddress("1/2/4"),
-            payload=DPTBinary(0),
-            telegramtype=TelegramType.GROUP_RESPONSE,
+            payload=GroupValueResponse(DPTBinary(0)),
         )
 
         self.loop.run_until_complete(switch1.process(telegram_on))
@@ -170,10 +169,12 @@ class TestSwitch(unittest.TestCase):
         self.assertEqual(switch.state, None)
 
         telegram_inv_on = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(0)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(0)),
         )
         telegram_inv_off = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
 
         self.loop.run_until_complete(switch.process(telegram_inv_on))
@@ -189,7 +190,8 @@ class TestSwitch(unittest.TestCase):
             xknx, "TestInput", group_address="1/2/3", reset_after=reset_after_sec
         )
         telegram_on = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
 
         self.loop.run_until_complete(switch.process(telegram_on))
@@ -208,7 +210,8 @@ class TestSwitch(unittest.TestCase):
             xknx, "TestInput", group_address="1/2/3", reset_after=reset_after_sec
         )
         telegram_on = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueResponse(DPTBinary(1)),
         )
 
         self.loop.run_until_complete(switch.process(telegram_on))
@@ -238,7 +241,8 @@ class TestSwitch(unittest.TestCase):
         switch.register_device_updated_cb(async_after_update_callback)
 
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         self.loop.run_until_complete(switch.process(telegram))
 
@@ -256,7 +260,10 @@ class TestSwitch(unittest.TestCase):
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(1)),
+            ),
         )
 
     #
@@ -271,7 +278,10 @@ class TestSwitch(unittest.TestCase):
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTBinary(0)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(0)),
+            ),
         )
 
     #
@@ -287,7 +297,10 @@ class TestSwitch(unittest.TestCase):
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTBinary(0)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(0)),
+            ),
         )
 
         self.loop.run_until_complete(switch.set_off())
@@ -295,7 +308,10 @@ class TestSwitch(unittest.TestCase):
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(1)),
+            ),
         )
 
     #
diff --git a/test/io_tests/tunnelling_test.py b/test/io_tests/tunnelling_test.py
index 198fe70e..47ffd17d 100644
--- a/test/io_tests/tunnelling_test.py
+++ b/test/io_tests/tunnelling_test.py
@@ -8,6 +8,7 @@ from xknx.dpt import DPTArray
 from xknx.io import Tunnelling, UDPClient
 from xknx.knxip import ErrorCode, KNXIPFrame, KNXIPServiceType, TunnellingAck
 from xknx.telegram import GroupAddress, IndividualAddress, Telegram
+from xknx.telegram.apci import GroupValueWrite
 
 
 class TestTunnelling(unittest.TestCase):
@@ -29,7 +30,8 @@ class TestTunnelling(unittest.TestCase):
         communication_channel_id = 23
         udp_client = UDPClient(xknx, ("192.168.1.1", 0), ("192.168.1.2", 1234))
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTArray((0x1, 0x2, 0x3))
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTArray((0x1, 0x2, 0x3))),
         )
         sequence_counter = 42
         src_address = IndividualAddress("2.2.2")
diff --git a/test/knxip_tests/cemi_frame_test.py b/test/knxip_tests/cemi_frame_test.py
index c3804a2c..84993d92 100644
--- a/test/knxip_tests/cemi_frame_test.py
+++ b/test/knxip_tests/cemi_frame_test.py
@@ -6,8 +6,9 @@ from pytest import fixture, raises
 from xknx.dpt import DPTBinary, DPTComparator
 from xknx.exceptions import ConversionError, CouldNotParseKNXIP, UnsupportedCEMIMessage
 from xknx.knxip.cemi_frame import CEMIFrame
-from xknx.knxip.knxip_enum import APCICommand, CEMIFlags, CEMIMessageCode
+from xknx.knxip.knxip_enum import CEMIFlags, CEMIMessageCode
 from xknx.telegram import GroupAddress, IndividualAddress, Telegram
+from xknx.telegram.apci import GroupValueRead
 
 
 def get_data(code, adil, flags, src, dst, mpdu_len, tpci_apci, payload):
@@ -40,10 +41,9 @@ def test_valid_command(frame):
     """Test for valid frame parsing"""
     packet_len = frame.from_knx(get_data(0x29, 0, 0, 0, 0, 1, 0, []))
     assert frame.code == CEMIMessageCode.L_DATA_IND
-    assert frame.cmd == APCICommand.GROUP_READ
     assert frame.flags == 0
     assert frame.mpdu_len == 1
-    assert frame.payload == DPTBinary(0)
+    assert frame.payload == GroupValueRead()
     assert frame.src_addr == IndividualAddress(0)
     assert frame.dst_addr == IndividualAddress(0)
     assert packet_len == 11
@@ -64,10 +64,9 @@ def test_invalid_apdu_len(frame):
 def test_invalid_src_addr(frame):
     """Test for invalid src addr"""
     frame.code = CEMIMessageCode.L_DATA_IND
-    frame.cmd = APCICommand.GROUP_READ
     frame.flags = 0
     frame.mpdu_len = 1
-    frame.payload = DPTBinary(0)
+    frame.payload = GroupValueRead()
     frame.src_addr = None
     frame.dst_addr = IndividualAddress(0)
 
@@ -78,10 +77,9 @@ def test_invalid_src_addr(frame):
 def test_invalid_dst_addr(frame):
     """Test for invalid dst addr"""
     frame.code = CEMIMessageCode.L_DATA_IND
-    frame.cmd = APCICommand.GROUP_READ
     frame.flags = 0
     frame.mpdu_len = 1
-    frame.payload = DPTBinary(0)
+    frame.payload = GroupValueRead()
     frame.src_addr = IndividualAddress(0)
     frame.dst_addr = None
 
@@ -89,24 +87,9 @@ def test_invalid_dst_addr(frame):
         frame.to_knx()
 
 
-def test_no_payload(frame):
-    """Test for having no payload set"""
-    frame.code = CEMIMessageCode.L_DATA_IND
-    frame.cmd = APCICommand.GROUP_READ
-    frame.flags = 0
-    frame.mpdu_len = 1
-    frame.payload = None
-    frame.src_addr = IndividualAddress(0)
-    frame.dst_addr = IndividualAddress(0)
-
-    assert [41, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0] == frame.to_knx()
-    assert 11 == frame.calculated_length()
-
-
 def test_invalid_payload(frame):
     """Test for having wrong payload set"""
     frame.code = CEMIMessageCode.L_DATA_IND
-    frame.cmd = APCICommand.GROUP_READ
     frame.flags = 0
     frame.mpdu_len = 1
     frame.payload = DPTComparator()
@@ -134,21 +117,6 @@ def test_from_knx_with_not_implemented_cemi(frame):
         )
 
 
-def test_invalid_telegram(frame):
-    """Test for invalid telegram type"""
-    packet_len = frame.from_knx(get_data(0x29, 0, 0, 0, 0, 1, 0, []))
-    frame.cmd = None
-    with raises(ConversionError, match=r".*Telegram not implemented for.*"):
-        tg = frame.telegram
-
-
-def test_set_invalid_telegram(frame):
-    """Test for setting invalid telegram type"""
-    tg = Telegram(telegramtype=None)
-    with raises(TypeError):
-        frame.telegram = tg
-
-
 def test_invalid_invalid_len(frame):
     """Test for invalid cemi len"""
     with raises(UnsupportedCEMIMessage, match=r".*CEMI too small.*"):
diff --git a/test/knxip_tests/routing_indication_test.py b/test/knxip_tests/routing_indication_test.py
index fed7dda7..4a2dc6f0 100644
--- a/test/knxip_tests/routing_indication_test.py
+++ b/test/knxip_tests/routing_indication_test.py
@@ -6,7 +6,8 @@ import unittest
 from xknx import XKNX
 from xknx.dpt import DPTArray, DPTBinary, DPTTemperature, DPTTime
 from xknx.knxip import CEMIFrame, KNXIPFrame, KNXIPServiceType, RoutingIndication
-from xknx.telegram import GroupAddress, IndividualAddress, Telegram, TelegramType
+from xknx.telegram import GroupAddress, IndividualAddress, Telegram
+from xknx.telegram.apci import GroupValueRead, GroupValueResponse, GroupValueWrite
 
 
 class Test_KNXIP(unittest.TestCase):
@@ -36,8 +37,8 @@ class Test_KNXIP(unittest.TestCase):
         self.assertEqual(knxipframe.body.cemi.src_addr, IndividualAddress("1.2.2"))
         self.assertEqual(knxipframe.body.cemi.dst_addr, GroupAddress(337))
 
-        self.assertEqual(len(knxipframe.body.cemi.payload.value), 1)
-        self.assertEqual(knxipframe.body.cemi.payload.value[0], 0xF0)
+        self.assertEqual(len(knxipframe.body.cemi.payload.value.value), 1)
+        self.assertEqual(knxipframe.body.cemi.payload.value.value[0], 0xF0)
 
     def test_from_knx_to_knx(self):
         """Test parsing and streaming CEMIFrame KNX/IP."""
@@ -61,7 +62,9 @@ class Test_KNXIP(unittest.TestCase):
 
         telegram = Telegram(
             destination_address=GroupAddress(337),
-            payload=DPTArray(DPTTime().to_knx(time.strptime("13:23:42", "%H:%M:%S"))),
+            payload=GroupValueWrite(
+                DPTArray(DPTTime().to_knx(time.strptime("13:23:42", "%H:%M:%S")))
+            ),
         )
 
         knxipframe.body.cemi.telegram = telegram
@@ -86,8 +89,8 @@ class Test_KNXIP(unittest.TestCase):
 
         self.assertEqual(telegram.destination_address, GroupAddress(337))
 
-        self.assertEqual(len(telegram.payload.value), 1)
-        self.assertEqual(telegram.payload.value[0], 0xF0)
+        self.assertEqual(len(telegram.payload.value.value), 1)
+        self.assertEqual(telegram.payload.value.value[0], 0xF0)
 
     #
     # End-tox-End tests:
@@ -109,7 +112,7 @@ class Test_KNXIP(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("329"),
-                payload=DPTBinary(1),
+                payload=GroupValueWrite(DPTBinary(1)),
                 source_address=IndividualAddress("15.15.249"),
             ),
         )
@@ -137,7 +140,7 @@ class Test_KNXIP(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("329"),
-                payload=DPTBinary(0),
+                payload=GroupValueWrite(DPTBinary(0)),
                 source_address=IndividualAddress("15.15.249"),
             ),
         )
@@ -165,7 +168,7 @@ class Test_KNXIP(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("331"),
-                payload=DPTArray(0x65),
+                payload=GroupValueWrite(DPTArray(0x65)),
                 source_address=IndividualAddress("15.15.249"),
             ),
         )
@@ -193,7 +196,7 @@ class Test_KNXIP(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("2049"),
-                payload=DPTArray(DPTTemperature().to_knx(19.85)),
+                payload=GroupValueWrite(DPTArray(DPTTemperature().to_knx(19.85))),
                 source_address=IndividualAddress("1.4.2"),
             ),
         )
@@ -221,7 +224,7 @@ class Test_KNXIP(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("440"),
-                telegramtype=TelegramType.GROUP_READ,
+                payload=GroupValueRead(),
                 source_address=IndividualAddress("15.15.249"),
             ),
         )
@@ -249,8 +252,7 @@ class Test_KNXIP(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("392"),
-                telegramtype=TelegramType.GROUP_RESPONSE,
-                payload=DPTBinary(1),
+                payload=GroupValueResponse(DPTBinary(1)),
                 source_address=IndividualAddress("1.3.1"),
             ),
         )
@@ -270,7 +272,7 @@ class Test_KNXIP(unittest.TestCase):
         """Test parsing and streaming CEMIFrame KNX/IP packet, testing maximum APCI."""
         telegram = Telegram(
             destination_address=GroupAddress(337),
-            payload=DPTBinary(DPTBinary.APCI_MAX_VALUE),
+            payload=GroupValueWrite(DPTBinary(DPTBinary.APCI_MAX_VALUE)),
             source_address=IndividualAddress("1.3.1"),
         )
         xknx = XKNX()
diff --git a/test/knxip_tests/tunnelling_request_test.py b/test/knxip_tests/tunnelling_request_test.py
index d13eca7a..8cbc92d5 100644
--- a/test/knxip_tests/tunnelling_request_test.py
+++ b/test/knxip_tests/tunnelling_request_test.py
@@ -7,6 +7,7 @@ from xknx.dpt import DPTBinary
 from xknx.exceptions import CouldNotParseKNXIP
 from xknx.knxip import CEMIFrame, KNXIPFrame, KNXIPServiceType, TunnellingRequest
 from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueWrite
 
 
 class Test_KNXIP_TunnelingReq(unittest.TestCase):
@@ -59,13 +60,17 @@ class Test_KNXIP_TunnelingReq(unittest.TestCase):
 
         self.assertEqual(
             knxipframe.body.cemi.telegram,
-            Telegram(destination_address=GroupAddress("9/0/8"), payload=DPTBinary(1)),
+            Telegram(
+                destination_address=GroupAddress("9/0/8"),
+                payload=GroupValueWrite(DPTBinary(1)),
+            ),
         )
 
         knxipframe2 = KNXIPFrame(xknx)
         knxipframe2.init(KNXIPServiceType.TUNNELLING_REQUEST)
         knxipframe2.body.cemi.telegram = Telegram(
-            destination_address=GroupAddress("9/0/8"), payload=DPTBinary(1)
+            destination_address=GroupAddress("9/0/8"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         knxipframe2.body.sequence_counter = 23
         knxipframe2.normalize()
diff --git a/test/remote_value_tests/remote_value_1count_test.py b/test/remote_value_tests/remote_value_1count_test.py
index bcf5b411..ab4a9052 100644
--- a/test/remote_value_tests/remote_value_1count_test.py
+++ b/test/remote_value_tests/remote_value_1count_test.py
@@ -7,6 +7,7 @@ from xknx.dpt import DPTArray, DPTBinary
 from xknx.exceptions import CouldNotParseTelegram
 from xknx.remote_value import RemoteValue1Count
 from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueWrite
 
 
 class TestRemoteValue1Count(unittest.TestCase):
@@ -43,7 +44,8 @@ class TestRemoteValue1Count(unittest.TestCase):
         self.assertEqual(
             telegram,
             Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTArray((0x64,))
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray((0x64,))),
             ),
         )
         self.loop.run_until_complete(
@@ -56,7 +58,8 @@ class TestRemoteValue1Count(unittest.TestCase):
         self.assertEqual(
             telegram,
             Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTArray((0x65,))
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray((0x65,))),
             ),
         )
 
@@ -65,7 +68,8 @@ class TestRemoteValue1Count(unittest.TestCase):
         xknx = XKNX()
         remote_value = RemoteValue1Count(xknx, group_address=GroupAddress("1/2/3"))
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTArray((0x64,))
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTArray((0x64,))),
         )
         self.loop.run_until_complete(remote_value.process(telegram))
         self.assertEqual(remote_value.value, 100)
@@ -76,16 +80,19 @@ class TestRemoteValue1Count(unittest.TestCase):
         remote_value = RemoteValue1Count(xknx, group_address=GroupAddress("1/2/3"))
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(1)),
             )
             self.loop.run_until_complete(remote_value.process(telegram))
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray(
-                    (
-                        0x64,
-                        0x65,
+                payload=GroupValueWrite(
+                    DPTArray(
+                        (
+                            0x64,
+                            0x65,
+                        )
                     )
                 ),
             )
diff --git a/test/remote_value_tests/remote_value_climate_mode_test.py b/test/remote_value_tests/remote_value_climate_mode_test.py
index 2f745e36..4ce93b59 100644
--- a/test/remote_value_tests/remote_value_climate_mode_test.py
+++ b/test/remote_value_tests/remote_value_climate_mode_test.py
@@ -13,6 +13,7 @@ from xknx.remote_value import (
 )
 from xknx.remote_value.remote_value_climate_mode import _RemoteValueBinaryClimateMode
 from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueWrite
 
 
 class TestRemoteValueDptValue1Ucount(unittest.TestCase):
@@ -178,7 +179,8 @@ class TestRemoteValueDptValue1Ucount(unittest.TestCase):
         self.assertEqual(
             telegram,
             Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTArray((0x03,))
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray((0x03,))),
             ),
         )
         self.loop.run_until_complete(
@@ -189,7 +191,8 @@ class TestRemoteValueDptValue1Ucount(unittest.TestCase):
         self.assertEqual(
             telegram,
             Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTArray((0x04,))
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray((0x04,))),
             ),
         )
 
@@ -207,7 +210,8 @@ class TestRemoteValueDptValue1Ucount(unittest.TestCase):
         self.assertEqual(
             telegram,
             Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTBinary(True)
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(True)),
             ),
         )
         self.loop.run_until_complete(
@@ -218,7 +222,8 @@ class TestRemoteValueDptValue1Ucount(unittest.TestCase):
         self.assertEqual(
             telegram,
             Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTBinary(False)
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(False)),
             ),
         )
 
@@ -231,7 +236,8 @@ class TestRemoteValueDptValue1Ucount(unittest.TestCase):
             climate_mode_type=RemoteValueClimateMode.ClimateModeType.HVAC_MODE,
         )
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTArray((0x00,))
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTArray((0x00,))),
         )
         self.loop.run_until_complete(remote_value.process(telegram))
         self.assertEqual(remote_value.value, HVACOperationMode.AUTO)
@@ -245,7 +251,8 @@ class TestRemoteValueDptValue1Ucount(unittest.TestCase):
             operation_mode=HVACOperationMode.FROST_PROTECTION,
         )
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(True)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(True)),
         )
         self.loop.run_until_complete(remote_value.process(telegram))
         self.assertEqual(remote_value.value, HVACOperationMode.FROST_PROTECTION)
@@ -260,16 +267,19 @@ class TestRemoteValueDptValue1Ucount(unittest.TestCase):
         )
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(1)),
             )
             self.loop.run_until_complete(remote_value.process(telegram))
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray(
-                    (
-                        0x64,
-                        0x65,
+                payload=GroupValueWrite(
+                    DPTArray(
+                        (
+                            0x64,
+                            0x65,
+                        )
                     )
                 ),
             )
@@ -285,16 +295,19 @@ class TestRemoteValueDptValue1Ucount(unittest.TestCase):
         )
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTArray((0x01,))
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray((0x01,))),
             )
             self.loop.run_until_complete(remote_value.process(telegram))
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray(
-                    (
-                        0x64,
-                        0x65,
+                payload=GroupValueWrite(
+                    DPTArray(
+                        (
+                            0x64,
+                            0x65,
+                        )
                     )
                 ),
             )
diff --git a/test/remote_value_tests/remote_value_color_rgb_test.py b/test/remote_value_tests/remote_value_color_rgb_test.py
index e6c33dd0..6c102680 100644
--- a/test/remote_value_tests/remote_value_color_rgb_test.py
+++ b/test/remote_value_tests/remote_value_color_rgb_test.py
@@ -7,6 +7,7 @@ from xknx.dpt import DPTArray, DPTBinary
 from xknx.exceptions import ConversionError, CouldNotParseTelegram
 from xknx.remote_value import RemoteValueColorRGB
 from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueWrite
 
 
 class TestRemoteValueColorRGB(unittest.TestCase):
@@ -66,7 +67,7 @@ class TestRemoteValueColorRGB(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray((0x64, 0x65, 0x66)),
+                payload=GroupValueWrite(DPTArray((0x64, 0x65, 0x66))),
             ),
         )
         self.loop.run_until_complete(remote_value.set((100, 101, 104)))
@@ -76,7 +77,7 @@ class TestRemoteValueColorRGB(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray((0x64, 0x65, 0x68)),
+                payload=GroupValueWrite(DPTArray((0x64, 0x65, 0x68))),
             ),
         )
 
@@ -86,7 +87,7 @@ class TestRemoteValueColorRGB(unittest.TestCase):
         remote_value = RemoteValueColorRGB(xknx, group_address=GroupAddress("1/2/3"))
         telegram = Telegram(
             destination_address=GroupAddress("1/2/3"),
-            payload=DPTArray((0x64, 0x65, 0x66)),
+            payload=GroupValueWrite(DPTArray((0x64, 0x65, 0x66))),
         )
         self.loop.run_until_complete(remote_value.process(telegram))
         self.assertEqual(remote_value.value, (100, 101, 102))
@@ -97,12 +98,13 @@ class TestRemoteValueColorRGB(unittest.TestCase):
         remote_value = RemoteValueColorRGB(xknx, group_address=GroupAddress("1/2/3"))
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(1)),
             )
             self.loop.run_until_complete(remote_value.process(telegram))
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray((0x64, 0x65, 0x66, 0x67)),
+                payload=GroupValueWrite(DPTArray((0x64, 0x65, 0x66, 0x67))),
             )
             self.loop.run_until_complete(remote_value.process(telegram))
diff --git a/test/remote_value_tests/remote_value_color_rgbw_test.py b/test/remote_value_tests/remote_value_color_rgbw_test.py
index 5b929b23..5eba8f4f 100644
--- a/test/remote_value_tests/remote_value_color_rgbw_test.py
+++ b/test/remote_value_tests/remote_value_color_rgbw_test.py
@@ -7,6 +7,7 @@ from xknx.dpt import DPTArray, DPTBinary
 from xknx.exceptions import ConversionError, CouldNotParseTelegram
 from xknx.remote_value import RemoteValueColorRGBW
 from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueWrite
 
 
 class TestRemoteValueColorRGBW(unittest.TestCase):
@@ -94,7 +95,7 @@ class TestRemoteValueColorRGBW(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray((0x64, 0x65, 0x66, 0x67, 0x00, 0x0F)),
+                payload=GroupValueWrite(DPTArray((0x64, 0x65, 0x66, 0x67, 0x00, 0x0F))),
             ),
         )
         self.loop.run_until_complete(remote_value.set((100, 101, 104, 105)))
@@ -104,7 +105,7 @@ class TestRemoteValueColorRGBW(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray((0x64, 0x65, 0x68, 0x69, 0x00, 0x0F)),
+                payload=GroupValueWrite(DPTArray((0x64, 0x65, 0x68, 0x69, 0x00, 0x0F))),
             ),
         )
 
@@ -114,7 +115,7 @@ class TestRemoteValueColorRGBW(unittest.TestCase):
         remote_value = RemoteValueColorRGBW(xknx, group_address=GroupAddress("1/2/3"))
         telegram = Telegram(
             destination_address=GroupAddress("1/2/3"),
-            payload=DPTArray((0x64, 0x65, 0x66, 0x67, 0x00, 0x0F)),
+            payload=GroupValueWrite(DPTArray((0x64, 0x65, 0x66, 0x67, 0x00, 0x0F))),
         )
         self.loop.run_until_complete(remote_value.process(telegram))
         self.assertEqual(remote_value.value, [100, 101, 102, 103])
@@ -125,18 +126,21 @@ class TestRemoteValueColorRGBW(unittest.TestCase):
         remote_value = RemoteValueColorRGBW(xknx, group_address=GroupAddress("1/2/3"))
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(1)),
             )
             self.loop.run_until_complete(remote_value.process(telegram))
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray((0x64, 0x65, 0x66)),
+                payload=GroupValueWrite(DPTArray((0x64, 0x65, 0x66))),
             )
             self.loop.run_until_complete(remote_value.process(telegram))
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray((0x00, 0x00, 0x0F, 0x64, 0x65, 0x66, 0x67)),
+                payload=GroupValueWrite(
+                    DPTArray((0x00, 0x00, 0x0F, 0x64, 0x65, 0x66, 0x67))
+                ),
             )
             self.loop.run_until_complete(remote_value.process(telegram))
diff --git a/test/remote_value_tests/remote_value_dpt_2_byte_unsigned_test.py b/test/remote_value_tests/remote_value_dpt_2_byte_unsigned_test.py
index e34c8dcc..ffcd3f88 100644
--- a/test/remote_value_tests/remote_value_dpt_2_byte_unsigned_test.py
+++ b/test/remote_value_tests/remote_value_dpt_2_byte_unsigned_test.py
@@ -7,6 +7,7 @@ from xknx.dpt import DPTArray, DPTBinary
 from xknx.exceptions import ConversionError, CouldNotParseTelegram
 from xknx.remote_value import RemoteValueDpt2ByteUnsigned
 from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueWrite
 
 
 class TestRemoteValueDptValue2Ucount(unittest.TestCase):
@@ -55,7 +56,7 @@ class TestRemoteValueDptValue2Ucount(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray((0x0A, 0x0B)),
+                payload=GroupValueWrite(DPTArray((0x0A, 0x0B))),
             ),
         )
         self.loop.run_until_complete(remote_value.set(5500))
@@ -65,10 +66,12 @@ class TestRemoteValueDptValue2Ucount(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray(
-                    (
-                        0x15,
-                        0x7C,
+                payload=GroupValueWrite(
+                    DPTArray(
+                        (
+                            0x15,
+                            0x7C,
+                        )
                     )
                 ),
             ),
@@ -81,7 +84,8 @@ class TestRemoteValueDptValue2Ucount(unittest.TestCase):
             xknx, group_address=GroupAddress("1/2/3")
         )
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTArray((0x0A, 0x0B))
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTArray((0x0A, 0x0B))),
         )
         self.loop.run_until_complete(remote_value.process(telegram))
         self.assertEqual(remote_value.value, 2571)
@@ -94,22 +98,26 @@ class TestRemoteValueDptValue2Ucount(unittest.TestCase):
         )
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(1)),
             )
             self.loop.run_until_complete(remote_value.process(telegram))
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTArray((0x64,))
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray((0x64,))),
             )
             self.loop.run_until_complete(remote_value.process(telegram))
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray(
-                    (
-                        0x64,
-                        0x53,
-                        0x42,
+                payload=GroupValueWrite(
+                    DPTArray(
+                        (
+                            0x64,
+                            0x53,
+                            0x42,
+                        )
                     )
                 ),
             )
diff --git a/test/remote_value_tests/remote_value_dpt_value_1_ucount_test.py b/test/remote_value_tests/remote_value_dpt_value_1_ucount_test.py
index 5f3ae1eb..dbf5d230 100644
--- a/test/remote_value_tests/remote_value_dpt_value_1_ucount_test.py
+++ b/test/remote_value_tests/remote_value_dpt_value_1_ucount_test.py
@@ -7,6 +7,7 @@ from xknx.dpt import DPTArray, DPTBinary
 from xknx.exceptions import ConversionError, CouldNotParseTelegram
 from xknx.remote_value import RemoteValueDptValue1Ucount
 from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueWrite
 
 
 class TestRemoteValueDptValue1Ucount(unittest.TestCase):
@@ -54,7 +55,8 @@ class TestRemoteValueDptValue1Ucount(unittest.TestCase):
         self.assertEqual(
             telegram,
             Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTArray((0x0A,))
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray((0x0A,))),
             ),
         )
         self.loop.run_until_complete(remote_value.set(11))
@@ -63,7 +65,8 @@ class TestRemoteValueDptValue1Ucount(unittest.TestCase):
         self.assertEqual(
             telegram,
             Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTArray((0x0B,))
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray((0x0B,))),
             ),
         )
 
@@ -74,7 +77,8 @@ class TestRemoteValueDptValue1Ucount(unittest.TestCase):
             xknx, group_address=GroupAddress("1/2/3")
         )
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTArray((0x0A,))
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTArray((0x0A,))),
         )
         self.loop.run_until_complete(remote_value.process(telegram))
         self.assertEqual(remote_value.value, 10)
@@ -87,16 +91,19 @@ class TestRemoteValueDptValue1Ucount(unittest.TestCase):
         )
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(1)),
             )
             self.loop.run_until_complete(remote_value.process(telegram))
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray(
-                    (
-                        0x64,
-                        0x65,
+                payload=GroupValueWrite(
+                    DPTArray(
+                        (
+                            0x64,
+                            0x65,
+                        )
                     )
                 ),
             )
diff --git a/test/remote_value_tests/remote_value_scene_number_test.py b/test/remote_value_tests/remote_value_scene_number_test.py
index a356bd7c..303e12db 100644
--- a/test/remote_value_tests/remote_value_scene_number_test.py
+++ b/test/remote_value_tests/remote_value_scene_number_test.py
@@ -7,6 +7,7 @@ from xknx.dpt import DPTArray, DPTBinary
 from xknx.exceptions import ConversionError, CouldNotParseTelegram
 from xknx.remote_value import RemoteValueSceneNumber
 from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueWrite
 
 
 class TestRemoteValueSceneNumber(unittest.TestCase):
@@ -52,7 +53,8 @@ class TestRemoteValueSceneNumber(unittest.TestCase):
         self.assertEqual(
             telegram,
             Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTArray((0x0A,))
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray((0x0A,))),
             ),
         )
         self.loop.run_until_complete(remote_value.set(12))
@@ -61,7 +63,8 @@ class TestRemoteValueSceneNumber(unittest.TestCase):
         self.assertEqual(
             telegram,
             Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTArray((0x0B,))
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray((0x0B,))),
             ),
         )
 
@@ -70,7 +73,8 @@ class TestRemoteValueSceneNumber(unittest.TestCase):
         xknx = XKNX()
         remote_value = RemoteValueSceneNumber(xknx, group_address=GroupAddress("1/2/3"))
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTArray((0x0A,))
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTArray((0x0A,))),
         )
         self.loop.run_until_complete(remote_value.process(telegram))
         self.assertEqual(remote_value.value, 11)
@@ -81,16 +85,19 @@ class TestRemoteValueSceneNumber(unittest.TestCase):
         remote_value = RemoteValueSceneNumber(xknx, group_address=GroupAddress("1/2/3"))
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(1)),
             )
             self.loop.run_until_complete(remote_value.process(telegram))
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray(
-                    (
-                        0x64,
-                        0x65,
+                payload=GroupValueWrite(
+                    DPTArray(
+                        (
+                            0x64,
+                            0x65,
+                        )
                     )
                 ),
             )
diff --git a/test/remote_value_tests/remote_value_step_test.py b/test/remote_value_tests/remote_value_step_test.py
index 5a326954..6f8f923b 100644
--- a/test/remote_value_tests/remote_value_step_test.py
+++ b/test/remote_value_tests/remote_value_step_test.py
@@ -7,6 +7,7 @@ from xknx.dpt import DPTArray, DPTBinary
 from xknx.exceptions import ConversionError, CouldNotParseTelegram
 from xknx.remote_value import RemoteValueStep
 from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueWrite
 
 
 class TestRemoteValueStep(unittest.TestCase):
@@ -81,14 +82,20 @@ class TestRemoteValueStep(unittest.TestCase):
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTBinary(0)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(0)),
+            ),
         )
         self.loop.run_until_complete(remote_value.increase())
         self.assertEqual(xknx.telegrams.qsize(), 1)
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(1)),
+            ),
         )
 
     def test_process(self):
@@ -96,7 +103,8 @@ class TestRemoteValueStep(unittest.TestCase):
         xknx = XKNX()
         remote_value = RemoteValueStep(xknx, group_address=GroupAddress("1/2/3"))
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(0)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(0)),
         )
         self.assertEqual(remote_value.value, None)
         self.loop.run_until_complete(remote_value.process(telegram))
@@ -108,12 +116,14 @@ class TestRemoteValueStep(unittest.TestCase):
         remote_value = RemoteValueStep(xknx, group_address=GroupAddress("1/2/3"))
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTArray(0x01)
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray(0x01)),
             )
             self.loop.run_until_complete(remote_value.process(telegram))
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTBinary(3)
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(3)),
             )
             self.loop.run_until_complete(remote_value.process(telegram))
             # pylint: disable=pointless-statement
diff --git a/test/remote_value_tests/remote_value_string_test.py b/test/remote_value_tests/remote_value_string_test.py
index 78ac4949..f34b47db 100644
--- a/test/remote_value_tests/remote_value_string_test.py
+++ b/test/remote_value_tests/remote_value_string_test.py
@@ -7,6 +7,7 @@ from xknx.dpt import DPTArray, DPTBinary
 from xknx.exceptions import ConversionError, CouldNotParseTelegram
 from xknx.remote_value import RemoteValueString
 from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueWrite
 
 
 class TestRemoteValueString(unittest.TestCase):
@@ -93,7 +94,9 @@ class TestRemoteValueString(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray((97, 115, 100, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)),
+                payload=GroupValueWrite(
+                    DPTArray((97, 115, 100, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
+                ),
             ),
         )
         self.loop.run_until_complete(remote_value.set("ASDF"))
@@ -103,7 +106,9 @@ class TestRemoteValueString(unittest.TestCase):
             telegram,
             Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray((65, 83, 68, 70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)),
+                payload=GroupValueWrite(
+                    DPTArray((65, 83, 68, 70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
+                ),
             ),
         )
 
@@ -113,22 +118,24 @@ class TestRemoteValueString(unittest.TestCase):
         remote_value = RemoteValueString(xknx, group_address=GroupAddress("1/2/3"))
         telegram = Telegram(
             destination_address=GroupAddress("1/2/3"),
-            payload=DPTArray(
-                (
-                    0x41,
-                    0x41,
-                    0x41,
-                    0x41,
-                    0x41,
-                    0x42,
-                    0x42,
-                    0x42,
-                    0x42,
-                    0x42,
-                    0x43,
-                    0x43,
-                    0x43,
-                    0x43,
+            payload=GroupValueWrite(
+                DPTArray(
+                    (
+                        0x41,
+                        0x41,
+                        0x41,
+                        0x41,
+                        0x41,
+                        0x42,
+                        0x42,
+                        0x42,
+                        0x42,
+                        0x42,
+                        0x43,
+                        0x43,
+                        0x43,
+                        0x43,
+                    )
                 )
             ),
         )
@@ -141,16 +148,19 @@ class TestRemoteValueString(unittest.TestCase):
         remote_value = RemoteValueString(xknx, group_address=GroupAddress("1/2/3"))
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(1)),
             )
             self.loop.run_until_complete(remote_value.process(telegram))
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
                 destination_address=GroupAddress("1/2/3"),
-                payload=DPTArray(
-                    (
-                        0x64,
-                        0x65,
+                payload=GroupValueWrite(
+                    DPTArray(
+                        (
+                            0x64,
+                            0x65,
+                        )
                     )
                 ),
             )
diff --git a/test/remote_value_tests/remote_value_switch_test.py b/test/remote_value_tests/remote_value_switch_test.py
index 325dc042..bd088a7a 100644
--- a/test/remote_value_tests/remote_value_switch_test.py
+++ b/test/remote_value_tests/remote_value_switch_test.py
@@ -7,6 +7,7 @@ from xknx.dpt import DPTArray, DPTBinary
 from xknx.exceptions import ConversionError, CouldNotParseTelegram
 from xknx.remote_value import RemoteValueSwitch
 from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueWrite
 
 
 class TestRemoteValueSwitch(unittest.TestCase):
@@ -65,14 +66,20 @@ class TestRemoteValueSwitch(unittest.TestCase):
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(1)),
+            ),
         )
         self.loop.run_until_complete(remote_value.off())
         self.assertEqual(xknx.telegrams.qsize(), 1)
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTBinary(0)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(0)),
+            ),
         )
 
     def test_process(self):
@@ -80,7 +87,8 @@ class TestRemoteValueSwitch(unittest.TestCase):
         xknx = XKNX()
         remote_value = RemoteValueSwitch(xknx, group_address=GroupAddress("1/2/3"))
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         self.assertEqual(remote_value.value, None)
         self.loop.run_until_complete(remote_value.process(telegram))
@@ -92,7 +100,8 @@ class TestRemoteValueSwitch(unittest.TestCase):
         xknx = XKNX()
         remote_value = RemoteValueSwitch(xknx, group_address=GroupAddress("1/2/3"))
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(0)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(0)),
         )
         self.assertEqual(remote_value.value, None)
         self.loop.run_until_complete(remote_value.process(telegram))
@@ -105,12 +114,14 @@ class TestRemoteValueSwitch(unittest.TestCase):
         remote_value = RemoteValueSwitch(xknx, group_address=GroupAddress("1/2/3"))
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTArray(0x01)
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray(0x01)),
             )
             self.loop.run_until_complete(remote_value.process(telegram))
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTBinary(3)
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(3)),
             )
             self.loop.run_until_complete(remote_value.process(telegram))
             # pylint: disable=pointless-statement
diff --git a/test/remote_value_tests/remote_value_test.py b/test/remote_value_tests/remote_value_test.py
index 44c528a0..4ac9071a 100644
--- a/test/remote_value_tests/remote_value_test.py
+++ b/test/remote_value_tests/remote_value_test.py
@@ -8,6 +8,7 @@ from xknx.dpt import DPTArray, DPTBinary
 from xknx.exceptions import CouldNotParseTelegram
 from xknx.remote_value import RemoteValue, RemoteValueSwitch
 from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueWrite
 
 
 class TestRemoteValue(unittest.TestCase):
@@ -84,6 +85,26 @@ class TestRemoteValue(unittest.TestCase):
         remote_value = RemoteValue(xknx)
         self.assertEqual(remote_value.unit_of_measurement, None)
 
+    def test_process_unsupported_payload(self):
+        """Test if exception is raised when processing telegram with unsupported payload."""
+        xknx = XKNX()
+        remote_value = RemoteValue(xknx)
+        with patch("xknx.remote_value.RemoteValue.payload_valid") as patch_valid, 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(
+                destination_address=GroupAddress("1/2/1"),
+                payload=object(),
+            )
+            with self.assertRaisesRegex(
+                CouldNotParseTelegram,
+                r".*payload not a GroupValueWrite or GroupValueResponse.*",
+            ):
+                self.loop.run_until_complete(remote_value.process(telegram))
+
     def test_process_invalid_payload(self):
         """Test if exception is raised when processing telegram with invalid payload."""
         xknx = XKNX()
@@ -96,9 +117,9 @@ class TestRemoteValue(unittest.TestCase):
 
             telegram = Telegram(
                 destination_address=GroupAddress("1/2/1"),
-                payload=DPTArray((0x01, 0x02)),
+                payload=GroupValueWrite(DPTArray((0x01, 0x02))),
             )
-            with self.assertRaises(CouldNotParseTelegram):
+            with self.assertRaisesRegex(CouldNotParseTelegram, r".*payload invalid.*"):
                 self.loop.run_until_complete(remote_value.process(telegram))
 
     def test_read_state(self):
@@ -113,7 +134,8 @@ class TestRemoteValue(unittest.TestCase):
 
             fut = asyncio.Future()
             telegram = Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(1)),
             )
             fut.set_result(telegram)
             patch_read.return_value = fut
@@ -158,7 +180,8 @@ class TestRemoteValue(unittest.TestCase):
             patch_valid.return_value = True
             test_payload = DPTArray((0x01, 0x02))
             telegram = Telegram(
-                destination_address=GroupAddress("1/1/1"), payload=test_payload
+                destination_address=GroupAddress("1/1/1"),
+                payload=GroupValueWrite(test_payload),
             )
             self.assertTrue(
                 self.loop.run_until_complete(
diff --git a/test/remote_value_tests/remote_value_updown_test.py b/test/remote_value_tests/remote_value_updown_test.py
index 303c9fb4..04483779 100644
--- a/test/remote_value_tests/remote_value_updown_test.py
+++ b/test/remote_value_tests/remote_value_updown_test.py
@@ -7,6 +7,7 @@ from xknx.dpt import DPTArray, DPTBinary
 from xknx.exceptions import ConversionError, CouldNotParseTelegram
 from xknx.remote_value import RemoteValueUpDown
 from xknx.telegram import GroupAddress, Telegram
+from xknx.telegram.apci import GroupValueWrite
 
 
 class TestRemoteValueUpDown(unittest.TestCase):
@@ -81,14 +82,20 @@ class TestRemoteValueUpDown(unittest.TestCase):
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(1)),
+            ),
         )
         self.loop.run_until_complete(remote_value.up())
         self.assertEqual(xknx.telegrams.qsize(), 1)
         telegram = xknx.telegrams.get_nowait()
         self.assertEqual(
             telegram,
-            Telegram(destination_address=GroupAddress("1/2/3"), payload=DPTBinary(0)),
+            Telegram(
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(0)),
+            ),
         )
 
     def test_process(self):
@@ -96,7 +103,8 @@ class TestRemoteValueUpDown(unittest.TestCase):
         xknx = XKNX()
         remote_value = RemoteValueUpDown(xknx, group_address=GroupAddress("1/2/3"))
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(1)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         self.assertEqual(remote_value.value, None)
         self.loop.run_until_complete(remote_value.process(telegram))
@@ -108,12 +116,14 @@ class TestRemoteValueUpDown(unittest.TestCase):
         remote_value = RemoteValueUpDown(xknx, group_address=GroupAddress("1/2/3"))
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTArray(0x01)
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTArray(0x01)),
             )
             self.loop.run_until_complete(remote_value.process(telegram))
         with self.assertRaises(CouldNotParseTelegram):
             telegram = Telegram(
-                destination_address=GroupAddress("1/2/3"), payload=DPTBinary(3)
+                destination_address=GroupAddress("1/2/3"),
+                payload=GroupValueWrite(DPTBinary(3)),
             )
             self.loop.run_until_complete(remote_value.process(telegram))
             # pylint: disable=pointless-statement
diff --git a/test/str_test.py b/test/str_test.py
index 7972402f..2d95776a 100644
--- a/test/str_test.py
+++ b/test/str_test.py
@@ -56,6 +56,7 @@ from xknx.knxip import (
 )
 from xknx.remote_value import RemoteValue
 from xknx.telegram import GroupAddress, IndividualAddress, Telegram, TelegramDirection
+from xknx.telegram.apci import GroupValueWrite
 
 
 # pylint: disable=too-many-public-methods,invalid-name
@@ -283,7 +284,7 @@ class TestStringRepresentations(unittest.TestCase):
         telegram = Telegram(
             destination_address=GroupAddress("1/2/3"),
             direction=TelegramDirection.INCOMING,
-            payload=DPTArray(0x40),
+            payload=GroupValueWrite(DPTArray(0x40)),
         )
         self.loop.run_until_complete(sensor.process_group_write(telegram))
         self.assertEqual(
@@ -352,7 +353,7 @@ class TestStringRepresentations(unittest.TestCase):
         telegram = Telegram(
             destination_address=GroupAddress("7/0/10"),
             direction=TelegramDirection.INCOMING,
-            payload=DPTBinary(1),
+            payload=GroupValueWrite(DPTBinary(1)),
         )
         self.loop.run_until_complete(weather.process_group_write(telegram))
 
@@ -469,12 +470,13 @@ class TestStringRepresentations(unittest.TestCase):
     def test_telegram(self):
         """Test string representation of Telegram."""
         telegram = Telegram(
-            destination_address=GroupAddress("1/2/3"), payload=DPTBinary(7)
+            destination_address=GroupAddress("1/2/3"),
+            payload=GroupValueWrite(DPTBinary(7)),
         )
         self.assertEqual(
             str(telegram),
-            '<Telegram direction="Outgoing" telegramtype="GroupValueWrite" source_address="0.0.0" '
-            'destination_address="1/2/3" payload="<DPTBinary value="7" />" />',
+            '<Telegram direction="Outgoing" source_address="0.0.0" '
+            'destination_address="1/2/3" payload="<GroupValueWrite value="<DPTBinary value="7" />" />" />',
         )
 
     def test_dib_generic(self):
@@ -642,7 +644,7 @@ class TestStringRepresentations(unittest.TestCase):
         self.assertEqual(
             str(tunnelling_request),
             '<TunnellingRequest communication_channel_id="23" sequence_counter="42" cemi="<CEMIFrame SourceAddress="IndividualAddress("0.0.0")"'
-            ' DestinationAddress="GroupAddress("0/0/0")" Flags="               0" Command="APCICommand.GROUP_READ" payload="None" />" />',
+            ' DestinationAddress="GroupAddress("0/0/0")" Flags="               0" payload="None" />" />',
         )
 
     def test_tunnelling_ack(self):
@@ -662,12 +664,13 @@ class TestStringRepresentations(unittest.TestCase):
         cemi_frame = CEMIFrame(xknx)
         cemi_frame.src_addr = GroupAddress("1/2/3")
         cemi_frame.telegram = Telegram(
-            destination_address=GroupAddress("1/2/5"), payload=DPTBinary(7)
+            destination_address=GroupAddress("1/2/5"),
+            payload=GroupValueWrite(DPTBinary(7)),
         )
         self.assertEqual(
             str(cemi_frame),
-            '<CEMIFrame SourceAddress="GroupAddress("1/2/3")" DestinationAddress="GroupAddress("1/2/5")" Flags="1011110011100000" Command="APCIC'
-            'ommand.GROUP_WRITE" payload="<DPTBinary value="7" />" />',
+            '<CEMIFrame SourceAddress="GroupAddress("1/2/3")" DestinationAddress="GroupAddress("1/2/5")" Flags="1011110011100000" '
+            'payload="<GroupValueWrite value="<DPTBinary value="7" />" />" />',
         )
 
     def test_knxip_frame(self):
@@ -710,5 +713,5 @@ class TestStringRepresentations(unittest.TestCase):
         ri = RoutingIndication(xknx)
         self.assertEqual(
             str(ri),
-            '<RoutingIndication cemi="<CEMIFrame SourceAddress="IndividualAddress("0.0.0")" DestinationAddress="GroupAddress("0/0/0")" Flags="               0" Command="APCICommand.GROUP_READ" payload="None" />" />',
+            '<RoutingIndication cemi="<CEMIFrame SourceAddress="IndividualAddress("0.0.0")" DestinationAddress="GroupAddress("0/0/0")" Flags="               0" payload="None" />" />',
         )
diff --git a/test/telegram_tests/apci_test.py b/test/telegram_tests/apci_test.py
new file mode 100644
index 00000000..7cb42ea8
--- /dev/null
+++ b/test/telegram_tests/apci_test.py
@@ -0,0 +1,172 @@
+"""Unit test for APCI objects."""
+import unittest
+
+from pytest import raises
+from xknx.dpt import DPTArray, DPTBinary
+from xknx.exceptions import ConversionError
+from xknx.telegram.apci import APCI, GroupValueRead, GroupValueResponse, GroupValueWrite
+
+
+class TestAPCI(unittest.TestCase):
+    """Test class for APCI objects."""
+
+    def test_resolve_apci(self):
+        """Test resolve_apci for supported services."""
+        test_cases = [
+            (GroupValueRead.code.value, GroupValueRead),
+            (GroupValueWrite.code.value, GroupValueWrite),
+            (GroupValueResponse.code.value, GroupValueResponse),
+        ]
+
+        for code, clazz in test_cases:
+            self.assertIsInstance(APCI.resolve_apci(code), clazz)
+
+    def test_resolve_apci_unsupported(self):
+        """Test resolve_apci for unsupported services."""
+
+        with raises(ConversionError, match=r".*Class not implemented for APCI.*"):
+            APCI.resolve_apci(0x0100)
+
+        with raises(
+            ConversionError, match=r".*Class not implemented for extended APCI.*"
+        ):
+            APCI.resolve_apci(0x03C0)
+
+
+class TestGroupValueRead(unittest.TestCase):
+    """Test class for GroupValueRead objects."""
+
+    def test_calculated_length(self):
+        """Test the test_calculated_length method."""
+        payload = GroupValueRead()
+
+        self.assertEqual(payload.calculated_length(), 1)
+
+    def test_from_knx(self):
+        """Test the from_knx method."""
+        payload = GroupValueRead()
+        payload.from_knx(bytes([0x00, 0x00]))
+
+        self.assertEqual(payload, GroupValueRead())
+
+    def test_to_knx(self):
+        """Test the to_knx method."""
+        payload = GroupValueRead()
+
+        self.assertEqual(payload.to_knx(), bytes([0x00, 0x00]))
+
+    def test_str(self):
+        """Test the __str__ method."""
+        payload = GroupValueRead()
+
+        self.assertEqual(str(payload), "<GroupValueRead />")
+
+
+class TestGroupValueWrite(unittest.TestCase):
+    """Test class for GroupValueWrite objects."""
+
+    def test_calculated_length(self):
+        """Test the test_calculated_length method."""
+        payload_a = GroupValueWrite(DPTArray((0x01, 0x02, 0x03)))
+        payload_b = GroupValueWrite(DPTBinary(1))
+
+        self.assertEqual(payload_a.calculated_length(), 4)
+        self.assertEqual(payload_b.calculated_length(), 1)
+
+    def test_calculated_length_exception(self):
+        """Test the test_calculated_length method for unsupported dpt."""
+        payload = GroupValueWrite(object())
+
+        with self.assertRaises(TypeError):
+            payload.calculated_length()
+
+    def test_from_knx(self):
+        """Test the from_knx method."""
+        payload_a = GroupValueWrite()
+        payload_a.from_knx(bytes([0x00, 0x80, 0x05, 0x04, 0x03, 0x02, 0x01]))
+        payload_b = GroupValueWrite()
+        payload_b.from_knx(bytes([0x00, 0x82]))
+
+        self.assertEqual(
+            payload_a, GroupValueWrite(DPTArray((0x05, 0x04, 0x03, 0x02, 0x01)))
+        )
+        self.assertEqual(payload_b, GroupValueWrite(DPTBinary(0x02)))
+
+    def test_to_knx(self):
+        """Test the to_knx method."""
+
+        payload_a = GroupValueWrite(DPTArray((0x01, 0x02, 0x03)))
+        payload_b = GroupValueWrite(DPTBinary(1))
+
+        self.assertEqual(payload_a.to_knx(), bytes([0x00, 0x80, 0x01, 0x02, 0x03]))
+        self.assertEqual(payload_b.to_knx(), bytes([0x00, 0x81]))
+
+    def test_to_knx_exception(self):
+        """Test the to_knx method for unsupported dpt."""
+        payload = GroupValueWrite(object())
+
+        with self.assertRaises(TypeError):
+            payload.to_knx()
+
+    def test_str(self):
+        """Test the __str__ method."""
+        payload = GroupValueWrite(DPTBinary(1))
+
+        self.assertEqual(
+            str(payload), '<GroupValueWrite value="<DPTBinary value="1" />" />'
+        )
+
+
+class TestGroupValueResponse(unittest.TestCase):
+    """Test class for TestGroupValueResponse objects."""
+
+    def test_calculated_length(self):
+        """Test the test_calculated_length method."""
+        payload_a = GroupValueResponse(DPTArray((0x01, 0x02, 0x03)))
+        payload_b = GroupValueResponse(DPTBinary(1))
+
+        self.assertEqual(payload_a.calculated_length(), 4)
+        self.assertEqual(payload_b.calculated_length(), 1)
+
+    def test_calculated_length_exception(self):
+        """Test the test_calculated_length method for unsupported dpt."""
+        payload = GroupValueResponse(object())
+
+        with self.assertRaises(TypeError):
+            payload.calculated_length()
+
+    def test_from_knx(self):
+        """Test the from_knx method."""
+        payload_a = GroupValueResponse()
+        payload_a.from_knx(bytes([0x00, 0x80, 0x05, 0x04, 0x03, 0x02, 0x01]))
+        payload_b = GroupValueResponse()
+        payload_b.from_knx(bytes([0x00, 0x82]))
+
+        self.assertEqual(
+            payload_a, GroupValueResponse(DPTArray((0x05, 0x04, 0x03, 0x02, 0x01)))
+        )
+        self.assertEqual(payload_b, GroupValueResponse(DPTBinary(0x02)))
+
+    def test_to_knx(self):
+        """Test the to_knx method."""
+
+        payload_a = GroupValueResponse(DPTArray((0x01, 0x02, 0x03)))
+        payload_b = GroupValueResponse(DPTBinary(1))
+
+        self.assertEqual(payload_a.to_knx(), bytes([0x00, 0x40, 0x01, 0x02, 0x03]))
+        self.assertEqual(payload_b.to_knx(), bytes([0x00, 0x41]))
+
+    def test_to_knx_exception(self):
+        """Test the to_knx method for unsupported dpt."""
+        payload = GroupValueResponse(object())
+
+        with self.assertRaises(TypeError):
+            payload.to_knx()
+
+    def test_str(self):
+        """Test the __str__ method."""
+        payload = GroupValueResponse(DPTBinary(1))
+
+        self.assertEqual(
+            str(payload), '<GroupValueResponse value="<DPTBinary value="1" />" />'
+        )
diff --git a/test/telegram_tests/telegram_test.py b/test/telegram_tests/telegram_test.py
index 37e75993..d41c8949 100644
--- a/test/telegram_tests/telegram_test.py
+++ b/test/telegram_tests/telegram_test.py
@@ -1,7 +1,9 @@
 """Unit test for Telegram objects."""
 import unittest
 
-from xknx.telegram import GroupAddress, Telegram, TelegramDirection, TelegramType
+from xknx.dpt import DPTBinary
+from xknx.telegram import GroupAddress, Telegram, TelegramDirection
+from xknx.telegram.apci import GroupValueRead, GroupValueWrite
 
 
 class TestTelegram(unittest.TestCase):
@@ -13,25 +15,25 @@ class TestTelegram(unittest.TestCase):
     def test_telegram_equal(self):
         """Test equals operator."""
         self.assertEqual(
-            Telegram(GroupAddress("1/2/3"), TelegramType.GROUP_READ),
-            Telegram(GroupAddress("1/2/3"), TelegramType.GROUP_READ),
+            Telegram(GroupAddress("1/2/3"), payload=GroupValueRead()),
+            Telegram(GroupAddress("1/2/3"), payload=GroupValueRead()),
         )
 
     def test_telegram_not_equal(self):
         """Test not equals operator."""
         self.assertNotEqual(
-            Telegram(GroupAddress("1/2/3"), TelegramType.GROUP_READ),
-            Telegram(GroupAddress("1/2/4"), TelegramType.GROUP_READ),
+            Telegram(GroupAddress("1/2/3"), payload=GroupValueRead()),
+            Telegram(GroupAddress("1/2/4"), payload=GroupValueRead()),
         )
         self.assertNotEqual(
-            Telegram(GroupAddress("1/2/3"), TelegramType.GROUP_READ),
-            Telegram(GroupAddress("1/2/3"), TelegramType.GROUP_WRITE),
+            Telegram(GroupAddress("1/2/3"), payload=GroupValueRead()),
+            Telegram(GroupAddress("1/2/3"), payload=GroupValueWrite(DPTBinary(1))),
         )
         self.assertNotEqual(
-            Telegram(GroupAddress("1/2/3"), TelegramType.GROUP_READ),
+            Telegram(GroupAddress("1/2/3"), payload=GroupValueRead()),
             Telegram(
                 GroupAddress("1/2/3"),
-                TelegramType.GROUP_READ,
                 TelegramDirection.INCOMING,
+                payload=GroupValueRead(),
             ),
         )