Repository: XKNX/xknx (python)

Issue:
Invalid date causes error / disconnect
**Description of problem:**

- [ ] using xknx standalone
- [x] using Home-Assistant knx plugin

I do see this error in my logfile exactly every 10 minutes:
```
2020-09-20 18:22:52 WARNING (MainThread) [xknx.log] CEMI not supported: <UnsupportedCEMIMessage description="APCI not supported: 0b1111000000" />
2020-09-20 18:22:52 ERROR (MainThread) [xknx.log] <CouldNotParseKNXIP description="KNXIP data has wrong length" />
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/xknx/io/udp_client.py", line 88, in data_received_callback
    knxipframe.from_knx(raw)
  File "/usr/local/lib/python3.8/site-packages/xknx/knxip/knxip.py", line 81, in from_knx
    raise CouldNotParseKNXIP("KNXIP data has wrong length")
xknx.exceptions.exception.CouldNotParseKNXIP: <CouldNotParseKNXIP description="KNXIP data has wrong length" />
2020-09-20 18:22:53 WARNING (MainThread) [xknx.log] CEMI not supported: <UnsupportedCEMIMessage description="APCI not supported: 0b1111000000" />
2020-09-20 18:22:53 ERROR (MainThread) [xknx.log] <CouldNotParseKNXIP description="KNXIP data has wrong length" />
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/xknx/io/udp_client.py", line 88, in data_received_callback
    knxipframe.from_knx(raw)
  File "/usr/local/lib/python3.8/site-packages/xknx/knxip/knxip.py", line 81, in from_knx
    raise CouldNotParseKNXIP("KNXIP data has wrong length")
xknx.exceptions.exception.CouldNotParseKNXIP: <CouldNotParseKNXIP description="KNXIP data has wrong length" />
2020-09-20 18:22:54 WARNING (MainThread) [xknx.log] Error: KNX bus responded to request of type 'ConnectionState' with error in 'ConnectionStateResponse': ErrorCode.E_CONNECTION_ID
2020-09-20 18:22:56 WARNING (MainThread) [xknx.log] Error: KNX bus did not respond in time to request of type 'Tunnelling'
2020-09-20 18:22:56 WARNING (MainThread) [xknx.log] Sending of telegram failed. Retrying a second time.
2020-09-20 18:22:56 WARNING (MainThread) [xknx.log] Error: KNX bus responded to request of type 'ConnectionState' with error in 'ConnectionStateResponse': ErrorCode.E_CONNECTION_ID
2020-09-20 18:22:56 WARNING (MainThread) [xknx.log] Error: KNX bus responded to request of type 'ConnectionState' with error in 'ConnectionStateResponse': ErrorCode.E_CONNECTION_ID
2020-09-20 18:22:57 WARNING (MainThread) [xknx.log] Error: KNX bus did not respond in time to request of type 'Tunnelling'
2020-09-20 18:22:57 WARNING (MainThread) [xknx.log] Resending telegram failed. Reconnecting to tunnel.
2020-09-20 18:22:57 WARNING (MainThread) [xknx.log] Error: KNX bus responded to request of type 'Disconnect' with error in 'DisconnectResponse': ErrorCode.E_CONNECTION_ID

```

When activating the ETS monitor I noticed that this telegram causes this error:

![image](https://user-images.githubusercontent.com/1945577/93716449-327a3480-fb70-11ea-9185-e0b2f44884c4.png)

It is the current date/time (DPT 19.0001), sent by my MDT IP Interface.

I disabled the time broadcast every 10 minutes and the error / disconnect was gone.

**Version information:**
- xknx / Home-Assistant release with the issue: 0.115.2

**KNX installation:**
<!--
Please provide details about your installation.
- Manufacturer and model of relevant actors, sensors or interfaces.
- if you have access to ETS:
  - provide relevant group address parameters (DPT, Flags)
  - if applicable: excerpt of bus monitor output
-->

IP Interface: MDT SCN-IP000.03 

**Problem-relevant `xknx.yaml` or `configuration.yaml` entries (fill out even if it seems unimportant):**

knx.yaml:
```
tunneling:
  host: '192.168.100.5'
  port: 3671
fire_event: true
fire_event_filter: ["1/*/*", "8/2/*"]
state_updater: true
binary_sensor: !include knx_binary_sensors.yaml
sensor: !include knx_sensors.yaml
cover: !include knx_covers.yaml
climate: !include knx_climate.yaml
light: !include knx_lights.yaml
switch: !include knx_switches.yaml
expose:
```

Pull request description:
Bugfix: Unsupported CEMI Message breaking tunnel connections
<!--
  You are awesome! Thanks for contributing to our project!
  Please, DO NOT DELETE ANY TEXT from this template!.
-->
## Description
<!--
Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context.
List any dependencies that are required for this change.
-->
- Handle unsupported CEMI Message
In TunnelingRequest header length was added to total length resulting in KNXIPFrame() raising CouldNotParseKNXIP. This was probably a typo resulting in loosing tunnel connections when such frames were received

additionally I have added some details to debug logs / exceptions messages
- add raw CEMI Frame to cemi exceptions for better debugability
- add individual address to "start routing" log message

Fixes #408 


## Type of change
<!--
Please tick the applicable options.
NOTE: Ticking multiple options most likely indicates
that your change is to big and it is suggested to split it into several smaller PRs.
-->

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] 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
- [ ] The Homeassistant plugin has been adjusted in case of new config options


Patch:
diff --git a/changelog.md b/changelog.md
index 5ed77445..bf26e319 100644
--- a/changelog.md
+++ b/changelog.md
@@ -13,6 +13,7 @@
 
 - HA Switch entity: keep state without state_address
 - Cover: fix `set_position` without writable position / auto_stop_if_necessary
+- handle unsupported CEMI Messages without loosing tunnel connection
 
 ## 0.15.2 Winter is coming
 
diff --git a/xknx/io/knxip_interface.py b/xknx/io/knxip_interface.py
index 16163650..27fd0ad1 100644
--- a/xknx/io/knxip_interface.py
+++ b/xknx/io/knxip_interface.py
@@ -155,7 +155,7 @@ class KNXIPInterface:
     async def start_routing(self, local_ip: str) -> None:
         """Start KNX/IP Routing."""
         validate_ip(local_ip, address_name="Local IP address")
-        logger.debug("Starting Routing from %s", local_ip)
+        logger.debug("Starting Routing from %s as %s", local_ip, self.xknx.own_address)
         self.interface = Routing(self.xknx, self.telegram_received, local_ip)
         await self.interface.start()
 
diff --git a/xknx/io/udp_client.py b/xknx/io/udp_client.py
index 17e8556b..1bed0091 100644
--- a/xknx/io/udp_client.py
+++ b/xknx/io/udp_client.py
@@ -78,7 +78,7 @@ class UDPClient:
         self.transport = None
         self.callbacks = []
 
-    def data_received_callback(self, raw):
+    def data_received_callback(self, raw: bytes):
         """Parse and process KNXIP frame. Callback for having received an UDP packet."""
         if raw:
             try:
diff --git a/xknx/knxip/cemi_frame.py b/xknx/knxip/cemi_frame.py
index aa8fbbb6..29065cff 100644
--- a/xknx/knxip/cemi_frame.py
+++ b/xknx/knxip/cemi_frame.py
@@ -134,7 +134,9 @@ class CEMIFrame:
             self.code = CEMIMessageCode(raw[0])
         except ValueError:
             raise UnsupportedCEMIMessage(
-                "CEMIMessageCode not implemented: {} ".format(raw[0])
+                "CEMIMessageCode not implemented: {} in CEMI: {}".format(
+                    raw[0], raw.hex()
+                )
             )
 
         if self.code not in (
@@ -143,7 +145,9 @@ class CEMIFrame:
             CEMIMessageCode.L_DATA_CON,
         ):
             raise UnsupportedCEMIMessage(
-                "Could not handle CEMIMessageCode: {} / {}".format(self.code, raw[0])
+                "Could not handle CEMIMessageCode: {} / {} in CEMI: {}".format(
+                    self.code, raw[0], raw.hex()
+                )
             )
 
         return self.from_knx_data_link_layer(raw)
