Handle KNX expose conversion exceptions and unavailable states (#124776)
parent
163795e73a
commit
633ff0ea42
|
@ -125,6 +125,8 @@ class KNXExposeSensor:
|
||||||
def _get_expose_value(self, state: State | None) -> bool | int | float | str | None:
|
def _get_expose_value(self, state: State | None) -> bool | int | float | str | None:
|
||||||
"""Extract value from state."""
|
"""Extract value from state."""
|
||||||
if state is None or state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE):
|
if state is None or state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE):
|
||||||
|
if self.expose_default is None:
|
||||||
|
return None
|
||||||
value = self.expose_default
|
value = self.expose_default
|
||||||
elif self.expose_attribute is not None:
|
elif self.expose_attribute is not None:
|
||||||
_attr = state.attributes.get(self.expose_attribute)
|
_attr = state.attributes.get(self.expose_attribute)
|
||||||
|
@ -154,12 +156,22 @@ class KNXExposeSensor:
|
||||||
if value is not None and (
|
if value is not None and (
|
||||||
isinstance(self.device.sensor_value, RemoteValueSensor)
|
isinstance(self.device.sensor_value, RemoteValueSensor)
|
||||||
):
|
):
|
||||||
if issubclass(self.device.sensor_value.dpt_class, DPTNumeric):
|
try:
|
||||||
return float(value)
|
if issubclass(self.device.sensor_value.dpt_class, DPTNumeric):
|
||||||
if issubclass(self.device.sensor_value.dpt_class, DPTString):
|
return float(value)
|
||||||
# DPT 16.000 only allows up to 14 Bytes
|
if issubclass(self.device.sensor_value.dpt_class, DPTString):
|
||||||
return str(value)[:14]
|
# DPT 16.000 only allows up to 14 Bytes
|
||||||
return value
|
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:
|
async def _async_entity_changed(self, event: Event[EventStateChangedData]) -> None:
|
||||||
"""Handle entity change."""
|
"""Handle entity change."""
|
||||||
|
|
|
@ -108,6 +108,11 @@ async def test_expose_attribute(hass: HomeAssistant, knx: KNXTestKit) -> None:
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
await knx.assert_telegram_count(0)
|
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(
|
async def test_expose_attribute_with_default(
|
||||||
hass: HomeAssistant, knx: KNXTestKit
|
hass: HomeAssistant, knx: KNXTestKit
|
||||||
|
@ -131,7 +136,7 @@ async def test_expose_attribute_with_default(
|
||||||
await knx.receive_read("1/1/8")
|
await knx.receive_read("1/1/8")
|
||||||
await knx.assert_response("1/1/8", (0,))
|
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", {})
|
hass.states.async_set(entity_id, "on", {})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
await knx.assert_write("1/1/8", (0,))
|
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 hass.async_block_till_done()
|
||||||
await knx.assert_no_telegram()
|
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
|
# Change state and attribute
|
||||||
hass.states.async_set(entity_id, "on", {attribute: 3})
|
hass.states.async_set(entity_id, "on", {attribute: 3})
|
||||||
await hass.async_block_till_done()
|
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
|
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(
|
async def test_expose_conversion_exception(
|
||||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, knx: KNXTestKit
|
hass: HomeAssistant,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
knx: KNXTestKit,
|
||||||
|
invalid_attribute: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test expose throws exception."""
|
"""Test expose throws exception."""
|
||||||
|
|
||||||
|
@ -313,16 +333,17 @@ async def test_expose_conversion_exception(
|
||||||
await knx.receive_read("1/1/8")
|
await knx.receive_read("1/1/8")
|
||||||
await knx.assert_response("1/1/8", (3,))
|
await knx.assert_response("1/1/8", (3,))
|
||||||
|
|
||||||
|
caplog.clear()
|
||||||
# Change attribute: Expect no exception
|
# Change attribute: Expect no exception
|
||||||
hass.states.async_set(
|
hass.states.async_set(
|
||||||
entity_id,
|
entity_id,
|
||||||
"on",
|
"on",
|
||||||
{attribute: 101},
|
{attribute: invalid_attribute},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
await knx.assert_no_telegram()
|
await knx.assert_no_telegram()
|
||||||
assert (
|
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
|
in caplog.text
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue