Repository: XKNX/xknx

Issue:
Error when reading group addresses with timers
I have an extensive KNX installation with all kinds of actors, sensors, switches, etc.
My integration with Home Assistant works perfect, but I do get the following error in the logs when input is detected on an object linked to a timer:

> Error doing job: Exception in callback UDPTransport._on_read_ready
Traceback (most recent call last):
File "uvloop/cbhandles.pyx", line 69, in uvloop.loop.Handle._run
File "uvloop/handles/udp.pyx", line 64, in uvloop.loop.UDPTransport._on_read_ready
File "/usr/local/lib/python3.6/site-packages/xknx/io/udp_client.py", line 55, in datagram_received self.data_received_callback(data)
File "/usr/local/lib/python3.6/site-packages/xknx/io/udp_client.py", line 85, in data_received_callback knxipframe.from_knx(raw)
File "/usr/local/lib/python3.6/site-packages/xknx/knxip/knxip.py", line 78, in from_knx pos += self.body.from_knx(data[pos:])
File "/usr/local/lib/python3.6/site-packages/xknx/knxip/tunnelling_request.py", line 48, in from_knx pos += self.cemi.from_knx(raw[pos:])
File "/usr/local/lib/python3.6/site-packages/xknx/knxip/cemi_frame.py", line 116, in from_knx return self.from_knx_data_link_layer(raw)
File "/usr/local/lib/python3.6/site-packages/xknx/knxip/cemi_frame.py", line 147, in from_knx_data_link_layer self.cmd = APCICommand(tpci_apci & 0xFFC0)
File "/usr/local/lib/python3.6/enum.py", line 291, in __call__ return cls.__new__(cls, value)
File "/usr/local/lib/python3.6/enum.py", line 533, in __new__ return cls._missing_(value)
File "/usr/local/lib/python3.6/enum.py", line 546, in _missing_ raise ValueError("%r is not a valid %s" % (value, cls.__name__))
ValueError: 960 is not a valid APCICommand

Two minutes after this error I then receive:
`Error: reading rading group address from KNX bus failed: ErrorCode.E_CONNECTION_ID`

Comments:

Patch:
diff --git a/xknx/exceptions/__init__.py b/xknx/exceptions/__init__.py
index 0875fb6f..95609fa0 100644
--- a/xknx/exceptions/__init__.py
+++ b/xknx/exceptions/__init__.py
@@ -2,4 +2,5 @@
 # flake8: noqa
 from .exception import (
     ConversionError, CouldNotParseAddress, CouldNotParseKNXIP,
-    CouldNotParseTelegram, DeviceIllegalValue, XKNXException)
+    CouldNotParseTelegram, DeviceIllegalValue, UnsupportedCEMIMessage,
+    XKNXException)
diff --git a/xknx/exceptions/exception.py b/xknx/exceptions/exception.py
index 626299bd..18702952 100644
--- a/xknx/exceptions/exception.py
+++ b/xknx/exceptions/exception.py
@@ -39,7 +39,7 @@ class CouldNotParseKNXIP(XKNXException):
     """Exception class for wrong KNXIP data."""
 
     def __init__(self, description=""):
-        """Initialize CouldNotParseTelegram class."""
+        """Initialize CouldNotParseKNXIP class."""
         super().__init__()
         self.description = description
 
@@ -49,6 +49,20 @@ class CouldNotParseKNXIP(XKNXException):
             .format(self.description)
 
 
+class UnsupportedCEMIMessage(XKNXException):
+    """Exception class for unsupported CEMI Messages."""
+
+    def __init__(self, description=""):
+        """Initialize UnsupportedCEMIMessage class."""
+        super().__init__()
+        self.description = description
+
+    def __str__(self):
+        """Return object as readable string."""
+        return '<UnsupportedCEMIMessage description="{0}" />' \
+            .format(self.description)
+
+
 class ConversionError(XKNXException):
     """Exception class for error while converting one type to another."""
 
