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 errorpull/65442/head
parent
3dde12f887
commit
1a6964448c
|
@ -2,10 +2,12 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
|
import logging
|
||||||
|
|
||||||
from xknx import XKNX
|
from xknx import XKNX
|
||||||
from xknx.devices import DateTime, ExposeSensor
|
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 xknx.remote_value import RemoteValueSensor
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
@ -22,6 +24,8 @@ from homeassistant.helpers.typing import ConfigType, StateType
|
||||||
from .const import KNX_ADDRESS
|
from .const import KNX_ADDRESS
|
||||||
from .schema import ExposeSchema
|
from .schema import ExposeSchema
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def create_knx_exposure(
|
def create_knx_exposure(
|
||||||
|
@ -101,7 +105,10 @@ class KNXExposeSensor:
|
||||||
"""Initialize state of the exposure."""
|
"""Initialize state of the exposure."""
|
||||||
init_state = self.hass.states.get(self.entity_id)
|
init_state = self.hass.states.get(self.entity_id)
|
||||||
state_value = self._get_expose_value(init_state)
|
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
|
@callback
|
||||||
def shutdown(self) -> None:
|
def shutdown(self) -> None:
|
||||||
|
@ -132,6 +139,13 @@ class KNXExposeSensor:
|
||||||
and issubclass(self.device.sensor_value.dpt_class, DPTNumeric)
|
and issubclass(self.device.sensor_value.dpt_class, DPTNumeric)
|
||||||
):
|
):
|
||||||
return float(value)
|
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
|
return value
|
||||||
|
|
||||||
async def _async_entity_changed(self, event: Event) -> None:
|
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:
|
async def _async_set_knx_value(self, value: StateType) -> None:
|
||||||
"""Set new value on xknx ExposeSensor."""
|
"""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:
|
class KNXExposeTime:
|
||||||
|
|
|
@ -128,6 +128,73 @@ async def test_expose_attribute_with_default(hass: HomeAssistant, knx: KNXTestKi
|
||||||
await knx.assert_write("1/1/8", (0,))
|
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")
|
@patch("time.localtime")
|
||||||
async def test_expose_with_date(localtime, hass: HomeAssistant, knx: KNXTestKit):
|
async def test_expose_with_date(localtime, hass: HomeAssistant, knx: KNXTestKit):
|
||||||
"""Test an expose with a date."""
|
"""Test an expose with a date."""
|
||||||
|
@ -138,7 +205,7 @@ async def test_expose_with_date(localtime, hass: HomeAssistant, knx: KNXTestKit)
|
||||||
CONF_TYPE: "datetime",
|
CONF_TYPE: "datetime",
|
||||||
KNX_ADDRESS: "1/1/8",
|
KNX_ADDRESS: "1/1/8",
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
assert not hass.states.async_all()
|
assert not hass.states.async_all()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue