Handle KNX expose conversion exceptions and unavailable states (#124776)

pull/124781/head
Matthias Alphart 2024-08-28 13:14:34 +02:00 committed by GitHub
parent 163795e73a
commit 633ff0ea42
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 43 additions and 10 deletions

View File

@ -125,6 +125,8 @@ class KNXExposeSensor:
def _get_expose_value(self, state: State | None) -> bool | int | float | str | None:
"""Extract value from state."""
if state is None or state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE):
if self.expose_default is None:
return None
value = self.expose_default
elif self.expose_attribute is not None:
_attr = state.attributes.get(self.expose_attribute)
@ -154,12 +156,22 @@ class KNXExposeSensor:
if value is not None and (
isinstance(self.device.sensor_value, RemoteValueSensor)
):
if issubclass(self.device.sensor_value.dpt_class, DPTNumeric):
return float(value)
if issubclass(self.device.sensor_value.dpt_class, DPTString):
# DPT 16.000 only allows up to 14 Bytes
return str(value)[:14]
return value
try:
if issubclass(self.device.sensor_value.dpt_class, DPTNumeric):
return float(value)
if issubclass(self.device.sensor_value.dpt_class, DPTString):
# DPT 16.000 only allows up to 14 Bytes
return str(value)[:14]
except (ValueError, TypeError) as err:
_LOGGER.warning(
'Could not expose %s %s value "%s" to KNX: Conversion failed: %s',
self.entity_id,
self.expose_attribute or "state",
value,
err,
)
return None
return value # type: ignore[no-any-return]
async def _async_entity_changed(self, event: Event[EventStateChangedData]) -> None:
"""Handle entity change."""

View File

@ -108,6 +108,11 @@ async def test_expose_attribute(hass: HomeAssistant, knx: KNXTestKit) -> None:
await hass.async_block_till_done()
await knx.assert_telegram_count(0)
# Ignore "unavailable" state
hass.states.async_set(entity_id, "unavailable", {attribute: None})
await hass.async_block_till_done()
await knx.assert_telegram_count(0)
async def test_expose_attribute_with_default(
hass: HomeAssistant, knx: KNXTestKit
@ -131,7 +136,7 @@ async def test_expose_attribute_with_default(
await knx.receive_read("1/1/8")
await knx.assert_response("1/1/8", (0,))
# Change state to "on"; no attribute
# Change state to "on"; no attribute -> default
hass.states.async_set(entity_id, "on", {})
await hass.async_block_till_done()
await knx.assert_write("1/1/8", (0,))
@ -146,6 +151,11 @@ async def test_expose_attribute_with_default(
await hass.async_block_till_done()
await knx.assert_no_telegram()
# Use default for "unavailable" state
hass.states.async_set(entity_id, "unavailable")
await hass.async_block_till_done()
await knx.assert_write("1/1/8", (0,))
# Change state and attribute
hass.states.async_set(entity_id, "on", {attribute: 3})
await hass.async_block_till_done()
@ -290,8 +300,18 @@ async def test_expose_value_template(
assert "Error rendering value template for KNX expose" in caplog.text
@pytest.mark.parametrize(
"invalid_attribute",
[
101.0,
"invalid", # can't cast to float
],
)
async def test_expose_conversion_exception(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, knx: KNXTestKit
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
knx: KNXTestKit,
invalid_attribute: str,
) -> None:
"""Test expose throws exception."""
@ -313,16 +333,17 @@ async def test_expose_conversion_exception(
await knx.receive_read("1/1/8")
await knx.assert_response("1/1/8", (3,))
caplog.clear()
# Change attribute: Expect no exception
hass.states.async_set(
entity_id,
"on",
{attribute: 101},
{attribute: invalid_attribute},
)
await hass.async_block_till_done()
await knx.assert_no_telegram()
assert (
'Could not expose fake.entity fake_attribute value "101.0" to KNX:'
f'Could not expose fake.entity fake_attribute value "{invalid_attribute}" to KNX:'
in caplog.text
)