@@ -153,7 +157,7 @@ class CEMIFrame:
         if len(cemi) < 11:
             # eg. ETS Line-Scan issues L_DATA_IND with length 10
             raise UnsupportedCEMIMessage(
-                "CEMI too small. Length: {}; CEMI: {}".format(len(cemi), cemi)
+                "CEMI too small. Length: {}; CEMI: {}".format(len(cemi), cemi.hex())
             )
 
         # AddIL (Additional Info Length), as specified within
@@ -183,13 +187,17 @@ class CEMIFrame:
             self.cmd = APCICommand(tpci_apci & 0xFFC0)
         except ValueError:
             raise UnsupportedCEMIMessage(
-                "APCI not supported: {:#012b}".format(tpci_apci & 0xFFC0)
+                "APCI not supported: {:#012b} in CEMI: {}".format(
+                    tpci_apci & 0xFFC0, cemi.hex()
+                )
             )
 
         apdu = cemi[10 + addil :]
         if len(apdu) != self.mpdu_len:
             raise CouldNotParseKNXIP(
-                "APDU LEN should be {} but is {}".format(self.mpdu_len, len(apdu))
+                "APDU LEN should be {} but is {} in CEMI: {}".format(
+                    self.mpdu_len, len(apdu), cemi.hex()
+                )
             )
 
         if len(apdu) == 1:
diff --git a/xknx/knxip/tunnelling_request.py b/xknx/knxip/tunnelling_request.py
index 953765d1..83133a1a 100644
--- a/xknx/knxip/tunnelling_request.py
+++ b/xknx/knxip/tunnelling_request.py
@@ -62,10 +62,10 @@ class TunnellingRequest(KNXIPBody):
         try:
             pos += self.cemi.from_knx(raw[pos:])
         except UnsupportedCEMIMessage as unsupported_cemi_err:
-            logger.warning("CEMI not supported: %s", unsupported_cemi_err)
+            logger.debug("CEMI not supported: %s", unsupported_cemi_err)
             # Set cemi to None - this is checked in Tunnel() to send Ack even for unsupported CEMI messages.
             self.cemi = None
-            pos += len(raw)
+            return len(raw)
         return pos
 
     def to_knx(self):
diff --git a/xknx/telegram/address.py b/xknx/telegram/address.py
index cfce86d8..b70f6418 100644
--- a/xknx/telegram/address.py
+++ b/xknx/telegram/address.py
@@ -147,9 +147,13 @@ class PhysicalAddress(BaseAddress):
         """Return `True` if this address is a valid line address."""
         return not self.is_device
 
+    def __str__(self) -> str:
+        """Return object as in KNX notation (e.g. '1.2.3')."""
+        return f"{self.area}.{self.main}.{self.line}"
+
     def __repr__(self) -> str:
         """Return this object as parsable string."""
-        return 'PhysicalAddress("{0.area}.{0.main}.{0.line}")'.format(self)
+        return f'PhysicalAddress("{self}")'
 
 
 class GroupAddressType(Enum):


