Add value_template option to KNX expose (#117732)
* Add value_template option to KNX expose * template exception handling
This commit is contained in:
parent
622d1e4c50
commit
70cf176d93
3 changed files with 85 additions and 18 deletions
|
@ -13,6 +13,7 @@ from xknx.remote_value import RemoteValueSensor
|
|||
|
||||
from homeassistant.const import (
|
||||
CONF_ENTITY_ID,
|
||||
CONF_VALUE_TEMPLATE,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_UNAVAILABLE,
|
||||
|
@ -25,7 +26,9 @@ from homeassistant.core import (
|
|||
State,
|
||||
callback,
|
||||
)
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers.event import async_track_state_change_event
|
||||
from homeassistant.helpers.template import Template
|
||||
from homeassistant.helpers.typing import ConfigType, StateType
|
||||
|
||||
from .const import CONF_RESPOND_TO_READ, KNX_ADDRESS
|
||||
|
@ -79,6 +82,9 @@ class KNXExposeSensor:
|
|||
)
|
||||
self.expose_default = config.get(ExposeSchema.CONF_KNX_EXPOSE_DEFAULT)
|
||||
self.expose_type: int | str = config[ExposeSchema.CONF_KNX_EXPOSE_TYPE]
|
||||
self.value_template: Template | None = config.get(CONF_VALUE_TEMPLATE)
|
||||
if self.value_template is not None:
|
||||
self.value_template.hass = hass
|
||||
|
||||
self._remove_listener: Callable[[], None] | None = None
|
||||
self.device: ExposeSensor = self.async_register(config)
|
||||
|
@ -87,13 +93,10 @@ class KNXExposeSensor:
|
|||
@callback
|
||||
def async_register(self, config: ConfigType) -> ExposeSensor:
|
||||
"""Register listener."""
|
||||
if self.expose_attribute is not None:
|
||||
_name = self.entity_id + "__" + self.expose_attribute
|
||||
else:
|
||||
_name = self.entity_id
|
||||
name = f"{self.entity_id}__{self.expose_attribute or "state"}"
|
||||
device = ExposeSensor(
|
||||
xknx=self.xknx,
|
||||
name=_name,
|
||||
name=name,
|
||||
group_address=config[KNX_ADDRESS],
|
||||
respond_to_read=config[CONF_RESPOND_TO_READ],
|
||||
value_type=self.expose_type,
|
||||
|
@ -132,24 +135,33 @@ class KNXExposeSensor:
|
|||
else:
|
||||
value = state.state
|
||||
|
||||
if self.value_template is not None:
|
||||
try:
|
||||
value = self.value_template.async_render_with_possible_json_value(
|
||||
value, error_value=None
|
||||
)
|
||||
except (TemplateError, TypeError, ValueError) as err:
|
||||
_LOGGER.warning(
|
||||
"Error rendering value template for KNX expose %s %s: %s",
|
||||
self.device.name,
|
||||
self.value_template.template,
|
||||
err,
|
||||
)
|
||||
return None
|
||||
|
||||
if self.expose_type == "binary":
|
||||
if value in (1, STATE_ON, "True"):
|
||||
return True
|
||||
if value in (0, STATE_OFF, "False"):
|
||||
return False
|
||||
if (
|
||||
value is not None
|
||||
and isinstance(self.device.sensor_value, RemoteValueSensor)
|
||||
and issubclass(self.device.sensor_value.dpt_class, DPTNumeric)
|
||||
if value is not None and (
|
||||
isinstance(self.device.sensor_value, RemoteValueSensor)
|
||||
):
|
||||
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]
|
||||
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
|
||||
|
||||
async def _async_entity_changed(self, event: Event[EventStateChangedData]) -> None:
|
||||
|
|
|
@ -37,6 +37,7 @@ from homeassistant.const import (
|
|||
CONF_NAME,
|
||||
CONF_PAYLOAD,
|
||||
CONF_TYPE,
|
||||
CONF_VALUE_TEMPLATE,
|
||||
Platform,
|
||||
)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
@ -559,6 +560,7 @@ class ExposeSchema(KNXPlatformSchema):
|
|||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
vol.Optional(CONF_KNX_EXPOSE_ATTRIBUTE): cv.string,
|
||||
vol.Optional(CONF_KNX_EXPOSE_DEFAULT): cv.match_all,
|
||||
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
|
||||
}
|
||||
)
|
||||
ENTITY_SCHEMA = vol.Any(EXPOSE_SENSOR_SCHEMA, EXPOSE_TIME_SCHEMA)
|
||||
|
|
|
@ -8,7 +8,12 @@ import pytest
|
|||
|
||||
from homeassistant.components.knx import CONF_KNX_EXPOSE, DOMAIN, KNX_ADDRESS
|
||||
from homeassistant.components.knx.schema import ExposeSchema
|
||||
from homeassistant.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_TYPE
|
||||
from homeassistant.const import (
|
||||
CONF_ATTRIBUTE,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_TYPE,
|
||||
CONF_VALUE_TEMPLATE,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
|
@ -237,6 +242,54 @@ async def test_expose_cooldown(hass: HomeAssistant, knx: KNXTestKit) -> None:
|
|||
await knx.assert_write("1/1/8", (3,))
|
||||
|
||||
|
||||
async def test_expose_value_template(
|
||||
hass: HomeAssistant, knx: KNXTestKit, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Test an expose with value_template."""
|
||||
entity_id = "fake.entity"
|
||||
attribute = "brightness"
|
||||
binary_address = "1/1/1"
|
||||
percent_address = "2/2/2"
|
||||
await knx.setup_integration(
|
||||
{
|
||||
CONF_KNX_EXPOSE: [
|
||||
{
|
||||
CONF_TYPE: "binary",
|
||||
KNX_ADDRESS: binary_address,
|
||||
CONF_ENTITY_ID: entity_id,
|
||||
CONF_VALUE_TEMPLATE: "{{ not value == 'on' }}",
|
||||
},
|
||||
{
|
||||
CONF_TYPE: "percentU8",
|
||||
KNX_ADDRESS: percent_address,
|
||||
CONF_ENTITY_ID: entity_id,
|
||||
CONF_ATTRIBUTE: attribute,
|
||||
CONF_VALUE_TEMPLATE: "{{ 255 - value }}",
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
# Change attribute to 0
|
||||
hass.states.async_set(entity_id, "on", {attribute: 0})
|
||||
await hass.async_block_till_done()
|
||||
await knx.assert_write(binary_address, False)
|
||||
await knx.assert_write(percent_address, (255,))
|
||||
|
||||
# Change attribute to 255
|
||||
hass.states.async_set(entity_id, "off", {attribute: 255})
|
||||
await hass.async_block_till_done()
|
||||
await knx.assert_write(binary_address, True)
|
||||
await knx.assert_write(percent_address, (0,))
|
||||
|
||||
# Change attribute to null (eg. light brightness)
|
||||
hass.states.async_set(entity_id, "off", {attribute: None})
|
||||
await hass.async_block_till_done()
|
||||
# without explicit `None`-handling or default value this fails with
|
||||
# TypeError: unsupported operand type(s) for -: 'int' and 'NoneType'
|
||||
assert "Error rendering value template for KNX expose" in caplog.text
|
||||
|
||||
|
||||
async def test_expose_conversion_exception(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, knx: KNXTestKit
|
||||
) -> None:
|
||||
|
|
Loading…
Add table
Reference in a new issue