Fix KNX Expose for strings longer than 14 bytes (#63026)

* Fix KNX Expose for too long strings

* Fix tests

* Catch exception and avoid error during config entry setup for exposures

* Properly catch exceptions in knx expose

* Fix pylint

* Fix CI

* Add test for conversion error
pull/65442/head
Marvin Wichmann 2022-01-29 14:32:12 +01:00 committed by Paulus Schoutsen
parent 3dde12f887
commit 1a6964448c
2 changed files with 88 additions and 4 deletions

View File

@ -2,10 +2,12 @@
from __future__ import annotations
from collections.abc import Callable
import logging
from xknx import XKNX
from xknx.devices import DateTime, ExposeSensor
from xknx.dpt import DPTNumeric
from xknx.dpt import DPTNumeric, DPTString
from xknx.exceptions import ConversionError
from xknx.remote_value import RemoteValueSensor
from homeassistant.const import (
@ -22,6 +24,8 @@ from homeassistant.helpers.typing import ConfigType, StateType
from .const import KNX_ADDRESS
from .schema import ExposeSchema
_LOGGER = logging.getLogger(__name__)
@callback
def create_knx_exposure(
@ -101,7 +105,10 @@ class KNXExposeSensor:
"""Initialize state of the exposure."""
init_state = self.hass.states.get(self.entity_id)
state_value = self._get_expose_value(init_state)
self.device.sensor_value.value = state_value
try:
self.device.sensor_value.value = state_value
except ConversionError:
_LOGGER.exception("Error during sending of expose sensor value")
@callback
def shutdown(self) -> None:
@ -132,6 +139,13 @@ class KNXExposeSensor:
and issubclass(self.device.sensor_value.dpt_class, DPTNumeric)
):
return float(value)
if (
value is not None
and isinstance(self.device.sensor_value, RemoteValueSensor)
and issubclass(self.device.sensor_value.dpt_class, DPTString)
):
# DPT 16.000 only allows up to 14 Bytes
return str(value)[:14]
return value
async def _async_entity_changed(self, event: Event) -> None:
@ -148,7 +162,10 @@ class KNXExposeSensor:
async def _async_set_knx_value(self, value: StateType) -> None:
"""Set new value on xknx ExposeSensor."""
await self.device.set(value)
try:
await self.device.set(value)
except ConversionError:
_LOGGER.exception("Error during sending of expose sensor value")
class KNXExposeTime:

View File

@ -128,6 +128,73 @@ async def test_expose_attribute_with_default(hass: HomeAssistant, knx: KNXTestKi
await knx.assert_write("1/1/8", (0,))
async def test_expose_string(hass: HomeAssistant, knx: KNXTestKit):
"""Test an expose to send string values of up to 14 bytes only."""
entity_id = "fake.entity"
attribute = "fake_attribute"
await knx.setup_integration(
{
CONF_KNX_EXPOSE: {
CONF_TYPE: "string",
KNX_ADDRESS: "1/1/8",
CONF_ENTITY_ID: entity_id,
CONF_ATTRIBUTE: attribute,
ExposeSchema.CONF_KNX_EXPOSE_DEFAULT: "Test",
}
},
)
assert not hass.states.async_all()
# Before init default value shall be sent as response
await knx.receive_read("1/1/8")
await knx.assert_response(
"1/1/8", (84, 101, 115, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
)
# Change attribute; keep state
hass.states.async_set(
entity_id,
"on",
{attribute: "This is a very long string that is larger than 14 bytes"},
)
await knx.assert_write(
"1/1/8", (84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 118, 101, 114, 121)
)
async def test_expose_conversion_exception(hass: HomeAssistant, knx: KNXTestKit):
"""Test expose throws exception."""
entity_id = "fake.entity"
attribute = "fake_attribute"
await knx.setup_integration(
{
CONF_KNX_EXPOSE: {
CONF_TYPE: "percent",
KNX_ADDRESS: "1/1/8",
CONF_ENTITY_ID: entity_id,
CONF_ATTRIBUTE: attribute,
ExposeSchema.CONF_KNX_EXPOSE_DEFAULT: 1,
}
},
)
assert not hass.states.async_all()
# Before init default value shall be sent as response
await knx.receive_read("1/1/8")
await knx.assert_response("1/1/8", (3,))
# Change attribute: Expect no exception
hass.states.async_set(
entity_id,
"on",
{attribute: 101},
)
await knx.assert_no_telegram()
@patch("time.localtime")
async def test_expose_with_date(localtime, hass: HomeAssistant, knx: KNXTestKit):
"""Test an expose with a date."""
@ -138,7 +205,7 @@ async def test_expose_with_date(localtime, hass: HomeAssistant, knx: KNXTestKit)
CONF_TYPE: "datetime",
KNX_ADDRESS: "1/1/8",
}
},
}
)
assert not hass.states.async_all()