Test patch:
diff --git a/test/io_tests/tunnel_test.py b/test/io_tests/tunnel_test.py
new file mode 100644
index 00000000..7f93147c
--- /dev/null
+++ b/test/io_tests/tunnel_test.py
@@ -0,0 +1,70 @@
+"""Unit test for KNX/IP Tunnelling Request/Response."""
+import asyncio
+import unittest
+from unittest.mock import Mock, patch
+
+from xknx import XKNX
+from xknx.io import Tunnel
+from xknx.knxip import CEMIFrame
+from xknx.telegram import TelegramDirection
+
+
+class TestTunnelling(unittest.TestCase):
+    """Test class for xknx/io/Tunnelling objects."""
+
+    def setUp(self):
+        """Set up test class."""
+        self.loop = asyncio.new_event_loop()
+        asyncio.set_event_loop(self.loop)
+        self.xknx = XKNX()
+        self.tg_received_mock = Mock()
+        self.tunnel = Tunnel(
+            self.xknx,
+            local_ip="192.168.1.1",
+            gateway_ip="192.168.1.2",
+            gateway_port=3671,
+            telegram_received_callback=self.tg_received_mock,
+            auto_reconnect=False,
+            auto_reconnect_wait=3,
+        )
+
+    def tearDown(self):
+        """Tear down test class."""
+        self.loop.close()
+
+    @patch("xknx.io.Tunnel.send_ack")
+    def test_tunnel_request_received(self, send_ack_mock):
+        """Test Tunnel for calling send_ack on normal frames."""
+        # LDataInd GroupValueWrite from 1.1.22 to to 5/1/22 with DPT9 payload 0C 3F
+        # communication_channel_id: 0x02   sequence_counter: 0x21
+        raw = bytes.fromhex("0610 0420 0017 04 02 21 00 2900bcd011162916030080 0c 3f")
+        _cemi = CEMIFrame(self.xknx)
+        _cemi.from_knx(raw[10:])
+        telegram = _cemi.telegram
+        telegram.direction = TelegramDirection.INCOMING
+
+        self.tunnel.udp_client.data_received_callback(raw)
+        self.tg_received_mock.assert_called_once_with(telegram)
+        send_ack_mock.assert_called_once_with(0x02, 0x21)
+
+    @patch("xknx.io.Tunnel.send_ack")
+    def test_tunnel_request_received_unsupported_frames(self, send_ack_mock):
+        """Test Tunnel sending ACK for unsupported frames."""
+        # LDataInd APciPhysAddrRead from 0.0.1 to 0/0/0 broadcast - ETS scan for devices in programming mode
+        # <UnsupportedCEMIMessage description="APCI not supported: 0b0100000000 in CEMI: 2900b0d000010000010100" />
+        # communication_channel_id: 0x02   sequence_counter: 0x4f
+        raw = bytes.fromhex("0610 0420 0015 04 02 4f 00 2900b0d000010000010100")
+
+        self.tunnel.udp_client.data_received_callback(raw)
+        self.tg_received_mock.assert_not_called()
+        send_ack_mock.assert_called_once_with(0x02, 0x4F)
+        send_ack_mock.reset_mock()
+
+        # LDataInd T_Connect from 1.0.250 to 1.0.255 (xknx tunnel endpoint) - ETS Line-Scan
+        # <UnsupportedCEMIMessage description="CEMI too small. Length: 10; CEMI: 2900b06010fa10ff0080" />
+        # communication_channel_id: 0x02   sequence_counter: 0x81
+        raw = bytes.fromhex("0610 0420 0014 04 02 81 00 2900b06010fa10ff0080")
+
+        self.tunnel.udp_client.data_received_callback(raw)
+        self.tg_received_mock.assert_not_called()
+        send_ack_mock.assert_called_once_with(0x02, 0x81)
diff --git a/test/knxip_tests/cemi_frame_test.py b/test/knxip_tests/cemi_frame_test.py
index 0b1fa670..b911f8fb 100644
--- a/test/knxip_tests/cemi_frame_test.py
+++ b/test/knxip_tests/cemi_frame_test.py
@@ -11,20 +11,22 @@ from xknx.telegram import PhysicalAddress, Telegram
 
 
 def get_data(code, adil, flags, src, dst, mpdu_len, tpci_apci, payload):
-    return [
-        code,
-        adil,  # adil
-        (flags >> 8) & 255,  # flags
-        flags & 255,  # flags
-        (src >> 8) & 255,  # src
-        src & 255,  # src
-        (dst >> 8) & 255,  # dst
-        dst & 255,  # dst
-        mpdu_len,  # mpdu_len
-        (tpci_apci >> 8) & 255,  # tpci_apci
-        tpci_apci & 255,  # tpci_apci
-        *payload,  # payload
-    ]
+    return bytes(
+        [
+            code,
+            adil,  # adil
+            (flags >> 8) & 255,  # flags
+            flags & 255,  # flags
+            (src >> 8) & 255,  # src
+            src & 255,  # src
+            (dst >> 8) & 255,  # dst
+            dst & 255,  # dst
+            mpdu_len,  # mpdu_len
+            (tpci_apci >> 8) & 255,  # tpci_apci
+            tpci_apci & 255,  # tpci_apci
+            *payload,  # payload
+        ]
+    )
 
 
 @fixture(name="frame")
@@ -127,7 +129,9 @@ def test_from_knx_with_not_handleable_cemi(frame):
 def test_from_knx_with_not_implemented_cemi(frame):
     """Test for having not implemented CEMI set"""
     with raises(UnsupportedCEMIMessage, match=r".*Could not handle CEMIMessageCode:.*"):
-        frame.from_knx(get_data(CEMIMessageCode.L_BUSMON_IND, 0, 0, 0, 0, 2, 0, []))
+        frame.from_knx(
+            get_data(CEMIMessageCode.L_BUSMON_IND.value, 0, 0, 0, 0, 2, 0, [])
+        )
 
 
 def test_invalid_telegram(frame):
diff --git a/test/knxip_tests/routing_indication_test.py b/test/knxip_tests/routing_indication_test.py
index e4631de1..e9939b58 100644
--- a/test/knxip_tests/routing_indication_test.py
+++ b/test/knxip_tests/routing_indication_test.py
@@ -25,26 +25,7 @@ class Test_KNXIP(unittest.TestCase):
 
     def test_from_knx(self):
         """Test parsing and streaming CEMIFrame KNX/IP packet (payload=0xf0)."""
-        raw = (
-            0x06,
-            0x10,
-            0x05,
-            0x30,
-            0x00,
-            0x12,
-            0x29,
-            0x00,
-            0xBC,
-            0xD0,
-            0x12,
-            0x02,
-            0x01,
-            0x51,
-            0x02,
-            0x00,
-            0x40,
-            0xF0,
-        )
+        raw = bytes.fromhex("0610053000122900BCD012020151020040F0")
         xknx = XKNX()
         knxipframe = KNXIPFrame(xknx)
         knxipframe.from_knx(raw)
@@ -60,26 +41,7 @@ class Test_KNXIP(unittest.TestCase):
 
     def test_from_knx_to_knx(self):
         """Test parsing and streaming CEMIFrame KNX/IP."""
-        raw = (
-            0x06,
-            0x10,
-            0x05,
-            0x30,
-            0x00,
-            0x12,
-            0x29,
-            0x00,
-            0xBC,
-            0xD0,
-            0x12,
-            0x02,
-            0x01,
-            0x51,
-            0x02,
-            0x00,
-            0x40,
-            0xF0,
-        )
+        raw = bytes.fromhex("0610053000122900BCD012020151020040F0")
         xknx = XKNX()
         knxipframe = KNXIPFrame(xknx)
         knxipframe.from_knx(raw)
@@ -107,28 +69,7 @@ class Test_KNXIP(unittest.TestCase):
         knxipframe.body.cemi.set_hops(5)
         knxipframe.normalize()
 
-        raw = (
-            0x06,
-            0x10,
-            0x05,
-            0x30,
-            0x00,
-            0x14,
-            0x29,
-            0x00,
-            0xBC,
-            0xD0,
-            0x12,
-            0x02,
-            0x01,
-            0x51,
-            0x04,
-            0x00,
-            0x80,
-            13,
-            23,
-            42,
-        )
+        raw = bytes.fromhex("0610053000142900BCD012020151040080 0d 17 2a")
 
         self.assertEqual(knxipframe.header.to_knx(), list(raw[0:6]))
         self.assertEqual(knxipframe.body.to_knx(), list(raw[6:]))