diff --git a/xknx/knxip/cemi_frame.py b/xknx/knxip/cemi_frame.py
index 1fc80ead..dffa5064 100644
--- a/xknx/knxip/cemi_frame.py
+++ b/xknx/knxip/cemi_frame.py
@@ -12,7 +12,8 @@ Documentation within:
     File: AN117 v02.01 KNX IP Communication Medium DV.pdf
 """
 from xknx.dpt import DPTArray, DPTBinary
-from xknx.exceptions import ConversionError, CouldNotParseKNXIP
+from xknx.exceptions import (
+    ConversionError, CouldNotParseKNXIP, UnsupportedCEMIMessage)
 from xknx.telegram import GroupAddress, PhysicalAddress, Telegram, TelegramType
 
 from .body import KNXIPBody
@@ -106,20 +107,25 @@ class CEMIFrame(KNXIPBody):
     def from_knx(self, raw):
         """Parse/deserialize from KNX/IP raw data."""
         try:
-            self.code = CEMIMessageCode(raw[0])
-        except ValueError:
-            raise CouldNotParseKNXIP("Could not understand CEMIMessageCode: {0} ".format(raw[0]))
-
-        if self.code == CEMIMessageCode.L_DATA_IND or \
-                self.code == CEMIMessageCode.L_Data_REQ or \
-                self.code == CEMIMessageCode.L_DATA_CON:
-            return self.from_knx_data_link_layer(raw)
-        raise CouldNotParseKNXIP("Could not understand CEMIMessageCode: {0} / {1}".format(self.code, raw[0]))
+            try:
+                self.code = CEMIMessageCode(raw[0])
+            except ValueError:
+                raise UnsupportedCEMIMessage("CEMIMessageCode not implemented: {0} ".format(raw[0]))
+
+            if self.code == CEMIMessageCode.L_DATA_IND or \
+                    self.code == CEMIMessageCode.L_Data_REQ or \
+                    self.code == CEMIMessageCode.L_DATA_CON:
+                return self.from_knx_data_link_layer(raw)
+            raise UnsupportedCEMIMessage("Could not handle CEMIMessageCode: {0} / {1}".format(self.code, raw[0]))
+        except UnsupportedCEMIMessage as unsupported_cemi_err:
+            self.xknx.logger.warning("Ignoring not implemented CEMI: %s", unsupported_cemi_err)
+            return len(raw)
 
     def from_knx_data_link_layer(self, cemi):
         """Parse L_DATA_IND, CEMIMessageCode.L_Data_REQ, CEMIMessageCode.L_DATA_CON."""
         if len(cemi) < 11:
-            raise CouldNotParseKNXIP("CEMI too small")
+            # eg. ETS Line-Scan issues L_DATA_IND with length 10
+            raise UnsupportedCEMIMessage("CEMI too small. Length: {0}; CEMI: {1}".format(len(cemi), cemi))
 
         # AddIL (Additional Info Length), as specified within
         # KNX Chapter 3.6.3/4.1.4.3 "Additional information."
@@ -146,7 +152,7 @@ class CEMIFrame(KNXIPBody):
         try:
             self.cmd = APCICommand(tpci_apci & 0xFFC0)
         except ValueError:
-            raise CouldNotParseKNXIP(
+            raise UnsupportedCEMIMessage(
                 "APCI not supported: {0:#012b}".format(tpci_apci & 0xFFC0))
 
         apdu = cemi[10 + addil:]


Test patch:
diff --git a/test/knxip_tests/cemi_frame_test.py b/test/knxip_tests/cemi_frame_test.py
index b305a3b3..711762ec 100644
--- a/test/knxip_tests/cemi_frame_test.py
+++ b/test/knxip_tests/cemi_frame_test.py
@@ -5,7 +5,7 @@ from unittest.mock import MagicMock
 from pytest import fixture, raises
 
 from xknx.dpt import DPTBinary
-from xknx.exceptions import CouldNotParseKNXIP
+from xknx.exceptions import CouldNotParseKNXIP, UnsupportedCEMIMessage
 from xknx.knxip.cemi_frame import CEMIFrame
 from xknx.knxip.knxip_enum import APCICommand, CEMIMessageCode
 from xknx.telegram import PhysicalAddress
@@ -50,8 +50,8 @@ def test_valid_command(frame):
 
 def test_invalid_tpci_apci(frame):
     """Test for invalid APCICommand"""
-    with raises(CouldNotParseKNXIP, match=r".*APCI not supported: .*"):
-        frame.from_knx(get_data(0x29, 0, 0, 0, 0, 1, 0xFFC0, []))
+    with raises(UnsupportedCEMIMessage, match=r".*APCI not supported: .*"):
+        frame.from_knx_data_link_layer(get_data(0x29, 0, 0, 0, 0, 1, 0xFFC0, []))
 
 
 def test_invalid_apdu_len(frame):
@@ -62,11 +62,5 @@ def test_invalid_apdu_len(frame):
 
 def test_invalid_invalid_len(frame):
     """Test for invalid cemi len"""
-    with raises(CouldNotParseKNXIP, match=r".*CEMI too small"):
-        frame.from_knx(get_data(0x29, 0, 0, 0, 0, 2, 0, [])[:5])
-
-
-def test_invalid_invalid_code(frame):
-    """Test for invalid cemi code"""
-    with raises(CouldNotParseKNXIP, match=r".*Could not understand CEMIMessageCode"):
-        frame.from_knx(get_data(0x0, 0, 0, 0, 0, 2, 0, []))
+    with raises(UnsupportedCEMIMessage, match=r".*CEMI too small.*"):
+        frame.from_knx_data_link_layer(get_data(0x29, 0, 0, 0, 0, 2, 0, [])[:5])