@@ -136,26 +77,7 @@ class Test_KNXIP(unittest.TestCase):
 
     def test_telegram_get(self):
         """Test parsing and streaming CEMIFrame KNX/IP packet, group read."""
-        raw = (
-            0x06,
-            0x10,
-            0x05,
-            0x30,
-            0x00,
-            0x12,
-            0x29,
-            0x00,
-            0xBC,
-            0xD0,
-            0x12,
-            0x02,
-            0x01,
-            0x51,
-            0x02,
-            0x00,
-            0x40,
-            0xF0,
-        )
+        raw = bytes.fromhex("0610053000122900BCD012020151020040F0")
         xknx = XKNX()
         knxipframe = KNXIPFrame(xknx)
         knxipframe.from_knx(raw)
@@ -178,25 +100,7 @@ class Test_KNXIP(unittest.TestCase):
     def test_EndTOEnd_group_write_binary_on(self):
         """Test parsing and streaming CEMIFrame KNX/IP packet, switch on light in my kitchen."""
         # Switch on Kitchen-L1
-        raw = (
-            0x06,
-            0x10,
-            0x05,
-            0x30,
-            0x00,
-            0x11,
-            0x29,
-            0x00,
-            0xBC,
-            0xD0,
-            0xFF,
-            0xF9,
-            0x01,
-            0x49,
-            0x01,
-            0x00,
-            0x81,
-        )
+        raw = bytes.fromhex("0610053000112900BCD0FFF90149010081")
         xknx = XKNX()
         knxipframe = KNXIPFrame(xknx)
         knxipframe.from_knx(raw)
@@ -217,25 +121,7 @@ class Test_KNXIP(unittest.TestCase):
     def test_EndTOEnd_group_write_binary_off(self):
         """Test parsing and streaming CEMIFrame KNX/IP packet, switch off light in my kitchen."""
         # Switch off Kitchen-L1
-        raw = (
-            0x06,
-            0x10,
-            0x05,
-            0x30,
-            0x00,
-            0x11,
-            0x29,
-            0x00,
-            0xBC,
-            0xD0,
-            0xFF,
-            0xF9,
-            0x01,
-            0x49,
-            0x01,
-            0x00,
-            0x80,
-        )
+        raw = bytes.fromhex("0610053000112900BCD0FFF90149010080")
         xknx = XKNX()
         knxipframe = KNXIPFrame(xknx)
         knxipframe.from_knx(raw)
@@ -256,26 +142,7 @@ class Test_KNXIP(unittest.TestCase):
     def test_EndTOEnd_group_write_1byte(self):
         """Test parsing and streaming CEMIFrame KNX/IP packet, dimm light in my kitchen."""
         # Dimm Kitchen L1 to 0x65
-        raw = (
-            0x06,
-            0x10,
-            0x05,
-            0x30,
-            0x00,
-            0x12,
-            0x29,
-            0x00,
-            0xBC,
-            0xD0,
-            0xFF,
-            0xF9,
-            0x01,
-            0x4B,
-            0x02,
-            0x00,
-            0x80,
-            0x65,
-        )
+        raw = bytes.fromhex("0610053000122900BCD0FFF9014B02008065")
         xknx = XKNX()
         knxipframe = KNXIPFrame(xknx)
         knxipframe.from_knx(raw)
@@ -298,27 +165,7 @@ class Test_KNXIP(unittest.TestCase):
     def test_EndTOEnd_group_write_2bytes(self):
         """Test parsing and streaming CEMIFrame KNX/IP packet, setting value of thermostat."""
         # Incoming Temperature from thermostat
-        raw = (
-            0x06,
-            0x10,
-            0x05,
-            0x30,
-            0x00,
-            0x13,
-            0x29,
-            0x00,
-            0xBC,
-            0xD0,
-            0x14,
-            0x02,
-            0x08,
-            0x01,
-            0x03,
-            0x00,
-            0x80,
-            0x07,
-            0xC1,
-        )
+        raw = bytes.fromhex("0610053000132900BCD01402080103008007C1")
         xknx = XKNX()
         knxipframe = KNXIPFrame(xknx)
         knxipframe.from_knx(raw)
@@ -344,25 +191,7 @@ class Test_KNXIP(unittest.TestCase):
     def test_EndTOEnd_group_read(self):
         """Test parsing and streaming CEMIFrame KNX/IP packet, group read."""
         # State request
-        raw = (
-            0x06,
-            0x10,
-            0x05,
-            0x30,
-            0x00,
-            0x11,
-            0x29,
-            0x00,
-            0xBC,
-            0xD0,
-            0xFF,
-            0xF9,
-            0x01,
-            0xB8,
-            0x01,
-            0x00,
-            0x00,
-        )
+        raw = bytes.fromhex("0610053000112900BCD0FFF901B8010000")
         xknx = XKNX()
         knxipframe = KNXIPFrame(xknx)
         knxipframe.from_knx(raw)
@@ -385,25 +214,7 @@ class Test_KNXIP(unittest.TestCase):
     def test_EndTOEnd_group_response(self):
         """Test parsing and streaming CEMIFrame KNX/IP packet, group response."""
         # Incoming state
-        raw = (
-            0x06,
-            0x10,
-            0x05,
-            0x30,
-            0x00,
-            0x11,
-            0x29,
-            0x00,
-            0xBC,
-            0xD0,
-            0x13,
-            0x01,
-            0x01,
-            0x88,
-            0x01,
-            0x00,
-            0x41,
-        )
+        raw = bytes.fromhex("0610053000112900BCD013010188010041")
         xknx = XKNX()
         knxipframe = KNXIPFrame(xknx)
         knxipframe.from_knx(raw)
@@ -439,25 +250,8 @@ class Test_KNXIP(unittest.TestCase):
         knxipframe.body.cemi.set_hops(5)
         knxipframe.normalize()
 
-        raw = (
-            0x06,
-            0x10,
-            0x05,
-            0x30,
-            0x00,
-            0x11,
-            0x29,
-            0x00,
-            0xBC,
-            0xD0,
-            0x13,
-            0x01,
-            0x01,
-            0x51,
-            0x01,
-            0x00,
-            0xBF,
-        )
+        raw = bytes.fromhex("0610053000112900BCD0130101510100BF")
+
         self.assertEqual(knxipframe.to_knx(), list(raw))
 
         knxipframe2 = KNXIPFrame(xknx)
@@ -469,5 +263,5 @@ class Test_KNXIP(unittest.TestCase):
         """Test parsing and streaming CEMIFrame KNX/IP packet with unsupported CEMICode."""
         xknx = XKNX()
         ri = RoutingIndication(xknx)
-
-        self.assertEqual(11, ri.from_knx([43, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0]))
+        raw = bytes([43, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0])
+        self.assertEqual(11, ri.from_knx(raw))
diff --git a/test/str_test.py b/test/str_test.py
index a1b3260b..09b4a7ff 100644
--- a/test/str_test.py
+++ b/test/str_test.py
@@ -510,7 +510,7 @@ class TestStringRepresentations(unittest.TestCase):
             "<DIBDeviceInformation \n"
             '\tknx_medium="KNXMedium.TP1" \n'
             '\tprogramming_mode="False" \n'
-            '\tindividual_address="PhysicalAddress("1.1.0")" \n'
+            '\tindividual_address="1.1.0" \n'
             '\tinstallation_number="2" \n'
             '\tproject_number="564" \n'
             '\tserial_number="13:37:13:37:13:37" \n'